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