1/*! 2 * dom-delegate v2.2.1 (ftlabs/dom-delegate) â vendored standalone UMD build. 3 * Exposes window.domDelegate (a factory fn with a .Delegate constructor), 4 * matching the global previously produced by libs.min.js. MIT License. 5 */ 6(function (root, factory) { 7 if (typeof define === "function" && define.amd) { define([], factory); } 8 else if (typeof module === "object" && module.exports) { module.exports = factory(); } 9 else { root.domDelegate = factory(); } 10}(typeof self !== "undefined" ? self : this, function () { 11 var delegateModule = { exports: {} }; 12 (function (module, exports) { 13/*jshint browser:true, node:true*/ 14 15'use strict'; 16 17module.exports = Delegate; 18 19/** 20 * DOM event delegator 21 * 22 * The delegator will listen 23 * for events that bubble up 24 * to the root node. 25 * 26 * @constructor 27 * @param {Node|string} [root] The root node or a selector string matching the root node 28 */ 29function Delegate(root) { 30 31 /** 32 * Maintain a map of listener 33 * lists, keyed by event name. 34 * 35 * @type Object 36 */ 37 this.listenerMap = [{}, {}]; 38 if (root) { 39 this.root(root); 40 } 41 42 /** @type function() */ 43 this.handle = Delegate.prototype.handle.bind(this); 44} 45 46/** 47 * Start listening for events 48 * on the provided DOM element 49 * 50 * @param {Node|string} [root] The root node or a selector string matching the root node 51 * @returns {Delegate} This method is chainable 52 */ 53Delegate.prototype.root = function(root) { 54 var listenerMap = this.listenerMap; 55 var eventType; 56 57 // Remove master event listeners 58 if (this.rootElement) { 59 for (eventType in listenerMap[1]) { 60 if (listenerMap[1].hasOwnProperty(eventType)) { 61 this.rootElement.removeEventListener(eventType, this.handle, true); 62 } 63 } 64 for (eventType in listenerMap[0]) { 65 if (listenerMap[0].hasOwnProperty(eventType)) { 66 this.rootElement.removeEventListener(eventType, this.handle, false); 67 } 68 } 69 } 70 71 // If no root or root is not 72 // a dom node, then remove internal 73 // root reference and exit here 74 if (!root || !root.addEventListener) { 75 if (this.rootElement) { 76 delete this.rootElement; 77 } 78 return this; 79 } 80 81 /** 82 * The root node at which 83 * listeners are attached. 84 * 85 * @type Node 86 */ 87 this.rootElement = root; 88 89 // Set up master event listeners 90 for (eventType in listenerMap[1]) { 91 if (listenerMap[1].hasOwnProperty(eventType)) { 92 this.rootElement.addEventListener(eventType, this.handle, true); 93 } 94 } 95 for (eventType in listenerMap[0]) { 96 if (listenerMap[0].hasOwnProperty(eventType)) { 97 this.rootElement.addEventListener(eventType, this.handle, false); 98 } 99 } 100 101 return this; 102}; 103 104/** 105 * @param {string} eventType 106 * @returns boolean 107 */ 108Delegate.prototype.captureForType = function(eventType) { 109 return ['blur', 'error', 'focus', 'load', 'resize', 'scroll'].indexOf(eventType) !== -1; 110}; 111 112/** 113 * Attach a handler to one 114 * event for all elements 115 * that match the selector, 116 * now or in the future 117 * 118 * The handler function receives 119 * three arguments: the DOM event 120 * object, the node that matched 121 * the selector while the event 122 * was bubbling and a reference 123 * to itself. Within the handler, 124 * 'this' is equal to the second 125 * argument. 126 * 127 * The node that actually received 128 * the event can be accessed via 129 * 'event.target'. 130 * 131 * @param {string} eventType Listen for these events 132 * @param {string|undefined} selector Only handle events on elements matching this selector, if undefined match root element 133 * @param {function()} handler Handler function - event data passed here will be in event.data 134 * @param {boolean} [useCapture] see 'useCapture' in <https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener> 135 * @returns {Delegate} This method is chainable 136 */ 137Delegate.prototype.on = function(eventType, selector, handler, useCapture) { 138 var root, listenerMap, matcher, matcherParam; 139 140 if (!eventType) { 141 throw new TypeError('Invalid event type: ' + eventType); 142 } 143 144 // handler can be passed as 145 // the second or third argument 146 if (typeof selector === 'function') { 147 useCapture = handler; 148 handler = selector; 149 selector = null; 150 } 151 152 // Fallback to sensible defaults 153 // if useCapture not set 154 if (useCapture === undefined) { 155 useCapture = this.captureForType(eventType); 156 } 157 158 if (typeof handler !== 'function') { 159 throw new TypeError('Handler must be a type of Function'); 160 } 161 162 root = this.rootElement; 163 listenerMap = this.listenerMap[useCapture ? 1 : 0]; 164 165 // Add master handler for type if not created yet 166 if (!listenerMap[eventType]) { 167 if (root) { 168 root.addEventListener(eventType, this.handle, useCapture); 169 } 170 listenerMap[eventType] = []; 171 } 172 173 if (!selector) { 174 matcherParam = null; 175 176 // COMPLEX - matchesRoot needs to have access to 177 // this.rootElement, so bind the function to this. 178 matcher = matchesRoot.bind(this); 179 180 // Compile a matcher for the given selector 181 } else if (/^[a-z]+$/i.test(selector)) { 182 matcherParam = selector; 183 matcher = matchesTag; 184 } else if (/^#[a-z0-9\-_]+$/i.test(selector)) { 185 matcherParam = selector.slice(1); 186 matcher = matchesId; 187 } else { 188 matcherParam = selector; 189 matcher = matches; 190 } 191 192 // Add to the list of listeners 193 listenerMap[eventType].push({ 194 selector: selector, 195 handler: handler, 196 matcher: matcher, 197 matcherParam: matcherParam 198 }); 199 200 return this; 201}; 202 203/** 204 * Remove an event handler 205 * for elements that match 206 * the selector, forever 207 * 208 * @param {string} [eventType] Remove handlers for events matching this type, considering the other parameters 209 * @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed 210 * @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed 211 * @returns {Delegate} This method is chainable 212 */ 213Delegate.prototype.off = function(eventType, selector, handler, useCapture) { 214 var i, listener, listenerMap, listenerList, singleEventType; 215 216 // Handler can be passed as 217 // the second or third argument 218 if (typeof selector === 'function') { 219 useCapture = handler; 220 handler = selector; 221 selector = null; 222 } 223 224 // If useCapture not set, remove 225 // all event listeners 226 if (useCapture === undefined) { 227 this.off(eventType, selector, handler, true); 228 this.off(eventType, selector, handler, false); 229 return this; 230 } 231 232 listenerMap = this.listenerMap[useCapture ? 1 : 0]; 233 if (!eventType) { 234 for (singleEventType in listenerMap) { 235 if (listenerMap.hasOwnProperty(singleEventType)) { 236 this.off(singleEventType, selector, handler); 237 } 238 } 239 240 return this; 241 } 242 243 listenerList = listenerMap[eventType]; 244 if (!listenerList || !listenerList.length) { 245 return this; 246 } 247 248 // Remove only parameter matches 249 // if specified 250 for (i = listenerList.length - 1; i >= 0; i--) { 251 listener = listenerList[i]; 252 253 if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) { 254 listenerList.splice(i, 1); 255 } 256 } 257 258 // All listeners removed 259 if (!listenerList.length) { 260 delete listenerMap[eventType]; 261 262 // Remove the main handler 263 if (this.rootElement) { 264 this.rootElement.removeEventListener(eventType, this.handle, useCapture); 265 } 266 } 267 268 return this; 269}; 270 271 272/** 273 * Handle an arbitrary event. 274 * 275 * @param {Event} event 276 */ 277Delegate.prototype.handle = function(event) { 278 var i, l, type = event.type, root, phase, listener, returned, listenerList = [], target, /** @const */ EVENTIGNORE = 'ftLabsDelegateIgnore'; 279 280 if (event[EVENTIGNORE] === true) { 281 return; 282 } 283 284 target = event.target; 285 286 // Hardcode value of Node.TEXT_NODE 287 // as not defined in IE8 288 if (target.nodeType === 3) { 289 target = target.parentNode; 290 } 291 292 root = this.rootElement; 293 294 phase = event.eventPhase || ( event.target !== event.currentTarget ? 3 : 2 ); 295 296 switch (phase) { 297 case 1: //Event.CAPTURING_PHASE: 298 listenerList = this.listenerMap[1][type]; 299 break; 300 case 2: //Event.AT_TARGET: 301 if (this.listenerMap[0] && this.listenerMap[0][type]) listenerList = listenerList.concat(this.listenerMap[0][type]); 302 if (this.listenerMap[1] && this.listenerMap[1][type]) listenerList = listenerList.concat(this.listenerMap[1][type]); 303 break; 304 case 3: //Event.BUBBLING_PHASE: 305 listenerList = this.listenerMap[0][type]; 306 break; 307 } 308 309 // Need to continuously check 310 // that the specific list is 311 // still populated in case one 312 // of the callbacks actually 313 // causes the list to be destroyed. 314 l = listenerList.length; 315 while (target && l) { 316 for (i = 0; i < l; i++) { 317 listener = listenerList[i]; 318 319 // Bail from this loop if 320 // the length changed and 321 // no more listeners are 322 // defined between i and l. 323 if (!listener) { 324 break; 325 } 326 327 // Check for match and fire 328 // the event if there's one 329 // 330 // TODO:MCG:20120117: Need a way 331 // to check if event#stopImmediatePropagation 332 // was called. If so, break both loops. 333 if (listener.matcher.call(target, listener.matcherParam, target)) { 334 returned = this.fire(event, target, listener); 335 } 336 337 // Stop propagation to subsequent 338 // callbacks if the callback returned 339 // false 340 if (returned === false) { 341 event[EVENTIGNORE] = true; 342 event.preventDefault(); 343 return; 344 } 345 } 346 347 // TODO:MCG:20120117: Need a way to 348 // check if event#stopPropagation 349 // was called. If so, break looping 350 // through the DOM. Stop if the 351 // delegation root has been reached 352 if (target === root) { 353 break; 354 } 355 356 l = listenerList.length; 357 target = target.parentElement; 358 } 359}; 360 361/** 362 * Fire a listener on a target. 363 * 364 * @param {Event} event 365 * @param {Node} target 366 * @param {Object} listener 367 * @returns {boolean} 368 */ 369Delegate.prototype.fire = function(event, target, listener) { 370 return listener.handler.call(target, event, target); 371}; 372 373/** 374 * Check whether an element 375 * matches a generic selector. 376 * 377 * @type function() 378 * @param {string} selector A CSS selector 379 */ 380var matches = (function(el) { 381 if (!el) return; 382 var p = el.prototype; 383 return (p.matches || p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector); 384}(Element)); 385 386/** 387 * Check whether an element 388 * matches a tag selector. 389 * 390 * Tags are NOT case-sensitive, 391 * except in XML (and XML-based 392 * languages such as XHTML). 393 * 394 * @param {string} tagName The tag name to test against 395 * @param {Element} element The element to test with 396 * @returns boolean 397 */ 398function matchesTag(tagName, element) { 399 return tagName.toLowerCase() === element.tagName.toLowerCase(); 400} 401 402/** 403 * Check whether an element 404 * matches the root. 405 * 406 * @param {?String} selector In this case this is always passed through as null and not used 407 * @param {Element} element The element to test with 408 * @returns boolean 409 */ 410function matchesRoot(selector, element) { 411 /*jshint validthis:true*/ 412 if (this.rootElement === window) return element === document; 413 return this.rootElement === element; 414} 415
416/** 417 * Check whether the ID of 418 * the element in 'this' 419 * matches the given ID. 420 * 421 * IDs are case-sensitive. 422 * 423 * @param {string} id The ID to test against 424 * @param {Element} element The element to test with 425 * @returns boolean 426 */ 427function matchesId(id, element) { 428 return id === element.id; 429} 430 431/** 432 * Short hand for off() 433 * and root(), ie both 434 * with no parameters 435 * 436 * @return void 437 */ 438Delegate.prototype.destroy = function() { 439 this.off(); 440 this.root(); 441}; 442 443 })(delegateModule, delegateModule.exports); 444 var Delegate = delegateModule.exports; 445 function domDelegate(root) { return new Delegate(root); } 446 domDelegate.Delegate = Delegate; 447 domDelegate.default = Delegate; 448 return domDelegate; 449}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.