Created
August 30, 2013 18:41
-
-
Save lsmith/6393024 to your computer and use it in GitHub Desktop.
Fix for a bug in YUI 3 focus synthetic event when e.stopPropagation() is called between two delegate subscribers. Fix is on line 218.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Adds bubbling and delegation support to DOM events focus and blur. | |
* | |
* @module event | |
* @submodule event-focus | |
*/ | |
var Event = Y.Event, | |
YLang = Y.Lang, | |
isString = YLang.isString, | |
arrayIndex = Y.Array.indexOf, | |
useActivate = (function() { | |
// Changing the structure of this test, so that it doesn't use inline JS in HTML, | |
// which throws an exception in Win8 packaged apps, due to additional security restrictions: | |
// http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences | |
var supported = false, | |
doc = Y.config.doc, | |
p; | |
if (doc) { | |
p = doc.createElement("p"); | |
p.setAttribute("onbeforeactivate", ";"); | |
// onbeforeactivate is a function in IE8+. | |
// onbeforeactivate is a string in IE6,7 (unfortunate, otherwise we could have just checked for function below). | |
// onbeforeactivate is a function in IE10, in a Win8 App environment (no exception running the test). | |
// onbeforeactivate is undefined in Webkit/Gecko. | |
// onbeforeactivate is a function in Webkit/Gecko if it's a supported event (e.g. onclick). | |
supported = (p.onbeforeactivate !== undefined); | |
} | |
return supported; | |
}()); | |
function define(type, proxy, directEvent) { | |
var nodeDataKey = '_' + type + 'Notifiers'; | |
Y.Event.define(type, { | |
_useActivate : useActivate, | |
_attach: function (el, notifier, delegate) { | |
if (Y.DOM.isWindow(el)) { | |
return Event._attach([type, function (e) { | |
notifier.fire(e); | |
}, el]); | |
} else { | |
return Event._attach( | |
[proxy, this._proxy, el, this, notifier, delegate], | |
{ capture: true }); | |
} | |
}, | |
_proxy: function (e, notifier, delegate) { | |
var target = e.target, | |
currentTarget = e.currentTarget, | |
notifiers = target.getData(nodeDataKey), | |
yuid = Y.stamp(currentTarget._node), | |
defer = (useActivate || target !== currentTarget), | |
directSub; | |
notifier.currentTarget = (delegate) ? target : currentTarget; | |
notifier.container = (delegate) ? currentTarget : null; | |
// Maintain a list to handle subscriptions from nested | |
// containers div#a>div#b>input #a.on(focus..) #b.on(focus..), | |
// use one focus or blur subscription that fires notifiers from | |
// #b then #a to emulate bubble sequence. | |
if (!notifiers) { | |
notifiers = {}; | |
target.setData(nodeDataKey, notifiers); | |
// only subscribe to the element's focus if the target is | |
// not the current target ( | |
if (defer) { | |
directSub = Event._attach( | |
[directEvent, this._notify, target._node]).sub; | |
directSub.once = true; | |
} | |
} else { | |
// In old IE, defer is always true. In capture-phase browsers, | |
// The delegate subscriptions will be encountered first, which | |
// will establish the notifiers data and direct subscription | |
// on the node. If there is also a direct subscription to the | |
// node's focus/blur, it should not call _notify because the | |
// direct subscription from the delegate sub(s) exists, which | |
// will call _notify. So this avoids _notify being called | |
// twice, unnecessarily. | |
defer = true; | |
} | |
if (!notifiers[yuid]) { | |
notifiers[yuid] = []; | |
} | |
notifiers[yuid].push(notifier); | |
if (!defer) { | |
this._notify(e); | |
} | |
}, | |
_notify: function (e, container) { | |
var currentTarget = e.currentTarget, | |
notifierData = currentTarget.getData(nodeDataKey), | |
axisNodes = currentTarget.ancestors(), | |
doc = currentTarget.get('ownerDocument'), | |
delegates = [], | |
// Used to escape loops when there are no more | |
// notifiers to consider | |
count = notifierData ? | |
Y.Object.keys(notifierData).length : | |
0, | |
target, notifiers, notifier, yuid, match, tmp, i, len, sub, ret; | |
// clear the notifications list (mainly for delegation) | |
currentTarget.clearData(nodeDataKey); | |
// Order the delegate subs by their placement in the parent axis | |
axisNodes.push(currentTarget); | |
// document.get('ownerDocument') returns null | |
// which we'll use to prevent having duplicate Nodes in the list | |
if (doc) { | |
axisNodes.unshift(doc); | |
} | |
// ancestors() returns the Nodes from top to bottom | |
axisNodes._nodes.reverse(); | |
if (count) { | |
// Store the count for step 2 | |
tmp = count; | |
axisNodes.some(function (node) { | |
var yuid = Y.stamp(node), | |
notifiers = notifierData[yuid], | |
i, len; | |
if (notifiers) { | |
count--; | |
for (i = 0, len = notifiers.length; i < len; ++i) { | |
if (notifiers[i].handle.sub.filter) { | |
delegates.push(notifiers[i]); | |
} | |
} | |
} | |
return !count; | |
}); | |
count = tmp; | |
} | |
// Walk up the parent axis, notifying direct subscriptions and | |
// testing delegate filters. | |
while (count && (target = axisNodes.shift())) { | |
yuid = Y.stamp(target); | |
notifiers = notifierData[yuid]; | |
if (notifiers) { | |
for (i = 0, len = notifiers.length; i < len; ++i) { | |
notifier = notifiers[i]; | |
sub = notifier.handle.sub; | |
match = true; | |
e.currentTarget = target; | |
if (sub.filter) { | |
match = sub.filter.apply(target, | |
[target, e].concat(sub.args || [])); | |
// No longer necessary to test against this | |
// delegate subscription for the nodes along | |
// the parent axis. | |
delegates.splice( | |
arrayIndex(delegates, notifier), 1); | |
} | |
if (match) { | |
// undefined for direct subs | |
e.container = notifier.container; | |
ret = notifier.fire(e); | |
} | |
if (ret === false || e.stopped === 2) { | |
break; | |
} | |
} | |
delete notifiers[yuid]; | |
count--; | |
} | |
if (e.stopped !== 2) { | |
// delegates come after subs targeting this specific node | |
// because they would not normally report until they'd | |
// bubbled to the container node. | |
for (i = 0, len = delegates.length; i < len; ++i) { | |
notifier = delegates[i]; | |
sub = notifier.handle.sub; | |
if (sub.filter.apply(target, | |
[target, e].concat(sub.args || []))) { | |
e.container = notifier.container; | |
e.currentTarget = target; | |
ret = notifier.fire(e); | |
} | |
if (ret === false || e.stopped === 2 || | |
(e.stopped && delegates[i + 1] && delegates[i + 1].container !== notifier.container)) { | |
break; | |
} | |
} | |
} | |
if (e.stopped) { | |
break; | |
} | |
} | |
}, | |
on: function (node, sub, notifier) { | |
sub.handle = this._attach(node._node, notifier); | |
}, | |
detach: function (node, sub) { | |
sub.handle.detach(); | |
}, | |
delegate: function (node, sub, notifier, filter) { | |
if (isString(filter)) { | |
sub.filter = function (target) { | |
return Y.Selector.test(target._node, filter, | |
node === target ? null : node._node); | |
}; | |
} | |
sub.handle = this._attach(node._node, notifier, true); | |
}, | |
detachDelegate: function (node, sub) { | |
sub.handle.detach(); | |
} | |
}, true); | |
} | |
// For IE, we need to defer to focusin rather than focus because | |
// `el.focus(); doSomething();` executes el.onbeforeactivate, el.onactivate, | |
// el.onfocusin, doSomething, then el.onfocus. All others support capture | |
// phase focus, which executes before doSomething. To guarantee consistent | |
// behavior for this use case, IE's direct subscriptions are made against | |
// focusin so subscribers will be notified before js following el.focus() is | |
// executed. | |
if (useActivate) { | |
// name capture phase direct subscription | |
define("focus", "beforeactivate", "focusin"); | |
define("blur", "beforedeactivate", "focusout"); | |
} else { | |
define("focus", "focus", "focus"); | |
define("blur", "blur", "blur"); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment