2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
8 YUI.add('event-focus', function (Y, NAME) {
11 * Adds bubbling and delegation support to DOM events focus and blur.
14 * @submodule event-focus
20 isString = YLang.isString,
22 arrayIndex = Y.Array.indexOf,
24 useActivate = (function() {
26 // Changing the structure of this test, so that it doesn't use inline JS in HTML,
27 // which throws an exception in Win8 packaged apps, due to additional security restrictions:
28 // http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences
30 var supported = false,
36 p = doc.createElement("p");
37 p.setAttribute("onbeforeactivate", ";");
39 // onbeforeactivate is a function in IE8+.
40 // onbeforeactivate is a string in IE6,7 (unfortunate, otherwise we could have just checked for function below).
41 // onbeforeactivate is a function in IE10, in a Win8 App environment (no exception running the test).
43 // onbeforeactivate is undefined in Webkit/Gecko.
44 // onbeforeactivate is a function in Webkit/Gecko if it's a supported event (e.g. onclick).
46 supported = (p.onbeforeactivate !== undefined);
52 function define(type, proxy, directEvent) {
53 var nodeDataKey = '_' + type + 'Notifiers';
55 Y.Event.define(type, {
57 _useActivate : useActivate,
59 _attach: function (el, notifier, delegate) {
60 if (Y.DOM.isWindow(el)) {
61 return Event._attach([type, function (e) {
66 [proxy, this._proxy, el, this, notifier, delegate],
71 _proxy: function (e, notifier, delegate) {
72 var target = e.target,
73 currentTarget = e.currentTarget,
74 notifiers = target.getData(nodeDataKey),
75 yuid = Y.stamp(currentTarget._node),
76 defer = (useActivate || target !== currentTarget),
79 notifier.currentTarget = (delegate) ? target : currentTarget;
80 notifier.container = (delegate) ? currentTarget : null;
82 // Maintain a list to handle subscriptions from nested
83 // containers div#a>div#b>input #a.on(focus..) #b.on(focus..),
84 // use one focus or blur subscription that fires notifiers from
85 // #b then #a to emulate bubble sequence.
88 target.setData(nodeDataKey, notifiers);
90 // only subscribe to the element's focus if the target is
91 // not the current target (
93 directSub = Event._attach(
94 [directEvent, this._notify, target._node]).sub;
95 directSub.once = true;
98 // In old IE, defer is always true. In capture-phase browsers,
99 // The delegate subscriptions will be encountered first, which
100 // will establish the notifiers data and direct subscription
101 // on the node. If there is also a direct subscription to the
102 // node's focus/blur, it should not call _notify because the
103 // direct subscription from the delegate sub(s) exists, which
104 // will call _notify. So this avoids _notify being called
105 // twice, unnecessarily.
109 if (!notifiers[yuid]) {
110 notifiers[yuid] = [];
113 notifiers[yuid].push(notifier);
120 _notify: function (e, container) {
121 var currentTarget = e.currentTarget,
122 notifierData = currentTarget.getData(nodeDataKey),
123 axisNodes = currentTarget.ancestors(),
124 doc = currentTarget.get('ownerDocument'),
126 // Used to escape loops when there are no more
127 // notifiers to consider
128 count = notifierData ?
129 Y.Object.keys(notifierData).length :
131 target, notifiers, notifier, yuid, match, tmp, i, len, sub, ret;
133 // clear the notifications list (mainly for delegation)
134 currentTarget.clearData(nodeDataKey);
136 // Order the delegate subs by their placement in the parent axis
137 axisNodes.push(currentTarget);
138 // document.get('ownerDocument') returns null
139 // which we'll use to prevent having duplicate Nodes in the list
141 axisNodes.unshift(doc);
144 // ancestors() returns the Nodes from top to bottom
145 axisNodes._nodes.reverse();
148 // Store the count for step 2
150 axisNodes.some(function (node) {
151 var yuid = Y.stamp(node),
152 notifiers = notifierData[yuid],
157 for (i = 0, len = notifiers.length; i < len; ++i) {
158 if (notifiers[i].handle.sub.filter) {
159 delegates.push(notifiers[i]);
169 // Walk up the parent axis, notifying direct subscriptions and
170 // testing delegate filters.
171 while (count && (target = axisNodes.shift())) {
172 yuid = Y.stamp(target);
174 notifiers = notifierData[yuid];
177 for (i = 0, len = notifiers.length; i < len; ++i) {
178 notifier = notifiers[i];
179 sub = notifier.handle.sub;
182 e.currentTarget = target;
185 match = sub.filter.apply(target,
186 [target, e].concat(sub.args || []));
188 // No longer necessary to test against this
189 // delegate subscription for the nodes along
192 arrayIndex(delegates, notifier), 1);
196 // undefined for direct subs
197 e.container = notifier.container;
198 ret = notifier.fire(e);
201 if (ret === false || e.stopped === 2) {
206 delete notifiers[yuid];
210 if (e.stopped !== 2) {
211 // delegates come after subs targeting this specific node
212 // because they would not normally report until they'd
213 // bubbled to the container node.
214 for (i = 0, len = delegates.length; i < len; ++i) {
215 notifier = delegates[i];
216 sub = notifier.handle.sub;
218 if (sub.filter.apply(target,
219 [target, e].concat(sub.args || []))) {
221 e.container = notifier.container;
222 e.currentTarget = target;
223 ret = notifier.fire(e);
226 if (ret === false || e.stopped === 2 ||
227 // If e.stopPropagation() is called, notify any
228 // delegate subs from the same container, but break
229 // once the container changes. This emulates
230 // delegate() behavior for events like 'click' which
231 // won't notify delegates higher up the parent axis.
232 (e.stopped && delegates[i+1] &&
233 delegates[i+1].container !== notifier.container)) {
245 on: function (node, sub, notifier) {
246 sub.handle = this._attach(node._node, notifier);
249 detach: function (node, sub) {
253 delegate: function (node, sub, notifier, filter) {
254 if (isString(filter)) {
255 sub.filter = function (target) {
256 return Y.Selector.test(target._node, filter,
257 node === target ? null : node._node);
261 sub.handle = this._attach(node._node, notifier, true);
264 detachDelegate: function (node, sub) {
270 // For IE, we need to defer to focusin rather than focus because
271 // `el.focus(); doSomething();` executes el.onbeforeactivate, el.onactivate,
272 // el.onfocusin, doSomething, then el.onfocus. All others support capture
273 // phase focus, which executes before doSomething. To guarantee consistent
274 // behavior for this use case, IE's direct subscriptions are made against
275 // focusin so subscribers will be notified before js following el.focus() is
278 // name capture phase direct subscription
279 define("focus", "beforeactivate", "focusin");
280 define("blur", "beforedeactivate", "focusout");
282 define("focus", "focus", "focus");
283 define("blur", "blur", "blur");
287 }, '3.13.0', {"requires": ["event-synthetic"]});