Drupal 7: Triggering an AJAX callback externally in the Form API

Atomox picture Atomox · Sep 11, 2012 · Viewed 7.9k times · Source

In a Nutshell:

I'm trying to trigger an external event via jQuery, causing an AJAX callback on a Drupal form to be executed. This rebuilds several chained elements. It works once, but then breaks until the form is refreshed by a different element in Drupal.

The form_state detects the proper element as the triggered element on the first try, but defaults to the first available button in the form_state['buttons'] array after that. Any thoughts?

In Detail:

I have a complicated Drupal form that looks like this:

A (dropdown)

B (actually B_key and B_literal, 2 text fields) C (dropdown) D (dropdown) E (dropdown)

Where A refreshes B, B refreshes C, C refreshes D, and so on. With the exception of B, each of these are drop down elements. Each item is a key => value pair, where we use the key to rebuild the options in the elements it refreshes. So, the key of B refreshes the options in C, as C's key is used for the options of D.

These are entirely built as Drupal AJAX callbacks, and work properly. However, B is not a select, but 2 separate text fields, 1 a key, and 1 a literal. We do this so that we can do an AJAX autocomplete lookup for B, because the list is too large in size to use a select element.

I have written an external jQuery script which performs this lookup, and populates these two fields (B_key and B_literal). Once we populate them, we trigger a custom event, referenced by the filed's 'ajax' => 'event' => 'hs_changed', which we trigger manually in our jQuery with a jQuery('[name=b_literal]').trigger(). This works the first time I do this, causing C to rebuild with a new set of options.

However, once this happens once, it no longer rebuilds. I traced out the form_state, and noticed that when selecting a new value for B, the form state thinks a button has been clicked (whichever is the first button in the form_state['buttons'] array), causing this to fail.

Now, when I reload A, this usually has a pre-set value for B, so all children fields (B, C, D, E) refresh properly. Once this happens, I can then manually select B again, and it works as written once, before failing if we try again. It should be noted that my jQuery does not have a binding issue, as it is still able to find the fields for B, and updated the values. It's manually triggering the custom event (to trigger the drupal field's ajax) which fails each time after our first trigger.

Here's our code:

The form:

$form['a'] = array (
  '#type' => 'select',
  '#options' => $a_options,
  '#default_value' => $a_selected,
  '#required' => TRUE,
  '#prefix' => '<div id="a_wrapper">',
  '#suffix' => '</div>',
  '#ajax' => array(
    'callback' => 'a_ajax_callback',
    'method' => 'replace',
    'effect' => 'fade',
  ),
);

$form['b_key'] = array (
  '#type' => 'textfield',
  '#default_value' => $hierarchy['b_key'],
);

$form['b_literal'] = array (
  '#type' => 'textfield',
  '#default_value' => $hierarchy['b_literal'],
  '#size' => 32,
  '#required' => TRUE,
  '#ajax' => array(
    'callback' => 'b_ajax_callback',
    'method' => 'replace',
    'effect' => 'fade',
    'event' => 'hs_changed',
  ),
  '#prefix' => '<div id="b_wrapper">',
  '#suffix' => '</div>',
);

$form['c'] = array (
  '#type' => 'select',
  '#default_value' => $hierarchy['c'],
  '#empty_option' => '- Select -',
  '#options' => get_children_options_array($hierarchy['b_key'], TRUE),
  '#ajax' => array(
    'callback' => 'c_ajax_callback',
    'method' => 'replace',
    'effect' => 'fade',
  ),
  '#required' => TRUE,
  '#prefix' => '<div id="c_wrapper">',
  '#suffix' => '</div>',
);

//Assume D and E are identical to C

The Callback:

 function a_ajax_callback($form, &$form_state) {

   //Return the field to be rebuilt by the AJAX request  $commands = array();
   $commands[] = ajax_command_replace("#b_key_wrapper", render($form['b_key']));
   $commands[] = ajax_command_replace("#b_literal_wrapper", render($form['b_literal']));

   //We also unset a bunch of form_state['input'] values and such here

   return array('#type' => 'ajax', '#commands' => $commands);
 }

 //The b_ajax_callback and c_ajax_callback are pretty much identical

The jQuery:

jQuery(document).on("focus", '[name=my_search]:not(.ui-autocomplete-input)', function() { 
  jQuery(this).autocomplete({
    source: function(req, res) {
      ch_search(req, res);
    },
    minLength: 3,
    select: function(event, ui) {
      ch_render_dropdowns(ui.item.id);
    }
  }); 
});

