jQuery Plugin: triggerHandlers() - To Trigger Handlers On All Selected Elements

Posted August 2, 2011 at 10:34 AM by Ben Nadel

Tags: Javascript / DHTML

jQuery has a core method called trigger(). This method will trigger the given event on the selected elements and then propagate the event up the DOM before activating the default behavior (assuming it wasn't explicitly prevented somewhere in the bubbling phase). If you don't want to involve the bubbling and default behavior, jQuery also provides the method, triggerHandler(). This will trigger all the event handlers without any propagation; however, it will only do so on the very first element within the current collection.


 
 
 

 
  
 
 
 

Yesterday, on Twitter, Dan G. Switzer, II mentioned that it would be cool to find a compromise between these two methods; that it would be nice to keep the handler-only execution, but extend this execution to involve all selected elements.


 
 
 

 
Dan Switzer, II tweeting about jQuery's triggerHandler() method. 
 
 
 

I thought this would be a fun little augmentation to make to the jQuery library; so, I created a plugin called triggerHandlers() - notice the "s" for plural. This takes exactly the same arguments as the native triggerHandler() method; only, it doesn't limit the handler invocation to the first selected element.

To see this in action, let's take a look at a page that has an unordered list. In this demo, we're going to attach two "click" handlers to each LI in the list. This is to demonstrate that triggerHandler() still honors "immediate" propagation while preventing bubbling. Then, we'll create our triggerHandlers() plugin and invoke it on the collection of LI elements.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>jQuery Plugin: triggerHandlers()</title>
  •  
  • <!-- Include jQuery. -->
  • <script type="text/javascript" src="./jquery-1.6.1.js"></script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery Plugin: triggerHandlers()
  • </h1>
  •  
  • <ul class="friends">
  • <li>
  • Sarah
  • </li>
  • <li>
  • Joanna
  • </li>
  • <li>
  • Tricia
  • </li>
  • </ul>
  •  
  •  
  • <script type="text/javascript">
  •  
  •  
  • // Get a reference to the collection of friends.
  • var friends = $( "ul.friends" );
  •  
  • // Add an event to each of the friends.
  • friends.children().bind(
  • "click",
  • function( event ){
  •  
  • // Introduce yourself.
  • console.log(
  • "Hello, my name is",
  • $.trim( $( this ).text() )
  • );
  •  
  • }
  • );
  •  
  •  
  • // Add a subsequent event (of the same type) to each of
  • // the friends.
  • friends.children().bind(
  • "click",
  • function( event ){
  •  
  • // Excuse yourself.
  • console.log( "Sorry, but I really must go." );
  •  
  • }
  • );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // First, we're going to use the native triggerHandler()
  • // method. This will activate all of the handlers on the
  • // first element only. These events to not propagate.
  • console.log( "Native triggerHandler()" );
  • console.log( "---------------------------------------" );
  •  
  • // Trigger the handlers.
  • friends.children().triggerHandler( "click" );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Now, we're going to define a plugin for the "fn" namespace
  • // (which is the jQuery prototype reference). We'll create a
  • // triggerHandlers() (notice - plural) that will do the same
  • // thing, but on all elements.
  • $.fn.triggerHandlers = function( type, data ){
  •  
  • // Flag that we want to use handlers only. This will take
  • // care of all the bubbling and propagation limitations.
  • var handlersOnly = true;
  •  
  • // Loop over each element in the current collection to
  • // trigger the handlers on each.
  • this.each(
  • function(){
  •  
  • jQuery.event.trigger(
  • type,
  • data,
  • this,
  • handlersOnly
  • );
  •  
  • }
  • );
  •  
  • // Return this object reference for method chaining.
  • return( this );
  •  
  • };
  •  
  •  
  • // Now that we have defined our plugin, we're going to invoke
  • // the handlers on all selected elements.
  • console.log( "\nPLUGIN triggerHandlers()" );
  • console.log( "---------------------------------------" );
  •  
  • // Now, call triggerHandlers() on the children.
  • friends.children().triggerHandlers( "click" );
  •  
  •  
  • </script>
  •  
  • </body>
  • </html>

As you can see, the triggerHandlers() plugin turns around and invokes the native method:

jQuery.event.trigger( type, data, element, handlersOnly )

Both the core fn.trigger() and fn.triggerHandler() methods invoke this native method as well. The aspect of our code that makes the triggerHandlers() plugin work is the fact that it loops over each() element and passes in true as the fourth argument.

When we run the above code, we get the following console output:

Native triggerHandler()
---------------------------------------
Hello, my name is Sarah
Sorry, but I really must go.

PLUGIN triggerHandlers()
---------------------------------------
Hello, my name is Sarah
Sorry, but I really must go.
Hello, my name is Joanna
Sorry, but I really must go.
Hello, my name is Tricia
Sorry, but I really must go.

Notice that when we invoked the native triggerHandler() method, both "click" event handlers were invoked, but only on the first LI of the selected collection. Our jQuery plugin - triggerHandlers() - on the other hand, invoked both "click" event handlers on each of the selected LI elements.

Typically, when I invoke the native triggerHandler() method, I am doing so on a single element. As such, the first-selected-element limitation that jQuery imposes has never been a huge issue for me. But, I have definitely run into a few situations where it would be nice to activate the handlers over an entire set of elements. Luckily, jQuery's plugin architecture makes this incredibly easy.




Reader Comments

Aug 2, 2011 at 1:19 PM // reply »
2 Comments

Nice augmentation!


Aug 2, 2011 at 1:58 PM // reply »
170 Comments

@Ben:

For comparisons sake, here's what I ended up implementing yesterday:

// works like triggerHandler, but runs on all selected elements
$.fn.triggerHandlerAll = function (){
var self = this, args = arguments;
return self.each(function (i){
var $el = self.eq(i);
$el.triggerHandler.apply($el, args);
})
};

I decided not to use any internal methods--just in case they change the API in the future, so essentially my solution just loops through the collection and applies the triggerHandler() to each item.


Aug 2, 2011 at 2:13 PM // reply »
11,238 Comments

@Raghav,

Thanks :)

