Building Custom jQuery Event Types: Hesitate Event

Posted December 15, 2009 at 4:22 PM by Ben Nadel

Tags: Javascript / DHTML

If you've worked with jQuery for a while, you've probably bound event handlers to DOM elements. And, if you've done that, you may have also bound and triggered handlers to event types that aren't natively supported by the browser (ex. "drag", "drop"). This kind of seamless event management is, without a doubt, a huge part of what makes jQuery so powerful; but event binding is only part of it. In the jQuery Cookbook, Ariel Flesler explains that beyond binding to custom events, you can actually create custom events that will be triggered implicitly by the jQuery library.

I have seen this a few times before, but was never really able to wrap my head around it. With Ariel's tutorial in hand, however, I felt determined to try and play with this concept myself. As an experiment, I wanted to see if I could create a "hesitate" event type. The idea behind "hesitate" is that it would fire if the user moused over a given element and then remained over the element - without clicking - for a certain duration. The idea being that they intend to click the element, but they are hesitating to act upon that intent.

 
 
 
 
 
 
 
 
 
 

Before we get into the internals of the custom event type creation, let's take a look at the code that uses it. In the following demo, I am going to bind to the "hesitate" event on some image thumbnail links. If the user hesitates to click a thumbnail, I am going to increase the size of that thumbnail in an attempt to entice them to click it. As you will see below, the calling code can bind to our custom event type, "hesitate", just as it could to any other event type, native or otherwise.

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Building A Custom jQuery Event Type: Hesitate</title>
  • <style type="text/css">
  •  
  • a {
  • float: left ;
  • margin-right: 15px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.4a1.js"></script>
  • <script type="text/javascript" src="jquery.event.hesitate.js"></script>
  • <script type="text/javascript">
  •  
  • // Override the Hesitate event duration.
  • // Set it to be 1 second (1000 milliseconds).
  • jQuery.event.special.hesitate.duration = 1000;
  •  
  •  
  • // When the DOM is ready, interact.
  • jQuery(function( $ ){
  •  
  • // Gather the links.
  • var links = $( "a:has( > img )" );
  •  
  • // Set HREF and bind "hesitate" event.
  • links
  • .attr( "href", "javascript:void( 0 )" )
  • .bind(
  • "hesitate",
  • function(){
  • // If the user has hesitated over this
  • // link, then entise them by enlarging
  • // the nested image.
  • $( this )
  • .children()
  • .stop()
  • .animate(
  • {
  • width: 200
  • },
  • {
  • duration: 1000
  • }
  • )
  • ;
  • }
  • )
  • .bind(
  • "mouseout",
  • function(){
  • $( this )
  • .children()
  • .stop()
  • .animate(
  • {
  • width: 100
  • },
  • {
  • duration: 200
  • }
  • )
  • ;
  • }
  • )
  • ;
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <h1>
  • Building A Custom jQuery Event Type: Hesitate
  • </h1>
  •  
  • <p>
  • <a href="##"><img src="girl.jpg" width="100" /></a>
  • <a href="##"><img src="girl.jpg" width="100" /></a>
  • <a href="##"><img src="girl.jpg" width="100" /></a>
  • </p>
  •  
  • </body>
  • </html>

The first thing I am doing here is overriding the "duration" of the "hesitate" event type. This is the amount of time the user will have to be non-active over the target element before the "hesitate" event is fired. In my example, this is done across the board (as a generic event type) and not on a per-binding basis (which could also be done). Then, just as I would with any event type, I bind my event handlers to "hesitate" event on the target links.

If I mouse over one of the links, but do not commit to the click, the nested image will animate to a larger size and look like this:

 
 
 
 
 
 
Building Custom jQuery Event Types. 
 
 
 

What you'll notice above is that my demo code does not define the logic of the "hesitate" event type or how it gets triggered; it simply binds to the event type on the target elements. The logic and the mechanics of the event are all handled by jQuery and my custom event type setup, which is defined by the included Javascript file, jquery.event.hestiate.js. Now that you see how the event type is getting used, let's take a look at how the event type is defined:

jquery.event.hesitate.js

  • // Define the Hesitate event. This event fires if a person has
  • // moused-over the given element and paused for the given duration
  • // without clicking.
  • //
  • // NOTE: To create a custom duration, update the duration
  • // property (in milliseconds):
  • // jQuery.event.special.hesitate.duration.
  • (function( $ ){
  •  
  • // When the given element is entered, we need to set up the
  • // timeout that will trigger the Hesitate event after the
  • // appropriate pause duration.
  • var prepareHesitate = function( event ){
  • var target = $( this );
  •  
  • // Store the timeout with the element's data so that we
  • // can clear the timeout if the user acts within the
  • // given duration.
  • target.data(
  • "hesitate.timer",
  • setTimeout(
  • function(){
  • // Remove any hestitate context.
  • removeHesitate( event );
  •  
  • // Trigger the event handlers.
  • target.triggerHandler( "hesitate" );
  • },
  • $.event.special.hesitate.duration
  • )
  • );
  • };
  •  
  •  
  • // When the user mouses out of the given element or clicks
  • // on the given element, we want to remove the hesitation
  • // timer.
  • var removeHesitate = function( event ){
  • // Remove the timer and its data key.
  • removeHesitateTimer( $( this ) );
  • }
  •  
  •  
  • // This removes the hestation timer on the given element.
  • var removeHesitateTimer = function( target ){
  • // Clear the timer.
  • clearTimeout(
  • target.data( "hesitate.timer" )
  • );
  •  
  • // Remove the timer key.
  • target.removeData( "hesitate.timer" );
  • }
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • // Define our special event, "hestiate":
  • $.event.special.hesitate = {
  •  
  • // This method gets called the first time this event
  • // is bound to THIS particular element. It will be
  • // called once and ONLY once for EACH element.
  • setup: function( eventData, namespaces ){
  • // Bind the three event handlers that we are going
  • // to need to make sure this event fires correctly.
  • $( this )
  • .bind( "mouseenter", prepareHesitate )
  • .bind( "mouseleave", removeHesitate )
  • .bind( "click", removeHesitate )
  • ;
  •  
  • // Return void as we don't want jQuery to use the
  • // native event binding on this element.
  • return;
  • },
  •  
  • // This method gets called when this event us unbound
  • // from THIS particular element.
  • teardown: function( namespaces ){
  • var target = $( this );
  •  
  • // Remove bound events.
  • target
  • .unbind( "mouseenter", prepareHesitate )
  • .unbind( "mouseleave", removeHesitate )
  • .unbind( "click", removeHesitate )
  • ;
  •  
  • // We also want to remove the timer in case there is
  • // one in progress.
  • removeHesitateTimer( target );
  •  
  • // Return void as we don't want jQuery to use the
  • // native event binding on this element.
  • return;
  • },
  •  
  • // This is the duration a user must pause over the
  • // target element without acting before the hesitate
  • // event will be triggered.
  • duration: (2 * 1000)
  •  
  • };
  •  
  • })( jQuery );

This is my first time trying this, so I am 100% how it all wires together. But, from what I understand, all custom jQuery events are defined as named-structures off of:

jQuery.event.special

This event definition object should contain, at the very least, two core methods, setup() and teardown(). The setup() method is a sort of event constructor that gets called exactly once for each element the first time the custom event is bound to it. The teardown() method is a sort of event destructor that gets called exactly once for each element when the custom event is unbound from it.

What you do in these methods is up to you; but, because we are building completely custom events, we need to build on top of events that are natively supported by the browser (or at least other events that might be triggered in some way). As such, from within our setup() method, we need to bind event handlers to the core events that will power our custom event. In this case, for the "hesitate" event, there are three crucial event types to monitor:

  • MouseEnter. When the user mouses into the target element, we have to start keeping track of their subsequent hesitation to click.
  • MouseLeave. When the user mouses out of the target element, we no longer need to keep track of their activity.
  • Click. When the user clicks on the target element, they can no longer be considered inactive or hesitating.

Once we bind to these core methods, we can then go about using them to create our custom event. In our particular case, when the user enters the target area, we create a timer that waits for inactivity. If the user clicks on the target element, or mouses out of it, we kill the inactivity timer. If the user does neither of these two things, the timer will execute and its callback will trigger the "hesitate" event on the target element. At this point, any subsequent handlers bound to this target element for the "hesitate" event will be executed.

While this works well, it should be noted that the core events (upon which are custom event is powered) are not shielded in any way. Meaning, if someone were to trigger a "click" event on our target element, it would certainly trigger the "click" handler that we are using to power the "hesitate" event.

As this is the first time that I have created a custom jQuery event type, I am sure that I am leaving juicy pieces of information out of my explanation. For instance, I have not talked about namespaces, additional event data, or per-binding custom settings; hopefully I can address such things in a future post.




Reader Comments

Dec 15, 2009 at 9:04 PM // reply »
46 Comments

It would be interesting for you to compare the code to this: http://cherne.net/brian/resources/jquery.hoverIntent.html

You basically engineered it in a "clean room".


Dec 15, 2009 at 10:52 PM // reply »
6 Comments

Man, you are a blogging machine!

Great stuff as always. Might be neat to use hesitate to guide a user toward a task that you think they might be wanting? Kinda a newbie nudge ;)

Expect a tech tweet tomorrow @ 9:30AM CST


Dec 16, 2009 at 8:47 AM // reply »
18 Comments

I think it should be called "entice" instead of hesitate :)

(great post!)


Dec 16, 2009 at 10:42 AM // reply »
2 Comments

My first thought of using hesitate was to be able to track where users are leaving their mouse so you can do some investigation into usability issues of a site.

If you keep getting people holding their cursor over something, maybe they think they're supposed to be cliking on it, or it looks "clickable"... just my first thoughts.

Good info on custom events!


Dec 16, 2009 at 11:11 AM // reply »
10,743 Comments

@Glen,

I'll take a look at it. Quickly glanced and it looks like he based his on actual mouse movement / acceleration. Very cool.

@Elijah,

Thanks my man! I just love this stuff :)

