Mehrschritt- / Assistentenformulare

10

Ich versuche, ein mehrstufiges / Assistentenformular für Drupal 8 zu erstellen.

  • Der Benutzer füllt die Felder Vorname und Nachname aus
  • Klicken Sie auf die nächste Schaltfläche
  • Füllt weitere Informationen aus
  • Klicken Sie auf die Schaltfläche "Senden"

Derzeit gibt es viele Ressourcen auf vielstufige gewidmet oder Assistentenformulare für Drupal 7 wie diese ein und dies .

Andererseits hatte ich einige Probleme herauszufinden, welche "Drupal" -Methode zum Erstellen von Drupal 8-Mehrschritt- / Assistentenformularen ist.

Ich habe einige Nachforschungen angestellt und festgestellt, dass es mehrere Ansätze gibt:

  • Speichern Sie Werte mit dem neuen Konfigurationssystem
  • Verwenden Sie die Formularoberfläche des Assistenten (noch nicht im Kern ).
  • Speichern Sie Werte mit dem Drupal-Sitzungsobjekt (nicht sicher, ob dies vorhanden ist oder nicht)

Sind das gültige Ansätze für Drupal 8?

Chrisjlee
quelle

Antworten:

12

Der einfachste Weg, dies zu tun, ist die Verwendung von $ form_state. In Ihrer formBuild () -Methode haben Sie ein if / else oder einen Schalter, der auf so etwas basiert $form_state['step']und verschiedene Formularelemente anzeigt. Dann haben Sie entweder dasselbe in Ihrem Submit-Callback oder mehrere Submit-Callbacks, die etwas mit einem Objekt in $ form_state tun, das Sie erstellen, den Schritt ändern und das $form_state['rebuild']Flag auf TRUE setzen.

Dieser Ansatz hat einige Nachteile, weshalb (unter anderem) der ctools-Formularassistent erstellt wurde. Es kann kompliziert werden, wenn Sie mehrere Schritte haben und all dies in einer einzigen Formularfunktion / -klasse definieren müssen und alles in POST-Anforderungen geschieht.

Der ctools-Formularassistent gruppiert mehrere separate Formulare und steuert die Navigation von einem zum anderen. Sie verwenden auch den ctools-Objektcache, um Ihr Objekt anstelle von $ form_state zu speichern, da dies nicht mehr für Ihre Formulare freigegeben ist.

Während das System noch nicht existiert, die CTOOLS Objekt - Cache auf 8.x portiert wurde und nun Benutzer tempstore, als Service genannt: \Drupal::service('user.private_tempstore')(vor 8,0-beta8 genannt user.tempstore). Dies ist eine Schicht über dem Speicher für auslauffähige Schlüsselwerte, die den Besitz der darin gespeicherten Daten einführt. Dies ist es, was die bekannte Nachricht in Ansichten antreibt, dass ein anderer Benutzer diese Ansicht gerade bearbeitet und sie aus diesem Grund gesperrt ist. Ein weiterer Vorteil gegenüber der Verwendung von $ _SESSION besteht darin, dass Ihre Daten nur bei Bedarf geladen werden müssen, wenn Sie 3 Ansichten bearbeiten. Wenn Sie dann $ _SESSION bearbeiten, müssen Sie sie bei jeder einzelnen Seitenanforderung laden und herumtragen.

Wenn Sie das nicht benötigen, können Sie sich auf die Sitzung verlassen oder sie auch direkt in einem Speicher für ablaufbare Schlüsselwerte ablegen ($ form_state wird jetzt auch dort gespeichert, kein Pseudo-Cache wie in 7.x).

Das Konfigurationssystem passt jedoch nicht gut zusammen. Dies ist nicht für Inhalte pro Benutzer (oder überhaupt für Inhalte) gedacht, da es nicht wirklich skalierbar ist, um Tausende oder Zehntausende von Datensätzen zu speichern, und möglicherweise einige Annahmen trifft, um alles vorzuladen, was für eine bestimmte Seitenanforderung erforderlich ist ( noch nicht, aber es gibt ein Problem, das dies ermöglicht)

Berdir
quelle
Noch eine Frage zu Ihrer Antwort. Dies könnte eine dumme Frage sein: Ist \ Drupal :: service ('user.tempstore') auch für anonyme Benutzer verfügbar?
Chrisjlee
Ja, es wird auf die Sitzungs-ID für nervige Benutzer zurückgegriffen. Siehe api.drupal.org/api/drupal/…
Berdir
4

Normalerweise können Sie Formularwerte zwischen Schritten entweder mithilfe des cTools-Objektcaches (ähnlich wie bei mehrstufigen Formularen in Drupal 7 ) oder über $form_state(gemäß diesem Lernprogramm ) speichern .

