Wednesday, December 16, 2009

Using setTimeout to Delay Showing Event-Delegation Tooltips

Using setTimeout to Delay Showing Event-Delegation Tooltips:

In my last two tutorials, I explained how to use event delegation for showing and hiding tooltips. In a comment on the first one, Jan Aagaard asked how we might go about enhancing the script by adding a small delay before showing a tooltip. The answer lies with two JavaScript functions, setTimeout() and clearTimeout().

Setting Up

Picking up where we left off last time, I'm going to create the tooltip div, declare a couple variables, and then bind 'mouseover,' 'mousemove,' and 'mouseout' all at once. In addition to $livetip and tipTitle, I'm declaring showTip (and leaving it undefined for the moment) and delay. The showTip variable will be used as a reference for the setTimeout function, and delay will be used to set the time after which the function within setTimeout's argument will execute (in this case, 300ms).

JavaScript:
  1. var $liveTip = $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '',
  3. showTip,
  4. delay = 300;
  5. $('#mytable').bind('mouseover mouseout mousemove', function(event) {
  6. // ... code continues
  7. });

Of course, you don't have to use a variable for the delay, but I like to have that sort of setting up top where I can find it easily. Also, if I ever decide to convert this script into a plugin, having the delay stored in a variable will make it simpler to convert it to an option.

Setting the Timeout

Inside the .bind(), and after the business of determining whether the event is being triggered by a link or one of its descendants, the setTimeout is called on line 12:

JavaScript:
  1. var $liveTip = $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '',
  3. showTip,
  4. delay = 300;
  5. $('#mytable').bind('mouseover mouseout mousemove', function(event) {
  6. var $link = $(event.target).closest('a');
  7. if (!$link.length) { return; }
  8. var link = $link[0];
  9. if (event.type == 'mouseover') {
  10. showTip = window.setTimeout(function() {
  11. $link.data('tipActive', true);
  12. tipTitle = link.title;
  13. link.title = '';
  14. $liveTip
  15. .html('<div>' + tipTitle + '</div><div>' + link.href + '</div>')
  16. .show()
  17. .css({
  18. top: event.pageY + 12,
  19. left: event.pageX + 12
  20. });
  21. }, delay);
  22. }
  23. // ... code continues
  24. });

setTimeout is actually a method of the window object; as such, the this keyword inside its function argument will refer to window. It's not of much concern here because, with event delegation, we're relying on event.target rather than this, but I thought it might be worth mentioning anyway.

Note that within the setTimeout function, I'm storing a little data (line 13) for the link to indicate that the tooltip is active on it. This tipActive information then determines what to do on mouseout.

Clearing the Timeout

On mouseout, if the link has the tipActive data, that data is removed (line 29, below), the tooltip is hidden (line 30), and the link's original title attribute value is restored if tipTitle has a value (line 31). If that tipActive data is not associated with the link, then the timeout that was set on mouseover is reset with clearTimeout (line 33).

JavaScript:
  1. var $liveTip = $('<div id="livetip"></div>').hide().appendTo('body');
  2. var tipTitle = '',
  3. showTip,
  4. delay = 300;
  5. $('#mytable').bind('mouseover mouseout mousemove', function(event) {
  6. var $link = $(event.target).closest('a');
  7. if (!$link.length) { return; }
  8. var link = $link[0];
  9. if (event.type == 'mouseover') {
  10. showTip = window.setTimeout(function() {
  11. $link.data('tipActive', true);
  12. tipTitle = link.title;
  13. link.title = '';
  14. $liveTip
  15. .html('<div>' + tipTitle + '</div><div>' + link.href + '</div>')
  16. .show()
  17. .css({
  18. top: event.pageY + 12,
  19. left: event.pageX + 12
  20. });
  21. }, delay);
  22. }
  23. if (event.type == 'mouseout') {
  24. if ($link.data('tipActive')) {
  25. $link.removeData('tipActive');
  26. $liveTip.hide();
  27. link.title = tipTitle || link.title;
  28. } else {
  29. window.clearTimeout(showTip);
  30. }
  31. }
  32. if (event.type == 'mousemove' && $link.data('tipActive')) {
  33. $liveTip.css({
  34. top: event.pageY + 12,
  35. left: event.pageX + 12
  36. });
  37. }
  38. });

The mousemove part hasn't changed from what it was in the previous post, except that it first checks if the 'tipActive' data is assigned to the link. I figured that setting the top and left properties of the tooltip is probably more work than doing a little check on the link's data, so why bother setting them if I don't have to?

So, that's it. Just add a simple setTimeout delay and conditionally clear it. If you want to set a delay on mouseout, you can do that the same way.

Check out the demo or download the zip (or neither, if you're stubborn).