$('img').bind('click', function(event){ console.log('Hello world!'); });
Or, using the shorthand:
$('img').click(function(event){ console.log('Hello world!'); });
We'll put our code in an object:
Gallery.prototype = { init: function(elem) { //Save reference to 'this' for callbacks var self = this; this.$gallery = $(elem); this.$gallery.find('img').click(function(){ self.zoom(); }); }, zoom: function() { //Code... } };
Now let's introduce some more concepts...
We may want to unbind just our events later, without accidentally removing event handlers from other plugins.
var self = this; this.$gallery.find('img').bind('click.gallery', { self.zoom(); });
Now we can target our click event when unbinding:
this.$gallery.find('img').unbind('click.gallery');
Or, unbind all of your events:
this.$gallery.find('img').unbind('.gallery');
Often you'll create an anonymous function just to call a single method.
var self = this; this.$gallery.find('img').bind('click.gallery', function(){ self.zoom(); });
This is JavaScript! Can't we just pass the function name?
Well, this won't work since jQuery changes the context:
this.$gallery.find('img').bind('click.gallery', this.zoom);
zoom: function() { this.$gallery; //Undefined :((( }
Returns a new function that always runs in the supplied context:
$.proxy(function, context)
Using our example:
this.$gallery.find('img').bind('click.gallery', $.proxy(this.zoom, this));
zoom: function(event) { this.$gallery; //object! :))) //Still need the DOM element that was clicked? $(event.target); }
Attaches an event handler to the document root.
$('img').live('click', $.proxy(this.zoom, this));
But...
$('img').live('click', $.proxy(this.zoom, this));
Effectively replaces 'live'
//This old line of 'live' code: $('img').live('click', $.proxy(this.zoom, this)); //Is equivalent to: $(document).delegate('img', 'click', $.proxy(this.zoom, this));
We can attach the event handler to any element:
this.$gallery.delegate('img', 'click', $.proxy(this.zoom, this));
Attaches one event handler to this.$gallery instead of one for each 'img'.
Custom events allow you to bind and trigger non-standard events.
Custom events act like regular events, including bubbling up the DOM.
$('p').bind('turnRed', function(){ $(this).css('color', 'red'); }); $('p:first').trigger('turnRed');
Coupled with namespacing, makes a great way for plugins to expose functionality.
$('#gallery').trigger('next.gallery');
Instead of manually calling the methods of one module from another, you can manually trigger and listen to your own event types.
//Module A: zoom: function(event) { $(event.target).trigger('zoom.gallery'); }
//Module B: $(document).delegate('img', 'zoom.gallery', function(){ $(this); //Someone zoomed this image in a gallery });
This avoids creating a dependency where Module A will fail if Module B isn't present.
Create a super simple publish/subscribe (pubsub) system:
//Module A: zoom: function() { $(document).trigger('zoom.gallery'); }
//Module B: $(document).bind('zoom.gallery', function(){ $(this); //Someone zoomed this image in a gallery });
By always triggering and binding our custom events against the document, we've essentially created a centralised messaging system.
Arrowing through content is totally awesome:
var self = this; $(document).keydown(function(event){ switch (event.which) { case 37: //Left arrow self.prev(); break; case 39: //Right arrow self.next(); break; } });
Clever keyboard navigation can add a lot to a site.
Any significantly interactive site should have keyboard navigation.
Instead of forcing you to use different methods for event binding vs. event delegation, you'll be able to use the unified 'on' and 'off' functions.
Instead of:
$elem.bind('click', $.proxy(this.func, this));
You can write this:
$elem.on('click', $.proxy(this.func, this));
The 'off' function works the same way, except for unbinding.
Instead of:
$('img').live('click', $.proxy(this.func, this));
You can write this:
$(document).on('click', 'img', $.proxy(this.func, this));
Instead of:
$gallery.delegate('.next', 'click', $.proxy(this.next, this));
You can write this:
$gallery.on('click', '.next', $.proxy(this.func, this));
They'll still be available for the foreseeable future.
If you're writing code specifically for a new project using jQuery 1.7, 'on' and 'off' have really great APIs.
If you're writing a plugin, it's a good idea to stick with 'bind' and 'delegate' for now.
Gallery.prototype = { init: function(){ this.$gallery = $('#gallery'); this.$gallery .delegate('img', 'click.gallery', $.proxy(this.zoom, this)) .delegate('.next', 'click.gallery', $.proxy(this.next, this)) .delegate('.prev', 'click.gallery', $.proxy(this.prev, this)); $(document).bind('keydown.gallery', function(event){ if (event.which === 37) { self.prev(); } else if (event.which === 39) { self.next(); } }); }, zoom: function(){ ... }, next: function(){ ... }, prev: function(){ ... } };
That was a lot of code for something conceptually simple. Plus, we had to repeat our event handling logic for mouse and keyboard events separately.
Anyone familiar with Backbone.js knows how it handles events:
var DocumentView = Backbone.View.extend({ events: { 'dblclick' : 'open', 'click .icon.doc' : 'select', 'contextmenu .icon.doc' : 'showMenu', 'click .show_notes' : 'toggleNotes', 'click .title .lock' : 'editAccessLevel', 'mouseover .title .date' : 'showTooltip' }
But what if we want this syntax in a regular, non-Backbone page? And what if we want to take this idea even further?
jQuery plugin written in CoffeeScript for handling events in object oriented JavaScript using a Backbone-inspired events hash.
$('#element').eventralize({ 'event' : 'functionName', 'event selector' : 'functionName', 'event selector, event selector' : 'functionName' }, context);
The 'context' parameter is where your methods are stored and the context in which they will run. In most cases, the context will be this.
The 'selector' parameter can be any jQuery selector, or;
You can also use document, window and body as selectors if you need to capture events higher up the DOM.
Simple keyboard handling syntax:
$container.eventralize({ 'click .next, keydown(right) document' : 'next' }, this);
Handles key combinations elegantly:
$container.eventralize({ 'keydown(alt+ctrl+right) document' : 'no_way' }, this);
No more nested ifs manually checking key codes!
No more repeating your event binding logic between clicks and key presses!
The method name is used as the namespace by default!
If you'd like to selectively override this, you can namespace any of your events the old fashioned way:
$container.eventralize({ 'click.gallery .next, keydown.gallery(right) document' : 'next' }, this);
Or, if you decide to pass in a namespace as the third parameter, Eventralize will handle the rest. Namespaces delcared inline will take precedence.
$container.eventralize({ 'click .next, keydown(right) document' : 'next' }, this, 'gallery'); // <-- There's our namespace!
The specified function will provide you with the 'event' object just like a regular jQuery callback.
next: function(event) { event.target; //The element that triggered the event event.preventDefault(); }
Rather than declaring our events inline, we'll store them in Gallery:
Gallery.prototype = { events: { 'click' : 'focus', 'click img' : 'zoom', 'click .next, keydown(right) document' : 'next', 'click .prev, keydown(left) document' : 'prev' }, init: function() { this.$gallery = $('#gallery'); this.$gallery.eventralize(this.events, this); }, //Methods... };
Eventralize lets you add and remove entire collections of event handlers without breaking a sweat.
It's now possible to remove all of your event handlers with one line:
destroy: function() { this.$gallery.uneventralize(this.events); }
Storing our event hash pays off! :)
Follow me on Twitter! @markdalgleish