In Drupal 8 können Sie eine FormBaseKlasse erben , um eine neue mehrstufige Klasse zu erstellen.

In dem Artikel Wie 8 Multi-Schritt - Formulare in Drupal bauen können Sie einen einfachen Weg zu finden , ein mehrstufiges Formular in Drupal 8 zu erstellen.

Zunächst müssten Sie die Basisklasse erstellen, die für das Einfügen der erforderlichen Abhängigkeiten zuständig ist.

Wir werden alle Formularklassen zusammenfassen und sie in einem neuen Ordner namens platzieren, der Multistepsich im FormPlugin-Verzeichnis unseres Demo-Moduls befindet. Dies dient lediglich dazu, eine saubere Struktur zu haben und schnell erkennen zu können, welche Formulare Teil unseres mehrstufigen Formularprozesses sind.

Hier ist der Demo-Code (für MultistepFormBase.phpDatei):

/**
 * @file
 * Contains \Drupal\demo\Form\Multistep\MultistepFormBase.
 */

namespace Drupal\demo\Form\Multistep;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Session\SessionManagerInterface;
use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;

abstract class MultistepFormBase extends FormBase {

  /**
   * @var \Drupal\user\PrivateTempStoreFactory
   */
  protected $tempStoreFactory;

  /**
   * @var \Drupal\Core\Session\SessionManagerInterface
   */
  private $sessionManager;

  /**
   * @var \Drupal\Core\Session\AccountInterface
   */
  private $currentUser;

  /**
   * @var \Drupal\user\PrivateTempStore
   */
  protected $store;

  /**
   * Constructs a \Drupal\demo\Form\Multistep\MultistepFormBase.
   *
   * @param \Drupal\user\PrivateTempStoreFactory $temp_store_factory
   * @param \Drupal\Core\Session\SessionManagerInterface $session_manager
   * @param \Drupal\Core\Session\AccountInterface $current_user
   */
  public function __construct(PrivateTempStoreFactory $temp_store_factory, SessionManagerInterface $session_manager, AccountInterface $current_user) {
    $this->tempStoreFactory = $temp_store_factory;
    $this->sessionManager = $session_manager;
    $this->currentUser = $current_user;

    $this->store = $this->tempStoreFactory->get('multistep_data');
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('user.private_tempstore'),
      $container->get('session_manager'),
      $container->get('current_user')
    );
  }

  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    // Start a manual session for anonymous users.
    if ($this->currentUser->isAnonymous() && !isset($_SESSION['multistep_form_holds_session'])) {
      $_SESSION['multistep_form_holds_session'] = true;
      $this->sessionManager->start();
    }

    $form = array();
    $form['actions']['#type'] = 'actions';
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => $this->t('Submit'),
      '#button_type' => 'primary',
      '#weight' => 10,
    );

    return $form;
  }

  /**
   * Saves the data from the multistep form.
   */
  protected function saveData() {
    // Logic for saving data goes here...
    $this->deleteStore();
    drupal_set_message($this->t('The form has been saved.'));

  }

  /**
   * Helper method that removes all the keys from the store collection used for
   * the multistep form.
   */
  protected function deleteStore() {
    $keys = ['name', 'email', 'age', 'location'];
    foreach ($keys as $key) {
      $this->store->delete($key);
    }
  }
}

Anschließend können Sie die eigentliche Formularklasse in einer Datei mit dem Namen MultistepOneForm.php:

/**
 * @file
 * Contains \Drupal\demo\Form\Multistep\MultistepOneForm.
 */

namespace Drupal\demo\Form\Multistep;

use Drupal\Core\Form\FormStateInterface;

class MultistepOneForm extends MultistepFormBase {

  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'multistep_form_one';
  }

  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $form = parent::buildForm($form, $form_state);

    $form['name'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Your name'),
      '#default_value' => $this->store->get('name') ? $this->store->get('name') : '',
    );

    $form['email'] = array(
      '#type' => 'email',
      '#title' => $this->t('Your email address'),
      '#default_value' => $this->store->get('email') ? $this->store->get('email') : '',
    );

    $form['actions']['submit']['#value'] = $this->t('Next');
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->store->set('email', $form_state->getValue('email'));
    $this->store->set('name', $form_state->getValue('name'));
    $form_state->setRedirect('demo.multistep_two');
  }
}

