Stop using jQuery.once

3 minutes

The web is gradually moving away from using jQuery, mainly because JavaScript in its new versions includes many of the things we used to use jQuery for, here is a link to a very interesting site that shows you how you can replace certain jQuery functions with pure JavaScript https://youmightnotneedjquery.com/.

What is jQuery.once used for?

Before explaining this, it is necessary to understand that to integrate our JavaScript code into Drupal, it is recommended to use the Drupal.behaviors, which makes it execute when the page has finished loading or when new elements are added to the DOM.

This means that if new elements are added to the page via AJAX, your code will be executed again, which is useful in many cases, but in other cases, you really need it to only be executed once. For example, on this site, I use the sticky-js library to fix the sidebar information, as it is a specific block that I want to fix, it is necessary for the JavaScript to be executed only once.

How is jQuery.once used?

When defining our library in the *.libraries.yml, we must have core/jquery and core/jquery.once as dependencies

my-library:
  version: 1.x
  js:
    my-library.js: {}
  dependencies:
    - core/drupal
    - core/jquery
    - core/jquery.once

And our js uses it like this:

(function ($, Drupal) {
  Drupal.behaviors.sticky = {
    attach: function (context, settings) {
      $('#' + 'body', context).once('sticky', function () {
        var sticky = new Sticky('.sticky-el');
      });
    }
  };
}(jQuery, Drupal));

What this code does is add a class to the element where sticky is applied, so the next time the Drupal behaviors are executed, this class will be found and the code will not be executed again.

Can we do it without jQuery?

(function (Drupal) {
  Drupal.behaviors.sticky = {
    attach: function (context, settings) {
      const body = context.querySelector('body');
      if (!body.classList.contains('sticky-processed')) {
        var sticky = new Sticky('.sticky-el');
        body.classList.add('sticky-processed');
      }
    }
  };
 })(Drupal);

Looks a bit more code, but it's doing exactly the same thing without depending on jQuery, so we can leave the dependency in our library definition.

The new once API

Since Drupal 9.2.0, we can use this new API that is included in the core, it's the replacement for jQuery.once, and our code would be like this.

To define our library, we must have core/once as the dependency

my-library:
  version: 1.x
  js:
    my-library.js: {}
  dependencies:
    - core/drupal
    - core/once

And our js uses it like this:

(function (Drupal, once) {
  Drupal.behaviors.sticky = {
    attach: function (context) {
      once('sticky', 'body', context).forEach(function () {
        var sticky = new Sticky('.sticky-el');
      });
    }
  };
 })(Drupal, once);

The once function can receive up to 3 arguments, although only 2 are required:

  1. A identifier.
  2. A selector for the element where it will be applied, which can be written as any CSS selector.
  3. (Optional) a context, in my case I prefer to use it so that my code doesn't have to search every time in the whole document.

Conclusion

With the aim of improving the performance of your site, it is better to avoid depending on jQuery and start using the tools that Drupal and JavaScript offer in their new versions.

Jidrone Drupal Developer
J. Ivan Duarte
Drupal Senior Developer

Share