@Dan,

Yeah, that's probably more stable, long run. I was thinking of doing something along those lines; but, when I looked up to see how triggerHandler() was implemented, as-is, I decided to use the core trigger() method.

Also, I checked your blog this morning before I wrote this up to see if you had posted anything. I didn't want to step on any toes ... seeing as I got the idea from you :)


Aug 2, 2011 at 2:23 PM // reply »
1 Comments

Hey Ben, I'm with Dan! Please don't call

  • jQuery.event.trigger

directly. It's not part of the public API for jQuery and may change. In fact it has changed _very_ recently, as part of jQuery 1.6:

https://github.com/jquery/jquery/commit/235080e1256fc10468ce09b9d1e8db712c797f24

If people start depending on the behavior of jQuery internals, their code will eventually break and we'll face the cruel choice of either making jQuery better/faster or avoiding regressions on undocumented behavior.


Aug 2, 2011 at 2:32 PM // reply »
11,238 Comments

@Dave,

I'll agree with you guys. You raise a really valid point. When I was writing this up, that internal API thought never occurred to me. I may have thought the trigger() method was public... who knows. But, I like what you're saying.


Aug 2, 2011 at 3:01 PM // reply »
170 Comments

@Ben/Dave:

I've spent the better part of fixing and QA'ing changes to our application due to changes between jQuery v1.3.2 and v1.6.2.

Part of what I had to fix was code that relied on undocumented jQuery functions--which is why I choose to use the triggerHandler() in my method. It's a little more expensive processing-wise, but should be forward compatible.


Aug 5, 2011 at 9:20 AM // reply »
13 Comments

@Dan,

I was intrigued by your use of .eq for getting the jQuery objects inside your each loop. It got me thinking that it would be nice to roll that into an "eachjQ" method. Seems Ben Alman ( and probably others ) beat me to it.

.each2()
http://benalman.com/projects/jquery-misc-plugins/#each2

I tried revising a jsPerf test of .each2() against .eq() and $(this) but kept getting a error 101 on submitting the revision. Anyway in a few informal test in my Chrome console using .eq() ...

  • // works like triggerHandler, but runs on all selected elements
  • $.fn.triggerHandlerAll = function (){
  • var self = this, args = arguments;
  • return self.each(function (i){
  • var $el = self.eq(i);
  • $el.triggerHandler.apply($el, args);
  • })
  • };

appears to be slower than $(this) ...

  • // works like triggerHandler, but runs on all selected elements
  • $.fn.triggerHandlerAll = function (){
  • var args = arguments;
  • return this.each(function (i){
  • var $el = $(this);
  • $el.triggerHandler.apply($el, args);
  • })
  • };

Is there any benefit to .eq() in this scenario?

@all,

Does anyone have any thoughts on Ben Alman's each2? On the issue of future stability is directly assigning the jq.context and indexed values dangerous?

  • // from each2
  • jq.context = jq[0] = this[++i]


Aug 5, 2011 at 10:19 AM // reply »
1 Comments

> On the issue of future stability is directly assigning the jq.context and indexed values dangerous?

Assigning to jq[0] is fine as long as the current jQuery object has only one element (meaning the length property is 1). You'll just replace the current element by doing that.

Assigning to jq.context is overkill since it's not used much inside jQuery (http://api.jquery.com/context/). The only documented use is for calling .live() and you should NOT be using .live() now (use .delegate() instead).

All of the .each() replacements are targeted to the situation where you're looping over collections with many hundreds or thousands of objects. If you need to do that, my advice would be to ditch .each() entirely and use a simple loop along with the jq[0] trick. Here's why:

http://jsperf.com/each-vs-for-loops


Feb 4, 2012 at 8:55 AM // reply »
1 Comments

Hi guys (Dan and Ben) I am trying to implement what you posted and it still only runs on the first object in the collection.

This is what I currently have:

a bunch of input boxes with the same class (e.g.

  • <input type="checkbox" id="checkX" class="allchecks" value="show" />

)

Then a button that i want to trigger a click on all of them and run the click handler on each

  • <input type="button" value="select all" value="click me" id="button1" />
  • $j('#button1').click(function(){
  • $j('.allchecks').each(function(){
  • if(!$j(this).is(':checked'){
  • $j(this).trigger('click');
  • }
  • }); // end each
  • }); // end click

seems no matter what i do only the first object has it's bound click event fired.

Any advice would be greatly appreciated


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 19, 2013 at 2:31 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
It's funny really just how well that image describes the way I would imagine most people that go with angular for some project is. I have had a similar roller-coaster ride with it as well, but not qu ... read »
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools