Todays article dicusses a problem I have been encountering when assigning on demand
click
events to the document from a blur
event. The problem arrises as the click
and blur
events have no gauranteed order of firing, and because the click
event is attached to the document, it is easily triggered. Suppose the following problem: when a blur
event fires you want to show a tooltip message that should disappear on a click
event, so you attach the click
event listener to the document in response to blur
event. However, the user clicks out of the input element triggering both a click
event and the blur
event. Most of the time, in most browsers, the blur
event fires before the click
event finishes bubbling, so the click
event on the document fires as soon as it is attached.
The Problem
Here is an example of how to setup the issue:
YUI().use(node, function(Y) { var tooltipNode = new Y.get(#myTooltipElement); // this is the click event that we attach to the document var clickCallback = function() { docNode.detach(click, clickCallback); tooltipNode.setStyle(display,none); }; // this is the triggering callback var blurCallback = function() { var docNode = new Y.Node(document); docNode.on(click, clickCallback); tooltipNode.setStyle(display,block); }; Y.get(#anInputElement).on(blur, blurCallback); });
Solution 1: Ignore The First Click
The first possible solution, is to ignore the first click after attaching the document click
listener:
YUI().use(node, function(Y) { var tooltipNode = new Y.get(#myTooltipElement); var clickCount = 0; // this is the click event that we attach to the document var clickCallback = function() { if (clickCount) { docNode.detach(click, clickCallback); tooltipNode.setStyle(display,none); } clickCount += 1; }; // this is the triggering callback var blurCallback = function() { clickCount = 0; var docNode = new Y.Node(document); docNode.on(click, clickCallback); tooltipNode.setStyle(display,block); }; Y.get(#anInputElement).on(blur, blurCallback); });
This is a simple solution and works, but is a bandaid for the real problem, and may lead to undesirable user experience, where end-users have a difficult time understand how to clear the tooltip.
Solution 2: Using A Timeout To Attach The Click Listener
The second possible solution, is to use a timeout to attach the document click
listener:
YUI().use(node, function(Y) { var tooltipNode = new Y.get(#myTooltipElement); // this is the click event that we attach to the document var clickCallback = function() { docNode.detach(click, clickCallback); tooltipNode.setStyle(display,none); }; // this is the triggering callback var blurCallback = function() { var docNode = new Y.Node(document); setTimeout(500, function() { docNode.on(click, clickCallback); }); tooltipNode.setStyle(display,block); }; Y.get(#anInputElement).on(blur, blurCallback); });
This also works, but is just another bandaid for the real problem, and cannot gaurantee that it will work correctly. By waiting a small amount of time we allow the browser to finish handling the blur
event and any related events, before attaching the click
event listener. After some experimentation, I learned that most browsers, on average machines, will finish processing the events in less than 450ms, so I choose 500 milliseconds.
Conclusion
I have not found a perfect solution for attaching click
events to the document in response to a blur
event. Each browser handles bubbling and the order of event firing slightly differently, so a solution cannot depend on event order. In my own code, I have been using the second example, because it works correctly most of the time. I would be interested in hearing if anyone has a better solution.