jQuery Custom Element and Global Events

This last week I learned a new thing about jQuery custom events, particularly the ones of global nature. There's good documentation and examples about custom element events, but not much for the global ones.

Why do we need custom events?

Custom events make it easier to keep complex pages under control. They are a pillar for loosely-coupled UI scripts. Let's start with a simple example.

Suppose we have a fairly complex and dynamic page where many elements are Ajax-editable, using in-place editors or any other approach that posts updates to the server. Depending on how quickly the server responds to the request, there's a chance the user can start another simultaneous request before the first one finishes, maybe even seeing inconsistent results, by clicking a button too soon.

In our example — a fraction of what a real complex page would be — what we want to do is disable some of these buttons while the data is being changed, and re-enable them once we hear back from the server.

Click the field to edit it:<br>

<input type="text" readonly="readonly" id="email" name="email"
   value="joe@doe.com" style="background-color: #eee;"/> 

<input type="button" class="userOperation" id="sendButton" value="Send Message">
<input type="button" class="userOperation" id="summaryButton" value="Summary">

Custom Element Events

Let's tackle this problem first with the custom element events. Below is a summary of how these custom events are used.

$('#publisher').trigger('eventName');

$('#publisher1').bind('eventName', function() {
   //eventName happened. React here.
   $('#subscriber1').doStuff();
   $('#subscriber2').doOtherStuff();
   // more...
});

In this case we will make the elements being edited announce that they entered edit mode so that any other element can act on that announcement.

$('#email').
click(function(){
	$(this).removeAttr('readonly').css({backgroundColor: ''});
	$(this).trigger('editStart');
}).
blur(function(){
	$(this).attr('readonly', 'readonly').css({backgroundColor: '#eee'});
	$.post('/updateEmail', $('#email').serialize(), function() {
		$(this).trigger('editComplete');
	});
}).
bind('editStart', function(){
	// "this" is the #email element
	console.log('edit started, this =  ' + this.id);
	$('.userOperation').attr('disabled', 'disabled');
}).
bind('editComplete', function(){
	// "this" is the #email element
	console.log('edit complete, this =  ' + this.id);
	$('.userOperation').removeAttr('disabled');		
});

$('#sendButton').click(function(){
	//code to send a message
	alert('Message sent');
});

$('#summaryButton').click(function(){
	//code to generate summary
	alert('Summary created');
});

This approach works well in the beginning but gets really ugly as more elements need to publish their own similar events or when other new elements need to do somethings with these events too. We will need to bind handlers to all these element's events and the code inside these handlers will start getting longer and probably too far from the rest of the code that relates to it.

One step forward with page level events

Since the events we are producing here really reflect the document state more than any individual field's state, let's move that event to a more top level element, namely the body element:

$('#email').
click(function(){
	$(this).removeAttr('readonly').css({backgroundColor: ''});
	$('body').trigger('editStart');
}).
blur(function(){
	$(this).attr('readonly', 'readonly').css({backgroundColor: '#eee'});
	$.post('/updateEmail', $('#email').serialize(), function() {
		$('body').trigger('editComplete');
	});
});

$('body').
bind('editStart', function(){
	// "this" is the body element
	console.log('edit started, this =  ' + this.tagName);
	$('.userOperation').attr('disabled', 'disabled');
}).
bind('editComplete', function(){
	// "this" is the body element
	console.log('edit complete, this =  ' + this.tagName);
	$('.userOperation').removeAttr('disabled');		
});

$('#sendButton').click(function(){
	//code to send a message
	alert('Message sent');
});

$('#summaryButton').click(function(){
	//code to generate summary
	alert('Summary created');
});

Now we're getting somewhere. We reduced the number of event sources to just one, so guaranteed less duplication. But it still has some shortcomings.

The code is still bound to a different element than the one we want to operate on. What I mean by that is that the event handlers are in the context of the elements publishing the event and the code in the handlers is typically geared towards the elements that need to react to that event, that is, the this keyword is less useful than in most of your common event handlers.

The pattern of these page-level events is:

$('body').trigger('eventName');

$('body').bind('eventName', function() {
   //eventName happened. React here.
   $('#subscriber1').doStuff();
   $('#subscriber2').doOtherStuff();
   // more...
});

But wait, jQuery has real global events too

I had settled down with using the above style of global events until someone at work pointed out that there's another way of doing this, which unfortunately isn't as well discussed: the custom global events.

Here's our code using global custom events:

$('#email').click(function(){
	$(this).removeAttr('readonly').css({backgroundColor: ''});
	$.event.trigger('editStart');
}).blur(function(){
	$(this).attr('readonly', 'readonly').css({backgroundColor: '#eee'});
	$.post('/updateEmail', $('#email').serialize(), function() {
		$.event.trigger('editComplete');
	});
});

$('.userOperation').bind('editStart', function(){
	// "this" is a .userOperation button
	console.log('edit started, button: ' + this.id);
	$('.userOperation').attr('disabled', 'disabled');
}).bind('editComplete', function(){
	// "this" is a .userOperation button
	console.log('edit complete, button: ' + this.id);
	$('.userOperation').removeAttr('disabled');		
});

$('#sendButton').click(function(){
	//code to send a message
	alert('Message sent');
});

$('#summaryButton').click(function(){
	//code to generate summary
	alert('Summary created');
});

What is great about this type of event is that they are in the context of the subscribing elements, as if these elements were the publishers of the event, much like the majority of the event handling code we write.

They also allow us to move more code next to the other event handler for the subscribing elements, and even chain them all together. As an example, let's modify the event handlers of the #sendButton element to add some different behavior when the editStart event happens.

$('#sendButton').click(function(){
	//code to send a message
	alert('Message sent');
}).bind('editStart', function(){
	// "this" is the #sendButton button
	this.value = 'Send message (please refresh)';
	// change the click event handler.
	$(this).unbind('click').click(function(){
		alert('Sorry, refresh page before sending message');
	});
});

And here is the simplified representation of the global events code.

$.event.trigger('eventName');

$('#subscriber1').bind('eventName', function() {
   //eventName happened. React here.
   $(this).doStuff();
});

$('#subscriber2').bind('eventName', function() {
   //eventName happened. React here.
   $(this).doOtherStuff();
});
//more...

Conclusion

Event-based programming is the usual way we write UI code. By understanding the different types of events that jQuery provides we can allow our UI to grow without getting into a messy nightmare of event handling code scattered all over the place.


Posted 02-21-2010 12:09 PM by sergiopereira
Filed under: ,

[Advertisement]

Comments

DotNetKicks.com wrote jQuery Custom Element and Global Events
on 02-21-2010 2:15 PM

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Chris Brandsma wrote re: jQuery Custom Element and Global Events
on 02-21-2010 2:20 PM

Good write up.  I've heard this was possible for a long time, just never saw it used.

Now I need a do-over for my last project.  :)

Sanjeev Agarwal wrote Daily tech links for .net and related technologies - Feb 22-24, 2010
on 02-22-2010 5:02 AM

Daily tech links for .net and related technologies - Feb 22-24, 2010 Web Development Partial Validation

Jon wrote re: jQuery Custom Element and Global Events
on 02-24-2010 4:40 AM

Cheers, I've learnt something there

Shawn Marincas wrote re: jQuery Custom Element and Global Events
on 07-30-2010 1:50 PM

Awesome, my mind started wrapping around more event-centric, asynchronous (even more that usual), UI and web app design a couple months ago and this a great new piece for my puzzle.

Anyone know the overhead for these  global events?  Does jQuery fire the event on every node of the DOM?  If so, implementing this in complex document structures could get really expensive.

Shawn Marincas wrote re: jQuery Custom Element and Global Events
on 07-30-2010 2:27 PM

Alright, I was actually was so curious that I checked out the global triggering in the jQuery source and it seems like here:

<code>

// Handle a global trigger

if ( !elem ) {

// Don't bubble custom events when global (to avoid too much overhead)

event.stopPropagation();

// Only trigger if we've ever bound an event for it

if ( jQuery.event.global[ type ] ) {

jQuery.each( jQuery.cache, function() {

if ( this.events && this.events[type] ) {

jQuery.event.trigger( event, data, this.handle.elem );

}

});

}

}

</code>

Looks like jQuery uses it's cache of what objects it has bound data to and only triggers on those elements, which is sweet!

In addition, jQuery also keeps track of what events have been used so if you trigger a global event that hasn't been bound, jQuery won't waste any processing.

Good stuff!  Thanks so much!

Aaron Hancock wrote re: jQuery Custom Element and Global Events
on 09-14-2010 11:41 PM

I have found custom jquery events extremely useful!

[EDIT: link was screwed up by community server. Fixed.]

Barnaby wrote re: jQuery Custom Element and Global Events
on 03-18-2011 1:16 PM

Thank you thank you thank you.

This is EXACTLY what I was looking for. I'm going to echo a lot of the other posters in saying that I knew this was possible I just didn't know how, and after hours of googling your post was the first I found that answered my question.

klynt wrote re: jQuery Custom Element and Global Events
on 06-14-2012 6:36 AM

Thx for your post, it was really helpful ! I was triggering event on document and cannot manage to do what I needed : global events. And it was already here ! Thank you and thank you jQuery. :)

Michiel Helvensteijn wrote re: jQuery Custom Element and Global Events
on 07-10-2012 3:54 PM

This *sounds* good, but $.event is not part of the public interface, as mentioned in the jQuery source. That means it is subject to change without notice, and using it can break your code.

Erik wrote re: jQuery Custom Element and Global Events
on 09-18-2012 11:17 AM

Exactly what i was looking for!

However i have one problem, it seems you cant attach handlers for global events using on/delegate/live expect it to work with dynamically added content. The event does not get to the handler.

Emil wrote re: jQuery Custom Element and Global Events
on 10-09-2012 5:56 AM

Thanks you for this blog post . Helped me in desperate times .

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)