Пример трёхшаговой формы:
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+
Похожие записи
- Препроцессинг настроек форматтера перед сохранением
- Интеграция плавающих лейблов (float label)
- Перенести определённый виджет параграфа в вкладку "Behavior"
- Показать второй шаг многошаговой формы в модальном окне
- Навесить на элемент managed_file свой ajax callback (Как обновить всю форму при загрузки файла в managed_file)
Комментарии
Хорошо починить поведение по Ф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 , \то будеи намного гибче и удобнее
Добавить комментарий