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 »
164 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 »
10,743 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 »
10,743 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 »
164 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
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 21, 2012 at 1:58 AM
Updated: Converting A ColdFusion Query To CSV Using QueryToCSV()
Hi Ben, why do you need to have so many double quotes when adding the field and field name to the row data? ----------------------------------------- <cfset LOCAL.RowData[ LOCAL.ColumnIndex ] = ... read »
AXL
May 21, 2012 at 1:24 AM
URL Rewriting And ColdFusion's WriteToBrowser Image Functionality (CFFileServlet)
@Mounir, Open your lower case URL Rewrite rule and add the following condition. Condition input: {REQUEST_URI} Check if input string: Does Not Match the Pattern Pattern: ^/CFFileServlet/_cf_ca ... read »
May 20, 2012 at 4:28 AM
Understanding The Complex And Circular Relationships Between Objects In JavaScript
@Will Vaughn I tried your javascript example but got this error:- foo.print is not a function ... read »
May 19, 2012 at 5:37 AM
A Graphical Explanation Of Javascript Closures In A jQuery Context
Thanks for this article, but I fear you missed an important point. If variables in the outer context change, these changes affect the inner anonymous functions as well. That means: if you change the ... read »
May 18, 2012 at 3:39 PM
Parsing CSV Data With An Input Stream And A Finite State Machine
Can you use file upload button with this? and read live? or does the file have to already be on the server saved? ... read »
May 18, 2012 at 1:06 AM
VIRGO (Aug. 23-Sept. 22): Dead On The Money!
A friend of mine and I were arguing about astrology and she told me that he believes in astrology. She hasn't provided me with any evidence that the belief makes any sense to me. She she been telling ... read »
May 17, 2012 at 11:32 PM
Using ColdFusion to Handle 404 Errors (Page Not Found) On Development Server
Very easy the configuration. I read a lot pages and I can't find the solution. I open the administrator and change this Administrator/server settings/Error Handlers/Missing Template Handler and p ... read »
May 17, 2012 at 3:13 PM
LOCAL Variables Scope Conflicts With ColdFusion Query of Queries
I never cease to be amazed that almost EVERY random CF issue I come across lands me on your site. Thank you for documenting your findings for the world. ... read »