Binding Events To Non-DOM Objects With jQuery

Posted March 6, 2009 at 9:45 AM by Ben Nadel

Tags: Javascript / DHTML

Yesterday, Dan G. Switzer, II was giving me some really great jQuery tips. Among the things that we discussed was the ability to define an anonymous function and then call it immediately:

  • <script type="text/javascript">
  •  
  • (
  • function(){
  • var strMessage = "Hello";
  •  
  • alert( strMessage );
  • }
  • )();
  •  
  • </script>

This is not jQuery specific, but the benefits of doing this is that you can create a totally private memory space in which to execute your anonymous function. In this memory space, locally declared variables won't be stored in the window object (the same as with any local function variable); but, since the function itself is anonymous, it doesn't get stored in the window object either. So basically, by declaring and then immediately executing an anonymous method, it's like it runs without leaving any footprint.

So this morning, I started to experiment with that, which led me down another path altogether - using jQuery to bind events to non-DOM (Document Object Model) objects. Generally, when we think of events, we think about elements in the HTML markup - clicking a link element, submitting a form element, listening for key events on a textarea or the document element - these events are all DOM-centric. However, jQuery takes the event model a big step further and allows us to actually bind and trigger events on any object including Javascript objects.

To experiment with this, I thought it would be cool to have our document listen for changes in the browser location. As you know, window.location is a Javascript object that contains information about the URL including hash and search strings. Let's use jQuery to bind a "change" event directly to the window.location object:

  • <script type="text/javascript" src="jquery-1.3.2.js"></script>
  • <script type="text/javascript">
  •  
  • // Our plugin will be defined within an immediately
  • // executed method.
  • (
  • function( $ ){
  • // Default to the current location.
  • var strLocation = window.location.href;
  • var strHash = window.location.hash;
  • var strPrevLocation = "";
  • var strPrevHash = "";
  •  
  • // This is how often we will be checkint for
  • // changes on the location.
  • var intIntervalTime = 100;
  •  
  • // This method removes the pound from the hash.
  • var fnCleanHash = function( strHash ){
  • return(
  • strHash.substring( 1, strHash.length )
  • );
  • }
  •  
  • // This will be the method that we use to check
  • // changes in the window location.
  • var fnCheckLocation = function(){
  • // Check to see if the location has changed.
  • if (strLocation != window.location.href){
  •  
  • // Store the new and previous locations.
  • strPrevLocation = strLocation;
  • strPrevHash = strHash;
  • strLocation = window.location.href;
  • strHash = window.location.hash;
  •  
  • // The location has changed. Trigger a
  • // change event on the location object,
  • // passing in the current and previous
  • // location values.
  • $( window.location ).trigger(
  • "change",
  • {
  • currentHref: strLocation,
  • currentHash: fnCleanHash( strHash ),
  • previousHref: strPrevLocation,
  • previousHash: fnCleanHash( strPrevHash )
  • }
  • );
  •  
  • }
  • }
  •  
  • // Set an interval to check the location changes.
  • setInterval( fnCheckLocation, intIntervalTime );
  • }
  • )( jQuery );
  •  
  • </script>

Here, we are using the "immediate execution of anonymous methods" trick - we are wrapping our anonymous method in parenthesis and then executing it, passing in the jQuery object as an argument. I am passing in jQuery as an argument so that I can use the $ parameter to refer to jQuery internally to the method no matter what other conflicting variables might be on the page (another huge benefit of this execution technique).

Once inside the anonymous method, we define a local method that will actually check the location's HREF value. This method gets setup on interval so that it can be continually checking to see if the location has changed. Now, here's where it gets exciting - if the location value does change, the function uses jQuery to trigger a "change" event on the window.location object itself:

  • $( window.location ).trigger(
  • "change",
  • {
  • currentHref: strLocation,
  • currentHash: fnCleanHash( strHash ),
  • previousHref: strPrevLocation,
  • previousHash: fnCleanHash( strPrevHash )
  • }
  • );

As part of the event, we are passing in the current and previous location convenience values.

