Friday, July 6, 2012

Give Controls Control - trigger.js

When you put a <button> or an <a> or an <input type="image"> into your markup, you intend them to do something, not just sit there and look pretty.  This inevitably means writing code like this:
$('a.blah').on('click', function() {
    console.log('blah');
});
STOP!  You actually meant to use a custom event, right?  Let's try that again:
$('a.blah').on('click', function() {
    $(this).trigger('blah');
});
$(document).on('blah', function() {
    console.log('blah');
});
Ah, much better. Sure, it's longer, but this way you are self-documenting and making your code easier to test, extend and maintain.

But what if it didn't really have to be longer? What if the button itself could be told to trigger the custom event. After all, that ".on('click'...)" business is just boilerplate and not particularly friendly to the world of keyboard accessibility. Well, this is what trigger.js does for you. Instead of setting up the custom event with some boilerplate javascript, we just extend HTML with a 'trigger' attribute:

<a trigger="blah">Blather</a>
$(document).on('blah', function(e) {
    console.log('blah');
});
Now there's no "blah blah blah" boilerplate; i get to focus on what i'm doing more than how i'm doing it.  It gives my controls control, rather than shoving their functions off into a separate file.  And this can be extended to accomplish even more.

But before we talk about the "more", here is trigger.js:


Wait, you say! Why isn't it just like this:
It could be that simple, true. If that's all you need, use that. But it's hard to resist adding a little flair when you are inventing the wheel. The first thing i wanted was sequenced events.  Why trigger just one when you can trigger more? After all, i like to "use custom events like crazy", and many "actions" in applications are actually combinations of sequential, dependent, but separate actions. Consider:

<button trigger="validate submit">Submit</button>

This way, my "validate" code doesn't have to be tied to "submit" code and so on.  And when "validate" fails, it can still call e.preventDefault() to keep "submit" from firing.

Once i had sequenced events in my markup, it was natural and easy to add them to jQuery's trigger function, especially for debugging things in the console.

$('#cancel').trigger('reset back');

That done, i remembered that i have long wanted to have my own custom event properties.  Instead of "moveUp" and "moveDown", i wanted to have "move" events where i could test for "up" or "down" properties, just like i test e.ctrlKey.  So, trigger.js now supports:

<button trigger="move:up next">Up</button>
$('#game').on('move', function(e) {
    var roll = dice.value();
    if (e.up) player.climb(roll);
    if (e.down) player.slide(roll);
    if (player.winner) {
        e.preventDefault();// all done
    }
}).on('next', function() {
    player = player.next();
});
Finally, i occasionally envied the ability to associate data with an event, like you can when calling trigger() on a jQuery collection. So i added that as well:
<button trigger="level[10]">Loud</button>
<button trigger="level[11]">Spinal Tap</button>
$(document).on('level', function(e, level) {
    speakers.goTo(level);
});
And finally, i wanted my apps to be more keyboard accessible. So, trigger.js also listens for 'enter' events on elements that don't naturally convert them to clicks (div, span, a[!href]) and does that for them.

By all means, if these extra features aren't useful to you, just use the simple, four-line version. But if you want to give your document's controls a bit more control, trigger.js is ready to help. :)