Automating with Special Events
David Walsh had a great idea to coerce an element that is given a click event to have a pointer for its cursor. The pointer is a universal symbol for users to know that something is clickable. Using this technique it would be “automatic” that any element given a click event would have a pointer. David set out to use the Special Event hooks within jQuery to make this work. Unfortunately for David those hooks are not widely documented, so his first attempt was unsuccessful. So, lets see how we can use the Special Event hooks to make this work as desired.
If you haven’t done so, I recommend reading the Special Events blog post which explains what special events are and how to use them. In the upcoming jQuery 1.3.31.4 there will be two more event hooks.
The Special Event
The code, once you understand the Special Event hooks, is fairly straight forward. When a click event is bound to an element, we want to make sure it has a pointer for its cursor. Here is what the code looks like to make this work.
jQuery.event.special.click = {
setup: function() {
jQuery( this ).css( 'cursor', 'pointer' );
return false;
},
teardown: fuction() {
jQuery( this ).css( 'cursor', '' );
return false;
}
};
So lets break this small piece of code down. There are two hooks, setup
and teardown
, that are called once per an element. In the setup
hook we add the >CSS rule to make the cursor a pointer and in the teardown
hook we revert the change since it is no longer clickable. The teardown
hook is only called after the last click event is unbound.
The only difference here from David’s example is the return false
statements in both hooks. The return value in Special Event hooks is particularly important (and easy to overlook). Special Events assume that you will most likely handle the actual binding of the event yourself. In other words, when you don’t return false jQuery skips the actual native binding (addEventListner
or attachEvent
) of the event to the element. In this particular case we want jQuery to go ahead and run its native binding code so the event is actually registered with the browser. To do this we need to return false from our hooks.
Usage
Using the same example from David’s post, the actual usage stays the same. Just bind a click event handler as you normally would using either .bind()
or .click()
.
jQuery( document ).ready(function( $ ) {
$( '#click-me' ).click(function() {
var red = Math.floor( Math.random() * 256 ),
green = Math.floor( Math.random() * 256 ),
blue = Math.floor( Math.random() * 256 ),
color = 'rgb(' + red + ',' + green + ',' + blue + ')';
$( this ).css( 'background', color );
});
});
Enhancement
There is one enhancement that I’d like to recommend. Instead of adding the styles via JavaScript, lets just add a class name instead. Maybe the name could be “clickable”. Then we can add a rule in our CSS that says anything with the class of “clickable” should have a pointer. This would make it easier to style specific instances of a clickable element. For example, we could add a three pixel blue border to a clickable image. :) So, the final special event with this enhancement would look like this.
jQuery.event.special.click = {
setup: function() {
jQuery( this ).addClass( 'clickable' );
return false;
},
teardown: fuction() {
jQuery( this ).removeClass( 'clickable' );
return false;
}
};
And in our CSS we would need to add a global rule that adds the pointer to .clickable
elements.
.clickable { cursor: pointer; }
Now we can specialize our .clickable
class to handle certain elements differently.
img.clickable { border: 3px solid blue; cursor: pointer; }
Last thought…
I had one last thought about this technique. This should not be a replacement for using semantic markup or using progressive enhancement. Most of the time if something should be clickable it should probably be clickable and actually do something when JavaScript is not available.