Drupal → Создание многошаговой формы (multistep form) (8)

03.02.2024

Пример трёхшаговой формы:

class ExampleMultistepForm extends FormBase {

  /**
   * {@inheritDoc}
   */
  public function getFormId(): string {
    return 'example_multistep_form';
  }

  /**
   * {@inheritDoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state): array {
    $step = $form_state->get('step');
    if (!$step) {
      $step = 1;
      $form_state->set('step', $step);
      $form_state->set('steps_values', []);
    }
    $is_last_step = !method_exists($this, 'buildStep' . ($step + 1));

    $form = $this->{'buildStep' . $step}($form, $form_state);

    $form['actions'] = [
      '#type' => 'actions',
    ];

    if ($step > 1) {
      $form['actions']['prev'] = [
        '#type' => 'submit',
        '#value' => $this->t('Prev'),
        '#name' => 'prev',
        '#submit' => ['::stepNavigation'],
        '#limit_validation_errors' => [],
      ];
    }
    if (!$is_last_step) {
      $form['actions']['next'] = [
        '#type' => 'submit',
        '#value' => $this->t('Next'),
        '#name' => 'next',
        '#submit' => ['::stepNavigation'],
      ];
    }
    if ($is_last_step) {
      $form['actions']['submit'] = [
        '#type' => 'submit',
        '#value' => $this->t('Submit'),
      ];
    }

    return $form;
  }

  /**
   * Step 1 form.
   */
  public function buildStep1(array $form, FormStateInterface $form_state): array {
    $form['first_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('First name'),
      '#default_value' => $form_state->getValue('first_name'),
    ];

    return $form;
  }

  /**
   * Step 2 form.
   */
  public function buildStep2(array $form, FormStateInterface $form_state): array {
    $form['last_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Last name'),
      '#default_value' => $form_state->getValue('last_name'),
      '#required' => TRUE,
    ];

    $form['#attributes']['novalidate'] = TRUE;

    return $form;
  }

  /**
   * Step 3 form.
   */
  public function buildStep3(array $form, FormStateInterface $form_state): array {
    $form['phone'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Phone'),
      '#default_value' => $form_state->getValue('phone'),
    ];

    return $form;
  }

  /**
   * Prev/Next button submit callback.
   */
  public function stepNavigation(array $form, FormStateInterface $form_state): void {
    $triggering_button_name = $form_state->getTriggeringElement()['#name'];

    // Change step
    $step = $form_state->get('step');
    $form_state->set('step', $triggering_button_name == 'next' ? $step + 1 : $step - 1);

    // Store current values to all-steps values
    $steps_values = $form_state->get('steps_values');
    if ($triggering_button_name == 'next') {
      $steps_values = NestedArray::mergeDeep($steps_values, $form_state->cleanValues()->getValues());
      $form_state->set('steps_values', $steps_values);
    }

    // Copy all-steps values to current values, so that they are available in buildForm
    $form_state->setValues($steps_values);

    // Disable form reload (redirect)
    $form_state->setRebuild(TRUE);
  }

  /**
   * {@inheritDoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    $values = NestedArray::mergeDeep($form_state->get('steps_values'), $form_state->cleanValues()->getValues());
    dsm($values);
  }

}

Принцип очень похож на реализацию в Drupal 7.

У хранения переменных в $form_state есть один недостаток — если пользователь кликнул кнопку "Next", а потом нажал в браузере F5, то всё, что хранилось в $form_state удалится, т.е. данные прошлых шагов пропадут. Это происходит потому, что при каждой отправке формы друпал генерирует новый form_build_id, под которым сохраняет данные в кэше, а когда пользователь нажимает F5, то браузер отсылает на сервер прошлый form_build_id которого уже не существует в базе. Фиксится добавлением в stepNavigation() строчки $form_state->setCached(FALSE);.

Демо модуль.
Ещё один пример

P.S: создал issue на drupal.org

Написанное актуально для
Drupal 8+
Похожие записи

Комментарии

Хорошо починить поведение по Ф5

@andypost я думаю это не чинится, by design как говорится

Если переменную степа формы перенести session storage тогда проблем с F5 не будет.
Так же вместо
$form = $this->{'buildStep' . $step}($form, $form_state);
лучше использовать
$form = call_user_func([$this, $buildStep . step], $form, $form_state);

На счет именования степов фоормы с инкрементом я бы сделал через ReflectionClass , \то будеи намного гибче и удобнее

Добавить комментарий