Using SetTimeout() To Delay The Closing Of A Related UI Element Based On User Interactions

Posted October 4, 2010 at 11:07 AM by Ben Nadel

Tags: Javascript / DHTML

The other day, in the comments of my bottom-positioned slideUp() / slideDown() jQuery post, Hardy asked me if there was a way to delay the closing of a bottom-positioned element until the user had a chance to properly interact with it. This type of interaction model - related UI actions - is common in web application design and is typically powered by Javascript's setTimeout() and clearTimeout() functions. While the demo I have below is for a fixed, bottom-positioned element, this same approach can be applied to many related UI interaction scenarios.

 
 
 
 
 
 
 
 
 
 

In Javascript, there are two functions that allow a programmer to execute something "in the future." These are setTimeout() and setInterval(). Both of these functions take two arguments: a function reference and an interval in milliseconds:

  • setTimeout( function, interval )
  • setInterval( function, interval )

NOTE: There are actually other ways to invoke these functions; but, using a function reference is the only way that I would recommend using them.

Each of these functions will invoke the given callback (function argument) after the given interval has passed. The difference between these two functions is that setTimeout() will only invoke the callback once whereas setInterval() will invoke the given callback over and over again, pausing between invokations for a duration defined by the given interval.

When you call these functions - setTimeout() and setInterval() - they pass back a unique token that represents the timer that you have just configured:

  • var timerID = setTimeout( function, interval );
  • var timerID = setInterval( function, interval );

There is no requirement to store this token; however, storing this token will allow you to cancel the future invocation of your callbacks, should the conditions of your application change. Once you have these timer tokens, you can cancel the timers using either the clearTimeout() or clearInterval() function:

  • clearTimeout( timerID )
  • clearInterval( timerID )

As you might have guessed, clearTimeout() can be used in conjunction with the setTimeout() tokens and clearInterval() can be used in conjunction with the setInterval() tokens. Once a timer has been cleared, it will not fire again. Should you want to re-configure a timer, you will need to call either the setTimeout() or setInterval() functions again.

