Многошаговые / мастера форм

10

Я пытаюсь создать многошаговую / волшебную форму для Drupal 8.

  • Пользователь заполняет поля имени, фамилии
  • Нажатие на следующую кнопку
  • Заполняет больше информации
  • Клики по кнопке отправки

Есть в настоящее время много ресурсов , посвященных многоступенчатой или мастера форм для Drupal 7 как этот один и это .

С другой стороны, у меня возникли проблемы с выяснением того, что представляет собой «Drupal» способ создания многошаговых / волшебных форм Drupal 8.

Я провел некоторое исследование и понял, что есть несколько подходов:

  • Храните значения с новой системой конфигурации
  • Используйте интерфейс формы мастера ( пока не в ядре )
  • Сохранять значения с помощью объекта сеанса drupal (не уверен, существует он или нет)

Эти подходящие подходы для Drupal 8?

chrisjlee
источник

Ответы:

12

Самый простой способ сделать это - использовать $ form_state. В вашем методе formBuild () у вас есть if / else или переключатель, основанный на чем-то подобном, $form_state['step']и отображающий различные элементы формы. Затем у вас будет то же самое в обратном вызове отправки или несколько обратных вызовов отправки, которые что-то делают с объектом в $ form_state, который вы строите, измените шаг и установите $form_state['rebuild']флаг в значение ИСТИНА.

У этого подхода есть несколько недостатков, поэтому (среди прочих причин) был создан мастер форм 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).

Однако система конфигурации не очень подходит. Это не предназначено для пользовательского контента (или контента вообще), так как он не масштабируется для хранения тысяч или десятков тысяч записей и может делать некоторые предположения для предварительной загрузки всего, что может понадобиться для данного запроса страницы ( пока нет, но есть проблема, чтобы это произошло)

Berdir
источник
Еще один вопрос о вашем ответе. Это может быть глупый вопрос: \ 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данные для доступа к нескольким запросам. Если нам нужно больше шагов, все, что нам нужно сделать, это создать еще несколько форм, добавить их между существующими и внести пару корректировок.

kenorb
источник
1

Упомянутый вами многошаговый мастер уже интегрирован с CTools, см .: Поддержка мастера для 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 {

}
kenorb
источник
Вы знаете пример, который может помочь понять, как использовать многошаговый мастер CTools?
Дунканмоо
1
@Duncanmoo У меня нет, но не стесняйтесь задавать другой вопрос с конкретной проблемой, которая у вас есть, или искать в Tests/Wizard/CToolsWizard*файлах, где можно найти некоторые тесты (например testWizardSteps).
Кенорб