B A C K
Implementing AJAX Field Updates in Drupal 8

Implementing AJAX Field Updates in Drupal 8

Knowledge

This guide demonstrates how to create a form in Drupal 8 where selecting a value in one field automatically updates another field using AJAX.

Step 1: Create a Custom Module

First, create a new custom module. Let's call it custom_ajax_form.

# custom_ajax_form.info.yml
name: Custom Ajax Form
type: module
description: 'Provides a form with AJAX field updates.'
core: 8.x
package: Custom

Step 2: Create the Form Class

Create a new form class in src/Form/DynamicFieldForm.php:

<?php
namespace Drupal\custom_ajax_form\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\ReplaceCommand;

class DynamicFieldForm extends FormBase {

  public function getFormId() {
    return 'dynamic_field_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['primary_field'] = [
      '#type'    => 'select',
      '#title'   => $this->t('Select an option'),
      '#options' => [
        ''  => $this->t('- Select -'),
        '1' => $this->t('Option 1'),
        '2' => $this->t('Option 2'),
        '3' => $this->t('Option 3'),
      ],
      '#ajax' => [
        'callback' => '::updateSecondaryField',
        'wrapper'  => 'secondary-field-wrapper',
        'event'    => 'change',
      ],
    ];
    $form['secondary_field_wrapper'] = [
      '#type'       => 'container',
      '#attributes' => ['id' => 'secondary-field-wrapper'],
    ];
    $form['secondary_field_wrapper']['secondary_field'] = [
      '#type'     => 'textfield',
      '#title'    => $this->t('Dynamic content'),
      '#readonly' => TRUE,
      '#value'    => $this->getSecondaryFieldValue($form_state),
    ];
    $form['submit'] = ['#type' => 'submit', '#value' => $this->t('Submit')];
    return $form;
  }

  public function updateSecondaryField(array &$form, FormStateInterface $form_state) {
    return $form['secondary_field_wrapper'];
  }

  protected function getSecondaryFieldValue(FormStateInterface $form_state) {
    $trigger = $form_state->getTriggeringElement();
    $value = '';
    if ($trigger) {
      $selected_value = $form_state->getValue('primary_field');
      $values_map = [
        '1' => 'Content for Option 1',
        '2' => 'Content for Option 2',
        '3' => 'Content for Option 3',
      ];
      $value = isset($values_map[$selected_value]) ? $values_map[$selected_value] : '';
    }
    return $value;
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    \Drupal::messenger()->addMessage($this->t('Form submitted successfully.'));
  }
}

Step 3: Create a Route

Create custom_ajax_form.routing.yml:

custom_ajax_form.dynamic_form:
  path: '/custom-ajax-form'
  defaults:
    _form: '\Drupal\custom_ajax_form\Form\DynamicFieldForm'
    _title: 'Dynamic AJAX Form'
  requirements:
    _permission: 'access content'

Step 4 (Optional): Add a Menu Link

Create custom_ajax_form.links.menu.yml:

custom_ajax_form.dynamic_form:
  title: 'Dynamic AJAX Form'
  route_name: custom_ajax_form.dynamic_form
  menu_name: main

Advanced Implementation with Entity Reference

Here's how to modify the form to work with entity reference fields:

protected function getSecondaryFieldValue(FormStateInterface $form_state) {
  $trigger = $form_state->getTriggeringElement();
  $value = '';
  if ($trigger) {
    $entity_id = $form_state->getValue('primary_field');
    if ($entity_id && $entity = \Drupal::entityTypeManager()->getStorage('node')->load($entity_id)) {
      $value = $entity->get('field_example')->value;
    }
  }
  return $value;
}

Using with Views and Entity Reference

To integrate with Views and entity reference fields:

  1. Create a View that returns the entities you want to reference.
  2. Modify the form to use the View results:
protected function getPrimaryFieldOptions() {
  $options = ['' => $this->t('- Select -')];
  $view = Views::getView('your_view_name');
  if ($view) {
    $view->execute();
    foreach ($view->result as $row) {
      $entity = $row->_entity;
      $options[$entity->id()] = $entity->label();
    }
  }
  return $options;
}

Performance Considerations

Add caching metadata to your form:

public function buildForm(array $form, FormStateInterface $form_state) {
  $form['#cache'] = [
    'contexts' => ['user.permissions'],
    'tags'     => ['node_list'],
  ];
  // ... rest of the form build
}

Use proper cache tags in your AJAX callback:

public function updateSecondaryField(array &$form, FormStateInterface $form_state) {
  $response = new AjaxResponse();
  $response->addCommand(new ReplaceCommand(
    '#secondary-field-wrapper',
    $form['secondary_field_wrapper']
  ));
  $response->getCacheableMetadata()->addCacheTags(['node_list']);
  return $response;
}

Error Handling

Add proper error handling to your AJAX callback:

public function updateSecondaryField(array &$form, FormStateInterface $form_state) {
  try {
    return $form['secondary_field_wrapper'];
  }
  catch (\Exception $e) {
    watchdog_exception('custom_ajax_form', $e);
    $response = new AjaxResponse();
    $response->addCommand(new ReplaceCommand(
      '#secondary-field-wrapper',
      '<div class="messages messages--error">' . $this->t('An error occurred. Please try again.') . '</div>'
    ));
    return $response;
  }
}

This implementation provides a solid foundation for AJAX-based field updates in Drupal 8, with proper error handling, caching, and extensibility options.