In der buildForm()Methode definieren wir unsere beiden Dummy-Formularelemente. Beachten Sie, dass wir zuerst die vorhandene Formulardefinition aus der übergeordneten Klasse abrufen. Die Standardwerte für diese Felder werden als die Werte festgelegt, die im Speicher für diese Schlüssel gefunden wurden (damit Benutzer die Werte sehen können, die sie in diesem Schritt eingegeben haben, wenn sie darauf zurückkommen). Schließlich ändern wir den Wert der Aktionsschaltfläche in Weiter (um anzuzeigen, dass dieses Formular nicht das endgültige ist).

Bei der submitForm()Methode speichern wir die übermittelten Werte im Speicher und leiten sie dann zum zweiten Formular weiter (das sich auf der Route befindet demo.multistep_two). Beachten Sie, dass wir hier keine Validierung durchführen, um den Code hell zu halten. Die meisten Anwendungsfälle erfordern jedoch eine Eingabevalidierung.

Und aktualisieren Sie Ihre Routing-Datei im Demo-Modul ( demo.routing.yml):

demo.multistep_one:
  path: '/demo/multistep-one'
  defaults:
    _form: '\Drupal\demo\Form\Multistep\MultistepOneForm'
    _title: 'First form'
  requirements:
    _permission: 'access content'
demo.multistep_two:
  path: '/demo/multistep-two'
  defaults:
    _form: '\Drupal\demo\Form\Multistep\MultistepTwoForm'
    _title: 'Second form'
  requirements:
    _permission: 'access content'

Erstellen Sie abschließend das zweite Formular ( MultistepTwoForm):

/**
 * @file
 * Contains \Drupal\demo\Form\Multistep\MultistepTwoForm.
 */

namespace Drupal\demo\Form\Multistep;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;

class MultistepTwoForm extends MultistepFormBase {

  /**
   * {@inheritdoc}.
   */
  public function getFormId() {
    return 'multistep_form_two';
  }

  /**
   * {@inheritdoc}.
   */
  public function buildForm(array $form, FormStateInterface $form_state) {

    $form = parent::buildForm($form, $form_state);

    $form['age'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Your age'),
      '#default_value' => $this->store->get('age') ? $this->store->get('age') : '',
    );

    $form['location'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Your location'),
      '#default_value' => $this->store->get('location') ? $this->store->get('location') : '',
    );

    $form['actions']['previous'] = array(
      '#type' => 'link',
      '#title' => $this->t('Previous'),
      '#attributes' => array(
        'class' => array('button'),
      ),
      '#weight' => 0,
      '#url' => Url::fromRoute('demo.multistep_one'),
    );

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {
    $this->store->set('age', $form_state->getValue('age'));
    $this->store->set('location', $form_state->getValue('location'));

    // Save the data
    parent::saveData();
    $form_state->setRedirect('some_route');
  }
}

Innerhalb der submitForm()Methode speichern wir die Werte erneut im Speicher und verschieben sie auf die übergeordnete Klasse, um diese Daten nach eigenem Ermessen beizubehalten. Wir leiten dann zu jeder gewünschten Seite weiter (die Route, die wir hier verwenden, ist eine Dummy-Route).

Wir sollten jetzt ein funktionierendes mehrstufiges Formular haben, das das verwendet PrivateTempStore, um Daten über mehrere Anforderungen hinweg verfügbar zu halten. Wenn wir weitere Schritte benötigen, müssen wir lediglich weitere Formulare erstellen, diese zwischen die vorhandenen hinzufügen und einige Anpassungen vornehmen.

Kenorb
quelle
1

Der von Ihnen erwähnte mehrstufige Assistent ist bereits in CTools integriert. Weitere Informationen finden Sie unter: Assistentenunterstützung für 8.x-3.x . Sie können ihn daher möglicherweise erweitern your_module.services.yml, z

services:
  ctools.wizard.form:
    class: Drupal\MyModuleMultistep\Controller\MyWizardForm

Erweitern Sie dann die Klasse in src/Controller/MyWizardForm.php:

<?php

/**
 * @file
 * Contains \Drupal\MyModuleMultistep\Controller\MyWizardForm
 */

namespace Drupal\MyModuleMultistep\Controller;

/**
 * Wrapping controller for wizard forms that serve as the main page body.
 */
class MyWizardForm extends WizardFormController {

}
Kenorb
quelle
Kennen Sie ein Beispiel, das Ihnen helfen könnte, den mehrstufigen CTools-Assistenten zu verwenden?
Duncanmoo
1
@ Duncanmoo habe ich nicht, aber Tests/Wizard/CToolsWizard*Sie können gerne eine andere Frage mit einem bestimmten Problem stellen, das Sie haben, oder in Dateien suchen, in denen Sie einige Tests finden können (z testWizardSteps. B. ).
Kenorb