JavaScript API overview

Last updated on
10 December 2022

Since Drupal 8, the available JavaScript files, which were referenced in .info files in Drupal 7, are now referenced in .yml files. Also, stylesheets (CSS) and JavaScript (JS) are loaded through the same system as modules (code) and themes: asset libraries.

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. More info

Drupal.behaviors compared to jQuery(document).ready() for deferring execution

Example: Using jQuery(document).ready()

When using jQuery it is standard for a large majority of situations to wrap almost all code inside the jQuery(document).ready() function, like this:

jQuery(document).ready(function ($) {
  // Do some fancy stuff.
});

This ensures that our code will only run after the DOM has loaded and all elements are available.

Example: Using Drupal.behaviors

However, with Drupal there is an alternate better method; using the functionality of Drupal.behaviors and once(). If used properly, 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.

A really basic example:

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');
      }
    );
  }
};

Any object defined as a property of Drupal.behaviors will get its 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.)

Drupal Behaviors are fired whenever attachBehaviors is called. The context variable that is passed in can often give you a better idea of what DOM element is being processed, but it is not a sure way to know if you are processing something again. 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.

Detailed examples of using the "context" parameter with once are provided on npm package page once API.

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 Drupals equivalent to $(document).ready( myInit() );

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

Note: 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. So the whole JavaScript file would look something like this:

(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

The full documentation is available on npm package page once API.

See also:

Help improve this page

Page status: No known problems

You can: