Форми багатоступінчасті / майстра


10

Я намагаюся створити форму для багатоступінчастості / майстра для Drupal 8.

  • Користувач заповнює поля імені та прізвища
  • Клацання на наступній кнопці
  • Заповнює більше інформації
  • Клацання кнопкою подання

Є в даний час багато ресурсів , присвячених багатоступінчастої або майстра форм для Drupal 7 як цей один і це .

З іншого боку, у мене виникли проблеми з розгадами, що таке "Drupal" спосіб створення багатоступінкових / майстерних форм Drupal 8.

Я зробив кілька досліджень і зрозумів, що існує кілька підходів:

  • Зберігайте значення в новій системі конфігурації
  • Використовуйте інтерфейс форми майстра (ще не в основі )
  • Зберігайте значення з об’єктом сеансу drupal (не впевнений, існує чи ні)

Чи є ці дійсні підходи для Drupal 8?

Відповіді:


12

Найпростіший спосіб зробити це - використовувати $ form_state. У вашому методі formBuild () у вас є if / else або перемикач, заснований на щось подібне $form_state['step']і відображати різні елементи форми. Тоді ви або маєте те саме у зворотному режимі відправки, або у вас є кілька зворотних зворотів для надсилання дзвінків, які роблять щось із об’єктом у $ form_state, який ви будуєте, змінюєте крок і встановлюєте $form_state['rebuild']прапор на TRUE.

У цього підходу є кілька недоліків, саме тому (серед інших причин) створено майстра форми ctools. Це може ускладнитися, якщо у вас є кілька кроків і вам доведеться визначити все це в єдиній формі функції / класу, і все відбувається в POST-запитах.

Майстер форм ctools робить групування декількох, окремих форм разом і керування навігацією від однієї до іншої. Ви також використовуєте кеш-об'єкт ctools, щоб зберігати ваш об'єкт замість $ form_state, оскільки він більше не поділяється у ваших формах.

Поки цієї системи ще не існує, кеш об'єктів ctools перенесено до 8.x і тепер називається користувачем tempstore, доступним як послуга: \Drupal::service('user.private_tempstore')(раніше 8.0-beta8 викликався user.tempstore). Це шар зверху вичерпаного сховища ключових значень, який вносить право власності на збережені дані там. Тож саме це надає загальновідоме повідомлення у поглядах, які інший користувач зараз редагує цей погляд, і це заблоковано з цієї причини. Ще одна перевага перед використанням $ _SESSION для цього полягає в тому, що ваші дані потрібно завантажувати лише при необхідності, коли ви редагуєте 3 перегляди, тоді використання $ _SESSION означатиме, що вам доведеться завантажувати та переносити їх у кожному запиті на кожну сторінку.

Якщо вам це не потрібно, ви можете покластися на сеанс або також безпосередньо помістити його в накопичувальний запас ключових значень ($ form_state зберігається і зараз, а не псевдо-кеш, як це було в 7.x).

Однак система конфігурації не дуже підходить. Це не призначено для вмісту користувача (або контенту взагалі), оскільки він насправді не може зберігати тисячі чи десятки тисяч записів і може зробити певні припущення для попереднього завантаження всього, що може знадобитися для певного запиту сторінки ( ще немає, але є проблема, щоб це сталося)


Ще одне запитання щодо вашої відповіді. Це може бути дурним питанням: чи доступний \ Drupal :: service ('user.tempstore') також для анонімних користувачів?
chrisjlee

Так, він повертається до ідентифікатора сеансу для анонімних користувачів. Дивіться api.drupal.org/api/drupal/…
Бердір

4

Зазвичай ви можете зберігати значення форми між кроками, використовуючи або кеш-об'єкт cTools (подібний до мультиступінкових форм у Drupal 7 ), або через $form_state(відповідно до цього підручника ).

У Drupal 8 ви можете успадкувати FormBaseклас, щоб створити новий багатоступеневий клас.

У статті Як побудувати багатоетапні форми в Drupal 8 ви можете знайти простий спосіб створити багатоступінчату форму в Drupal 8.

Перш за все, вам потрібно створити базовий клас, який відповідатиме за введення необхідних залежностей.

Ми згрупуємо всі класи форм разом і розмістимо їх у новій папці під назвою, що Multistepзнаходиться в Formкаталозі плагінів нашого демо-модуля. Це виключно для того, щоб мати чисту структуру та можливість швидко визначити, які форми є частиною нашого багатоступеневого процесу форм.

Ось демонстраційний код (для MultistepFormBase.phpфайлу):

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

Тоді ви можете створити фактичний клас форм всередині файлу під назвою 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');
  }
}

У buildForm()методі ми визначаємо наші два елемента форми манекена. Зауважте, що ми спочатку отримуємо існуюче визначення форми з батьківського класу. Значення за замовчуванням для цих полів встановлюються як значення, знайдені в магазині для цих клавіш (щоб користувачі могли бачити значення, які вони заповнили на цьому кроці, якщо вони повернуться до нього). Нарешті, ми змінюємо значення кнопки дії на Далі (щоб вказати, що ця форма не є остаточною).

У submitForm()методі ми зберігаємо подані значення в магазин, а потім перенаправляємо до другої форми (яку можна знайти в маршруті demo.multistep_two). Майте на увазі, що ми не проводимо жодної перевірки, щоб код не засвітився. Але більшість випадків використання вимагають певної перевірки вводу.

І оновіть файл маршрутизації в демонстраційному модулі ( 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'

Нарешті, створіть другу форму ( 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');
  }
}

Всередині submitForm()методу ми знову зберігаємо значення у сховищі та відкладаємо до батьківського класу, щоб зберігати ці дані будь-яким способом, який він вважає за потрібне. Потім ми переспрямовуємо на будь-яку сторінку, яку ми хочемо (маршрут, який ми тут використовуємо, є манекеном).

Тепер у нас повинна бути робоча багатоступінчаста форма, яка використовує PrivateTempStoreдля збереження даних, доступних для кількох запитів. Якщо нам потрібно більше кроків, все, що нам потрібно зробити, - це створити ще кілька форм, додати їх між існуючими та внести пару коригувань.


1

Майстер багатоступінчастий , що ви згадали, це вже інтегрований з CTools, см: Wizard Підтримка 8.x-3.x , так що ви можете розглянути питання про розширення його your_module.services.yml, наприклад ,

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

потім продовжте клас на 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 {

}

чи знаєте ви приклад, який може допомогти зрозуміти, як використовувати майстер багатоступінчастості CTools?
Данканму

1
@Duncanmoo У мене немає, але не соромтеся задати інше питання з конкретною проблемою, яке у вас виникає, або загляньте у Tests/Wizard/CToolsWizard*файли, де ви можете знайти тести (наприклад testWizardSteps).
kenorb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.