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
            Y.log("delegate requires type, callback, parent, & filter", "warn");
102
            return;
103
        }
104
 
105
        container = (query) ? Y.Selector.query(query, null, true) : el;
106
 
107
        if (!container && isString(el)) {
108
            handle = Y.on('available', function () {
109
                Y.mix(handle, Y.delegate.apply(Y, args), true);
110
            }, el);
111
        }
112
 
113
        if (!handle && container) {
114
            args.splice(2, 2, container); // remove the filter
115
 
116
            handle = Y.Event._attach(args, { facade: false });
117
            handle.sub.filter  = filter;
118
            handle.sub._notify = delegate.notifySub;
119
        }
120
    }
121
 
122
    if (handle && cat) {
123
        categories = detachCategories[cat]  || (detachCategories[cat] = {});
124
        categories = categories[type] || (categories[type] = []);
125
        categories.push(handle);
126
    }
127
 
128
    return handle;
129
}
130
 
131
/**
132
Overrides the <code>_notify</code> method on the normal DOM subscription to
133
inject the filtering logic and only proceed in the case of a match.
134
 
135
This method is hosted as a private property of the `delegate` method
136
(e.g. `Y.delegate.notifySub`)
137
 
138
@method notifySub
139
@param thisObj {Object} default 'this' object for the callback
140
@param args {Array} arguments passed to the event's <code>fire()</code>
141
@param ce {CustomEvent} the custom event managing the DOM subscriptions for
142
             the subscribed event on the subscribing node.
143
@return {Boolean} false if the event was stopped
144
@private
145
@static
146
@since 3.2.0
147
**/
148
delegate.notifySub = function (thisObj, args, ce) {
149
    // Preserve args for other subscribers
150
    args = args.slice();
151
    if (this.args) {
152
        args.push.apply(args, this.args);
153
    }
154
 
155
    // Only notify subs if the event occurred on a targeted element
156
    var currentTarget = delegate._applyFilter(this.filter, args, ce),
157
        //container     = e.currentTarget,
158
        e, i, len, ret;
159
 
160
    if (currentTarget) {
161
        // Support multiple matches up the the container subtree
162
        currentTarget = toArray(currentTarget);
163
 
164
        // The second arg is the currentTarget, but we'll be reusing this
165
        // facade, replacing the currentTarget for each use, so it doesn't
166
        // matter what element we seed it with.
167
        e = args[0] = new Y.DOMEventFacade(args[0], ce.el, ce);
168
 
169
        e.container = Y.one(ce.el);
170
 
171
        for (i = 0, len = currentTarget.length; i < len && !e.stopped; ++i) {
172
            e.currentTarget = Y.one(currentTarget[i]);
173
 
174
            ret = this.fn.apply(this.context || e.currentTarget, args);
175
 
176
            if (ret === false) { // stop further notifications
177
                break;
178
            }
179
        }
180
 
181
        return ret;
182
    }
183
};
184
 
185
/**
186
Compiles a selector string into a filter function to identify whether
187
Nodes along the parent axis of an event's target should trigger event
188
notification.
189
 
190
This function is memoized, so previously compiled filter functions are
191
returned if the same selector string is provided.
192
 
193
This function may be useful when defining synthetic events for delegate
194
handling.
195
 
196
Hosted as a property of the `delegate` method (e.g. `Y.delegate.compileFilter`).
197
 
198
@method compileFilter
199
@param selector {String} the selector string to base the filtration on
200
@return {Function}
201
@since 3.2.0
202
@static
203
**/
204
delegate.compileFilter = Y.cached(function (selector) {
205
    return function (target, e) {
206
        return selectorTest(target._node, selector,
207
            (e.currentTarget === e.target) ? null : e.currentTarget._node);
208
    };
209
});
210
 
211
/**
212
Regex to test for disabled elements during filtering. This is only relevant to
213
IE to normalize behavior with other browsers, which swallow events that occur
214
to disabled elements. IE fires the event from the parent element instead of the
215
original target, though it does preserve `event.srcElement` as the disabled
216
element. IE also supports disabled on `<a>`, but the event still bubbles, so it
217
acts more like `e.preventDefault()` plus styling. That issue is not handled here
218
because other browsers fire the event on the `<a>`, so delegate is supported in
219
both cases.
220
 
221
@property _disabledRE
222
@type {RegExp}
223
@protected
224
@since 3.8.1
225
**/
226
delegate._disabledRE = /^(?:button|input|select|textarea)$/i;
227
 