Now, triggering an event is only have of the fun. Let's use jQuery to bind "change" event listeners directly to the window.location Javascript object:

  • <script type="text/javascript">
  •  
  • // Bind a change handler to the window location.
  • $( window.location ).bind(
  • "change",
  • function( objEvent, objData ){
  • var jLog = $( "#log" );
  •  
  • // Add the URL change.
  • jLog.append(
  • "<li>" +
  • "Hash changed from " +
  • "<strong>" + objData.previousHash + "</strong>" +
  • " to " +
  • "<strong>" + objData.currentHash + "</strong>" +
  • "</li>"
  • );
  • }
  • );
  •  
  • </script>

Our event handler is now listening for the "change" event on the Javascript window.location object and when a change event occurs, it outputs it to the screen.

I then set up a small HTML page that had different anchor links for testing:

  • <body>
  •  
  • <h1>
  • jQuery Top-Level Closure Example
  • </h1>
  •  
  • <p>
  • <a href="#linkA">Link A</a> &nbsp;|&nbsp;
  • <a href="#linkB">Link B</a> &nbsp;|&nbsp;
  • <a href="#linkC">Link C</a>
  • </p>
  •  
  • <p>
  • The following is a change log of URL updates:
  • </p>
  •  
  • <ol id="log">
  • <!--- Items to be injected. --->
  • </ol>
  •  
  • </body>

... and, when I clicked several of the links, I got the following output:

Hash changed from to linkB
Hash changed from linkB to linkA
Hash changed from linkA to linkC
Hash changed from linkC to linkB
Hash changed from linkB to linkC
Hash changed from linkC to linkA

How cool is that! jQuery just makes this stuff so easy. As we get more into an object oriented (OO) mindset when programming Javascript, I can see how, in an event driven world, this kind of functionality might be really awesome for the publish-subscriber design pattern.




Reader Comments

Mar 6, 2009 at 7:05 PM // reply »
1 Comments

Great article! Any idea if this method can allow jQuery to control XUL elements as well? For example, using jquery to modify a firefox addons 'skin' or xul elements?


Mar 9, 2009 at 8:34 AM // reply »
10,638 Comments

@Vin,

I don't have any experience with the XUL stuff, so I cannot offer any advice.


Jul 18, 2009 at 8:02 PM // reply »
2 Comments

Ben,
Thanks to your inspiration above, I have been working on a way to use this technique to implement a tabbed page in which I change some html classes when the location.hash value associated with a tab changes. The attraction of using the hash value to control how the page is rendered is that it enables the browser's back button.

I got this working nicely in Firefox, Safari, and Chrome. But now I find that in IE, it only works in the 'forward' direction, when hash changes are triggered by clicks on the tabs. In the 'reverse' direction, when hash changes are triggered by the back button, the function I have bound to that change does not fire.

