Skip to main content

How to implement Ajax autosave in Drupal 8 forms

Jill Lasak
Front-End Web Developer

Although Drupal 8 uses Garlic (a JS library) to cache the state of a partially completed form to the local storage of your browser, sometimes a more permanent type of storage is required. In this case, we needed to save a form field to the database immediately after it was filled out, not just on "submit". Using Drupal 8's form API, we can easily add Ajax autosave to fields in a form. In Drupal 8, the Ajax callback must return HTML markup or a set of Ajax commands. Even though these don't really serve a purpose for our autosave, I chose to return HTML markup in order to avoid an error. The 'ajax_response' form element, and the 'wrapper' references below create a place where the markup is returned. In my example below, I'm adding autosave to a node field, but you could easily modify this code to make it apply to another entity or a custom form.

Step one: Add the Ajax widget to the field

In Drupal 8, Ajax widgets can be added almost the same way as in Drupal 7. Via a hook_form_alter:


use Drupal\Core\Form\FormStateInterface;

/**
 * Implements hook_form_alter().
 */

function your_module_form_[form_id]_alter(&$form, FormStateInterface $form_state, $form_id) {
  $form['sample_field_name']['widget']['#ajax'] = array(
    'callback' => 'Drupal\your_module\Form\FormAlter::autosave',
    'event' => 'change',
    'wrapper' => 'ajax_placeholder',
    'progress' => array(
      'type' => 'throbber',
      'message' => NULL,
    ),
  );

  // Add placeholder for Ajax response markup
  $form['ajax_response'] = array(
    '#type' => 'html_tag',
    '#tag' => 'div',
    '#value' => t('Placeholder for ajax response'),
    '#attributes' => array(
      'class' => array('hidden'),
      'id' => array('ajax_placeholder'),
    ),
  );
}

or if you are creating the form from scratch, add it to the form element definition:

$form['field_name'] = array(
  '#type' => 'radios',
  '#title' => $this->t('Sample field title'),
  '#default_value' => 1,
  '#options' => array(0 => $this->t('Option 1'), 1 => $this->t('Option 2')),
  '#ajax' => array(
    'callback' => 'Drupal\your_module\Form\FormAlter::autosave',
    'event' => 'change',
    'wrapper' => 'ajax_placeholder',
    'progress' => array(
      'type' => 'throbber',
      'message' => NULL,
    ),
  ),
);

// Add placeholder for Ajax response markup
$form['ajax_response'] = array(
  '#type' => 'html_tag',
  '#tag' => 'div',
  '#value' => t('Placeholder for ajax response'),
  '#attributes' => array(
    'class' => array('hidden'),
    'id' => array('ajax_placeholder'),
  ),
);

If you are implementing via a hook_form_alter, the placement of the Ajax widget may change depending on the type of field. The above will work for radio buttons and select boxes. For textareas, you may need to add it at

$value['widget'][0]['value']['#ajax']

rather than

$value['widget']['#ajax']

Step two: Add the callback function to autosave

Here we are getting the node object and the changed form element from the $form_state variable, and setting the new value of the changed element to the node before saving. We then return a response to the form. Make sure the class name below matches the callback you wrote in the previous step.

namespace Drupal\your_module\Form;

use Drupal\Core\Form\FormStateInterface;
use Drupal\node\Entity\Node;

class FormAlter {

  public static function autosave(array &$form, FormStateInterface $form_state) {

    // Load the node and get changed form element
    $node = $form_state->getFormObject()->getEntity();
    $triggering_element = $form_state->getTriggeringElement();

    // Set and save node with new value for field
    $node->set($triggering_element, $triggering_element['#value']);
    $node->save();

    // Add an Ajax response to avoid error 
    $response = [
      '#markup' => '<div class="hidden">Saved</div>',
    ];
    return $response;

  }

}

If you need to autosave more than one field on the form, you can replace

$node = $form_state->getFormObject()->getEntity();

with

$node_id = $form_state->getFormObject()->getEntity()->id();
$node = Node::load($node_id); 

so the newly autosaved node is reloaded at each call.

With this, you should be able to create autosave in a node, or with slight modifications, in another entity. Good luck! Reference: https://api.drupal.org/api/drupal/core!core.api.php/group/ajax/8.2.x