Unclickable links using bubbling only
Bubble through a wormhole to short-circuit your JS logic.
While implementing Gravity Department’s typeahead library an issue was discovered that affects all versions of Internet Explorer:
You can delegate a click
event for a link which will bubble to the document
and trigger a callback — which can destroy the link (and its default behavior) — all before the link itself receives the click
event.
Strangely, the quirk manifests due to the DOM structure and not behavior changes using e.preventDefault()
or e.stopPropagation()
.
The normal flow
This event delegation flow is consistent across browsers:
- Click a link.
- The event originates on the
<a>
triggering the default behavior (browser navigates to thehref
). - The event bubbles up the DOM to the
document
triggering the delegated event. - The callback replaces the inner HTML of
.wrap
with the message. - End ➔ the browser redirected.
<div class="wrap">
<a href="http://aaa.com">AAA</a>
<a href="http://bbb.com">BBB</a>
<a href="http://ccc.com">CCC</a>
</div>
jQuery(document).on('click', 'a', function (e) {
jQuery('.wrap').html('Loading');
});
Short-circuit the default event with a bubbling wormhole
A small HTML change creates a race condition between default events and delegated events in IE. See the <div>
inside the link?
<div class="wrap">
<a href="http://aaa.com"><div>AAA</div></a>
<a href="http://bbb.com"><div>BBB</div></a>
<a href="http://ccc.com"><div>CCC</div></a>
</div>
That creates a wormhole. Now the callback will execute before the link’s default behavior and prevent it from happening.
- Click a link.
- The event originates on the
<div>
inside the<a>
and starts bubbling up. - Bubbling reaches the
<a>
and eventuallydocument
, but something unexpected happens. - Our delegated event executes before the click registers on the
<a>
. - The callback replaces the inner HTML of
.wrap
with the message. - The DOM containing the anchors is destroyed (so is the default link behavior).
- End ➔ the browser never redirects.
It makes no sense, but IE will bubble clicks on the child element to its furthest ancestor faster than its parent <a>
can execute default behavior.
Technically this is our fault for trusting the browser to execute default and delegated events synchronously. They are asynchronous. Bubbling can wormhole the space/time continuum.
Affected browsers
IE11 and below still have this problem.
Functional browsers
Microsoft Edge and other modern browsers are either “friendly” to programmers, or coincidentally don’t exhibit the race condition (for now).
Best practice
You can handle the async nature of events. Always cancel then reproduce default behavior when implementing delegated events, so you can direction the execution order.
jQuery(document).on('click', 'a', function (e) {
// Cancel the default behavior
e.preventDefault();
// Manually replicate the default behavior
var href = jQuery(this).attr('href');
if (typeof href !== 'undefined' && href !== '') {
window.location = href;
}
// Replace the DOM with message
jQuery('.links').html('Loading');
});