---
title: "Understanding JavaScript behaviors in Drupal"
url: "/articles/understanding-javascript-behaviors-in-drupal"
type: article
date: 2015-02-03
updated: 2021-01-12
---

# Understanding JavaScript behaviors in Drupal

#  Understanding JavaScript behaviors in Drupal 

 Make your JavaScript more efficient by mastering Drupal behaviors.

 By

 [  Juampy NR   ](/about/juampy-nr) 

 February 3, 2015

  

I can barely remember the first time I added JavaScript to a Drupal page using a custom module. I'm sure looked at the documentation, took the example snippet, tweaked it, tested it, and moved on to something else. It was only later, using a debugger, that I saw how Drupal's "behavior" system worked and realized that my code was not being executed as I expected.

In this article, we'll cover the key facts about Drupal behaviors, then look at a real Drupal project to inspect its behaviors and optimize them.

## Unraveling Drupal behaviors

[Drupalâ&#128;&#153;s official JavaScript](https://www.drupal.org/node/756722) documentation suggests that modules should implement JavaScript by attaching logic to `Drupal.behaviors`. Here is an example taken from that page:

  ```

Drupal.behaviors.exampleModule = {
  attach: function (context, settings) {
    $('.example', context).click(function () {
      $(this).next('ul').toggle('show');
    });
  }
};
  
```

Drupal core will call attached behaviors when the [DOM](https://en.wikipedia.org/wiki/Document_Object_Model) (the object representation of the HTML) is fully loaded, passing in two arguments:

- context: which contains the DOM.
- settings: which contains all the settings injected server side.

We can confirm this at the following snippet extracted from Drupal coreâ&#128;&#153;s `misc/drupal.js`:

  ```

// Attach all behaviors.
$(function () {
  Drupal.attachBehaviors(document, Drupal.settings);
});
  
```

*NOTE: `$(function ()` is a shorthand for `$(document).ready()`. See <https://api.jquery.com/ready/> for further details.*

Now here is the point where I had that â&#128;&#156;aha!â&#128;&#157; moment the first time I watched the code execute in a debugger: `Drupal.attachBehaviors()` may be called more times under different circumstances after the DOM is loaded. For instance, Drupal core will also call `Drupal.attachBehaviors()` in these scenarios:

- After an administration overlay has been loaded into the page.
- After the [AJAX Form API](https://www.drupal.org/node/752056) has submitted a form.
- When an AJAX request returns a command that modifies the HTML, such as [ajax\_command\_replace()](https://api.drupal.org/api/drupal/includes%21ajax.inc/function/ajax_command_replace/7).

Furthermore, modules may call `Drupal.attachBehaviors()` as well. Here are some examples:

- CTools calls it after a modal has been loaded.
- Media calls it after the media browser has been loaded.
- Panels calls it after in-place editing has been completed.
- Views calls it after loading a new page that uses AJAX.
- Views Load More calls it after loading the next chunk of items.
- JavaScript from custom modules may call `Drupal.attachBehaviors()` when they add or change parts of the page.

The first time that `Drupal.attachBehaviors()` is called, the `context` variable contains the `document` object representing the DOM, but for the rest of the calls `context` will hold the affected piece of HTML. This is often overlooked by developers, leading to inefficient code that either causes tricky bugs or stresses the browser.

How can we make sure that existing Drupal behaviors take this into account? In the following section we will debug and analyze them in a real Drupal project.

## Navigating through Drupal behaviors

While working on a Drupal project, I like to open a page and debug which behaviors are executed to make sure that they are efficient. I set a breakpoint at `Drupal.attachBehaviors()`, then open a page and follow the code of each behavior to see what it is doing.

Letâ&#128;&#153;s take the [Syfy](https://www.syfy.com/) project as an example, where Lullabot helped with its redesign and launch. Using [Google Chrome Developer Tools](https://developer.chrome.com/devtools), we will open Syfyâ&#128;&#153;s homepage and set a breakpoint at the following line of coreâ&#128;&#153;s `misc/drupal.js`:

 ![Google Chrome Debugging Console](/sites/default/files/styles/max_900/public/assets/2015-09/attach.png.webp?itok=4-vJKbkI "attach.png")

After reloading the page, the debugger will kick in and stop code executionâ&#128;&#148;waiting for us to make a decision on what to do:

 ![Debugger started](/sites/default/files/styles/max_900/public/assets/2015-09/debugger.png.webp?itok=q78JtndN "debugger.png")

If you do not have experience using a debugger, have a look at the [Debugging JavaScript](https://developer.chrome.com/docs/devtools/) section for Google Chrome. A debugger lets you follow the code step by step inspecting the current variables and letting you write statements against the current context.

We said before that "Drupal will call all attached behaviors when the DOM is fully loaded, passing as arguments the DOM and all the settings injected server side.â&#128;&#157; Letâ&#128;&#153;s verify this in the debugger. Here we can see the `context` and `settings` variables at the `Scope Variables` panel:

 ![Scope Variables Panel](/sites/default/files/styles/max_900/public/assets/2015-09/variables.png.webp?itok=-ucyIoAn "variables.png")

Most custom behaviors are to be executed just once, so on this first loop things are normally fine. The tricky bit comes later, once all behaviors have been processed and something calls `Drupal.attachBehaviors()` again. Letâ&#128;&#153;s see an example.

## Testing behaviors on subsequent calls

Syfyâ&#128;&#153;s homepage has a View of tiles that uses Views Load More module to load extra items at the bottom. We will now scroll down and click on Load More to see what happens:

   

   

 ![Load More Button](/sites/default/files/styles/wide_xs/public/assets/2015-09/load_more.png.webp?itok=cbpHbHew "load_more.png")

Drupal calls `/views/ajax` to load the next list of tiles and once it has appended them to the DOM, it executes `Drupal.attachBehaviors()` with a subtle difference that we can discover when we inspect its variables in the debugger:

   

   

 ![Gotta love context](/sites/default/files/styles/wide_xs/public/assets/2015-09/context_aha.png.webp?itok=-7bTiPn2 "context_aha.png")

The `context` variable does not contain the full HTML document but the updated view. This difference is huge. Drupal behaviors that take the `context` variable into account when selecting portions of the DOM such as `$('#some-id', context)` will be quickly skipped since they wonâ&#128;&#153;t find what they are looking for. Unfortunately, it is a common mistake to overlook this and not use the `context` variable in jQuery selectors. Here is a behavior that I found while debugging behaviors in this second loop:

  ```

/**
  * Hide menu when Esc is pressed.
  */
Drupal.behaviors.syfyGlobalHideMenu = {
  attach: function (context, settings) {
    $(document).keyup(function (e) {
      if (e.keyCode == 27) {
        $('.nav-flyout', context).removeClass('js-flyout-active');
      }
    });
  }
};
  
```

It sets a listener at the `document` level to check if the pressed key was Esc in order to gracefully hide the main menu, which is expanded in the following screenshot:

   

   

 ![Syfy's menu expanded](/sites/default/files/styles/wide_xs/public/assets/2015-09/menu.png.webp?itok=bEIE_egw "menu.png")

What is the problem here? It uses the `context` variable to find the menu at `$('.nav-flyout', context)`, but it does not use `context` when it sets the keyup listener at `$(document).keyup(function(e)`. This means that every time Drupal behaviors are processed, it will add a new `keyup` listener.

## Making behaviors behave

There are a few ways to fix the above behavior so it sets a listener just once. Letâ&#128;&#153;s see each of them and alter the above behavior accordingly. Here they are.

### Using jQuery Once

This is the recommended approach at the [Drupal.org JavaScript documentation](https://www.drupal.org/node/756722#jquery-once). jQuery Once makes sure that something is processed just one time by adding a class on a DOM element after the code has run.

  ```

/**
  * Hide menu when Esc is pressed.
  */
Drupal.behaviors.syfyGlobalHideMenu = {
  attach: function (context, settings) {
    $('.nav-flyout', context).once('remove-modals', function () {
      $(document).keyup(function (e) {
        if (e.keyCode == 27) {
          $('.nav-flyout', context).removeClass('js-flyout-active');
        }
      });
    });
  }
};
  
```

The above code will add a class `remove-modals-processed` the first time it runs. The next time that `Drupal.attachBehaviors()` is called, `.once()` will find the class and skip.

### Using the context variable in the jQuery selector

Not using the context variable in jQuery selectors is the source of most of headaches related with debugging buggy JavaScript behaviors. Here is an example where we make use of the context variable in our selector:

  ```

/**
  * Hide menu when Esc is pressed.
  */
Drupal.behaviors.syfyGlobalHideMenu = {
  attach: function (context, settings) {
    $(document, context).keyup(function (e) {
      if (e.keyCode == 27) {
        $('.nav-flyout', context).removeClass('js-flyout-active');
      }
    });
  }
};
  
```

The above approach is simple and effective. jQuery will find the `document` object in the context variable only the first time that `Drupal.attachBehaviors()` is called. Note, however, that if you have custom code that calls `Drupal.attachBehaviors(document);` then the condition will be met and another listener will be bound.

### Going our own way

Our last alternative is ignoring Drupal's behavior system and simply waiting for the `document` object to be ready:

  ```

/**
  * Hide menu when Esc is pressed.
  */
$(function () {
  $(document).keyup(function (e) {
    if (e.keyCode == 27) {
      $('.nav-flyout').removeClass('js-flyout-active');
    }
  });
});
  
```

The above code will work as expected, and If you don't need access to the niceties of Drupal behaviors, it's a perfectly valid approach.

## Conclusion

The main concept that we should take into account is that Behaviors will be called first when the DOM is loaded, and may be called more times with a context representing new additions or changes to the DOM. Our code has to be crafted so it kicks in only when it is needed.

Did this article spark your curiosity to open a project and navigate through its behaviors? I hope it did. Open a page, dive through each behavior and ask yourself questions like when should this piece of code run? should it run when the DOM is ready or after some AJAX request completes? This mindset will help you to write sharp and efficient JavaScript.

 Published in:

- [  Drupal Development   ](/topics/drupal-development)
- [  Front-end Development   ](/topics/frontend-development)

 

   You must have JavaScript enabled to use this form.

## Get in touch with us

### Tell us about your project. We'd love to hear from you!

 First Name  

 Last Name  

 Your email address  

 Your organization  

 How you heard about us  

 Tell us more 

     Leave this field blank