Hướng dẫn viết module Ajax in Forms trên Drupal 8

Why reload the whole page, when you can just update certain parts of the DOM? Ajax allows you to do just this, to dynamically update content. Just one of the many great uses of Ajax is Form Validation. In this example, we will see how to implement this.

We will be making a simple form which will contain a text field that will validate if the username entered exists, and a button that will replace the text field value with a random existing username.

Building The Form

First, we need to define our two form elements:

$form['user_name'] = array(
  '#type' => 'textfield',
  '#title' => 'Username',
  '#description' => 'Please enter in a username',
);

$form['random_user'] = array(
  '#type' => 'button',
  '#value' => 'Random Username',
);

Next, to start using Ajax in Drupal, all you need to specify is the “callback”, or function to call, when the “event”, or trigger, is fired on that certain form element, in an array under the “#ajax” key:

$form['user_name'] = array(
  '#type' => 'textfield',
  '#title' => 'Username',
  '#description' => 'Please enter in a username',
  '#ajax' => array(
    // Function to call when event on form element triggered.
    'callback' => 'Drupal\ajax_example\Form\AjaxExampleForm::usernameValidateCallback',
    // Javascript event to trigger Ajax. Currently for: 'onchange'.
    'event' => 'change',
);

In the “callback”, include the full namespaced class and function you want to call. The event can be any Javascript event without the “on”. A list of Javascript events can be found here.

Once you have added these two keys, you can add extra options such as “effect”, and “progress”. More options can be found on the Ajax API. Here are the finished elements:

$form['user_name'] = array(
  '#type' => 'textfield',
  '#title' => 'Username',
  '#description' => 'Please enter in a username',
  '#ajax' => array(
    // Function to call when event on form element triggered.
    'callback' => 'Drupal\ajax_example\Form\AjaxExampleForm::usernameValidateCallback',
    // Effect when replacing content. Options: 'none' (default), 'slide', 'fade'.
    'effect' => 'fade',
    // Javascript event to trigger Ajax. Currently for: 'onchange'.
    'event' => 'change',
    'progress' => array(
      // Graphic shown to indicate ajax. Options: 'throbber' (default), 'bar'.
      'type' => 'throbber',
      // Message to show along progress graphic. Default: 'Please wait...'.
      'message' => NULL,
    ),
  ),
);

$form['random_user'] = array(
  '#type' => 'button',
  '#value' => 'Random Username',
  '#ajax' => array(
    'callback' => 'Drupal\ajax_example\Form\AjaxExampleForm::randomUsernameCallback',
    'event' => 'click',
    'progress' => array(
      'type' => 'throbber',
      'message' => 'Getting Random Username',
    ),
    
  ),
);

Creating The Callbacks

After creating our form elements, it is time to create the callback functions which will return the response of what to update on the page.

These callbacks will return an instance of \Drupal\Core\Ajax\AjaxResponse. Each AjaxResponse instance will contain jQuery commands that will execute on the form. You can use the “addCommand()” method on AjaxResponse to add commands that implement \Drupal\Core\Ajax\CommandInterface.

Some commands such as CssCommand and ChangedCommand did not work. Thankfully, there is InvokeCommand which allows you to run any jQuery command. You can construct it with a jQuery selector, method, and arguments:

public InvokeCommand::__construct($selector, $method, array $arguments = array())

Here are the two callbacks for our form

public function usernameValidateCallback(array &$form, FormStateInterface $form_state) {
  // Instantiate an AjaxResponse Object to return.
  $ajax_response = new AjaxResponse();
 
  // Check if Username exists and is not Anonymous User ('').
  if (user_load_by_name($form_state->getValue('user_name')) && $form_state->getValue('user_name') != false) {
    $text = 'User Found';
    $color = 'green';
  } else {
    $text = 'No User Found';
    $color = 'red';
  }
 
  // Add a command to execute on form, jQuery .html() replaces content between tags.
  // In this case, we replace the desription with wheter the username was found or not.
  $ajax_response->addCommand(new HtmlCommand('#edit-user-name--description', $text));
 
  // CssCommand did not work.
  //$ajax_response->addCommand(new CssCommand('#edit-user-name--description', array('color', $color)));
 
  // Add a command, InvokeCommand, which allows for custom jQuery commands.
  // In this case, we alter the color of the description.
  $ajax_response->addCommand(new InvokeCommand('#edit-user-name--description', 'css', array('color', $color)));
 
  // Return the AjaxResponse Object.
  return $ajax_response;
}

public function randomUsernameCallback(array &$form, FormStateInterface $form_state) {
  // Get all User Entities.
  $all_users = entity_load_multiple('user');
 
  // Remove Anonymous User.
  array_shift($all_users);
 
  // Pick Random User.
  $random_user = $all_users[array_rand($all_users)];

  // Instantiate an AjaxResponse Object to return.
  $ajax_response = new AjaxResponse();
 
  // ValCommand does not exist, so we can use InvokeCommand.
  $ajax_response->addCommand(new InvokeCommand('#edit-user-name', 'val' , array($random_user->get('name')->getString())));
 
  // ChangedCommand did not work.
  //$ajax_response->addCommand(new ChangedCommand('#edit-user-name', '#edit-user-name'));
 
  // We can still invoke the change command on #edit-user-name so it triggers Ajax on that element to validate username.
  $ajax_response->addCommand(new InvokeCommand('#edit-user-name', 'change'));
 
  // Return the AjaxResponse Object.
  return $ajax_response;
}

Finished Form

Here is our finished Ajax Example Form:

Drupal Core System.