Now that we see how to plan the future execution of callbacks, let's see how these functions can be used to delay UI reactions based on user interactions. In the following demo, we have an external link that triggers the opening of a footer area. If the user does not interact with the footer in a given amount of time (4 seconds), the footer will close automatically.

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Delayed Hide Of A Related Element</title>
  •  
  • <style type="text/css">
  •  
  • #footer {
  • background-color: #F0F0F0 ;
  • border-top: 1px solid #CCCCCC ;
  • bottom: 0px ;
  • display: none ;
  • left: 0px ;
  • position: fixed ;
  • width: 100% ;
  • }
  •  
  • #footerInner {
  • padding: 10px 15px 35px 15px ;
  • }
  •  
  • </style>
  • </head>
  • <body>
  •  
  • <h1>
  • Delayed Hide Of A Related Element
  • </h1>
  •  
  • <p>
  • <a id="openFooter" href="##">Show Footer</a>
  • </p>
  •  
  •  
  • <div id="footer">
  • <div id="footerInner">
  •  
  • <p>
  • Hello - I am the footer. I might have a lot of
  • neat things in me to which the the user wants
  • to gain access.
  • </p>
  •  
  • </div>
  • </div>
  •  
  •  
  • <!-- Load scripts after our DOM is ready. -->
  • <script type="text/javascript" src="./jquery-1.4.2.js"></script>
  • <script type="text/javascript">
  •  
  • // Get references to our key dom elements.
  • var openFooterLink = $( "#openFooter" );
  • var footer = $( "#footer" );
  •  
  •  
  • // Create a function that sets an inactive timer on the
  • // footer. This will be called when the footer is opened
  • // or when the user mouses out of the footer.
  • function setInactiveTimer(){
  • // We are goint to store this timer as a property of
  • // the footer itself.
  • footer.data(
  • "inactiveTimer",
  • setTimeout( closeFooter, (4 * 1000) )
  • );
  • }
  •  
  •  
  • // I remove any inactive timer on the footer.
  • function clearIntactiveTimer(){
  • clearTimeout( footer.data( "inactiveTimer" ) );
  • }
  •  
  •  
  • // I open the footer.
  • function openFooter(){
  • // Open the footer.
  • footer.slideDown( 500 );
  • }
  •  
  •  
  • // I close the footer.
  • function closeFooter(){
  • // Slide the footer up (to close it).
  • footer.slideUp( 500 );
  • }
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Hook up the open footer link. When we do this, not only
  • // do we want to open the footer, we also want to set a timer
  • // so that the footer will close if it is not being used.
  • openFooterLink.click(
  • function( event ){
  • // Cancel the default event as this is not a true
  • // link action.
  • event.preventDefault();
  •  
  • // Check to see if the footer is currently animating.
  • // If it is, then we don't want to listen to this
  • // click as it might be a double-click.
  • if (footer.is( ":animated" )){
  • return;
  • }
  •  
  • // Clearn any existing timer since the user clearly
  • // want to interact with the footer.
  • clearIntactiveTimer();
  •  
  • // Call the open footer.
  • openFooter();
  •  
  • // Set the inactive timer.
  • setInactiveTimer();
  • }
  • );
  •  
  •  
  • // Attach a mouse enter handler on the footer to hook into
  • // user interaction. If the user mouses over the footer,
  • // then we want to remove any timers.
  • footer.mouseenter(
  • function(){
  • // Check to see if the footer is currently
  • // animating. If it is, then let's ignore since
  • // we are not wired to stop closing mid-animation.
  • if (footer.is( ":animated" )){
  • return;
  • }
  •  
  • // First, clear any existing timer that might take
  • // control of the footer display.
  • clearIntactiveTimer();
  • }
  • );
  •  
  •  
  • // Attach a mouse exit handler on teh footer to hook into
  • // user disinterest. If the user mouses out of the footer,
  • // then we want to start thinking about closing it.
  • footer.mouseleave(
  • function(){
  • // Check to see if the footer is currently
  • // animating. If it is, then let's ignore since
  • // we are not wired to stop closing mid-animation.
  • if (footer.is( ":animated" )){
  • return;
  • }
  •  
  • // Set inactivity timer for future-close.
  • setInactiveTimer();
  • }
  • );
  •  
  • </script>
  • </body>
  • </html>

In this demo, the user can actively engage in three different interactions:

  • Open the footer.
  • Mouse into the footer.
  • Mouse out of the footer.

When the user clicks the open-footer link, we want to show the footer and configure a timer that will close the footer if the user chooses not to engage with it. We do this through the use of setTimeout(). In the above code, if the user does not engage with the footer within 4,000 milliseconds (4 seconds), our timer will execute the closeFooter() function, sliding the footer down and out of view.

If the user does choose to engage with the footer, however, we want to make sure that the footer does not close while the user is interacting with it. As such, when the user mouses into (mouseenter) the footer, we use clearTimeout() to cancel the future invocation of our closeFooter() callback.

In this particular demo, I am checking the animation status of the footer before I perform any further UI logic. In this approach, we don't have to worry about double-clicking and mid-animation interactions. We can always expand our logic to deal with such situations later; however, that quickly adds complexity that is not relevant for this explanation.

While approaches to this kind of situation may vary widely, I think you'll find that they typically operate off of the setTimeout() and clearTimeout() functions. I hope that this helps shed some light on delayed interface reactions based on user interactions.




Reader Comments

Oct 4, 2010 at 11:15 AM // reply »
19 Comments

Ben, take a look at my jQuery doTimeout plugin, which is very useful for delayed code execution, especially when dealing with polling loops or hover intent:

http://benalman.com/projects/jquery-dotimeout-plugin/

In addition, my jQuery throttle / debounce plugin is geared towards rate-limiting function execution, so you might find that useful when dealing with other events:

http://benalman.com/projects/jquery-throttle-debounce-plugin/


