Skip to main content

Drupal Integration with JavaScript & jQuery, knowledge base and few tips

The Drupal.behaviors object

Drupal uses a high-level principle: assets (CSS or JS) are still only loaded if you tell Drupal it should load them. Drupal does not load all assets (CSS/JS) on all pages, because this is bad for front-end performance.

Configurable JavaScript is available with drupalSettings (the successor to Drupal 7's Drupal.settings). However, to make drupalSettings available to our JavaScript file: we have to declare a dependency on it. 

When using jQuery it is standard to wrap it using the functionality of Drupal like behaviors and once(), this will ensures that the code will run after the DOM has loaded and all elements are available.

This will ensure that your code runs both on normal page loads and when data is loaded by AJAX (or BigPipe!) - but not jQuery methods like load() which should be avoided as Drupal behaviors will fail to load for loading functions other than ajax(). 

The Drupal.behaviors object is itself a property of the Drupal object, and when we want our module/theme to add new behaviors, the best method is to simply extend this object.

Drupal.behaviors.myBehavior = {
  attach: function (context, settings) {
    // Use context to filter the DOM to only the elements of interest,
    // and use once() to guarantee that our callback function processes
    // any given element one time at most, regardless of how many times
    // the behaviour itself is called (it is not sufficient in general
    // to assume an element will only ever appear in a single context).
    once('myCustomBehavior', 'input.myCustomBehavior', context).forEach(
      function (element) {
        element.classList.add('processed');
      }
    );
  }
};

 

attach() method called when the DOM has loaded both initially and after any AJAX calls.

drupal.js has a $(document).ready() function which calls the Drupal.attachBehaviors() function, which in turn cycles through the Drupal.behaviors object calling every one of its properties, these all being functions declared by various modules as above, and passing in the document as the context.

On AJAX loads the same thing happens except the context is only the new content that the AJAX call loaded. (And since BigPipe internally builds on top of the AJAX system, anything that works with the AJAX system will also work with BigPipe.)

Using once with "context" is a good practice because then only the given context is searched and not the entire document. This becomes more important when attaching behaviors after an AJAX request.

To globally run functions only once on page load, use the 'html' or 'body' as the selector used in the once call: once('my-global-once', 'html'). Behaviors on this context are only fired once by Drupal, it is Drupal equivalent to $(document).ready( myInit() );

Drupal.behaviors.myBehavior = {
  attach: function (context, settings) {
    once('myBehavior', 'html').forEach(function (element) {
      myFunction(element);
    })
  }
}

Since Drupal uses jQuery.noConflict() and only loads JavaScript files when required, to use jQuery and the $ shortcode for jQuery you must include jQuery and Drupal as dependencies in the library definition in your MODULE.libraries.yml and add a wrapper around your function.

(function ($, Drupal, once) {
  Drupal.behaviors.myModuleBehavior = {
    attach: function (context, settings) {
      once('myCustomBehavior', 'input.myCustomBehavior', context).forEach(function (element) {
        // Apply the myCustomBehaviour effect to the elements only once.
      });
    }
  };
})(jQuery, Drupal, once);

To add once as an explicit dependency of a custom library, add core/onceas shown below in MODULE.libraries.yml or THEME.libraries.yml:

foobar:
  js:
    js/foobar.js: {}
  dependencies:
    - core/drupal
    - core/jquery
    - core/once