Ajax API Examples

Specs

Version
Drupal 7
Tools
Ajax
Form API
Created
22 Nov 2015

Summary

3 useful examples that demonstrate how to build Ajax links and forms.

  • Example 1: Basic Ajax link (URL example-ajax/example-1)
  • Example 2: Basic Ajax form (URL example-ajax/example-2)
  • Example 3: Multi-step Ajax form (URL example-ajax/example-3)

Code

1. Module file

MODULE_PATH/MODULE_NAME.module
The module file just defines the menu items and gives all Ajax replace commands the fade effect.
          
/**
 * Implements hook_menu()
 */
function MODULE_NAME_menu() {

  $items = array();

  $items['example-ajax/example-1'] = array(
    'title' => 'Example Ajax',
    'page callback' => 'MODULE_NAME_link_page',
    'access callback' => TRUE,
    'file' => 'MODULE_NAME.pages.inc',
  );

  $items['example-ajax/example-1/%/%'] = array(
    'title' => 'Example Ajax',
    'page callback' => 'MODULE_NAME_link_processing_page',
    'page arguments' => array(2, 3),
    'access callback' => TRUE,
    'file' => 'MODULE_NAME.pages.inc',
  );

  $items['example-ajax/example-2'] = array(
    'title' => 'Example Ajax',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('MODULE_NAME_example_2_form', 'arg1'),
    'access callback' => TRUE,
    'file' => 'MODULE_NAME.pages.inc',
  );

  $items['example-ajax/example-3'] = array(
    'title' => 'Example Ajax',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('MODULE_NAME_example_3_form', 'arg1'),
    'access callback' => TRUE,
    'file' => 'MODULE_NAME.pages.inc',
  );

  return $items;

}

/**
 * Implements hook_ajax_render_alter() to make all Ajax replace commands use the fade effect.
 * @param array $commands
 *   The Ajax commands
 */
function MODULE_NAME_ajax_render_alter(&$commands) {
  foreach ($commands as $key => $command) {
    if (isset($command['method']) && ($command['method'] == 'replaceWith')) {
      $commands[$key]['effect'] = 'fade';
    }
  }
}
          
          

2. Page callbacks file

MODULE_NAME.pages.inc
This file contains all the page callbacks defined in hook_menu().
          
<?php

/**
 *                      EXAMPLE 1: BASIC AJAX LINK
 */

/**
 * Page callback to build the Ajax link. It just needs to have the use-ajax class.
 */
function MODULE_NAME_link_page() {

  drupal_add_js(drupal_get_path('module', 'MODULE_NAME') . '/MODULE_NAME.js');

  // Too lazy to put this in a template.

  // add the "use-ajax" class to the link
  $output = l(t('Ajax Example Link'), 'example-ajax/example-1/argument_1/argument_2', array('attributes' => array('class' => array('use-ajax')))); // fancy Drupal way of writing <a href="" class="use-ajax"></a>
  $output .= '<div id="ajax-example-link-1-wrapper">Ajax Example State 1</div>';

  // Forms include the Ajax library out of the box. But links need the library added.
  drupal_add_library('system', 'drupal.ajax');

  return $output;

}

/**
 * Ajax page callback to process the Ajax commands.
 */
function MODULE_NAME_link_processing_page($arg1, $arg2) {
  
  $output = '<div id="ajax-example-link-1-wrapper">This div has been refreshed by Ajax.</div>';

  $commands = array();
  $commands[] = ajax_command_replace('#ajax-example-link-1-wrapper', $output);
  $commands[] = ajax_command_invoke(NULL, "MODULE_NAME_js_alert", array('This is an argument passed to jQuery from Drupal Ajax.'));
  
  $page = array('#type' => 'ajax', '#commands' => $commands);
  ajax_deliver($page);
  exit;

}

/**
 *                      EXAMPLE 2: BASIC AJAX FORM
 */

/**
 * Implements hook_form() to build form for example 2
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 * @param string $arg1
 *   An example of how to pass an argument from hook_menu()
 * @return array $form
 *   The form structure
 */
