Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Chris Phillips
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Chris Phillips

Binding Events To Non-DOM Objects With jQuery

By
Published in Comments (21)

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.

Want to use code from this post? Check out the license.

Reader Comments

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?

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?

15,912 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.

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.

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.

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();

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?

15,912 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:

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.

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.

15,912 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.

15,912 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.

15,912 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!

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!

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel