1

I have an AJAX form. I'm using drupal_add_js() after AJAX calls to update 'settings' and pass new information to my Drupal.behaviour object. However, the results are being merged instead of replaced. I know this is by design but it's messing up my Javascript code because I end up with information from the last callback. Does anyone know of a good workaround for this?

Update:

Current workaround is overriding the code so settings is replaced rather than merged, though only for the page my form is in.

Drupal.ajax.prototype.commands.settings = function (ajax, response, status) {
  ajax.settings = response.settings;
};

Feels wrong, but things are working as expected.

Solution:

The final solution I used, as solved by Alice Heaton, isn't to override code in ajax.js, instead I returned both the form and an AJAX command in the forms AJAX callback:

function my_ajax_callback($form, $form_state) {
  $commands = array();
  $commands[] = ajax_command_settings(array(
    'key1' => 'value1',
    'key2' => 'value2',
  ));
  $output = drupal_render($form['locator']);
  $commands[] = ajax_command_insert(NULL, $output);

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

So when you use drupal_add_js to add settings during an ajax callback, this indeed merges the results. On the server side this is done in ajax_render (called from ajax_deliver or equivalent) which adds an ajax_command_settings command, instructing it to merge the results.

On the client side the merging is done by calling jQuery.extend, which causes the behaviour you have seen, and cannot be changed.

There is no work around if you want your results to be inserted into Drupal.settings. However you can pass your settings to your behaviour directly without using Drupal.settings. For this you would add a call to ajax_command_settings yourself. It would be something like this:

function mymodule_ajax_callback() {
  $commands = array();
  // ...
  $commands[] = ajax_command_settings(array(
    'key1' => 'value1',
    'key2' => 'value2',
  ));
  ajax_deliver(array(
    '#type' => 'ajax',
    '#commands' => $commands
  ));
}

Note that if your callback is called fom the Form API's #ajax['callback'] then it runs ajax_deliver on the value returned by the callback, so you shouldn't call it yourself.

On your script on the client side, your settings will be available as the second parameter of your behaviour attach method:

Drupal.behaviors.mymodule = {
  attach: function (context, settings) {
    // Here settings contains your settings
  }
};

(edit: sorry, previous answer was very wrong)

| improve this answer | |
  • Thanks for the reply. I have it the way you mention above, using key value pairs, but the values are arrays (ie 'key1' => array()) and it is those that are being merged (the keys are 0,1,2,3,etc). – Web Assistant Mar 22 '13 at 16:18
  • Also, and I could be wrong, but I think that only applies during PHP execution so modules can add to it. During the AJAX process it gets merged at the end as mentioned here api.drupal.org/api/drupal/includes!ajax.inc/function/…. Overriding Drupal.ajax.prototype.commands.settings in ajax.js, so it replaces rather than merges, does work, but not sure if that's the best way. – Web Assistant Mar 22 '13 at 16:47
  • Sorry, my answer was wrong - I hope you will find this a bit more helpful. – Alice Heaton Mar 22 '13 at 16:53
  • Thanks for the update answer. Sorry if I'm being thick but I can't use ajax_deliver() because my code is within a form, using the FAPI. Also, it does call jQuery.extend yes, but I've managed to override the object function where that resides, so it now replaces. This works, but not sure if it's right, or if it will break other functionality. Thanks again for your help (ˆ_ˆ) – Web Assistant Mar 22 '13 at 17:00
  • If you use the callback from the Form API (defined by #ajax['callback']) then it calls ajax_deliver on the value returned by the callback - so in that case your callback would just return the array I pass to ajax_deliver in my example. – Alice Heaton Mar 22 '13 at 17:07
2

As Alice Heaton mentioned its a problem with jQuery.extend, However there is an alternate way to replace the settings in Drupal.settings i.e Creating your own AJAX command. Here is the steps to create them,

  • Create a callback for your new AJAX command,

    function ajax_command_settings_replace($argument) {
      return array(
        'command' => 'settingsReplace',
        'settings' => $argument,
      );
    }
    
  • Extend the Drupal.ajax.prototype.commands to add method for your custom AJAX command

    Drupal.ajax.prototype.commands.settingsReplace = function (ajax, response, status) {
      ajax.settings = response.settings;
    };
    
  • Now use your own AJAX command

    function my_ajax_callback($form, $form_state) {
      $commands = array();
      $commands[] = ajax_command_settings_replace(array(
        'key1' => 'value1',
        'key2' => 'value2',
      ));
      $output = drupal_render($form['locator']);
      $commands[] = ajax_command_insert(NULL, $output);
    
      return array('#type' => 'ajax', '#commands' => $commands);
    }
    
| improve this answer | |
  • This is not necessary - if you look at the second argument of ajax_command_settings (api.drupal.org/api/drupal/includes!ajax.inc/function/…) you will see that you can set the merging mode (by default it's off). the problem is that when this is called as a by product of using drupal_add_js in an AJAX callback, then it's always set to merge. – Alice Heaton Mar 26 '13 at 15:37
1

If you hog the settings array by overwriting it with ajax_command_replace, no other modules may pass their settings to the user. This can be a problem if your form relies on other modules.

It seems the only way to include other modules is to allow AJAX to merge the settings variable. Which means none of the above solutions work. An alternative may be to simply check if your jquery script has done its work and then simply deleting the relevant setting. Then you can keep your custom settings in $form['#attached'].

(function ($) {
  Drupal.behaviors.my_behaviour = {
    attach: function(context, settings) {
      if (work_is_done()) {
        settings.mysetting = null;
      }
      if (settings.mysetting) {
        do_work();
      }
    }
  }
})(jQuery, Drupal);
| improve this answer | |
  • This approach is perfect ! I used it with the same context and it allowed me to get the fresh data. – Christophe CARON Mar 23 at 9:29
0

If the settings you don't need to merge are custom settings, there is an option to pass them to frontend as a JSON string and decode them from JS. It will const you some additional JSON processing, but will avoid issues with merging.

| improve this answer | |

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.