Je veux transformer un formulaire de nœud en un formulaire en plusieurs étapes en utilisant ajax. J'ai ajouté des boutons de soumission compatibles ajax et de la logique dans un hook_form_alter
(Voir ci-dessous) pour gérer les étapes inc. Et décrémentielles, et afficher et masquer les éléments de formulaire en fonction de l'étape actuelle. Les allers-retours fonctionnent très bien, en termes de rendu des éléments corrects.
Le problème est que si j'entre des données en une seule étape, que j'avance ou recule, puis que je reviens à l'étape d'origine, les données que j'ai saisies ont disparu du formulaire. Si je vérifie la valeur de l'élément pour lequel j'ai entré des données à l'aide de FormStateInterface::getValue()
, les données sont là lorsque mon alter hook s'exécute en quittant l'étape d'origine, mais est passé aux transitions suivantes/suivantes. Ce problème ne s'applique pas non plus aux données qui sont déjà dans le formulaire (par exemple lors de la modification d'un nœud existant).
De toute évidence, je dois stocker les valeurs entrantes quelque part, mais où? Et puis, comment les restaurer dans les éléments du formulaire à l'étape appropriée?
Mon code (exemple minimal):
<?php
/**
* Implements hook_form_FORM_ID_alter().
*/
function my_module_form_node_ajax_test_form_alter(&$form, $form_state) {
// Define steps. Each child array is a step, and each string in the
// a child array is a field that should be present on that step.
$steps = [
['title'],
['body'],
];
// Add wrapper.
$form['#prefix'] = '<div id="ajax-form-wrapper">';
$form['#suffix'] = '</div>';
// If the step is not set, this is step 0.
if (!$form_state->has('step')) {
$form_state->set('step', 0);
}
// Get the current step.
$step = $form_state->get('step');
// If we have a triggering element, increment or decrement step as
// appropriate.
if ($trigger = $form_state->getTriggeringElement()) {
switch ($trigger['#name']) {
case 'next':
$form_state->set('step', ++$step);
break;
case 'back':
$form_state->set('step', --$step);
break;
}
}
// Show elements in the current step, hide others. Always show the
// `actions` element, and all `form_*` elements.
foreach (Element::children($form) as $element) {
$form[$element]['#access'] = (
in_array($element, $steps[$step])
|| $element == 'actions'
|| strpos($element, 'form') === 0
);
}
// Show the back button if this is not the first step.
$form['actions']['back'] = $step > 0 ? ajax_button('back') : NULL;
// Show the next button if this is not the last step.
$is_last_step = $step == count($steps) - 1;
$form['actions']['next'] = !$is_last_step ? ajax_button('next') : NULL;
// Show the submit button if this is the last step.
$form['actions']['submit']['#access'] = $is_last_step;
}
/**
* Return a render array for an ajax step button.
*/
function ajax_button($name) {
return [
'#type' => 'button',
'#value' => $name,
'#name' => $name,
'#ajax' => [
'wrapper' => 'ajax-form-wrapper',
],
];
}
Eh bien, je l'ai compris. (C'était avant la réponse de @ 4k4, ce qui est très proche de ce que j'ai fini avec).
J'avais besoin de créer l'entité à l'aide des valeurs de formulaire soumises, puis de définir le formulaire à reconstruire (qui place les valeurs de l'entité dans le formulaire) dans un gestionnaire de soumission. Voici comment je l'ai fait:
<?php
/**
* Return a render array for an ajax step button.
*/
function ajax_button($name) {
return [
// Changed from 'button', buttons don't invoke submit handlers,
// submits do.
'#type' => 'submit',
'#value' => $name,
'#name' => $name,
// Set the submit handler.
'#submit' = ['my_module_ajax_form_submit'],
'#ajax' => [
'wrapper' => 'ajax-form-wrapper',
],
];
}
/**
* Build entity using submitted form data and rebuild form.
*/
function my_module_ajax_form_submit($form, $form_state) {
// Get the NodeForm object.
$form_object = $form_state->getFormObject();
// NodeForm::buildEntity() maps form values into the entity object
// and returns it.
$entity = $form_object->buildEntity($form, $form_state);
// NodeForm::setEntity() stores the entity in the form object which
// is used to populate form fields when the form rebuilds.
$form_object->setEntity($entity);
// Set the form to rebuild.
$form_state->setRebuild()
}
Je suis arrivé ici en suivant de très près les différentes implémentations de ::buildEntity()
et ::buildForm()
dans NodeForm
et ses ancêtres ContentEntityForm
et EntityForm
.
Ce code est assez impressionnant. Je ne pensais pas qu'il était possible de créer un formulaire d'entité à plusieurs étapes avec quelques lignes dans un crochet de modification de formulaire. Pour garder cette simplicité, vous pouvez essayer d'utiliser l'objet entité pour stocker les valeurs, car l'objet formulaire est sérialisé et mis en cache entre les étapes et une méthode pour créer une telle entité à partir des valeurs soumises fait partie de la classe de formulaire entité:
if ($trigger = $form_state->getTriggeringElement()) {
$form_object = $form_state->getFormObject();
$new_entity = $form_object->buildEntity($form, $form_state);
$form_object->setEntity($new_entity);
Je pense que vous devez utiliser les sessions symfony
Définir la variable de session
$session = \Drupal::request()->getSession();
$session->set('mymodule_variable', 'value');
Plus tard, vous pouvez obtenir cette variable comme ceci
$session = \Drupal::request()->getSession();
$value = $session->get('mymodule_variable', 'default_value');
Les sessions Symfony sont équivalentes à php $ _SESSION
Il semble que la bonne façon de le faire soit avec PrivateTempStore , si vous allez voir CommentAdminOverview dans Core, vous verrez ceci:
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
...
$info = [];
/** @var \Drupal\comment\CommentInterface $comment */
foreach ($comments as $comment) {
$langcode = $comment->language()->getId();
$info[$comment->id()][$langcode] = $langcode;
}
$this->tempStoreFactory
->get('comment_multiple_delete_confirm')
->set($this->currentUser()->id(), $info);
$form_state->setRedirect('comment.multiple_delete_confirm');
}
}
Dans cette page, vous collectez l'ID de commentaire que vous souhaitez supprimer et avez effectué une redirection, et la suppression du commentaire se produit dans comment.multiple_delete_confirm
( ConfirmDeleteMultiple ) qui ont le code suivant:
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$this->commentInfo = $this->tempStoreFactory->get('comment_multiple_delete_confirm')->get($this->currentUser()->id());
if (empty($this->commentInfo)) {
return $this->redirect('comment.admin');
}
/** @var \Drupal\comment\CommentInterface[] $comments */
$comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo));
...
}
Les vues fonctionnent également avec PrivateTempStorage, c'est pourquoi lorsque vous revenez à une vue qui n'est pas enregistrée, vous pouvez voir à nouveau les modifications.