@Chris,

That's also a good thought. At the end of the day, I just needed *something* to use in order to experiment with the topic :)


Dec 16, 2009 at 12:50 PM // reply »
1 Comments

I just have to say, that you web developers are AWESOME! and don't get the credit you deserve :(

I have been following your blog for the last 2yrs. I had no idea all the stuff that goes into the websites that I navigate on a daily basis. WOW!

I have learned many cool things on your blog. I just feel like becoming a developer myself :)

Thanks!


Jan 5, 2010 at 9:43 AM // reply »
10,743 Comments

@Katie,

Thank you very much :D I think you just made everyone's day (on this blog)... even though I know this comment is a few weeks old.


Jan 20, 2010 at 9:15 AM // reply »
3 Comments

Thanks.

Always love to read and watch your posts.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 16, 2012 at 8:18 PM
Best Of ColdFusion 10 Contest Entry - HTML Email Utility
Just found this, looks good! I'm trying to run it on local, it's the 64bit version and I'm experiencing horrible lag. On average the generate.cfm processes the content change in 60-90 seconds. I've ... read »
May 16, 2012 at 6:40 PM
Maintaining Sessions Across Multiple ColdFusion CFHttp Requests
I am trying to integrate this CFHTTPsession into an application that will log into zeekrewards.com to post ads and I am not having any luck. The code works perfectly for logging into other websites, ... read »
May 16, 2012 at 2:44 PM
Creating A Sometimes-Fixed-Position Element With jQuery
Thank you, very useful technique! Worked like a charm. ... read »
May 16, 2012 at 1:58 PM
Movies As A Religious Experience
Acting can, in a way, ruin the movie-goer's experience. I used to be able to get so caught up in movies and their plots, and totally engaged. But lately, I haven't been able to as much with a lot o ... read »
May 16, 2012 at 1:52 PM
The Science Of Optimal Post-Exercise Nutrition
children of this age eat very less vegetables so u can opt for salads they will like it also carrot ,cucumber,onion and as far as pulses are concerned u can boil them ,give him along with mashed rice ... read »
May 16, 2012 at 1:34 PM
Strange ColdFusion JRUN Stack Overflow Error
Hey, Recently I updated my jrun4 using the latest updater 7 and now i am having memory issues :(:(:( any help is appreciated ... read »
May 16, 2012 at 9:56 AM
ColdFusion 10 Beta, Apache Tomcat, And Symbolic Links On Mac OSX
Hi, Now that ColdFusion 10 is out I have stumbled over this as well and I cannot figure out the proper solution. We're running virtual hosts via Apache2; the ColdFusion-applications store their fil ... read »
May 15, 2012 at 6:03 PM
Movies As A Religious Experience
@Ben, I don't know whether you'd consider this a religious observation, but it seems to me, in a sense, movies multiply how many lives we get to have. Each movie is like a little extra life we get ... read »