function ch_render_dropdowns(ch_node) {

  var token = jQuery('[name=ajax_token]').val();

  jQuery.ajax({
    url: '/ajax/myFunction/' + ch_node
    async: false,
    cache: false,
    data: {
      'token': token
    },
    success: function(data) {

      jQuery('#autoComplete_logic').html(data);
      jQuery('#autoComplete_logic').hide();

      // Once we have the initial results, fill in the d7 form fields
      // where we have values.
      var b_key = jQuery("#b_key").val();
      var b_name = jQuery("#b_literal").text();

      // Set our Drupal B Fields
      jQuery('[name=b_key]').val(b_key);
      jQuery('[name=b_literal]').val(b_name);

      //Trigger the refresh of all submenus in Drupal
      //This triggers the AJAX callback attached to the b_literal 
      //to rebuild c, d, and e.
      jQuery('[name=b]').trigger('hs_changed');
    }
  });
}

I have simplified everything down, as there is a lot of logic out of the purview of this issue. The autocomplete is not the issue. Triggering the b_literal field's attached Drupal AJAX to activate, causing a refresh of c, d, and e, does not work after the first time, unless we hit field a, which refreshed b, c, d, and e internally via Drupal's Form API AJAX logic.

Form_state ( properly executed the first time):

[triggering_element] => Array ( [#type] => textfield [#title] => B [#description] => [#default_value] => [#size] => 32 [#maxlength] => 255 [#required] => 1 [#disabled] => [#ajax] => Array ( [callback] => b_ajax_callback [method] => replace [effect] => fade [event] => hs_changed )

        [#prefix] => <div id="b_literal_wrapper">
        [#suffix] => </div>
        [#input] => 1
        [#autocomplete_path] => 
        [#process] => Array
            (
                [0] => ajax_process_form
            )

        [#theme] => textfield
        [#theme_wrappers] => Array
            (
                [0] => form_element
            )

        [#pre_render] => Array
            (
                [0] => ctools_dependent_pre_render
            )

        [#defaults_loaded] => 1
        [#tree] => 
        [#parents] => Array
            (
                [0] => b
            )

        [#array_parents] => Array
            (
                [0] => b
            )

        [#weight] => 0.002
        [#processed] => 
        [#attributes] => Array
            (
            )

        [#title_display] => before
        [#id] => edit-b--4
        [#name] => b_literal
        [#value] => 
        [#needs_validation] => 1
    )

Form_state ( improperly executed the second time, and every time after):

[triggering_element] => Array
    (
        [#type] => submit
        [#value] => Submit
        [#weight] => 100
        [#attributes] => Array
            (
                [class] => Array
                    (
                        [0] => btn
                        [1] => btn-primary
                    )

            )

        [#after_build] => Array
            (
                [0] => _load_assets_b_select
            )

        [#input] => 1
        [#name] => op
        [#button_type] => submit
        [#executes_submit_callback] => 1
        [#limit_validation_errors] => 
        [#process] => Array
            (
                [0] => ajax_process_form
            )

        [#theme_wrappers] => Array
            (
                [0] => button
            )

        [#defaults_loaded] => 1
        [#tree] => 
        [#parents] => Array
            (
                [0] => submit
            )

        [#array_parents] => Array
            (
                [0] => submit
            )

        [#processed] => 
        [#required] => 
        [#title_display] => before
        [#id] => edit-submit--2
    )

[clicked_button] => Array
    (
        [#type] => submit
        [#value] => Submit
        [#suffix] => </div></section>
        [#weight] => 100
        [#attributes] => Array
            (
                [class] => Array
                    (
                        [0] => btn
                        [1] => btn-primary
                    )

            )

        [#after_build] => Array
            (
                [0] => _load_assets_b_select
            )

        [#input] => 1
        [#name] => op
        [#button_type] => submit
        [#executes_submit_callback] => 1
        [#limit_validation_errors] => 
        [#process] => Array
            (
                [0] => ajax_process_form
            )

        [#theme_wrappers] => Array
            (
                [0] => button
            )

        [#defaults_loaded] => 1
        [#tree] => 
        [#parents] => Array
            (
                [0] => submit
            )

        [#array_parents] => Array
            (
                [0] => submit
            )

        [#processed] => 
        [#required] => 
        [#title_display] => before
        [#id] => edit-submit--2
    )

Answer

umesh picture umesh · Dec 10, 2012

I think that there is an issue when you are using autocomplete by jquery. Instead of that use #autocomplete_path Given in the Drupal 7 Form API, or use the following link it might solve your problem #autocomplete_path