4

How should I go about making my custom Drupal 7 Form element support AJAX events, just like other standard FAPI elements?

For example, with a drop-down list, I would do this:

$element['book'] = array(
  '#type' => 'select',
  '#title' => t('Book'),
  '#options' => $books,
  '#default_value' => $default_book,
  '#ajax' => array(
    'wrapper' => 'replace-me',
    'callback' => 'replacement-callback',
    'method' => 'replace',
  ),
);

But now that '#type' is my own custom element, the '#ajax' property is ignored. This is what I have implemented thus far:

function mymodule_element_info() {
  return array(
    'customelement' => array(
      '#input'          => TRUE,
      '#process'        => array('customelement_process'),
      '#theme'          => array('customelement'),
      '#theme_wrappers' => array('form_element'),
      '#tree'           => TRUE,
    ),
  );
}

function mymodule_theme($existing, $type, $theme, $path) {
  return array(
    'customelement' => array(
      'render element' => 'element',
    ),
  );
}

function theme_customelement($variables) {
  // render some elements
  return $output;
}

function form_type_customelement_value($element, $input = FALSE, &$form_state) {
  // manipulate form values
  return $input
}

function customelement_process($element, &$form_state, $complete_form) {

  // build the compound form array
  $element['#tree'] = TRUE;

  $element['book'] = array(
    '#type' => 'select',
    '#title' => t('Book'),
    '#options' => $books,
    '#required' => true,
  );

  $element['chapter'] = array(
    '#type' => 'textfield',
    '#title' => t('Chapter'),
    '#size' => 3,
    '#maxlength' => 3,
    '#required' => true,
  );

  return $element;

}

Using my custom element in a form goes like this:

function mymodule_menu() {
  $items = array();

  $items['path/to/page'] = array(
    'title'            => 'Test form',
    'type'             => MENU_LOCAL_TASK,
    'weight'           => 30,
    'page callback'    => 'drupal_get_form',
    'page arguments'   => array('mymodule_callback_testform'),
    'access callback'  => 'user_access',
    'access arguments' => array('mymodule_admin'),
  );

  return $items;
}

function mymodule_callback_testform() {
  $form = array();

  $form['test_element'] = array(
    '#type' => 'customelement',
    '#title' => 'Custom element test',
    '#required' => TRUE,
    '#ajax' => array(
      ... // this has no effect :(
    ),
  );

  return $form;
}

But unfortunately it doesn't "magically" support AJAX. I would like to trigger a Drupal AJAX event when any sub-element of my custom element has triggered certain events.

1

The custom element in the example is not a direct form element and act more as a wrapper (like a fieldset or item element) to create a compound form field. It's the same thing as putting an #ajax key on a fieldset, which does not make any sense for the form api. So, assuming that the trigger would be the select list, the only thing you need to do for it to work is to transfer the #ajax definition to the inside book element in the process function like this :

function customelement_process($element, &$form_state, $complete_form) {

  // build the compound form array
  $element['#tree'] = TRUE;

  $element['book'] = array(
    '#type' => 'select',
    '#title' => t('Book'),
    '#options' => array(1 => 1, 2 => 2),
    '#required' => true,
  );

  //If ajax exists, put the definition on the list element
  if(isset($element['#ajax'])) {
    $element['book']['#ajax'] = $element['#ajax'];
    unset($element['#ajax']);
  }

  $element['chapter'] = array(
    '#type' => 'textfield',
    '#title' => t('Chapter'),
    '#size' => 3,
    '#maxlength' => 3,
    '#required' => true,
  );

  return $element;
}

And then it will behave as expected. For an element that is not a compound element but totally custom, you have to set the event key of the #ajax definition inside the process function of your element. You will also have to add the ajax_process_form function. Here is an example :

function mymodule_element_info() {
  $elements = array();

  $elements['customelement'] = array(
    '#input' => 1,
    //Note the added ajax_process_form
    '#process' => ('customelement_process', 'ajax_process_form'),
    ....
  );

  return $elements;
}

function customelement_process($element) {
  // ... do whatever is needed to build the element

  if(isset($element['#ajax'])) {
    //Set the event to whatever is appropriate for the element
    $element['#ajax']['event'] = 'click';
  }
}

It's important to set the event key before the ajax_process_form because the latter function tries to figure out which event is best suited for the element and if it does not recognize the element it will return it without including the javascript libraries needed to perform the ajax call.

1

It has no effect because Drupal doesn't know what event to fire on.

If you look at the switch statement in ajax_pre_render_element() you can see that it has default events for all core element types. But it it doesn't know about your element type, and no event is provided, so it doesn't know how to continue. It returns the element as-is rather than attempt to make a decision its not qualified to make.

To fix you should just be able to pick an appropriate event for the code to fire on, e.g.

$form['test_element'] = array(
  '#type' => 'customelement',
  '#title' => 'Custom element test',
  '#required' => TRUE,
  '#ajax' => array(
    'event' => 'click',
    ...
  ),
);
  • This seemed like such an obvious solution that I accepted the answer before testing. But it still does not seem to trigger any AJAX activity. – rudolfbyker Jun 20 '15 at 8:17
  • @rudolfbyker Hmmm, I confess I didn't test this so there must be more to it. You can un-accept the answer, that way people will know you're still looking for a solution. I'll try to come back to this later and have another look, bit busy today – Clive Jun 20 '15 at 9:50
  • I'm curious if you got anywhere with this? If so, care to share? I'm struggling with the same issue. – mudddy Nov 20 '15 at 18:23
  • Nope. This is still in my issue queue. – rudolfbyker Nov 20 '15 at 19:22
0

It seemed to work for me to add 'ajax_process_form' to the #process tag of the element definition. Like this.

function mymodule_element_info() {
   return array(
      'customelement' => array(
      '#input'          => TRUE,
      '#process'        => array('customelement_process', 'ajax_process_form'),
      '#theme'          => array('customelement'),
      '#theme_wrappers' => array('form_element'),
      '#tree'           => TRUE,
    ),
  );
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.