228
/**
229
Walks up the parent axis of an event's target, and tests each element
230
against a supplied filter function.  If any Nodes, including the container,
231
satisfy the filter, the delegated callback will be triggered for each.
232
 
233
Hosted as a protected property of the `delegate` method (e.g.
234
`Y.delegate._applyFilter`).
235
 
236
@method _applyFilter
237
@param filter {Function} boolean function to test for inclusion in event
238
                 notification
239
@param args {Array} the arguments that would be passed to subscribers
240
@param ce   {CustomEvent} the DOM event wrapper
241
@return {Node|Node[]|undefined} The Node or Nodes that satisfy the filter
242
@protected
243
**/
244
delegate._applyFilter = function (filter, args, ce) {
245
    var e         = args[0],
246
        container = ce.el, // facadeless events in IE, have no e.currentTarget
247
        target    = e.target || e.srcElement,
248
        match     = [],
249
        isContainer = false;
250
 
251
    // Resolve text nodes to their containing element
252
    if (target.nodeType === 3) {
253
        target = target.parentNode;
254
    }
255
 
256
    // For IE. IE propagates events from the parent element of disabled
257
    // elements, where other browsers swallow the event entirely. To normalize
258
    // this in IE, filtering for matching elements should abort if the target
259
    // is a disabled form control.
260
    if (target.disabled && delegate._disabledRE.test(target.nodeName)) {
261
        return match;
262
    }
263
 
264
    // passing target as the first arg rather than leaving well enough alone
265
    // making 'this' in the filter function refer to the target.  This is to
266
    // support bound filter functions.
267
    args.unshift(target);
268
 
269
    if (isString(filter)) {
270
        while (target) {
271
            isContainer = (target === container);
272
            if (selectorTest(target, filter, (isContainer ? null: container))) {
273
                match.push(target);
274
            }
275
 
276
            if (isContainer) {
277
                break;
278
            }
279
 
280
            target = target.parentNode;
281
        }
282
    } else {
283
        // filter functions are implementer code and should receive wrappers
284
        args[0] = Y.one(target);
285
        args[1] = new Y.DOMEventFacade(e, container, ce);
286
 
287
        while (target) {
288
            // filter(target, e, extra args...) - this === target
289
            if (filter.apply(args[0], args)) {
290
                match.push(target);
291
            }
292
 
293
            if (target === container) {
294
                break;
295
            }
296
 
297
            target = target.parentNode;
298
            args[0] = Y.one(target);
299
        }
300
        args[1] = e; // restore the raw DOM event
301
    }
302
 
303
    if (match.length <= 1) {
304
        match = match[0]; // single match or undefined
305
    }
306
 
307
    // remove the target
308
    args.shift();
309
 
310
    return match;
311
};
312
 
313
/**
314
 * Sets up event delegation on a container element.  The delegated event
315
 * will use a supplied filter to test if the callback should be executed.
316
 * This filter can be either a selector string or a function that returns
317
 * a Node to use as the currentTarget for the event.
318
 *
319
 * The event object for the delegated event is supplied to the callback
320
 * function.  It is modified slightly in order to support all properties
321
 * that may be needed for event delegation.  'currentTarget' is set to
322
 * the element that matched the selector string filter or the Node returned
323
 * from the filter function.  'container' is set to the element that the
324
 * listener is delegated from (this normally would be the 'currentTarget').
325
 *
326
 * Filter functions will be called with the arguments that would be passed to
327
 * the callback function, including the event object as the first parameter.
328
 * The function should return false (or a falsey value) if the success criteria
329
 * aren't met, and the Node to use as the event's currentTarget and 'this'
330
 * object if they are.
331
 *
332
 * @method delegate
333
 * @param type {string} the event type to delegate
334
 * @param fn {function} the callback function to execute.  This function
335
 * will be provided the event object for the delegated event.
336
 * @param el {string|node} the element that is the delegation container
337
 * @param filter {string|function} a selector that must match the target of the
338
 * event or a function that returns a Node or false.
339
 * @param context optional argument that specifies what 'this' refers to.
340
 * @param args* 0..n additional arguments to pass on to the callback function.
341
 * These arguments will be added after the event object.
342
 * @return {EventHandle} the detach handle
343
 * @for YUI
344
 */
345
Y.delegate = Y.Event.delegate = delegate;
346
 
347
 
348
}, '3.18.1', {"requires": ["node-base"]});