Drupal - How can I implement AJAX form submission?

When working with ajax in forms you have to keep in mind the following things:

  • know if you are rebuilding the whole form or only part of it and wrap the form accordingly with div element that has ID attribute that you will use in the #ajax definition on the triggering element as 'wrapper'. Use #prefix and #suffix attributes for this($form['#prefix'] = '<div id="myform-ajax-wrapper">'; $form['#suffix'] = '</div>';). Also keep in mind that if you have custom template for your form to NOT render the prefix and suffix in this case({{ form|without('#prefix', '#suffix') }}) otherwise they will get rendered twice - by your template and also by the form theme wrapper. You cannot prevent this by setting #theme_wrappers to empty array since the form template contains the actual form html element.

  • in your ajax submit handler, return the whole form or portion of it that you've wrapped and want to rebuild(return $form or return $form['myelement']). You can additionally use ajax commands instead of just returning the form structure but that is more advanced stuff.

  • store every value in form state's storage until you submit the form. Do this in the submit handler($form_state->set('somevalue', $form_state->getValue('somevalue'))) and always call $form_state->setRebuild() if you are not doing the final form submission. I prefer having custom submit handlers but having more logic in the primary submit handler is totally ok as well.

  • always use #name attribute on the button that is doing the submission and if you have only single form submit handler use $for_state->getTriggeringElement()['#name'] to detect which element has submitted the form.

  • if you are using 'trigger_as' in the #ajax definition, in case you want to submit the form with select element for example, always use the same #ajax definition like you do on the button. In my experience it is required - although not stated in the documentation.

  • using #limit_validation_errors can get very tricky sometimes and to figure out why the form is not working can take quite some time, so use it carefully(this is good for isolating form errors only on the element(s) that you are actually rebuilding so that your code does not influence other parts of the form).

  • always use buttons to submit the form and if you want to have something fancy, like select being the triggering element, use the 'trigger_as' option of the #ajax configuration and hide the real button with 'js-hide' class for good UI.

  • in the form definition, get the default values from the form state's storage if they exist or assign them into the storage if they don't. Otherwise the form won't work properly.

  • don't use $this or anything else that you don't have access to externally, otherwise it will break the ajax. always use static ajax handlers.

  • when finally submitting the form, depending on the fact that you (don't)have a custom form submission handler for the ajax, disable the form rebuilding by calling $form_state->setRebuild(FALSE).

  • you can use the :: shorthand calls in the ajax submit element($element['#ajax']['callback'] = '::ajaxFormRebuild'; and $element['#submit'] = [['::ajaxFormSubmitHandler'];).

  • the ajax callback is purely to return the rebuilt form or ajax commands. Never return altered form(ie. do not alter the form array in this callback).


To add to this checklist, if you are displaying a form in a modal window there is a possibility that the error messages will not be displayed. As Ivan Jaros said, you have to ensure that the form has a wrapper:

$form['#prefix'] = '<div id="my-form-wrapper-id">';
$form['#suffix'] = '</div>';

You'll also need to add the following to the element that is submitting the form. In most cases it would be your submit button:

$form['submit'] = [
    '#type' => 'submit',
    '#value' => $this->t('Save Changes'),
    '#attributes' => [
        'class' => [
            'btn',
            'btn-md',
            'btn-primary',
            'use-ajax-submit'
        ]
    ],
    '#ajax' => [
        'wrapper' => 'my-form-wrapper-id',
    ]
];

I use the Contact ajax module. Some more details about it (from its project page):

Contact Ajax implements ajax submission for Contact form in Drupal 8.

How it works

After enable the module, each contact form will show a checkbox "Use ajax". When this checkbox is enabled the contact form will show you another option "Confirmation type" with these options:

  • Load the form: will send the form without reload the page.
  • Load from custom message: load a custom text.
  • Load from node: load a node after form submission.

This module could help you if:

  • you need to customize the confirmation message
  • you need to submit a contact form without reload the page.
  • you want load a custom text or another node after form submission.

Tags:

Forms

Ajax

8