views:

1250

answers:

3

I have an element on my page that I need to attach onclick and ondblclick event handlers to. When a single click happens, it should do something different than a double-click. When I first started trying to make this work, my head started spinning. Obviously, onclick will always fire when you double-click. So I tried using a timeout-based structure like this...

window.onload = function() {
  var timer;
  var el = document.getElementById('testButton');

  el.onclick = function() {
    timer = setTimeout(function() { alert('Single'); }, 150);        
  }

  el.ondblclick = function() {
    clearTimeout(timer);
    alert('Double');
  }
}

But I got inconsistent results (using IE8). It would work properly alot of times, but sometimes I would get the "Single" alert two times.

Has anybody done this before? Is there a more effective way?

A: 

I used Bind, which is a jQuery plug-in. It relieves the pain greatly.

Nissan Fan
Yeah, but the click event will *always* fire when you double-click. So you can't separate the functionality.
Josh Stodola
Have a look at the very bottom of this page: http://webdeveloper.com/forum/showthread.php?t=217775 It's a generic JavaScript solution.
Nissan Fan
a) Since when was `bind()` a plugin and not built-in? b) this does nothing to solve Josh's problem
Crescent Fresh
Any plans to contribute something useful crescentfresh, or do you just like popping in and laying into terminology? I'm well aware Bind is core to the library. You must be your company's resident guru know-it-all.
Nissan Fan
+4  A: 

If you're getting 2 alerts, it would seem your threshold for detecing a double click is too small. Try increasing 150 to 300ms.

Also - I'm not sure that you are guaranteed the order in which click and dblclick are fired. So, when your dblclick gets fired, it clears out the first click event, but if it fires before the second 'click' event, this second event will still fire on its own, and you'll end up with both a double click event firing and a single click event firing.

I see two possible solutions to this potential problem:

1) Set another timeout for actually firing the double-click event. Mark in your code that the double click event is about to fire. Then, when the 2nd 'single click' event fires, it can check on this state, and say "oops, dbl click pending, so I'll do nothing"

2) The second option is to swap your target functions out based on click events. It might look something like this:

window.onload = function() {
  var timer;
  var el = document.getElementById('testButton');

  var firing = false;
  var singleClick = function(){
    alert('Single');
  };

  var doubleClick = function(){ 
    alert('Double');
  };

  var firingFunc = singleClick;

  el.onclick = function() {
    // Detect the 2nd single click event, so we can stop it
    if(firing) 
      return;

    firing = true;
    timer = setTimeout(function() { 
       firingFunc(); 

       // Always revert back to singleClick firing function
       firingFunc = singleClick;
       firing = false;
    }, 150);

  }

  el.ondblclick = function() {
    firingFunc = doubleClick; 
    // Now, when the original timeout of your single click finishes, 
    // firingFunc will be pointing to your doubleClick handler        
  }
}

Basically what is happening here is you let the original timeout you set continue. It will always call firingFunc(); The only thing that changes is what firingFunc() is actually pointing to. Once the double click is detected, it sets it to doubleClick. And then we always revert back to singleClick once the timeout expires.

We also have a "firing" variable in there so we know to intercept the 2nd single click event.

Another alternative is to ignore dblclick events entirely, and just detect it with the single clicks and the timer:

window.onload = function() {
  var timer;
  var el = document.getElementById('testButton');

  var firing = false;
  var singleClick = function(){
    alert('Single');
  };

  var doubleClick = function(){ 
    alert('Double');
  };

  var firingFunc = singleClick;

  el.onclick = function() {
    // Detect the 2nd single click event, so we can set it to doubleClick
    if(firing){
      firingFunc = doubleClick; 
      return;
    }

    firing = true;
    timer = setTimeout(function() { 
       firingFunc(); 

       // Always revert back to singleClick firing function
       firingFunc = singleClick;
       firing = false;
    }, 150);

  }
}

This is untested :)

Matt
The first code example worked for me but ran into problems with the second.
michael
+3  A: 

Like Matt, I had a much better experience when I increased the timeout value slightly. Also, to mitigate the problem of single click firing twice (which I was unable to reproduce with the higher timer anyway), I added a line to the single click handler:

el.onclick = function() {
    if (timer) clearTimeout(timer);
    timer = setTimeout(function() { alert('Single'); }, 250);        
}

This way, if click is already set to fire, it will clear itself to avoid duplicate 'Single' alerts.

Soldarnal
Wow, I love the simplicity of your answer. I guess you could drop the dblclick listener all together and replace your IF block to: { clearTimeout(timer); myDoubleClickHandler(); return; }
Matt