Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
YUI.add('event-delegate', function (Y, NAME) {
2
 
3
/**
4
 * Adds event delegation support to the library.
5
 *
6
 * @module event
7
 * @submodule event-delegate
8
 */
9
 
10
var toArray          = Y.Array,
11
    YLang            = Y.Lang,
12
    isString         = YLang.isString,
13
    isObject         = YLang.isObject,
14
    isArray          = YLang.isArray,
15
    selectorTest     = Y.Selector.test,
16
    detachCategories = Y.Env.evt.handles;
17
 
18
/**
19
 * <p>Sets up event delegation on a container element.  The delegated event
20
 * will use a supplied selector or filtering function to test if the event
21
 * references at least one node that should trigger the subscription
22
 * callback.</p>
23
 *
24
 * <p>Selector string filters will trigger the callback if the event originated
25
 * from a node that matches it or is contained in a node that matches it.
26
 * Function filters are called for each Node up the parent axis to the
27
 * subscribing container node, and receive at each level the Node and the event
28
 * object.  The function should return true (or a truthy value) if that Node
29
 * should trigger the subscription callback.  Note, it is possible for filters
30
 * to match multiple Nodes for a single event.  In this case, the delegate
31
 * callback will be executed for each matching Node.</p>
32
 *
33
 * <p>For each matching Node, the callback will be executed with its 'this'
34
 * object set to the Node matched by the filter (unless a specific context was
35
 * provided during subscription), and the provided event's
36
 * <code>currentTarget</code> will also be set to the matching Node.  The
37
 * containing Node from which the subscription was originally made can be
38
 * referenced as <code>e.container</code>.
39
 *
40
 * @method delegate
41
 * @param type {String} the event type to delegate
42
 * @param fn {Function} the callback function to execute.  This function
43
 *              will be provided the event object for the delegated event.
44
 * @param el {String|node} the element that is the delegation container
45
 * @param filter {string|Function} a selector that must match the target of the
46
 *              event or a function to test target and its parents for a match
47
 * @param context optional argument that specifies what 'this' refers to.
48
 * @param args* 0..n additional arguments to pass on to the callback function.
49
 *              These arguments will be added after the event object.
50
 * @return {EventHandle} the detach handle
51
 * @static
52
 * @for Event
53
 */
54
function delegate(type, fn, el, filter) {
55
    var args     = toArray(arguments, 0, true),
56
        query    = isString(el) ? el : null,
57
        typeBits, synth, container, categories, cat, i, len, handles, handle;
58
 
59
    // Support Y.delegate({ click: fnA, key: fnB }, el, filter, ...);
60
    // and Y.delegate(['click', 'key'], fn, el, filter, ...);
61
    if (isObject(type)) {
62
        handles = [];
63
 
64
        if (isArray(type)) {
65
            for (i = 0, len = type.length; i < len; ++i) {
66
                args[0] = type[i];
67
                handles.push(Y.delegate.apply(Y, args));
68
            }
69
        } else {
70
            // Y.delegate({'click', fn}, el, filter) =>
71
            // Y.delegate('click', fn, el, filter)
72
            args.unshift(null); // one arg becomes two; need to make space
73
 
74
            for (i in type) {
75
                if (type.hasOwnProperty(i)) {
76
                    args[0] = i;
77
                    args[1] = type[i];
78
                    handles.push(Y.delegate.apply(Y, args));
79
                }
80
            }
81
        }
82
 
83
        return new Y.EventHandle(handles);
84
    }
85
 
86
    typeBits = type.split(/\|/);
87
 
88
    if (typeBits.length > 1) {
89
        cat  = typeBits.shift();
90
        args[0] = type = typeBits.shift();
91
    }
92
 
93
    synth = Y.Node.DOM_EVENTS[type];
94
 
95
    if (isObject(synth) && synth.delegate) {
96
        handle = synth.delegate.apply(synth, arguments);
97
    }
98
 
99
    if (!handle) {
100
        if (!type || !fn || !el || !filter) {
101
            return;
102
        }
103
 
104
        container = (query) ? Y.Selector.query(query, null, true) : el;
105
 
106
        if (!container && isString(el)) {
107
            handle = Y.on('available', function () {
108
                Y.mix(handle, Y.delegate.apply(Y, args), true);
109
            }, el);
110
        }
111
 
112
        if (!handle && container) {
113
            args.splice(2, 2, container); // remove the filter
114
 
115
            handle = Y.Event._attach(args, { facade: false });
116
            handle.sub.filter  = filter;
117
            handle.sub._notify = delegate.notifySub;
118
        }
119
    }
120
 
121
    if (handle && cat) {
122
        categories = detachCategories[cat]  || (detachCategories[cat] = {});
123
        categories = categories[type] || (categories[type] = []);
124
        categories.push(handle);
125
    }
126
 
127
    return handle;
128
}
129
 
130
/**
131
Overrides the <code>_notify</code> method on the normal DOM subscription to
132
inject the filtering logic and only proceed in the case of a match.
133
 
134
This method is hosted as a private property of the `delegate` method
135
(e.g. `Y.delegate.notifySub`)
136
 
137
@method notifySub
138
@param thisObj {Object} default 'this' object for the callback
139
@param args {Array} arguments passed to the event's <code>fire()</code>
140
@param ce {CustomEvent} the custom event managing the DOM subscriptions for
141
             the subscribed event on the subscribing node.
142
@return {Boolean} false if the event was stopped
143
@private
144
@static
145
@since 3.2.0
146
**/
147
delegate.notifySub = function (thisObj, args, ce) {
148
    // Preserve args for other subscribers
149
    args = args.slice();
150
    if (this.args) {
151
        args.push.apply(args, this.args);
152
    }
153
 
154
    // Only notify subs if the event occurred on a targeted element
155
    var currentTarget = delegate._applyFilter(this.filter, args, ce),
156
        //container     = e.currentTarget,
157
        e, i, len, ret;
158
 
159
    if (currentTarget) {
160
        // Support multiple matches up the the container subtree
161
        currentTarget = toArray(currentTarget);
162
 
163
        // The second arg is the currentTarget, but we'll be reusing this
164
        // facade, replacing the currentTarget for each use, so it doesn't
165
        // matter what element we seed it with.
166
        e = args[0] = new Y.DOMEventFacade(args[0], ce.el, ce);
167
 
168
        e.container = Y.one(ce.el);
169
 
170
        for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
171
            e.currentTarget = Y.one(currentTarget[i]);
172
 
173
            ret = this.fn.apply(this.context || e.currentTarget, args);
174
 
175
            if (ret === false) { // stop further notifications
176
                break;
177
            }
178
        }
179
 
180
        return ret;
181
    }
182
};
183
 
184
/**
185
Compiles a selector string into a filter function to identify whether
186
Nodes along the parent axis of an event's target should trigger event
187
notification.
188
 
189
This function is memoized, so previously compiled filter functions are
190
returned if the same selector string is provided.
191
 
192
This function may be useful when defining synthetic events for delegate
193
handling.
194
 
195
Hosted as a property of the `delegate` method (e.g. `Y.delegate.compileFilter`).
196
 
197
@method compileFilter
198
@param selector {String} the selector string to base the filtration on
199
@return {Function}
200
@since 3.2.0
201
@static
202
**/
203
delegate.compileFilter = Y.cached(function (selector) {
204
    return function (target, e) {
205
        return selectorTest(target._node, selector,
206
            (e.currentTarget === e.target) ? null : e.currentTarget._node);
207
    };
208
});
209
 
210
/**
211
Regex to test for disabled elements during filtering. This is only relevant to
212
IE to normalize behavior with other browsers, which swallow events that occur
213
to disabled elements. IE fires the event from the parent element instead of the
214
original target, though it does preserve `event.srcElement` as the disabled
215
element. IE also supports disabled on `<a>`, but the event still bubbles, so it
216
acts more like `e.preventDefault()` plus styling. That issue is not handled here
217
because other browsers fire the event on the `<a>`, so delegate is supported in
218
both cases.
219
 
220
@property _disabledRE
221
@type {RegExp}
222
@protected
223
@since 3.8.1
224
**/
225
delegate._disabledRE = /^(?:button|input|select|textarea)$/i;
226
 
227
/**
228
Walks up the parent axis of an event's target, and tests each element
229
against a supplied filter function.  If any Nodes, including the container,
230
satisfy the filter, the delegated callback will be triggered for each.
231
 
232
Hosted as a protected property of the `delegate` method (e.g.
233
`Y.delegate._applyFilter`).
234
 
235
@method _applyFilter
236
@param filter {Function} boolean function to test for inclusion in event
237
                 notification
238
@param args {Array} the arguments that would be passed to subscribers
239
@param ce   {CustomEvent} the DOM event wrapper
240
@return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter
241
@protected
242
**/
243
delegate._applyFilter = function (filter, args, ce) {
244
    var e         = args[0],
245
        container = ce.el, // facadeless events in IE, have no e.currentTarget
246
        target    = e.target || e.srcElement,
247
        match     = [],
248
        isContainer = false;
249
 
250
    // Resolve text nodes to their containing element
251
    if (target.nodeType === 3) {
252
        target = target.parentNode;
253
    }
254
 
255
    // For IE. IE propagates events from the parent element of disabled
256
    // elements, where other browsers swallow the event entirely. To normalize
257
    // this in IE, filtering for matching elements should abort if the target
258
    // is a disabled form control.
259
    if (target.disabled && delegate._disabledRE.test(target.nodeName)) {
260
        return match;
261
    }
262
 
263
    // passing target as the first arg rather than leaving well enough alone
264
    // making 'this' in the filter function refer to the target.  This is to
265
    // support bound filter functions.
266
    args.unshift(target);
267
 
268
    if (isString(filter)) {
269
        while (target) {
270
            isContainer = (target === container);
271
            if (selectorTest(target, filter, (isContainer ? null: container))) {
272
                match.push(target);
273
            }
274
 
275
            if (isContainer) {
276
                break;
277
            }
278
 
279
            target = target.parentNode;
280
        }
281
    } else {
282
        // filter functions are implementer code and should receive wrappers
283
        args[0] = Y.one(target);
284
        args[1] = new Y.DOMEventFacade(e, container, ce);
285
 
286
        while (target) {
287
            // filter(target, e, extra args...) - this === target
288
            if (filter.apply(args[0], args)) {
289
                match.push(target);
290
            }
291
 
292
            if (target === container) {
293
                break;
294
            }
295
 
296
            target = target.parentNode;
297
            args[0] = Y.one(target);
298
        }
299
        args[1] = e; // restore the raw DOM event
300
    }
301
 
302
    if (match.length <= 1) {
303
        match = match[0]; // single match or undefined
304
    }
305
 
306
    // remove the target
307
    args.shift();
308
 
309
    return match;
310
};
311
 
312
/**
313
 * Sets up event delegation on a container element.  The delegated event
314
 * will use a supplied filter to test if the callback should be executed.
315
 * This filter can be either a selector string or a function that returns
316
 * a Node to use as the currentTarget for the event.
317
 *
318
 * The event object for the delegated event is supplied to the callback
319
 * function.  It is modified slightly in order to support all properties
320
 * that may be needed for event delegation.  'currentTarget' is set to
321
 * the element that matched the selector string filter or the Node returned
322
 * from the filter function.  'container' is set to the element that the
323
 * listener is delegated from (this normally would be the 'currentTarget').
324
 *
325
 * Filter functions will be called with the arguments that would be passed to
326
 * the callback function, including the event object as the first parameter.
327
 * The function should return false (or a falsey value) if the success criteria
328
 * aren't met, and the Node to use as the event's currentTarget and 'this'
329
 * object if they are.
330
 *
331
 * @method delegate
332
 * @param type {string} the event type to delegate
333
 * @param fn {function} the callback function to execute.  This function
334
 * will be provided the event object for the delegated event.
335
 * @param el {string|node} the element that is the delegation container
336
 * @param filter {string|function} a selector that must match the target of the
337
 * event or a function that returns a Node or false.
338
 * @param context optional argument that specifies what 'this' refers to.
339
 * @param args* 0..n additional arguments to pass on to the callback function.
340
 * These arguments will be added after the event object.
341
 * @return {EventHandle} the detach handle
342
 * @for YUI
343
 */
344
Y.delegate = Y.Event.delegate = delegate;
345
 
346
 
347
}, '3.18.1', {"requires": ["node-base"]});