Drupal 7 Prevent Duplicating JavaScript Behaviors

By shane
Mon, 2012-09-10 11:24
13 comments

Share with Others

You may have run into a situation in Drupal 7 in which you only need to apply a JavaScript behavior one time. In most cases your JavaScript seems to be executing fine, however you might notice that sometimes things seem to be executing two times or more. This may be getting triggered by some type of AJAX call that causes your JavaScript behaviors to get executed again. In this post we will examine how we can prevent these duplicate behaviors from being applied by using a very simple example.

You may also be looking for How to Add JavaScript to your Drupal 7 Module.

Simple Duplicate JavaScript Behavior Example

First we will look at a small piece of HTML. This is just a simple h3 element with a div. The goal is to have the div expand when the h3 element is clicked (there are many reasons why this is not the best approach for achieving this functionality, but this will be suitable for a simple example).

<h3 class="slider-trigger">Slider Trigger</h3>
<div class="slider">This is my slider text. This text will automatically expand when the Slider Trigger is clicked</div>

Now we add our JavaScript:

(function ($) {
  Drupal.behaviors.myCustomJS = {
    attach: function(context, settings) {
 
      $('h3.slider-trigger').click(function(){
        $('.slider').slideToggle();
      });
    }
  };
})(jQuery);

Now in most cases this would work great. However, if you have something that makes an AJAX call and the JavaScript behaviors get applied again. Then you may end up with a situation in which clicking the Slider Trigger h3 element, will cause the slider text to open and close, thus duplicating the sliding behavior. This is obviously an issue, luckily Drupal 7 makes it easy to fix.

Solution: Using Jquery Once

Drupal 7 integrates the Jquery Once plugin into Drupal 7 core. This plugin adds a simple class to the HTML element to ensure that the behavior is only processed one time. Here is how we would modify our JavaScript to work with the Jquery Once plugin.

(function ($) {
  Drupal.behaviors.myCustomJS = {
    attach: function(context, settings) {
      $('h3.slider-trigger').once('myslider', function() {
        $('h3.slider-trigger').click(function(){
          $('.slider').slideToggle();
        });
      });
    }
  };
})(jQuery);

This JavaScript will now add a myslider-processed class to the h3 slider-trigger element to ensure the Jquery Click handler is only added one time.

Do you have any other examples of when this has been useful? If so, leave them in the comments below.

Comments

The think the correct way would be:

(function ($) {
  Drupal.behaviors.myCustomJS = {
    attach: function(context, settings) {
      $('h3.slider-trigger', context).click(function(){
        $('.slider').slideToggle();
      });
    }
  };
})(jQuery);

It was pointed out to me in this post:
https://drupal.org/node/1667874

And I have been doing it this way ever since.

This is also a good solution, although I have had a few instances before where using the context variable did not quite work the way I needed it too. Either way, this is a good and valid option and should probably be used for simple examples like the one I posted above.

Thanks.

There is no garantee that the context will not contains already processed DOM elements. It should be used for performances (so you don't scan the whole document if only a few div where added), jQuery,once() should be used to avoid processing the same element multiple-times.

You can also use the context variable to determine what the context is.

if (context == document) {

This is when the document calls it.
Sometimes you only want to do something when for example a new page of an ajax view is loaded, than you can use something like:

if (context == $('.view-id-browse')) {

Once is also good, it just depends on the need.

I have not used the context variable this way before, but if it works like you documented (and I don't see why it wouldn't), it is definitely good information to have.

Thanks for the comment.

Thank you for this. I had a case where I am using attach() to add some code to the page. It was appearing more than once. Checking if context equaled document did the trick for me.

if (context == document) {
var menu_html = 'Menu';
$('a#logo').after(menu_html);
}

context method should be better. When using .once jQuery go through the DOM to check each item, whereas the context method ensures the area has new HTML with nothing attached, and the DOM is lighter.

context should be used for performances, but once() should still be used to garantee the same element will not be processed twice.

If I were to made a module that always update the content of two diffrent divs on the page, then I would use a sinlge call Drupal.attachBehaviors using the divs closest common parent. But then, each time I do it, a behavior that does not use once() but whose selector match that parent will process it multiple time.

So if you want to balance stability and performance, always uses context AND jQuery.once(). Not just one of them.

I was trying to fix this for awhile. I also tried attaching and detaching to drupal behaviors, that helped in some instances but problem persisted after form errors - jquery would fire onclick multiple times = (.

However this solution solved everything!! The big issue for me was that I perceived the "once" function to only get fired initially (only the first time you click then thats it). In reality it will fire every event but restrict from firing multiple times!

Thank you very much CodeKarate, you have taken the biggest bug out of my site!!

Post new comment