function MODULE_NAME_example_2_form($form, &$form_state, $arg1 = NULL) {
  
  $form['ajax_example_container'] = array(
    '#type' => 'container',
    '#tree' => TRUE, // Setting to true makes the $form_state['values'] array multidimensional. View the submit handler.
  );
  
  $form['ajax_example_container']['first_name'] = array(
    '#type' => 'textfield', 
    '#title' => t('First Name'), 
    '#size' => 60, 
    '#maxlength' => 128, 
    '#required' => TRUE,
    '#maxlength' => 60,
  );

  $form['ajax_example_container']['last_name'] = array(
    '#type' => 'textfield', 
    '#title' => t('Last Name'), 
    '#size' => 60, 
    '#maxlength' => 128, 
    '#required' => TRUE,
    '#maxlength' => 60,
  );

  $form['actions'] = array('#type' => 'actions');
  
  $form['actions']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit Form through Ajax'),
    '#submit' => array('MODULE_NAME_example_2_form_submit'),
    '#ajax' => array(
      'callback' => 'MODULE_NAME_example_2_ajax_callback',
      'wrapper' => 'example-ajax-example-2-wrapper',
      'method' => 'replace',
      'effect' => 'fade',
      'event' => 'click',
      'progress' => array(
        'type' => 'throbber',
        'message' => NULL,
      )
    ),
  );

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

  return $form;

}

/**
 * Example validation function
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_2_form_validate(&$form, &$form_state) {

  // validation logic here

  if (is_numeric($form_state['values']['ajax_example_container']['first_name'])) {
    form_set_error('first_name', t('Numbers are not allowed for a first name.'));
  }

}

/**
 * Submit handler function
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_2_form_submit(&$form, &$form_state) {
  $first_name = $form_state['values']['ajax_example_container']['first_name'];
  $last_name = $form_state['values']['ajax_example_container']['last_name'];

  drupal_set_message(t('You submitted %first_name and %last_name', array('%first_name' => $first_name, '%last_name' => $last_name)));
}

/**
 * Ajax callback to handle the Ajax commands.
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_2_ajax_callback($form, $form_state) {

  if (form_get_errors()) {
    return $form;
  }

  $commands = array();

  // Drupal messages are normally lost in an Ajax callback. Use this to display the message set by this form's
  // submit handler. It will print above the Ajax loaded content.
  $commands[] = ajax_command_prepend('#main', theme('status_messages'));

  // You could put the $output in a template.
  $output = '<div>' . t('This div has been replaced by an Ajax command.') . '</div>';
  $commands[] = ajax_command_replace('#example-ajax-example-2-wrapper', $output);

  $page = array('#type' => 'ajax', '#commands' => $commands);
  ajax_deliver($page);
  exit;

}

/**
 *                      EXAMPLE 3: MULTI-STEP AJAX FORM
 */

/**
 * Implements hook_form() to build form for example 3
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 * @param string $arg1
 *   An example of how to pass an argument from hook_menu()
 * @return array $form
 *   The form structure
 */
function MODULE_NAME_example_3_form($form, &$form_state, $arg1 = NULL) {

  if (!isset($form_state['step'])) {
    $form_state['step'] = 1;
  }
  
  $form['ajax_example_container'] = array(
    '#type' => 'container',
    '#tree' => TRUE, // Setting to true makes the $form_state['values'] array multidimensional. View the submit handler.
  );
  
  switch ($form_state['step']) {

    case 1:

      $form['ajax_example_container']['first_name'] = array(
        '#type' => 'textfield', 
        '#title' => t('First Name'), 
        '#size' => 60, 
        '#maxlength' => 128, 
        '#required' => TRUE,
        '#maxlength' => 60,
      );

      $form['ajax_example_container']['last_name'] = array(
        '#type' => 'textfield', 
        '#title' => t('Last Name'), 
        '#size' => 60, 
        '#maxlength' => 128, 
        '#required' => TRUE,
        '#maxlength' => 60,
      );

    break;

    case 2:
    case 3:
    case 4:
    case 5:
    default:
      $form['ajax_example_container']['form_field_' . $form_state['step']] = array(
        '#type' => 'textfield', 
        '#title' => t('Form Field !step', array('!step' => $form_state['step'])), 
        '#size' => 60, 
        '#maxlength' => 128, 
        '#required' => TRUE,
        '#maxlength' => 60,
      );
    break;

  }

  $form['actions'] = array('#type' => 'actions');

  if (($form_state['step'] > 1) && ($form_state['step'] < 5)) {
    $form['actions']['back'] = array(
      '#type' => 'submit',
      '#value' => t('Back'),
      '#submit' => array('MODULE_NAME_example_3_form_back_submit'),
      '#weight' => 0,
      '#ajax' => array(
        'callback' => 'MODULE_NAME_return_form_ajax_callback',
        'wrapper' => 'example-ajax-example-3-wrapper',
        'method' => 'replace',
        'effect' => 'fade',
        'event' => 'click',
        'progress' => array(
          'type' => 'throbber',
          'message' => NULL,
        )
      ),
    );
  }

  if ($form_state['step'] < 5) {
    $form['actions']['next'] = array(
      '#type' => 'submit',
      '#value' => t('Next'),
      '#submit' => array('MODULE_NAME_example_3_form_next_submit'),
      '#ajax' => array(
        'callback' => 'MODULE_NAME_return_form_ajax_callback',
        'wrapper' => 'example-ajax-example-3-wrapper',
        'method' => 'replace',
        'effect' => 'fade',
        'event' => 'click',
        'progress' => array(
          'type' => 'throbber',
          'message' => NULL,
        )
      ),
    );
  }

  if ($form_state['step'] == 5) {
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Submit'),
      '#submit' => array('MODULE_NAME_example_3_form_submit'),
      '#weight' => 20,
      '#ajax' => array(
        'callback' => 'MODULE_NAME_example_3_form_ajax_callback',
        'wrapper' => 'example-ajax-example-3-wrapper',
        'method' => 'replace',
        'effect' => 'fade',
        'event' => 'click',
        'progress' => array(
          'type' => 'throbber',
          'message' => NULL,
        )
      ),
    );
  }

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

  return $form;

}

