I am building a custom multi-step form with back/forward buttons.
When a user clicks 'next' it saves the current steps values in form storage like this:
$storage['values'][$this->step] = $form_state->getValues();
$form_state->setStorage($storage);When the user clicks 'Back', I am pulling the values out of storage and setting them to $form_state->setUserInput(); like this:
$values = $form_state->getStorage()['values'][($this-step - 1)];
$form_state->setUserInput($values);
$form_state->setRebuild()All of that works except when you go back; regardless of what data is ACTUALLY in the $form_state->setUserInput(), all of the checkboxes on the form are checked (even if they aren't supposed to be). I double checked the pre $form_state->setUserInput() to see how the data is being stored, and checked it post setUserInput() and they are identical. The rest of the fields seem to be populating correctly (text areas, text fields, select options etc), only checkboxes appear to be affected by this.
Trying to figure out why this is, and what I can do to fix it.
Comments
I don't have a solution to
I don't have a solution to your issue, but I can tell you how I deal with multistep forms. Generally a multistep form will be creating one or more entities. Imagine a site with comments enabled on posts. An anonymous user may click 'add comment', and be brought to a form that allows them to create a comment, and an account to be set as the author for the comment. An example form would have two steps, the first collecting the comment, and the second step collecting the account details. Upon completion of the form, the account is created and set as the comment author.
On the initial form build, I create an entity for a user, and an entity for a comment, with any default values. I do not call ::save() on them however. They are just two unsaved entities. These are then stored in the form state, for retrieval anywhere the form state is available on any step, validation handler, and/or submit handler.
Then, for each step of the form, I populate the form fields I'm creating from the values stored on the entity. For the initial load of each step, these values will be empty, so there will be no default value. In the submit handler for each step, the relevant entity is retrieved from the form state, any previously stored values for the relevant field are cleared, and the newly submitted values in the current step are stored.
So for the example form, upon submission of the first step of the form, the comment entity would be retrieved from the form state, the comment body field on the comment entity would be cleared, and the submitted value from the first step of the form would be set on the comment entity. The second step of the form would work the same way, retrieving the account entity, and setting submitted info on the account object.
Now imagine the user clicks the back button from the account information step, to go back to the comment step. When the comment step is rebuild, the comment entity is retrieved from the form state, and the value of the comment body field is set as the default value to the form element, and on submission, the existing value is cleared, with the new value stored.
All steps of the form work the same way - form elements are populated with entity field values, which are empty on initial load of the step, but may have a value on future loads of that step when using the back button. And finally, on form save at the final step, after adding the submitted values of the account step on the account object, the account object is saved, the new account ID is set as the author of the comment, and the comment is also saved.
Contact me to contract me for D7 -> D10/11 migrations.
That's an interesting concept
That's an interesting concept that I am interested in. A few questions I have are:
How are you saving the entity
This can be done in formBuild() as the first thing before anything else:
you can then retrieve it at any time, whether later in the formBuild step, and/or in validation, submission and ajax handlers, with $form_state->get('user_object').
If I had a field for the username, for example. I would do this:
And in the submit handler for that step, I would do this:
Here's a tutorial I wrote on the matter for D7. The Form API has slightly changed, but the concepts are all the same, and the tutorial is effectively still good: https://www.jaypan.com/tutorial/advanced-ajax-enabled-multi-step-forms-c...
Contact me to contract me for D7 -> D10/11 migrations.
Yeah I was playing around
Yeah I was playing around with it a bit today, and got most of this implemented. The thing I am still struggling with though is the #default_value being replaced correctly on rebuild.
Example if the #default_value on initial build is set to 'test value' (based on the entities value), and the user changes the value to 'test value 2' and clicks next, which sets the entities value to 'test value 2' when I click the 'back button' (which triggers $form_state->setRebuild()), the #default_value is still 'test value' and not 'test value 2'. For some reason rebuild is ignoring the #default_value parameter on rebuild.
Thanks, by the way, for that much easier approach to handling multi-steps. Much easier to work with and works nicer with add/edit forms when they are the same form.
Yes, I can confirm that using
Yes, I can confirm that using '#default_value' on form rebuild does NOT work. Printing out the value of the entitys field during the 'back' process prints the correct value, but using that in #default_value returns null (The value it originally was before the user entered information into it).
This is the reason I was using setUserInput() to begin with, because the #default_value was being ignored on form rebuild.
EDIT: I am not sure what was wrong before, but the #default_value is now pulling in correctly between steps.
I did some refactoring so it's possible I had an edge case where it wasn't actually updating the entity under certain conditions.