It appears (now that I have been forced to research this issue) that this limitation begins with IE, which does not trigger a change event for the window or document (see http://www.quirksmode.org/dom/events/change.html), and continues with jQuery, which does not compensate for this limitation of IE.

Do you agree with this analysis? If so, can you suggest a workaround that will make your code work with IE and the back button?


Aug 5, 2009 at 8:32 PM // reply »
10,638 Comments

@Chris,

I believe there are jQuery plugins that deal with this better than I do. I think they use iFrames to track the changes rather than using the current window. I would do a search for that.


Aug 5, 2009 at 8:49 PM // reply »
2 Comments

Thanks Ben, you are correct. Originally I was hoping to use some code of my own that was an extension of your nice simple code, but no such luck! I have done quite a bit more research since I posted this, and found out more about how people have solved this problem in a comprehensive way.


Aug 5, 2009 at 8:50 PM // reply »
10,638 Comments

@Chris,

Awesome stuff my man. Did you end up going with an existing plugin?


Sep 7, 2009 at 4:18 PM // reply »
1 Comments

try your experiment with something like this:

// User is just a simple js "class".
var user = new User();

$( user ).bind("loggedin", function( objEvent, objData ){
$().log("logIN clicked");
});

I certainly can't get it to work. You get an error of invalid object initializer. I think your example only works because document is accessible from jQuery.


Sep 12, 2009 at 11:17 PM // reply »
10,638 Comments

@Jon,

I'll definitely try it when I am back on my work computer.


Apr 23, 2010 at 9:10 AM // reply »
1 Comments

@Ben, great post - just what I was looking for.

@Jon, works for me (in Firefox):

function User(name) {
var my_name = name;
this.get_name= function() {
$(this).trigger('got_name', my_name);
return my_name;
};
}
d = new User('dave');
$(d).bind('got_name', function(e, d){alert('got name: '+d)});
d.get_name();


Sep 13, 2010 at 12:22 PM // reply »
3 Comments

Hmm, very interesting technique. But I'm wondering how 'recommended' this is. As it requires wrapping custom objects in a jQuery object, which is not explicitly supported according to the API docs: http://api.jquery.com/jQuery/.

Any word on this practice by someone from the jQuery team as far as you know Ben?


Sep 14, 2010 at 9:06 PM // reply »
10,638 Comments

@Tim,

The word is still out as to whether or not this is actually supported. From my experimentation, it seems to be about 95% supported with certain events and object properties causing conflicts.

To close the support-gap, I played around with using jQuery to power a custom Publish-Subscribe mechanism:

http://www.bennadel.com/blog/2000-Powering-Publish-And-Subscribe-Functionality-With-Native-jQuery-Event-Management.htm

jQuery eventing is rather powerful and I think this approach can leverage the power and robust feature set (name-spaces, bind, unbind, event data, etc.) while at the same time, not falling pray to the limitations of raw, non-JS object binding.


Sep 21, 2010 at 9:59 AM // reply »
3 Comments

@Ben: Thanks for the reply. That 'detached-DOM-element' technique is a smart escape route in case custom object bindings turn out not to be supported in the end. Hadn't thought of it yet.


Sep 22, 2010 at 10:14 PM // reply »
10,638 Comments

@Tim,

I think it's kind of exciting stuff. jQuery event binding is just so powerful, it seems a shame not to be able to use it.


Nov 3, 2010 at 5:06 AM // reply »
1 Comments

Ben,
very nice Blog!

I first used somthing like the "detached-DOM-element" technique, but doesn't seems to be needed:
Quote:
"It was unintentional - way back in the day - but we've come to embrace
it. Expect those aforementioned methods (bind/unbind and data) to
continue to work on plain JavaScript object.

--John"

http://www.mail-archive.com/jquery-dev@googlegroups.com/msg06127.html


Nov 3, 2010 at 10:18 AM // reply »
10,638 Comments

@Florian,

The Javascript object bind/unbind is definitely cool; but it seems to not play nicely in a few outlier conditions. In my experience, if the JS object has a "length" property of zero, it breaks. Also, if the event is "submit", it seems to break as well. I am not sure if they have address some of these issues in the most recent releases of jQuery.


Nov 3, 2010 at 10:29 AM // reply »
3 Comments

It looks like jQuery 1.4.3 has made this support explicit. See http://blog.jquery.com/2010/10/16/jquery-143-released/ under "JavaScript Objects" and "Events."


Nov 3, 2010 at 11:13 AM // reply »
10,638 Comments

@Tim,

The JS Object updates look very interesting. At a cursory reading, I'm not sure I'm fully wrapping my head around the details. I'll have to put some time into really reviewing the 1.4.3 updates. Thanks so much for the link!


Mar 6, 2011 at 5:47 AM // reply »
1 Comments

Hello Ben,

First I want say that I find your site really useful and full of nice info and I'm really glad that I found it!

Regarding the topic I would like to ask if there is an update on the IE issue. I see that last post was 5 months back, but I would really like to get your plugin working on IE as well.

Thank you very much!


Oct 6, 2011 at 1:40 AM // reply »
3 Comments

Just what I needed. Thanks.



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 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »