By Simon Smith
While the concept of custom events in jQuery may not be a new one, I tend to only see them used within plugins, mainly to keep their events namespaced for fear of collusion with other events.
The truth is they can be leveraged in everyday jQuery code to help make life a little easier.
The problem
It’s not uncommon to see a single event handler finding itself responsible for many things:
$('.add-items').on('click', function(event) {
$('.loading-message').show();
$.ajax({
// etc
});
$('.something-else').addClass('foo')
event.preventDefault();
});
Having an event handler responsible for a lot of different tasks will typically result in large, incoherent event callbacks. The situation often gets worse when other developers have to add in additional functionality later.
Decoupling all the things
One route I like to take is to separate the logic relating to the DOM event. This means just handling anything relating to the event (preventDefault
or stopPropagation
calls, for example) and then firing a custom event and allowing another part of the application handle the ‘business’ side of things:
var doc = $(document);
$('.add-items').on('click', function(event) {
doc.trigger('addItem', [$(this)]);
event.preventDefault();
});
The first thing to note is that I’m grabbing a reference to the document wrapped as a jQuery object:
var doc = $(document);
By triggering the events on this global object, it can become a sort of mediator between other parts of code. Storing a reference to it outside of the event handler callback also means that it won’t be created each time the button is clicked. This is a good practice to get in the habit of.
Then all that is required is to trigger an event of our choice and pass any useful data along with it.
doc.trigger('addItem', [$(this)]);
An object or array can be passed along with the event. In this case I’m passing along a jQuery object that refers to the clicked button.
Responding to the event
Listening for the addItem event is as simple as creating an event listener on the document
object:
$(document).on('addItem', function(event, btnElement) {
$('.something-else').addClass('foo')
});
$(document).on('addItem', function(event, btnElement) {
$.ajax({
// etc
});
});
The event object in the above callback handlers are related to the
addItem
custom event and have nothing to do with the earlier click event.
Now the logic is nicely separated, and additional chunks of functionality can be added or removed without any need to worry about the other parts of the application.
Seeing as the global document object was chosen to be the mediator, these events can be attached in other places (perhaps a different file completely) without any extra effort.
It seems like a bit more work initially, but as the code expands, this way of decoupling events will start to pay back in droves.
Taking it a step further with Flight
If this technique takes your fancy then I strongly advise you to give Twitter’s Flight library a look. It builds upon the concept of using jQuery events to decouple modules of code, but adds on a plethora of other nice features. Plus it’s developed by some of the smartest JS developers around.
It’s also very easy to integrate into existing code bases, compared to switching to something more fully-fledged like Ember or Backbone.
I’ve also set up a crude little demo if you’d like to tinker.
This article was originally published at https://simonsmith.io/decoupling-with-custom-jquery-events/
The background image is courtesy of https://commons.wikimedia.org/wiki/File:Life_is_Beautiful.jpg
Should there be two event handlers with the same name?
You can have as many handlers listening to an event as you like. That is what makes using custom events so flexible.
Do you achieve any control over the order in which the listeners fire?
They will be fired in the order they are attached. It’s best to avoid needing a dependency on order though.
Hi Simon, I’m really liking this technique. Thanks for the write up. Do you know if this can cause any degrades to performance by having multiple listeners on the document?
Once the event bubbles up the DOM, it will fire any event handlers attached to the event. It only traverses upwards the one time, so there aren’t any performance degradations with this method as it pertains to DOM traversal.
I would recommend reading https://addyosmani.com/resources/essentialjsdesignpatterns/book/#mediatorpatternjavascript and using the mediator pattern (publish/subscribe) instead of the observer pattern. At least, create a global object that is not in the DOM, jQuery can do that just fine.
Nice read! Don’t forget to learn about Promises/A+ though! (Google for it and also search at Youtube!)
Instead of writing $(‘.add-items’).on(‘click’, function(event) {});, I’ve found that writing $(‘.add-items’).on(‘click’, add-itemsClicked);function add-ItemsClicked(event) {} has elevated my understanding of JavaScript to the next level. People will say that writing using this new style requires coming up for a name for the function, but I think of it as self-documentation. After all, naming something encapsulates the essence of the purpose of the function.