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