/**
 * Example validation function
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_3_form_validate(&$form, &$form_state) {

  // validation logic here

  if (is_numeric($form_state['values']['first_name'])) {
    form_set_error('first_name', t('Numbers are not allowed for a first name.'));
  }

}

/**
 * Back button submit handler. Decrements the step number and modifies the $form_state array
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_3_form_back_submit(&$form, &$form_state) {
  $form_state['step']--;
  $form_state['rebuild'] = TRUE;

  // $form_state['values'] is lost between Ajax requests because they are left completely off the form when not present in one of the steps.
  // This function stores them in $form_state['storage'], the only part of the $form_state array that I think is safe to use through
  // Ajax requests. This line is used here, the next button submit handler, and the final submit handler.
  $form_state['storage']['ajax_example_container'][$form_state['step']] = $form_state['values']['ajax_example_container'];
}

/**
 * Next button submit handler. Increments the step number and modifies the $form_state array
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_3_form_next_submit(&$form, &$form_state) {
  $form_state['step']++;
  $form_state['rebuild'] = TRUE;
  $form_state['storage']['ajax_example_container'][$form_state['step']] = $form_state['values']['ajax_example_container'];
}

/**
 * Submit handler for example 3 form
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_3_form_submit(&$form, &$form_state) {
  $form_state['storage']['ajax_example_container'][$form_state['step']] = $form_state['values']['ajax_example_container'];

  // submission logic here

  drupal_set_message(t('This is a message that will be removed in the Ajax callback.'));

}

/**
 * Generic ajax return $form callback
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 * @return array $form
 *   The form structure
 */
function MODULE_NAME_return_form_ajax_callback(&$form, &$form_state) {
  return $form;
}

/**
 * Ajax callback to handle Ajax commands for final step.
 * @param array $form
 *   The form structure
 * @param array $form_state
 *   The form data
 */
function MODULE_NAME_example_3_form_ajax_callback(&$form, &$form_state) {

  if (form_get_errors()) {
    return $form;
  }

  $commands = array();

  // Flush out the status messages so they don't display on next page load. Useful when altering
  // other modules.
  $messages = drupal_get_messages();

  $output = '<div>' . t('This div has been replaced by an Ajax command. Values:') . '</div>';

  // Because ajax_example_container was set to #tree => TRUE, its values are multidimensional. So it can be iterated without
  // any special logic to check keys.
  foreach ($form_state['storage']['ajax_example_container'] as $step => $fields) {
    foreach ($fields as $key => $value) {
      if (!empty($value)) {
        $output .= t('You submitted %value for %key.', array('%key' => $key, '%value' => $value)) . '<br>';
      }
    }
  }

  $commands[] = ajax_command_replace('#example-ajax-example-3-wrapper', $output);

  $page = array('#type' => 'ajax', '#commands' => $commands);
  ajax_deliver($page);
  exit;

}

          
          

3. Javascript example

MODULE_NAME.js
An example of how to pass an argument to jQuery from ajax_command_invoke() in PHP
          
(function($) {
  $.fn.MODULE_NAME_js_alert = function(message) {
    alert(message);
  };
})(jQuery);
          
          

Comments

Placeholder: I'll extend node comments with ajax and other functionality here.