Special Events

Thursday, March 26, 2009

jQuery, since 1.2.2, has had an API for “special events”. These events are special because they have the ability to do some extra work for specific events and even the option to bypass some of the internal jQuery event system. With these special events you can create custom events that require some setup work or you can completely overwrite the behavior of native events.

We use special events in jQuery to create the “mouseenter” and “mouseleave” events. In addition to those two events we used it to make the “ready” event and I use it to normalize the “mousewheel” event in the mouse wheel plugin.

A Special Event: “tripleclick”

To illustrate the API I’m going to create a new event type called “tripleclick”. To be fired it will require the user click the element three times. If we were to make this a typical jQuery plugin we would create jQuery.fn.tripleclick. However, I want to be able to take advantage of the bind syntax along with the other benefits like event normalization, data, and namespaces that the jQuery event system provides.

The first thing we need to do is create the special event. Each special event has a setup and a teardown method. The setup method gets called when the event is bound and the teardown method gets called when the event is unbound.

It is important to note that these two methods only get called the first time an event of a particular type is bound/unbound per an element. This is because the jQuery event system actually only binds one handler per an event type per an element and manages the other bound handlers itself. In jQuery 1.3 there is a new special event type called “Special All” that operates for all handlers but has a slightly different behavior. However, that is for another article.

The skeleton of our “tripleclick” special event looks like this.

jQuery.event.special.tripleclick = {
    setup: function(data, namespaces) {
        var elem = this;
    },

    teardown: function(namespaces) {
        var elem = this;
    }
};

Notice that the setup event gets passed the data and namespaces that were used when binding the event. Also that the teardown event gets passed the namespaces that were used when unbinding the event. Although, they are only marginally useful here since this is the data and namespaces associated with the very first event handler bound to a particular element. But you could use the data to configure the event for all the handlers of that type for an element. The scope, or the value of this, for these two methods is the element the event is being bound to.

Behind the scenes we are actually going to utilize the native “click” event to keep track of the number of clicks on an element. We’ll also need a handler that actually does the heavy work of keeping track. I’m going to add a third method called handler to the tripleclick special event. To make the code a little more simple I’m going to track the number of clicks on an element by using the jQuery data API.

The updated tripleclick special event looks like this.

jQuery.event.special.tripleclick = {
    setup: function(data, namespaces) {
        var elem = this, $elem = jQuery(elem);
        $elem.bind('click', jQuery.event.special.tripleclick.handler);
    },

    teardown: function(namespaces) {
        var elem = this, $elem = jQuery(elem);
        $elem.unbind('click', jQuery.event.special.tripleclick.handler);
    },

    handler: function(event) {
        var elem = this, $elem = jQuery(elem), clicks = $elem.data('clicks') || 0;
        clicks += 1;
        if ( clicks === 3 ) {
            clicks = 0;
            // set event type to "tripleclick"
            event.type = "tripleclick";
            // let jQuery handle the triggering of "tripleclick" event handlers
            jQuery.event.handle.apply(this, arguments)
        }
        $elem.data('clicks', clicks);
    }
};

To quickly break down the handler code. First we get the number of clicks via the data API and increment the number by 1. Then we check to see if it has been clicked 3 times. If so, we then need to reset the number of clicks and trigger the other event handlers as the comment indicates. Finally, we store the new value for the number clicks on the element via the data API.

The handler has to set the event type to “tripleclick” because behind the scenes we actually use a click event. jQuery uses the event type to know which handlers it should call and we want it to call the event handlers for our “tripleclick” event.

The Example

We can now use our special event just like we’d use any other event via the bind method. For example to bind a “tripleclick” event to all divs we’d write the following code.

jQuery('div').bind('tripleclick', function(event) {
    alert('triple clicked');
});

You can see an example of this new special event in action here.

You could enhance this event by requiring all three clicks to be within a certain timeframe. You could achieve this by also storing the event.timeStamp property of the previous click and comparing the distance between it and the latest click.

