Last Updated: September 09, 2019
·
10.26K
· pbuyle

JavaScript, jQuery and DOM Ready in Drupal 7

tl;dr: JavaScript code to process elements on page load on a Drupal 7 site should looks like this:

(function($) {
  Drupal.behaviors.doSomething = {
    attach: function(context, settings) {
      $('div.something', context).once('do-something').doSomething({
        param1: settings.somethingl.param,
        param2: 'something else'
      });
    }
  }
})(jQuery);

Drupal 7 provides jQuery in the no-conflict mode, which means that $ is not the jQuery object/namespace. This should not be an issue with properly written jQuery plugins that follow jQuery's plugins authoring guidelines. This is however an issue for code snippets mindlessly copy/pasted from random web pages. As most of them expect $ to be the jQuery namespace. This can be easily solved by wrapping theses snippets in immediately invoked anonymous function that will alias the jQuery namespace to $:

(function($) {
    // Here $ is the jQuery namespace.
})(jQuery);

Usually, JavaScript code that needs to run at page load, is also wrapped in a function passed as argument to jQuery or jQuery(document).ready:

$(function() {
  // Code here is executed when the DOM is loaded.
});

When combined, these two patterns are perfectly fine, even with Drupal. However if content (ie. new DOM elements) is added to the page after page load (AJAX calls, content generated from JavaScript, etc.) the code in such functions will never be able to process the added elements. Or if some portion of the content is removed or moved across the page, the code will have no option to unregistered event handlers or update information about the already processed elements. Drupal provides an API for this called behaviors. Using behavior is not required, but strongly recommended as a best practice to avoid future headaches (when code written six months ago starts behaving strangely when a contrib module is added to the project). A behavior is written like this:

Drupal.behaviors.behaviorName = {
  attach: function (context, settings) {
    // Do something.
  },
  detach: function (context, settings, trigger) {
    // Undo something.
  }
};

The attach function of all registered behaviors (all properties of the Drupal.behaviors object) will be invoked when behavior should be added to elements, either when the DOM is ready (ie. page laod) and when elements are added to the DOM. The detach function will be called when behaviors should be detached from elements: just before elements are removed from the DOM, moved in the DOM or a form is submitted. The context parameter will always be a parent of the added elements, the single added/removed/moved element itself or the document element. The settings parameters will be the settings for the context, usually the Drupal.settings object as set by calls to drupal_add_js() from PHP. For detach, the trigger parameter will contains the kind of event that triggered the call: 'unload' (elements removed), 'move' (elements moved) or 'serialize' (form is being submitted).

The attach (and detach) functions of a behavior can be used multiple time over the same portion of the DOM tree. So the same element could be processed multiple time by the same code. It is up to the code itself to avoid processing (ie. binding event handlers, altering CSS styles, etc.) multiple times for the same elements. The easiest solution for this is to use the jQuery Once plugin (which is provided by Drupal 7) like this:

$(selector).once('behavior-name').doSomething();
$(selector).once('behavior-name', function(){ /*do something*/ });

Since a behavior is being attached/detached to/from a context, the context object can be used to restrict your jQuery queries to only the affected element or DOM subtree, like this:

$(selector, context).doSomething();

Putting all this together means the base pattern to process elements on page load should looks like this:

(function($) {
  Drupal.behaviors.doSomething = {
    attach: function(context, settings) {
      $('div.something', context).once('do-something').doSomething({
        param1: settings.somethingl.param,
        param2: 'something else'
      });
    }
  }
})(jQuery);

References:

5 Responses
Add your response

Excellent article, I didn't know about jQuery "once" function ! Thanks !

over 1 year ago ·

1000 Views, I've made it, but really its a very nice and clean article thanks for sharing :)

over 1 year ago ·

Good article Pierre!

Do you think using the combination of Jquery.ready and the Jquery on() method is beeter than drupal.behaviors?.

I think the jQuery on() method has better performance and it is more powerfull.

over 1 year ago ·

Spot on - thanks for the great reference. If I was being incredibly pernickety then I would say that you should have a semi-colon after the penultimate } in your example code at the top of the page.

over 1 year ago ·

@angel alvarado
The attach method of a behavior is only a substitute to jQuery.ready(). In the attach method of a behavior, you can still uses jQuery's .on() to bind handlers, even with event delegation (using .delegate() in older jQuery version, or .on() in jQuery 1.7+). The only case where not using a behavior would make sense is to attach an handler to the document element. Any other element, including the body, could be added/replaced in JavaScript, so a behavior is needed to ensure whoever add/replace the element after the DOM ready event can still get your event handler attached. So I think it is safer and easier to always use behaviors as replacement of jQuery.ready().

over 1 year ago ·