jQuery forEach() Experiment For Branch-Wise Implicit Iteration

Posted February 15, 2010 at 3:44 PM by Ben Nadel

Tags: Javascript / DHTML

This morning, in my post about the jQuery plugin, closestParents(), Dan G. Switzer, II had mentioned that it would be cool to have a forEach() construct in jQuery the way there are in other functional programming languages. I had never heard of forEach() before, but from what I gathered, it would allow jQuery to perform its implicit iteration in a branch-wise manner, rather than across the entire collection. In this way, jQuery's pseudo selectors like, ":first", could be executed per branch rather than on the resultant collection.

 
 
 
 
 
 
 
 
 
 

I thought this was an interesting concept, so I tried playing around with it. What I did was create a forEach() plugin which ends up returning a ForEach class instance, rather than the expected jQuery collection. This ForEach instance then "intercepts" method calls to the target jQuery collection and applies those method calls to each node individually, rather than to the collection as a whole. To get back to the original jQuery collection, the ForEach class has an endEach() method which returns the initializing collection (very much in the same way end() moves back up the jQuery stack).

To see this in action, take a look at the following demo which is a refactoring of my previous post:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>jQuery ForEach() Experiment</title>
  • <style type="text/css">
  •  
  • div {
  • border: 1px solid #E0E0E0 ;
  • padding: 10px 10px 10px 10px ;
  • }
  •  
  • div.parent {
  • border-color: #CC0000 ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.4.1.js"></script>
  • <script type="text/javascript">
  •  
  • // Create a self-executing function to encapsulate teh
  • // definition of the ForEach class and forEach plugin.
  • (function( $ ){
  •  
  • // I am the ForEach class definition.
  • function ForEach( collection ){
  • var self = this;
  •  
  • // I am the original collection. This is what will
  • // be returned when the forEach() is ended.
  • this.collection = collection;
  •  
  • // I am the separation of each branch of the
  • // original collection. Each element is a jQuery
  • // collection unto itself (based on a single node).
  • this.branches = [];
  •  
  • // Using the original collection, populate the
  • // branches array with jQuery objects.
  • this.collection.each(
  • function( index, node ){
  •  
  • // Turn each node into its own branch.
  • self.branches.push( $( node ) );
  •  
  • }
  • );
  • };
  •  
  • // I am the prototype of the ForEach class.
  • ForEach.prototype = {
  •  
  • // I take the given method and arguments and
  • // apply them to each branch of the colleciton
  • // individually
  • doEach: function( methodName, parameters ){
  • var self = this;
  •  
  • // Loop over each branch.
  • $.each(
  • this.branches,
  • function( index, branch ){
  • // Apply the given jQuery method to
  • // the current branch.
  • var result = $.fn[ methodName ].apply(
  • branch,
  • parameters
  • );
  •  
  • // Check to see if we want to store
  • // the results back into the branch
  • // (for subsequent iterations). We only
  • // want to do this if the result was
  • // another jQuery collection.
  • if (
  • (typeof( result ) == "object") &&
  • ("jquery" in result)
  • ){
  •  
  • // Store the resultant collection.
  • self.branches[ index ] = result;
  •  
  • }
  • }
  • );
  •  
  • // Return the ForEach instance.
  • return( this );
  • },
  •  
  •  
  • // I end the ForEach() iteration, returning the
  • // original jQuery collection back to the user.
  • endEach: function( returnNewCollection ){
  • // Check to see if the user want to return a
  • // new, merged collection.
  • if (returnNewCollection){
  •  
  • // Create a new collection based on the
  • //combination of all teh current branches.
  • return(
  • this.collection.pushStack(
  • $.map(
  • this.branches,
  • function( branch ){
  • return( branch.get() )
  • }
  • ),
  • "forEach",
  • ""
  • )
  • );
  •  
  • } else {
  •  
  • // The user just wants the original
  • // collection back.
  • return( this.collection );
  •  
  • }
  • }
  •  
  • };
  •  
  •  
  • // Add the override classes to the ForEach prototype.
  • $.each(
  • [
  • "addClass",
  • "end",
  • "parent",
  • "parents"
  • ],
  • function( index, methodName ){
  •  
  • // Route the intercepted method call through
  • // the doEach() method.
  • ForEach.prototype[ methodName ] = function(){
  • return( this.doEach( methodName, arguments ) );
  • };
  •  
  • }
  • );
  •  
  •  
  • // ---------------------------------------------- //
  • // ---------------------------------------------- //
  •  
  •  
  • // Define the actual jQuery plugin. This wlil return
  • // a new instance of the ForEach class.
  • $.fn.forEach = function(){
  • return( new ForEach( this ) );
  • }
  •  
  • })( jQuery );
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // When the DOM is ready, initialize the scripts.
  • jQuery(function( $ ){
  •  
  • $( "a" )
  • .forEach()
  • .parents( "div:first" )
  • .addClass( "parent" )
  • .end()
  • .endEach()
  • .css( "font-weight", "bold" )
  • ;
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • jQuery ForEach() Experiment
  • </h1>
  •  
  • <div>
  • <div>
  • <span>
  • <a href="##">Some Link</a>
  • </span>
  • </div>
  • </div>
  •  
  • <br />
  •  
  • <div>
  • <div>
  • <span>
  • <a href="##">Some Link</a>
  • </span>
  • </div>
  • </div>
  •  
  • </body>
  • </html>

As you can see, once I have my link collection, I am calling the forEach() plugin to enter the forEach-mode of traversal. At that point, my subsequent calls to parents() and addClass() get applied to each individual link, rather than to the link collection as a whole. That is what enables the, ":first", pseudo selector to target all of the intended Div ancestors (one per link branch). To get out of the forEach mode, I call endEach() which, in this case, simply returns the original link collection.

When we run this code, we get the following output:

 
 
 
 
 
 
jQuery ForEach() Plugin To Allow Branch-Wise Implicit Iteration. 
 
 
 

This was just a proof of concept, so I am manually listing the methods that I want to "intercept." However, if you wanted to, you could probably loop over the methods in the jQuery.fn object and programmatically add them to the ForEach prototype. I am not sure if this is exactly what Dan had in mind, but I thought this was an interesting experiment; I could definitely see something like this being very useful in outlier situations.




Reader Comments

Feb 15, 2010 at 5:04 PM // reply »
160 Comments

Interesting approach changing the chain to an object. You could copy all of the jQuery functions as methods using:

var fn = [];
for( var k in $.fn ){
if( typeof $.fn[k] == "function" ) fn.push(k);
}

// Add the override classes to the ForEach prototype.
$.each(
fn,
function( index, methodName ){
// Route the intercepted method call through
// the doEach() method.
ForEach.prototype[ methodName ] = function(){
return( this.doEach( methodName, arguments ) );
};

}
);

However, you'd potentially run into issues with plug-ins not being detected if they're loaded after the forEach() initializes. You'd also be do a fair amount of dynamic evaluation--which I'm not sure if the overall payoff is worth.


Feb 15, 2010 at 5:07 PM // reply »
10,640 Comments

@Dan,

Yeah, good point on the plugins loaded after the forEach() was loaded. Not sure if there is a way to handle that.

As far as the dynamic evaluation, it shouldn't be too bad because the methods are routed in the ForEach prototype, which is only set up once (when the plugin script is loaded). Once that is in place, the ForEach class definition should be static.

Anyway, thanks for putting this on my radar - it was definitely a very interesting thought experiment.


Feb 16, 2010 at 6:41 AM // reply »
1 Comments

This website is the best place for me to learn, period. :)


Sep 5, 2010 at 3:39 PM // reply »
10,640 Comments

@Sereal,

Wow - what a super flattering thing to say :) I really appreciate that! I'm so happy that this stuff is providing value for you.


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
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »
Feb 9, 2012 at 10:29 PM
Learning ColdFusion 9: Application-Specific Data Sources
@Ben, No offence, but if people were really wanting advanced features they would be using a platform like ASP.NET MVC. CFML is so structurally compromised as a tag-based scripting language that ... read »
Feb 9, 2012 at 10:03 PM
Subversion - Cleanup Failed To Process The Following Paths
@Leviaguirre, do you still have problems with this? ... read »