Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Wrapped in closure to prevent 'use strict' errors on jshint #9

Merged
merged 3 commits into from

1 participant

@wilsonpage

No description provided.

@wilsonpage

Tested on Buster and all tests passing fine. Ready to merge.

@wilsonpage wilsonpage merged commit 718b9bb into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 22, 2013
  1. @wilsonpage
  2. @wilsonpage
Commits on Apr 8, 2013
  1. @wilsonpage

    Fix merge conflic

    wilsonpage authored
This page is out of date. Refresh to see the latest.
Showing with 350 additions and 324 deletions.
  1. +1 −0  .gitignore
  2. +349 −324 lib/delegate.js
View
1  .gitignore
@@ -0,0 +1 @@
+node_modules
View
673 lib/delegate.js
@@ -1,3 +1,6 @@
+/*jslint browser:true, node:true*/
+/*global define, Node*/
+
/**
* @preserve Create and manage a DOM event delegator.
*
@@ -7,403 +10,425 @@
* @license MIT License (see LICENSE.txt)
*/
-/*jslint browser:true, node:true*/
-/*global define, Node*/
-
+;(function(){
-/**
- * DOM event delegator
- *
- * The delegator will listen for events that bubble up to the root node.
- *
- * @constructor
- * @param {Node|string} root The root node or a selector string matching the root node
- */
-function Delegate(root) {
'use strict';
- var self = this;
-
- if (root) {
- this.root(root);
- }
-
/**
- * Maintain a map of listener lists, keyed by event name.
+ * DOM event delegator
+ *
+ * The delegator will listen
+ * for events that bubble up
+ * to the root node.
*
- * @type Object
+ * @constructor
+ * @param {Node|string} root The root node or a selector string matching the root node
*/
- this.listenerMap = {};
+ function Delegate(root) {
+ var self = this;
+ if (root) {
+ this.root(root);
+ }
- /** @type function() */
- this.handle = function(event) { Delegate.prototype.handle.call(self, event); };
-}
+ /**
+ * Maintain a map of listener
+ * lists, keyed by event name.
+ *
+ * @type Object
+ */
+ this.listenerMap = {};
+ /** @type function() */
+ this.handle = function(event) { Delegate.prototype.handle.call(self, event); };
+ }
-/**
- * @protected
- * @type ?boolean
- */
-Delegate.tagsCaseSensitive = null;
+ /**
+ * @protected
+ * @type ?boolean
+ */
+ Delegate.tagsCaseSensitive = null;
+
+ /**
+ * Start listening for events
+ * on the provided DOM element
+ *
+ * @param {Node} root The root node or a selector string matching the root node
+ */
+ Delegate.prototype.root = function(root) {
+ var listenerMap = this.listenerMap;
+ var eventType;
+ if (typeof root === 'string') {
+ root = document.querySelector(root);
+ }
-/**
- * Start listening for events on the provided DOM element
- * @param {Node} root The root node or a selector string matching the root node
- */
-Delegate.prototype.root = function(root) {
- 'use strict';
- var listenerMap = this.listenerMap;
- var eventType;
+ // Remove master event listeners
+ if (this.rootElement) {
+ for (eventType in listenerMap) {
+ if (listenerMap.hasOwnProperty(eventType)) {
+ this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
+ }
+ }
+ }
- if (typeof root === 'string') {
- root = document.querySelector(root);
- }
+ // If no root or root is not
+ // a dom node, then remove internal
+ // root reference and exit here
+ if (!root || !root.addEventListener) {
+ if (this.rootElement) {
+ delete this.rootElement;
+ }
+ return;
+ }
+
+ /**
+ * The root node at which
+ * listeners are attached.
+ *
+ * @type Node
+ */
+ this.rootElement = root;
- // Remove master event listeners
- if (this.rootElement) {
+ // Set up master event listeners
for (eventType in listenerMap) {
if (listenerMap.hasOwnProperty(eventType)) {
- this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
+ this.rootElement.addEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
- }
+ };
- // If no root or root is not a dom node, then
- // remove internal root reference and exit here
- if (!root || !root.addEventListener) {
- if (this.rootElement) {
- delete this.rootElement;
- }
- return;
- }
+ /**
+ * @param {string} eventType
+ * @returns boolean
+ */
+ Delegate.prototype.captureForType = function(eventType) {
+ return eventType === 'error';
+ };
/**
- * The root node at which listeners are attached.
+ * Attach a handler to one
+ * event for all elements
+ * that match the selector,
+ * now or in the future
*
- * @type Node
+ * The handler function receives
+ * three arguments: the DOM event
+ * object, the node that matched
+ * the selector while the event
+ * was bubbling and a reference
+ * to itself. Within the handler,
+ * 'this' is equal to the second
+ * argument.
+ *
+ * The node that actually received
+ * the event can be accessed via
+ * 'event.target'.
+ *
+ * @param {string} eventType Listen for these events (in a space-separated list)
+ * @param {string} selector Only handle events on elements matching this selector
+ * @param {function()} handler Handler function - event data passed here will be in event.data
+ * @param {Object} [eventData] Data to pass in event.data
+ * @returns {Delegate} This method is chainable
*/
- this.rootElement = root;
+ Delegate.prototype.on = function(eventType, selector, handler, eventData) {
+ var root, listenerMap, matcher, matcherParam, self = this, /** @const */ SEPARATOR = ' ';
- // Set up master event listeners
- for (eventType in listenerMap) {
- if (listenerMap.hasOwnProperty(eventType)) {
- this.rootElement.addEventListener(eventType, this.handle, this.captureForType(eventType));
+ if (!eventType) {
+ throw new TypeError('Invalid event type: ' + eventType);
}
- }
-};
-
-/**
- * @param {string} eventType
- * @returns boolean
- */
-Delegate.prototype.captureForType = function(eventType) {
- 'use strict';
- return eventType === 'error';
-};
-
-
-/**
- * Attach a handler to one event for all elements that match the selector, now or in the future
- *
- * The handler function receives three arguments: the DOM event object, the node that matched the selector while the event was bubbling
- * and a reference to itself. Within the handler, 'this' is equal to the second argument.
- * The node that actually received the event can be accessed via 'event.target'.
- *
- * @param {string} eventType Listen for these events (in a space-separated list)
- * @param {string} selector Only handle events on elements matching this selector
- * @param {function()} handler Handler function - event data passed here will be in event.data
- * @param {Object} [eventData] Data to pass in event.data
- * @returns {Delegate} This method is chainable
- */
-Delegate.prototype.on = function(eventType, selector, handler, eventData) {
- 'use strict';
- var root, listenerMap, matcher, matcherParam, self = this, /** @const */ SEPARATOR = ' ';
-
- if (!eventType) {
- throw new TypeError('Invalid event type: ' + eventType);
- }
-
- if (!selector) {
- throw new TypeError('Invalid selector: ' + selector);
- }
-
- // Support a separated list of event types
- if (eventType.indexOf(SEPARATOR) !== -1) {
- eventType.split(SEPARATOR).forEach(function(singleEventType) {
- self.on(singleEventType, selector, handler, eventData);
- });
-
- return this;
- }
-
- // Normalise undefined eventData to null
- if (eventData === undefined) {
- eventData = null;
- }
-
- if (typeof handler !== 'function') {
- throw new TypeError('Handler must be a type of Function');
- }
-
- root = this.rootElement;
- listenerMap = this.listenerMap;
-
- // Add master handler for type if not created yet
- if (!listenerMap[eventType]) {
- if (root) {
- root.addEventListener(eventType, this.handle, this.captureForType(eventType));
+ if (!selector) {
+ throw new TypeError('Invalid selector: ' + selector);
}
- listenerMap[eventType] = [];
- }
- // Compile a matcher for the given selector
- if (/^[a-z]+$/i.test(selector)) {
+ // Support a separated list of event types
+ if (eventType.indexOf(SEPARATOR) !== -1) {
+ eventType.split(SEPARATOR).forEach(function(singleEventType) {
+ self.on(singleEventType, selector, handler, eventData);
+ });
- // Lazily check whether tag names are case sensitive (as in XML or XHTML documents).
- if (Delegate.tagsCaseSensitive === null) {
- Delegate.tagsCaseSensitive = document.createElement('i').tagName === 'i';
+ return this;
}
- if (!Delegate.tagsCaseSensitive) {
- matcherParam = selector.toUpperCase();
- } else {
- matcherParam = selector;
+ // Normalise undefined eventData to null
+ if (eventData === undefined) {
+ eventData = null;
}
- matcher = this.matchesTag;
- } else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
- matcherParam = selector.slice(1);
- matcher = this.matchesId;
- } else {
- matcherParam = selector;
- matcher = this.matches;
- }
+ if (typeof handler !== 'function') {
+ throw new TypeError('Handler must be a type of Function');
+ }
- // Add to the list of listeners
- listenerMap[eventType].push({
- selector: selector,
- eventData: eventData,
- handler: handler,
- matcher: matcher,
- matcherParam: matcherParam
- });
+ root = this.rootElement;
+ listenerMap = this.listenerMap;
- return this;
-};
+ // Add master handler for type if not created yet
+ if (!listenerMap[eventType]) {
+ if (root) {
+ root.addEventListener(eventType, this.handle, this.captureForType(eventType));
+ }
+ listenerMap[eventType] = [];
+ }
+ // Compile a matcher for the given selector
+ if (/^[a-z]+$/i.test(selector)) {
-/**
- * Remove an event handler for elements that match the selector, forever
- *
- * @param {string} eventType Remove handlers for events matching this type, considering the other parameters
- * @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
- * @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
- * @returns {Delegate} This method is chainable
- */
-Delegate.prototype.off = function(eventType, selector, handler) {
- 'use strict';
- var i, listener, listenerMap, listenerList, singleEventType, self = this, /** @const */ SEPARATOR = ' ';
-
- listenerMap = this.listenerMap;
- if (!eventType) {
- for (singleEventType in listenerMap) {
- if (listenerMap.hasOwnProperty(singleEventType)) {
- this.off(singleEventType, selector, handler);
+ // Lazily check whether tag names are case sensitive (as in XML or XHTML documents).
+ if (Delegate.tagsCaseSensitive === null) {
+ Delegate.tagsCaseSensitive = document.createElement('i').tagName === 'i';
}
- }
- return this;
- }
+ if (!Delegate.tagsCaseSensitive) {
+ matcherParam = selector.toUpperCase();
+ } else {
+ matcherParam = selector;
+ }
- listenerList = listenerMap[eventType];
- if (!listenerList || !listenerList.length) {
- return this;
- }
+ matcher = this.matchesTag;
+ } else if (/^#[a-z0-9\-_]+$/i.test(selector)) {
+ matcherParam = selector.slice(1);
+ matcher = this.matchesId;
+ } else {
+ matcherParam = selector;
+ matcher = this.matches;
+ }
- // Support a separated list of event types
- if (eventType.indexOf(SEPARATOR) !== -1) {
- eventType.split(SEPARATOR).forEach(function(singleEventType) {
- self.off(singleEventType, selector, handler);
+ // Add to the list of listeners
+ listenerMap[eventType].push({
+ selector: selector,
+ eventData: eventData,
+ handler: handler,
+ matcher: matcher,
+ matcherParam: matcherParam
});
return this;
- }
+ };
- // Remove only parameter matches if specified
- for (i = listenerList.length - 1; i >= 0; i--) {
- listener = listenerList[i];
+ /**
+ * Remove an event handler
+ * for elements that match
+ * the selector, forever
+ *
+ * @param {string} eventType Remove handlers for events matching this type, considering the other parameters
+ * @param {string} [selector] If this parameter is omitted, only handlers which match the other two will be removed
+ * @param {function()} [handler] If this parameter is omitted, only handlers which match the previous two will be removed
+ * @returns {Delegate} This method is chainable
+ */
+ Delegate.prototype.off = function(eventType, selector, handler) {
+ var i, listener, listenerMap, listenerList, singleEventType, self = this, /** @const */ SEPARATOR = ' ';
+
+ listenerMap = this.listenerMap;
+ if (!eventType) {
+ for (singleEventType in listenerMap) {
+ if (listenerMap.hasOwnProperty(singleEventType)) {
+ this.off(singleEventType, selector, handler);
+ }
+ }
- if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) {
- listenerList.splice(i, 1);
+ return this;
}
- }
- // All listeners removed
- if (!listenerList.length) {
- delete listenerMap[eventType];
-
- // Remove the main handler
- if (this.rootElement) {
- this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
+ listenerList = listenerMap[eventType];
+ if (!listenerList || !listenerList.length) {
+ return this;
}
- }
- return this;
-};
+ // Support a separated list of event types
+ if (eventType.indexOf(SEPARATOR) !== -1) {
+ eventType.split(SEPARATOR).forEach(function(singleEventType) {
+ self.off(singleEventType, selector, handler);
+ });
+ return this;
+ }
-/**
- * Handle an arbitrary event.
- *
- * @param {Event} event
- */
-Delegate.prototype.handle = function(event) {
- 'use strict';
- var i, l, root, listener, returned, listenerList, target, /** @const */ EVENTIGNORE = 'ftLabsDelegateIgnore';
-
- if (event[EVENTIGNORE] === true) {
- return;
- }
-
- target = event.target;
- if (target.nodeType === Node.TEXT_NODE) {
- target = target.parentNode;
- }
-
- root = this.rootElement;
- listenerList = this.listenerMap[event.type];
-
- // Need to continuously check that the specific list is still populated in case one of the callbacks actually causes the list to be destroyed.
- l = listenerList.length;
- while (target && l) {
- for (i = 0; i < l; i++) {
+ // Remove only parameter matches if specified
+ for (i = listenerList.length - 1; i >= 0; i--) {
listener = listenerList[i];
- // Bail from this loop if the length changed and no more listeners are defined between i and l.
- if (!listener) {
- break;
+ if ((!selector || selector === listener.selector) && (!handler || handler === listener.handler)) {
+ listenerList.splice(i, 1);
}
+ }
- // Check for match and fire the event if there's one
- // TODO:MCG:20120117: Need a way to check if event#stopImmediateProgagation was called. If so, break both loops.
- if (listener.matcher.call(target, listener.matcherParam, target)) {
- returned = this.fire(event, target, listener);
- }
+ // All listeners removed
+ if (!listenerList.length) {
+ delete listenerMap[eventType];
- // Stop propagation to subsequent callbacks if the callback returned false
- if (returned === false) {
- event[EVENTIGNORE] = true;
- return;
+ // Remove the main handler
+ if (this.rootElement) {
+ this.rootElement.removeEventListener(eventType, this.handle, this.captureForType(eventType));
}
}
- // TODO:MCG:20120117: Need a way to check if event#stopProgagation was called. If so, break looping through the DOM.
- // Stop if the delegation root has been reached
- if (target === root) {
- break;
+ return this;
+ };
+
+
+ /**
+ * Handle an arbitrary event.
+ *
+ * @param {Event} event
+ */
+ Delegate.prototype.handle = function(event) {
+ var i, l, root, listener, returned, listenerList, target, /** @const */ EVENTIGNORE = 'ftLabsDelegateIgnore';
+
+ if (event[EVENTIGNORE] === true) {
+ return;
}
- l = listenerList.length;
- target = target.parentElement;
- }
-};
+ target = event.target;
+ if (target.nodeType === Node.TEXT_NODE) {
+ target = target.parentNode;
+ }
+ root = this.rootElement;
+ listenerList = this.listenerMap[event.type];
-/**
- * Fire a listener on a target.
- *
- * @param {Event} event
- * @param {Node} target
- * @param {Object} listener
- * @returns {boolean}
- */
-Delegate.prototype.fire = function(event, target, listener) {
- 'use strict';
- var returned, oldData;
+ // Need to continuously check
+ // that the specific list is
+ // still populated in case one
+ // of the callbacks actually
+ // causes the list to be destroyed.
+ l = listenerList.length;
+ while (target && l) {
+ for (i = 0; i < l; i++) {
+ listener = listenerList[i];
+
+ // Bail from this loop if
+ // the length changed and
+ // no more listeners are
+ // defined between i and l.
+ if (!listener) {
+ break;
+ }
+
+ // Check for match and fire
+ // the event if there's one
+ //
+ // TODO:MCG:20120117: Need a way
+ // to check if event#stopImmediateProgagation
+ // was called. If so, break both loops.
+ if (listener.matcher.call(target, listener.matcherParam, target)) {
+ returned = this.fire(event, target, listener);
+ }
+
+ // Stop propagation to subsequent
+ // callbacks if the callback returned
+ // false
+ if (returned === false) {
+ event[EVENTIGNORE] = true;
+ return;
+ }
+ }
- if (listener.eventData !== null) {
- oldData = event.data;
- event.data = listener.eventData;
- returned = listener.handler.call(target, event, target);
- event.data = oldData;
- } else {
- returned = listener.handler.call(target, event, target);
- }
+ // TODO:MCG:20120117: Need a way to
+ // check if event#stopProgagation
+ // was called. If so, break looping
+ // through the DOM. Stop if the
+ // delegation root has been reached
+ if (target === root) {
+ break;
+ }
- return returned;
-};
+ l = listenerList.length;
+ target = target.parentElement;
+ }
+ };
+ /**
+ * Fire a listener on a target.
+ *
+ * @param {Event} event
+ * @param {Node} target
+ * @param {Object} listener
+ * @returns {boolean}
+ */
+ Delegate.prototype.fire = function(event, target, listener) {
+ var returned, oldData;
+
+ if (listener.eventData !== null) {
+ oldData = event.data;
+ event.data = listener.eventData;
+ returned = listener.handler.call(target, event, target);
+ event.data = oldData;
+ } else {
+ returned = listener.handler.call(target, event, target);
+ }
-/**
- * Check whether an element matches a generic selector.
- *
- * @type function()
- * @param {string} selector A CSS selector
- */
-Delegate.prototype.matches = (function(p) {
- 'use strict';
- return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
-}(HTMLElement.prototype));
+ return returned;
+ };
+ /**
+ * Check whether an element matches a generic selector.
+ *
+ * @type function()
+ * @param {string} selector A CSS selector
+ */
+ Delegate.prototype.matches = (function(p) {
+ return (p.matchesSelector || p.webkitMatchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector);
+ }(HTMLElement.prototype));
-/**
- * Check whether an element matches a tag selector.
- *
- * Tags are NOT case-sensitive, except in XML (and XML-based languages such as XHTML).
- *
- * @param {string} tagName The tag name to test against
- * @param {Element} element The element to test with
- * @returns boolean
- */
-Delegate.prototype.matchesTag = function(tagName, element) {
- 'use strict';
- return tagName === element.tagName;
-};
+ /**
+ * Check whether an element
+ * matches a tag selector.
+ *
+ * Tags are NOT case-sensitive,
+ * except in XML (and XML-based
+ * languages such as XHTML).
+ *
+ * @param {string} tagName The tag name to test against
+ * @param {Element} element The element to test with
+ * @returns boolean
+ */
+ Delegate.prototype.matchesTag = function(tagName, element) {
+ return tagName === element.tagName;
+ };
+ /**
+ * Check whether the ID of
+ * the element in 'this'
+ * matches the given ID.
+ *
+ * IDs are case-sensitive.
+ *
+ * @param {string} id The ID to test against
+ * @param {Element} element The element to test with
+ * @returns boolean
+ */
+ Delegate.prototype.matchesId = function(id, element) {
+ return id === element.id;
+ };
-/**
- * Check whether the ID of the element in 'this' matches the given ID.
- *
- * IDs are case-sensitive.
- *
- * @param {string} id The ID to test against
- * @param {Element} element The element to test with
- * @returns boolean
- */
-Delegate.prototype.matchesId = function(id, element) {
- 'use strict';
- return id === element.id;
-};
-
-if (typeof define !== 'undefined' && define.amd) {
-
- // AMD. Register as an anonymous module.
- define(function() {
- 'use strict';
- return Delegate;
- });
-}
-
-if (typeof module !== 'undefined' && module.exports) {
- module.exports = function(root) {
- 'use strict';
- return new Delegate(root);
+ /**
+ * Short hand for off()
+ * and root(), ie both
+ * with no parameters
+ *
+ * @return void
+ */
+ Delegate.prototype.destroy = function() {
+ this.off();
+ this.root();
};
- module.exports.Delegate = Delegate;
-}
+ /**
+ * Expose `Delegate`
+ */
+ if (typeof module === "object") {
+ module.exports = function(root) {
+ return new Delegate(root);
+ };
+ module.exports.Delegate = Delegate;
+ } else if (typeof define === "function" && define.amd) {
+ define(function() {
+ return Delegate;
+ });
+ } else {
+ window.Delegate = Delegate;
+ }
-/**
- * Short hand for off() and root(), ie both with no parameters
- *
- * @return void
- */
-Delegate.prototype.destroy = function() {
- 'use strict';
- this.off();
- this.root();
-};
+})();
Something went wrong with that request. Please try again.