Some time ago, I made a mistake.[ref]Actually, I make mistakes quite often. Daily in fact. But let’s not dwell on this …[/ref] Rather than taking the time to truly understand how events worked in jQuery, I built my own JavaScript library to handle event delegation.
It worked, but why reinvent the wheel?
It turns out jQuery is perfectly capable of serving as a global event bus for all of your code. You just have to know how to use it.
What is an Event Bus?
In old-school programming models, every bit of code would talk to every other bit of code. If one changed an element on a page or the data stored in an object, it would immediately tell everyone else working on that page or object about the change. The central problem here is scalability – in order to keep things in sync, every piece of your code needs to know about every other piece of your code.
For a simple Hello World application, not a big deal. For a larger program, though, this becomes a cumbersome nightmare.
An event bus alleviates this problem and allows you to write fully decoupled code. Rather than looking at its neighbors to watch for changes, a piece of code will subscribe to an event through the event bus. When another piece of code fires an event (again, through the event bus), every observer is notified.
Why Use One?
In WordPress, for example, this pattern takes the form of action and filter hooks. You don’t need to know when or where an action or filter is fired, you just need to know its name and what parameters are passed. WordPress’ core code doesn’t know anything about your plugin, it just fires its actions and passes data through filters when needed. Likewise, your plugin code doesn’t know anything about the location of these hooks, only that they exist.
In this way, the [cci]do_action()[/cci]/[cci]apply_filters()[/cci] and [cci]add_action()[/cci]/[cci]add_filter()[/cci] functions serve as the API for a built-in PHP event bus.
This is actually what makes WordPress such an amazing platform to develop with. It’s nearly infinitely extensible through this event-driven API and very approachable for developers to begin tying new code into. This kind of extensibility, when placed in themes and plugins, makes those systems equally extensible.
Decoupling your code also makes it less fragile.
If your PHP code is tied to a WordPress action, and that action is removed, your code will merely stop. It won’t necessarily break[ref]Unless, of course, you’ve written it in such a way that it fails under these conditions.[/ref], and WordPress will continue to function as before. Likewise, if someone else’s code is tied to your action, and they remove their code, nothing will stop working.
Consider this example code:
function update_remote_database() {
$database_url = 'http://someremotesite.com/db';
wp_remote_post(
$database_url,
array( 'body' => date_create( 'now ') )
);
}
// In some other part of your code.
// Maybe another file even ...
wp_insert_post( $new_post_from_args );
update_remote_database();
Versus this example code:
function update_remote_database() {
$database_url = 'http://someremotesite.com/db';
wp_remote_post(
$database_url,
array( 'body' => date_create( 'now ') )
);
}
add_action( 'after_insert_post', 'update_remote_database' );
// In some other part of your code.
// Maybe another file even ...
wp_insert_post( $new_post_from_args );
do_action( 'after_insert_post', $new_post_from_args );
In the first example, you must always have the [cci]update_remote_database()[/cci] function present. As the application grows, this function might move to a location far removed from where it’s called. If, later on, the remote database is disabled, how do you stop calling it? In the first example, you have to remove the function and look for where it’s called to remove the reference as well.
In the second example, you just remove the hook. The original callee doesn’t change because, well, it wasn’t actually calling the function directly in the first place.
How jQuery Does It
Events in jQuery are easy. If you want to listen to the click event on a particular element, you add your handler inside [cci]jQuery(‘#element-id’).click(function() { … });[/cci]. If you’ve done much web development work at all, you’ve seen code similar to that quite often.
But did you know jQuery supports custom events?
In reality, the [cci].click()[/cci] method is just shorthand for [cci].on(‘click’)[/cci]. Looking at the second way of registering a click event listener, you can easily see how this can be extended with custom events.
Let’s say our code does some background processing of large chunks of data.[ref]I know, JavaScript is single-threaded. However, there are ways you can chunk up the processing of large quantities of data using [cci]setTimeout()[/cci] so that the browser doesn’t become non-responsive. You’ll still need a way to know when it’s done …[/ref] When the processing is complete, you want to trigger the “oliver-twist” event to let the system know you’re ready for more data.
To do things globally, it’s easiest to bind these events to the [cci]document[/cci] object rather than to specific elements of the DOM. This way, they’re accessible to everything, and your code doesn’t need to know anything about the page markup to function – remember, it’s all about decoupling.
In this example, we listen for the “oliver-twist” event and then pass in more data:
$(document).on('olver-twist', function() {
if(has_more_data) {
process_data();
} else {
alert('Done!');
}
});
Then, after our data is finished processing, we trigger the “oliver-twist” event:
function process_data() {
setTimeout( async_process_data, 100 );
}
function async_process_data() {
do_something();
if(done) {
$(document).trigger('olver-twist');
} else {
setTimeout( async_process_data, 100 );
}
}
What’s Next?
Is your code extensible? Does it use custom events to signal when it’s starting to do something or completing execution? Where could you add some custom events to make it easier for third-party libraries to tie themselves in?
The Plupload library, for example, is a heavy user of custom events to let you know when files are added to a queue, uploaded, erred, etc. It has proven to be a remarkably flexible library, and is hugely popular as a result – if you use WordPress and upload media, then you’re already using it and didn’t even know it.
Can your library be as flexible – and as widely used – as a result of enabling custom events?