The return Value

I mentioned in the beginning of this article that a special event had the ability to bypass some of the internal jQuery event system. The functionality that can be skipped is the actual binding of the event to the element using the addEventListener or attachEvent methods. This functionality is skipped based on the return value. Any value other than false prevents jQuery from actually binding the event to the element. In other words if you add return false to the setup and teardown methods of your special event, it will actually use the native DOM APIs to bind the event to the element. In the case for our “tripleclick” event we didn’t want to actually bind the event to the element using the native DOM APIs so we didn’t return anything (undefined).

Posted in jQuery with 14 comments

Comments

Wicked article Brandon! Article is advanced and articulate…well done.

By Joe McCann on Thursday, March 26, 2009 at 02:13 AM

Thanks Joe! :)

By Brandon Aaron on Thursday, March 26, 2009 at 02:23 AM

Thanks, that’s a really interesting article. I’m glad to read something deeper and more complex than what we usually read on jQuery. I’m looking forward to read your “Special All” article :)

I’m not sure to understand the “It is important to note that” part. I first thought that it meant the setup wouldn’t be called again when unbinding the event from an particular element and rebinding it, but tests show it actually would.

Isn’t it the expected behavior? What may be the confusion? Thinking setup is called each time the event is fired?

By oelmekki on Thursday, March 26, 2009 at 09:28 AM

@oelmekki, If you unbind the event and it is the last event of that type on that element then it will call the teardown method. If you then rebind that event it will call setup again. However, the setup method will not be called when you bind another event handler of the same event type to the same element. In other words… the initial setup applies to all events of that type on that element.

By Brandon Aaron on Thursday, March 26, 2009 at 02:06 PM

Oh, I see. I did my tests with only one bind of the same type on the same element. Thanks for precisions.

By oelmekki on Thursday, March 26, 2009 at 04:56 PM

hmm.. I think there should be a rest timer or a click interval timer, so that the event timesout after a certain period of time, just like double click events do.

I’m sure this would make it a little more complex, but still it’s a great example, tutorial and start!

By Micheil Smith on Wednesday, April  1, 2009 at 08:44 AM

Great idea, but 3 fast clicks select page content (Chrome), there should be timeout that reset clicks (like in normal doubleclick) - than it’s possible handle all “clicks” events. Great job.

By kr on Wednesday, April  1, 2009 at 09:17 AM

@Micheil Smith and @kr, Thanks for the kind words!

I mentioned towards the bottom of the article that you could implement the time constraints by looking at the event.timeStamp. It was meant to be an exercise for the reader looking to explore the special events a little more. :)

By Brandon Aaron on Wednesday, April  1, 2009 at 11:35 AM

Great code. I made a modification to control the time between clicks.

DEMO

POST

By Francisco on Wednesday, April  1, 2009 at 04:49 PM

@Francisco, cool and thanks for sharing! Did you try to use event.timeStamp instead of creating a time stamp yourself each click?

By Brandon Aaron on Wednesday, April  1, 2009 at 07:47 PM

That’s cool, I’m going to start using this to test for changes to the hash in the url for Ajax browser back and forward control. Right now I create my own stuff to test for changes.

By Mike on Friday, April  3, 2009 at 01:51 PM

This is so cool, almost a little too cool :) I am not sure how to take advantage of it!

By Ben Nadel on Friday, April  3, 2009 at 10:08 PM

Fantastic article, Brandon! Advanced features clearly explained. Thanks for writing it.

By Karl Swedberg on Saturday, April  4, 2009 at 12:34 AM

I think the value of the applied handler jQuery.event.handle should be returned by the tripleclick.handler to provide an easy way of suppressing the default browser click behavior (return false;).

var result;
if ( clicks === 3 ) {
    ...
    result = jQuery.event.handle.apply(this, arguments);
    ...
}
...
return result;

By sompylasar on Thursday, May 28, 2009 at 12:29 PM

New Comment