3
Copyright 2012 Yahoo! Inc. All rights reserved.
4
Licensed under the BSD License.
5
http://yuilibrary.com/license/
7
YUI.add('event-focus', function(Y) {
10
* Adds bubbling and delegation support to DOM events focus and blur.
13
* @submodule event-focus
17
isString = YLang.isString,
18
arrayIndex = Y.Array.indexOf,
19
useActivate = YLang.isFunction(
20
Y.DOM.create('<p onbeforeactivate=";"/>').onbeforeactivate);
22
function define(type, proxy, directEvent) {
23
var nodeDataKey = '_' + type + 'Notifiers';
25
Y.Event.define(type, {
27
_attach: function (el, notifier, delegate) {
28
if (Y.DOM.isWindow(el)) {
29
return Event._attach([type, function (e) {
34
[proxy, this._proxy, el, this, notifier, delegate],
39
_proxy: function (e, notifier, delegate) {
40
var target = e.target,
41
currentTarget = e.currentTarget,
42
notifiers = target.getData(nodeDataKey),
43
yuid = Y.stamp(currentTarget._node),
44
defer = (useActivate || target !== currentTarget),
47
notifier.currentTarget = (delegate) ? target : currentTarget;
48
notifier.container = (delegate) ? currentTarget : null;
50
// Maintain a list to handle subscriptions from nested
51
// containers div#a>div#b>input #a.on(focus..) #b.on(focus..),
52
// use one focus or blur subscription that fires notifiers from
53
// #b then #a to emulate bubble sequence.
56
target.setData(nodeDataKey, notifiers);
58
// only subscribe to the element's focus if the target is
59
// not the current target (
61
directSub = Event._attach(
62
[directEvent, this._notify, target._node]).sub;
63
directSub.once = true;
66
// In old IE, defer is always true. In capture-phase browsers,
67
// The delegate subscriptions will be encountered first, which
68
// will establish the notifiers data and direct subscription
69
// on the node. If there is also a direct subscription to the
70
// node's focus/blur, it should not call _notify because the
71
// direct subscription from the delegate sub(s) exists, which
72
// will call _notify. So this avoids _notify being called
73
// twice, unnecessarily.
77
if (!notifiers[yuid]) {
81
notifiers[yuid].push(notifier);
88
_notify: function (e, container) {
89
var currentTarget = e.currentTarget,
90
notifierData = currentTarget.getData(nodeDataKey),
91
axisNodes = currentTarget.ancestors(),
92
doc = currentTarget.get('ownerDocument'),
94
// Used to escape loops when there are no more
95
// notifiers to consider
96
count = notifierData ?
97
Y.Object.keys(notifierData).length :
99
target, notifiers, notifier, yuid, match, tmp, i, len, sub, ret;
101
// clear the notifications list (mainly for delegation)
102
currentTarget.clearData(nodeDataKey);
104
// Order the delegate subs by their placement in the parent axis
105
axisNodes.push(currentTarget);
106
// document.get('ownerDocument') returns null
107
// which we'll use to prevent having duplicate Nodes in the list
109
axisNodes.unshift(doc);
112
// ancestors() returns the Nodes from top to bottom
113
axisNodes._nodes.reverse();
115
// Store the count for step 2
117
axisNodes.some(function (node) {
118
var yuid = Y.stamp(node),
119
notifiers = notifierData[yuid],
124
for (i = 0, len = notifiers.length; i < len; ++i) {
125
if (notifiers[i].handle.sub.filter) {
126
delegates.push(notifiers[i]);
135
// Walk up the parent axis, notifying direct subscriptions and
136
// testing delegate filters.
137
while (count && (target = axisNodes.shift())) {
138
yuid = Y.stamp(target);
140
notifiers = notifierData[yuid];
143
for (i = 0, len = notifiers.length; i < len; ++i) {
144
notifier = notifiers[i];
145
sub = notifier.handle.sub;
148
e.currentTarget = target;
151
match = sub.filter.apply(target,
152
[target, e].concat(sub.args || []));
154
// No longer necessary to test against this
155
// delegate subscription for the nodes along
158
arrayIndex(delegates, notifier), 1);
162
// undefined for direct subs
163
e.container = notifier.container;
164
ret = notifier.fire(e);
167
if (ret === false || e.stopped === 2) {
172
delete notifiers[yuid];
176
if (e.stopped !== 2) {
177
// delegates come after subs targeting this specific node
178
// because they would not normally report until they'd
179
// bubbled to the container node.
180
for (i = 0, len = delegates.length; i < len; ++i) {
181
notifier = delegates[i];
182
sub = notifier.handle.sub;
184
if (sub.filter.apply(target,
185
[target, e].concat(sub.args || []))) {
187
e.container = notifier.container;
188
e.currentTarget = target;
189
ret = notifier.fire(e);
192
if (ret === false || e.stopped === 2) {
204
on: function (node, sub, notifier) {
205
sub.handle = this._attach(node._node, notifier);
208
detach: function (node, sub) {
212
delegate: function (node, sub, notifier, filter) {
213
if (isString(filter)) {
214
sub.filter = function (target) {
215
return Y.Selector.test(target._node, filter,
216
node === target ? null : node._node);
220
sub.handle = this._attach(node._node, notifier, true);
223
detachDelegate: function (node, sub) {
229
// For IE, we need to defer to focusin rather than focus because
230
// `el.focus(); doSomething();` executes el.onbeforeactivate, el.onactivate,
231
// el.onfocusin, doSomething, then el.onfocus. All others support capture
232
// phase focus, which executes before doSomething. To guarantee consistent
233
// behavior for this use case, IE's direct subscriptions are made against
234
// focusin so subscribers will be notified before js following el.focus() is
237
// name capture phase direct subscription
238
define("focus", "beforeactivate", "focusin");
239
define("blur", "beforedeactivate", "focusout");
241
define("focus", "focus", "focus");
242
define("blur", "blur", "blur");
246
}, '3.5.1' ,{requires:['event-synthetic']});