Formularze wieloetapowe / kreatora

10

Próbuję utworzyć formularz wieloetapowy / kreatora dla Drupal 8.

  • Użytkownik wypełnia pola imienia i nazwiska
  • Klika następny przycisk
  • Wypełnia więcej informacji
  • Klika przycisk Prześlij

Istnieje obecnie wiele Środki przeznaczone na wieloetapowej lub kreatora formularzy dla Drupal 7 jak ten jeden i ten .

Z drugiej strony miałem problem z ustaleniem, który jest „Drupal” sposób tworzenia wieloetapowych / magicznych formularzy Drupal 8.

Przeprowadziłem badania i doszedłem do wniosku, że istnieje kilka podejść:

  • Przechowuj wartości w nowym systemie konfiguracji
  • Użyj interfejsu formularza kreatora (jeszcze nie w rdzeniu )
  • Przechowuj wartości w obiekcie sesji drupal (nie jestem pewien, czy istnieje)

Czy są to prawidłowe podejścia do Drupala 8?

chrisjlee
źródło

Odpowiedzi:

12

Najprostszym sposobem na to jest użycie $ form_state. W metodzie formBuild () masz przełącznik if / else oparty na czymś podobnym $form_state['step']i wyświetlasz różne elementy formularza. Następnie masz to samo w wywołaniu zwrotnym przesyłania lub masz wiele wywołań zwrotnych, które robią coś dla obiektu w $ form_state, który budujesz, zmień krok i ustaw $form_state['rebuild']flagę na PRAWDA.

Istnieje kilka wad tego podejścia, dlatego (między innymi) stworzono kreatora formularzy narzędzi. Może się to skomplikować, jeśli masz wiele kroków i musisz to wszystko zdefiniować w jednej funkcji / klasie i wszystko dzieje się w żądaniach POST.

Kreator formularzy narzędzi to zgrupowanie wielu oddzielnych formularzy i sterowanie nawigacją między nimi. Używasz także pamięci podręcznej obiektu ctools do przechowywania obiektu zamiast $ form_state, ponieważ nie jest on już współużytkowany przez wszystkie formularze.

Mimo, że system nie ma jeszcze istnieją, ctools object cache został przeniesiony do 8.x i nazywa się teraz tempstore użytkownika, dostępny jako usługa: \Drupal::service('user.private_tempstore')(przed 8.0-beta8 nazywa user.tempstore). Jest to warstwa na szczycie oczekiwanej składnicy wartości klucza, która wprowadza do niej własność przechowywanych danych. To właśnie napędza dobrze znaną wiadomość w widokach, że inny użytkownik aktualnie edytuje ten widok i jest z tego powodu zablokowany. Kolejną zaletą w porównaniu do użycia $ _SESSION jest to, że twoje dane muszą być ładowane tylko w razie potrzeby, gdy edytujesz 3 widoki, wtedy użycie $ _SESSION oznaczałoby, że musisz je ładować i nosić przy każdym żądaniu strony.

Jeśli nie jest to potrzebne, możesz polegać na sesji lub bezpośrednio umieścić ją w przydatnym magazynie wartości klucza ($ form_state jest tam teraz również przechowywane, a nie pseudo-cache jak w 7.x).

System konfiguracji nie jest jednak dobrze dopasowany. Nie jest to przeznaczone dla treści na użytkownika (lub treści w ogóle), ponieważ tak naprawdę nie skaluje się do przechowywania tysięcy lub dziesiątek tysięcy rekordów i może przyjąć pewne założenia, że ​​wstępnie załaduje wszystko, czego może potrzebować na dane żądanie strony ( jeszcze nie, ale trzeba sprawić, aby tak się stało)

Berdir
źródło
Jeszcze jedno pytanie o twoją odpowiedź. To może być głupie pytanie: czy \ Drupal :: service ('user.tempstore') jest również dostępny dla anonimowych użytkowników?
chrisjlee
Tak, wraca do identyfikatora sesji dla anonimowych użytkowników. Zobacz api.drupal.org/api/drupal/…
Berdir
4

Zwykle wartości formularzy można przechowywać między krokami za pomocą pamięci podręcznej obiektów cTools (podobnie jak formularze Multistep w Drupal 7 ) lub poprzez $form_state(zgodnie z tym przewodnikiem ).

W Drupal 8 możesz odziedziczyć FormBaseklasę, aby utworzyć nową klasę wieloetapową.

W artykule Jak budować wieloetapowe formularze w Drupal 8 , możesz znaleźć prosty sposób na utworzenie wieloetapowego formularza w Drupal 8.

Przede wszystkim musisz stworzyć klasę podstawową, która będzie odpowiedzialna za wprowadzanie niezbędnych zależności.

Zgrupujemy wszystkie klasy formularzy razem i umieścimy je w nowym folderze o nazwie Multistepznajdującym się w Formkatalogu wtyczek naszego modułu demonstracyjnego. Jest to spowodowane czystą strukturą i możliwością szybkiego stwierdzenia, które formularze są częścią naszego wieloetapowego procesu tworzenia formularzy.

Oto kod demonstracyjny (dla MultistepFormBase.phppliku):

/**
 * @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);
    }
  }
}

Następnie możesz utworzyć rzeczywistą klasę formularzy w pliku o nazwie 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');
  }
}

W buildForm()metodzie definiujemy nasze dwa atrapy elementów formularza. Zauważ, że najpierw pobieramy istniejącą definicję formularza z klasy nadrzędnej. Wartości domyślne dla tych pól są ustawiane jako wartości znalezione w sklepie dla tych kluczy (aby użytkownicy mogli zobaczyć wartości, które wypełnili na tym etapie, jeśli wrócą do niego). Na koniec zmieniamy wartość przycisku akcji na Dalej (aby wskazać, że ten formularz nie jest ostateczny).

W submitForm()metodzie zapisujemy przesłane wartości do sklepu, a następnie przekierowujemy do drugiego formularza (który można znaleźć na trasie demo.multistep_two). Pamiętaj, że nie przeprowadzamy tutaj żadnego sprawdzania poprawności kodu. Ale większość przypadków użycia wymaga pewnej weryfikacji danych wejściowych.

I zaktualizuj plik routingu w module demonstracyjnym ( 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'

Na koniec utwórz drugą formę ( 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');
  }
}

Wewnątrz submitForm()metody ponownie zapisujemy wartości w sklepie i przechodzimy do klasy nadrzędnej, aby zachować te dane w dowolny sposób, który uzna za odpowiedni. Następnie przekierowujemy na dowolną stronę, którą chcemy (trasa, którą tu używamy, jest obojętna).

Powinniśmy teraz mieć działający formularz wieloetapowy, który używa PrivateTempStoredo przechowywania danych w wielu żądaniach. Jeśli potrzebujemy więcej kroków, musimy tylko stworzyć więcej formularzy, dodać je między już istniejącymi i wprowadzić kilka zmian.

kenorb
źródło
1

Kreator wieloetapowym , że już wspomniano, jest już zintegrowany z CTools patrz: Kreator Wsparcie dla 8.x-3.x , więc można rozważyć rozszerzenie go your_module.services.yml, np

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

następnie rozszerz klasę w 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
źródło
znasz przykład, który może pomóc zrozumieć, jak korzystać z wieloetapowego kreatora CTools?
Duncanmoo,
1
@Duncanmoo Nie mam, ale możesz zadać kolejne pytanie dotyczące określonego problemu lub poszukać Tests/Wizard/CToolsWizard*plików, w których można znaleźć niektóre testy (np testWizardSteps.).
kenorb