|
1 YUI.add('event-focus', function (Y, NAME) { |
|
2 |
|
3 /** |
|
4 * Adds bubbling and delegation support to DOM events focus and blur. |
|
5 * |
|
6 * @module event |
|
7 * @submodule event-focus |
|
8 */ |
|
9 var Event = Y.Event, |
|
10 |
|
11 YLang = Y.Lang, |
|
12 |
|
13 isString = YLang.isString, |
|
14 |
|
15 arrayIndex = Y.Array.indexOf, |
|
16 |
|
17 useActivate = (function() { |
|
18 |
|
19 // Changing the structure of this test, so that it doesn't use inline JS in HTML, |
|
20 // which throws an exception in Win8 packaged apps, due to additional security restrictions: |
|
21 // http://msdn.microsoft.com/en-us/library/windows/apps/hh465380.aspx#differences |
|
22 |
|
23 var supported = false, |
|
24 doc = Y.config.doc, |
|
25 p; |
|
26 |
|
27 if (doc) { |
|
28 |
|
29 p = doc.createElement("p"); |
|
30 p.setAttribute("onbeforeactivate", ";"); |
|
31 |
|
32 // onbeforeactivate is a function in IE8+. |
|
33 // onbeforeactivate is a string in IE6,7 (unfortunate, otherwise we could have just checked for function below). |
|
34 // onbeforeactivate is a function in IE10, in a Win8 App environment (no exception running the test). |
|
35 |
|
36 // onbeforeactivate is undefined in Webkit/Gecko. |
|
37 // onbeforeactivate is a function in Webkit/Gecko if it's a supported event (e.g. onclick). |
|
38 |
|
39 supported = (p.onbeforeactivate !== undefined); |
|
40 } |
|
41 |
|
42 return supported; |
|
43 }()); |
|
44 |
|
45 function define(type, proxy, directEvent) { |
|
46 var nodeDataKey = '_' + type + 'Notifiers'; |
|
47 |
|
48 Y.Event.define(type, { |
|
49 |
|
50 _useActivate : useActivate, |
|
51 |
|
52 _attach: function (el, notifier, delegate) { |
|
53 if (Y.DOM.isWindow(el)) { |
|
54 return Event._attach([type, function (e) { |
|
55 notifier.fire(e); |
|
56 }, el]); |
|
57 } else { |
|
58 return Event._attach( |
|
59 [proxy, this._proxy, el, this, notifier, delegate], |
|
60 { capture: true }); |
|
61 } |
|
62 }, |
|
63 |
|
64 _proxy: function (e, notifier, delegate) { |
|
65 var target = e.target, |
|
66 currentTarget = e.currentTarget, |
|
67 notifiers = target.getData(nodeDataKey), |
|
68 yuid = Y.stamp(currentTarget._node), |
|
69 defer = (useActivate || target !== currentTarget), |
|
70 directSub; |
|
71 |
|
72 notifier.currentTarget = (delegate) ? target : currentTarget; |
|
73 notifier.container = (delegate) ? currentTarget : null; |
|
74 |
|
75 // Maintain a list to handle subscriptions from nested |
|
76 // containers div#a>div#b>input #a.on(focus..) #b.on(focus..), |
|
77 // use one focus or blur subscription that fires notifiers from |
|
78 // #b then #a to emulate bubble sequence. |
|
79 if (!notifiers) { |
|
80 notifiers = {}; |
|
81 target.setData(nodeDataKey, notifiers); |
|
82 |
|
83 // only subscribe to the element's focus if the target is |
|
84 // not the current target ( |
|
85 if (defer) { |
|
86 directSub = Event._attach( |
|
87 [directEvent, this._notify, target._node]).sub; |
|
88 directSub.once = true; |
|
89 } |
|
90 } else { |
|
91 // In old IE, defer is always true. In capture-phase browsers, |
|
92 // The delegate subscriptions will be encountered first, which |
|
93 // will establish the notifiers data and direct subscription |
|
94 // on the node. If there is also a direct subscription to the |
|
95 // node's focus/blur, it should not call _notify because the |
|
96 // direct subscription from the delegate sub(s) exists, which |
|
97 // will call _notify. So this avoids _notify being called |
|
98 // twice, unnecessarily. |
|
99 defer = true; |
|
100 } |
|
101 |
|
102 if (!notifiers[yuid]) { |
|
103 notifiers[yuid] = []; |
|
104 } |
|
105 |
|
106 notifiers[yuid].push(notifier); |
|
107 |
|
108 if (!defer) { |
|
109 this._notify(e); |
|
110 } |
|
111 }, |
|
112 |
|
113 _notify: function (e, container) { |
|
114 var currentTarget = e.currentTarget, |
|
115 notifierData = currentTarget.getData(nodeDataKey), |
|
116 axisNodes = currentTarget.ancestors(), |
|
117 doc = currentTarget.get('ownerDocument'), |
|
118 delegates = [], |
|
119 // Used to escape loops when there are no more |
|
120 // notifiers to consider |
|
121 count = notifierData ? |
|
122 Y.Object.keys(notifierData).length : |
|
123 0, |
|
124 target, notifiers, notifier, yuid, match, tmp, i, len, sub, ret; |
|
125 |
|
126 // clear the notifications list (mainly for delegation) |
|
127 currentTarget.clearData(nodeDataKey); |
|
128 |
|
129 // Order the delegate subs by their placement in the parent axis |
|
130 axisNodes.push(currentTarget); |
|
131 // document.get('ownerDocument') returns null |
|
132 // which we'll use to prevent having duplicate Nodes in the list |
|
133 if (doc) { |
|
134 axisNodes.unshift(doc); |
|
135 } |
|
136 |
|
137 // ancestors() returns the Nodes from top to bottom |
|
138 axisNodes._nodes.reverse(); |
|
139 |
|
140 if (count) { |
|
141 // Store the count for step 2 |
|
142 tmp = count; |
|
143 axisNodes.some(function (node) { |
|
144 var yuid = Y.stamp(node), |
|
145 notifiers = notifierData[yuid], |
|
146 i, len; |
|
147 |
|
148 if (notifiers) { |
|
149 count--; |
|
150 for (i = 0, len = notifiers.length; i < len; ++i) { |
|
151 if (notifiers[i].handle.sub.filter) { |
|
152 delegates.push(notifiers[i]); |
|
153 } |
|
154 } |
|
155 } |
|
156 |
|
157 return !count; |
|
158 }); |
|
159 count = tmp; |
|
160 } |
|
161 |
|
162 // Walk up the parent axis, notifying direct subscriptions and |
|
163 // testing delegate filters. |
|
164 while (count && (target = axisNodes.shift())) { |
|
165 yuid = Y.stamp(target); |
|
166 |
|
167 notifiers = notifierData[yuid]; |
|
168 |
|
169 if (notifiers) { |
|
170 for (i = 0, len = notifiers.length; i < len; ++i) { |
|
171 notifier = notifiers[i]; |
|
172 sub = notifier.handle.sub; |
|
173 match = true; |
|
174 |
|
175 e.currentTarget = target; |
|
176 |
|
177 if (sub.filter) { |
|
178 match = sub.filter.apply(target, |
|
179 [target, e].concat(sub.args || [])); |
|
180 |
|
181 // No longer necessary to test against this |
|
182 // delegate subscription for the nodes along |
|
183 // the parent axis. |
|
184 delegates.splice( |
|
185 arrayIndex(delegates, notifier), 1); |
|
186 } |
|
187 |
|
188 if (match) { |
|
189 // undefined for direct subs |
|
190 e.container = notifier.container; |
|
191 ret = notifier.fire(e); |
|
192 } |
|
193 |
|
194 if (ret === false || e.stopped === 2) { |
|
195 break; |
|
196 } |
|
197 } |
|
198 |
|
199 delete notifiers[yuid]; |
|
200 count--; |
|
201 } |
|
202 |
|
203 if (e.stopped !== 2) { |
|
204 // delegates come after subs targeting this specific node |
|
205 // because they would not normally report until they'd |
|
206 // bubbled to the container node. |
|
207 for (i = 0, len = delegates.length; i < len; ++i) { |
|
208 notifier = delegates[i]; |
|
209 sub = notifier.handle.sub; |
|
210 |
|
211 if (sub.filter.apply(target, |
|
212 [target, e].concat(sub.args || []))) { |
|
213 |
|
214 e.container = notifier.container; |
|
215 e.currentTarget = target; |
|
216 ret = notifier.fire(e); |
|
217 } |
|
218 |
|
219 if (ret === false || e.stopped === 2 || |
|
220 // If e.stopPropagation() is called, notify any |
|
221 // delegate subs from the same container, but break |
|
222 // once the container changes. This emulates |
|
223 // delegate() behavior for events like 'click' which |
|
224 // won't notify delegates higher up the parent axis. |
|
225 (e.stopped && delegates[i+1] && |
|
226 delegates[i+1].container !== notifier.container)) { |
|
227 break; |
|
228 } |
|
229 } |
|
230 } |
|
231 |
|
232 if (e.stopped) { |
|
233 break; |
|
234 } |
|
235 } |
|
236 }, |
|
237 |
|
238 on: function (node, sub, notifier) { |
|
239 sub.handle = this._attach(node._node, notifier); |
|
240 }, |
|
241 |
|
242 detach: function (node, sub) { |
|
243 sub.handle.detach(); |
|
244 }, |
|
245 |
|
246 delegate: function (node, sub, notifier, filter) { |
|
247 if (isString(filter)) { |
|
248 sub.filter = function (target) { |
|
249 return Y.Selector.test(target._node, filter, |
|
250 node === target ? null : node._node); |
|
251 }; |
|
252 } |
|
253 |
|
254 sub.handle = this._attach(node._node, notifier, true); |
|
255 }, |
|
256 |
|
257 detachDelegate: function (node, sub) { |
|
258 sub.handle.detach(); |
|
259 } |
|
260 }, true); |
|
261 } |
|
262 |
|
263 // For IE, we need to defer to focusin rather than focus because |
|
264 // `el.focus(); doSomething();` executes el.onbeforeactivate, el.onactivate, |
|
265 // el.onfocusin, doSomething, then el.onfocus. All others support capture |
|
266 // phase focus, which executes before doSomething. To guarantee consistent |
|
267 // behavior for this use case, IE's direct subscriptions are made against |
|
268 // focusin so subscribers will be notified before js following el.focus() is |
|
269 // executed. |
|
270 if (useActivate) { |
|
271 // name capture phase direct subscription |
|
272 define("focus", "beforeactivate", "focusin"); |
|
273 define("blur", "beforedeactivate", "focusout"); |
|
274 } else { |
|
275 define("focus", "focus", "focus"); |
|
276 define("blur", "blur", "blur"); |
|
277 } |
|
278 |
|
279 |
|
280 }, '@VERSION@', {"requires": ["event-synthetic"]}); |