Adding JavaScript code with Drupal Behaviors

3 minutes

In Drupal, the correct way to add JavaScript code is through Drupal behaviors, which execute every time the DOM is fully loaded, that is, when all the elements on the page are present.

Another moment when they execute is after an AJAX response, because these often alter the existing HTML on the page.

As you can see, a behavior and two arguments are sent:

Drupal.behaviors.myBehavior = {
  attach: function (context, settings) {
    // Using once() to apply the myCustomBehaviour effect when you want to run just one function.
    once('myCustomBehavior', 'input.myCustomBehavior', context).forEach(function (element) {
      element.classList.add('processed');
    });
  }
}

Context: Here is the DOM, the first time it loads the page will be the entire document, but when it's by AJAX only contains the inserted HTML.

Settings: All the "Settings" sent from the PHP code.

Using the context variable correctly

As I explained earlier, the context variable contains the DOM and it's necessary to use it wisely to avoid unusual situations, for example when you're adding an event listener to a button like this:

document.querySelector('#my-button').addEventListener("click", function (e) {
  // code to run
});

If this code is inside a behavior, it's possible that it executes more than once, then you would be adding this event multiple times to the button and when you click it, the function would also execute multiple times, causing unwanted effects.

The correct thing to do in this case is to use the context variable so that your selector does not look for the element in the entire document, but only in the inserted DOM, so each time this behavior is executed, your events will not be added to the existing elements.

Just by replacing document with context your code will be fine:

context.querySelector('#my-button').addEventListener("click", function (e) {
  // code to run
});

Why they execute with each AJAX response?

A simple example is when you have a form that shows a selector list of countries and another for cities that contains only those of the selected country, in Drupal this is achieved using AJAX in the form elements so that when the country changes, the list of cities is obtained and replaced.

Now think about wanting to use some JS library that makes the lists look special, something that is very common to unify the styles of the form elements.

Normally such libraries require you to run some function on the elements and if this were only when the page loads, then when the user changes the country and the cities selector is re-generated, this will lose its improved appearance.

That's why it's important to run the code again, but only on the elements that were added or replaced in the page, so that the JS code that improves them applies to them again.

The next would be the code and I'll explain it step by step:

Drupal.behaviors.selectLists = {
  attach: function (context, settings) {
    context.querySelectorAll('select').forEach(function (select) {
      styleSelect(select);
    });
  }
}
  1. The Behavior must have a name in camelCase format, in this case it is selectLists.
  2. You can use a selector that points to all the select elements on your page and that iterates over each one.
  3. Using the context variable, when loading the page, it will find all the select elements, when the country changes and the cities list is re-loaded this will also be found within context, which will make it also be found without affecting the elements loaded initially.
  4. It executes the styleSelect function on each element found.

Related to Behaviors, there is another way to force the code to run only once, which I explained in another article about Drupal Once.

Conclusion

Running JavaScript code in Drupal is something that is needed with great frequency, and using the Drupal.Behaviors correctly, you'll save yourself many headaches and make the work easier.

Jidrone Drupal Developer
J. Ivan Duarte
Drupal Senior Developer

Share