Oct 4, 2010 at 12:56 PM // reply »
171 Comments

FYI - I've been meaning to mention this for a while, but you should consider increasing the volume on your mic. I always end up having to jack up my volume to hear any of your screencasts. The volume on your screencasts is easily 70%+ quieter than other sources I listen to. I have Winamp at 20% volume and it's still way louder than your screencasts. In order to hear you, I always have to turn off any other audio and jack the volume way up.


Oct 4, 2010 at 1:01 PM // reply »
6 Comments

<stron>Wow Ben - I did not expect such a amount of work just to make happen what I had in mind but it is exactly what I was looking for. Very well done as always. Thank you very, very much for spending your time on that. I think I can use that very often.


Oct 4, 2010 at 9:26 PM // reply »
11,243 Comments

@Cowboy,

Very cool plugins - you're like a jQuery plugin wizard. I remember at last year's jQuery conference you told me you had a lot of ideas for plugins... no doubt! I particularly the like throttle() concept.

@Dan,

I agree with you on that 100%. But, I'll be straight-up honest - I have no idea how to do that. Would that be a system setting, or an app setting (JING) do you think?

Also, I seem to remember you also writing some "debouncing" code a good while ago.

@Hardy,

Happy to help. Take a look at Ben Alman's jQuery plugins listed earlier in the comments; now that you are more familiar with the concept, his plugins should simplify your life a bit.


Oct 5, 2010 at 9:50 AM // reply »
6 Comments

@Ben
Yes, I have seen that plugin but you know what. All those plugins make you forget (or never tell you) how it all actually works. So I like the plugins in general since they speed up our work but I also like a good Tutorial very much. That is what makes me smart at the end. All other is just convenient. Looking forward to your next Tuto. Have a good one...


Oct 5, 2010 at 9:58 PM // reply »
11,243 Comments

@Hardy,

I completely agree - sometimes it's great to pick things apart or go back to the basics to remember how they work in the first place.


Oct 7, 2010 at 3:07 PM // reply »
14 Comments

I have used setTimeout() many a time, and never knew it returned a token or that you could use that to clear it! Awesome! All this time, I would have a global variable that I would set that would flag the scheduled function whether it should proceed or abort (wrap it's actions on a big if block).

This is much cleaner.


Oct 10, 2010 at 4:13 PM // reply »
11,243 Comments

@Joshua,

I used to go the "flag" route as well; having a way to programmatically clear a timer will simplify your life greatly.


Oct 16, 2010 at 9:31 AM // reply »
6 Comments

Ben....today I feel very "blocked" and maybe you can give me a hint to make it work. I use your script but I have 2 openFooterLink trigger on my site for opening the footer panel. 1 should work on mouseenter the other one on click. I just can not make it work. Can you point me? Cheers to NYC from rainy Berlin.


Oct 18, 2010 at 8:23 AM // reply »
6 Comments

@Ben - I figured out on my own. It was some kind of strange. I was on the right track with creating another var for the second trigger and setting up a click function for it but it was not working because the trigger was wrapped in a <a> tag (as I found out later). It always worked with mouseenter but not with the click event. I then wrapped my trigger in a <span> tag and it all worked like a charme.
Mission completed.


Oct 20, 2010 at 10:29 AM // reply »
11,243 Comments

@Hardy,

Glad you got it working. I am not sure what an A vs. SPAN would do; but, glad you got it working regardless :)


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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 23, 2013 at 5:19 AM
Ask Ben: Print Part Of A Web Page With jQuery
How to print also the background color of table cells and table lines ... read »
May 23, 2013 at 3:55 AM
Javascript Array Methods: Unshift(), Shift(), Push(), And Pop()
very interesting and helpful too. ... read »
May 22, 2013 at 5:35 PM
Script Tags, jQuery, And Html(), Text() And Contents()
This is still an issue 2 years later. jQuery is supposed to remediate these cross browser issues, no? I have been unable to find any statement from the jQuery team calling this behavior "by de ... read »
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools