diff --git a/src/events/events.js b/src/events/events.js index b7bab83..6638d7c 100644 --- a/src/events/events.js +++ b/src/events/events.js @@ -6,12 +6,12 @@ @see Firing event listeners @see Removing event listeners */ -(window.gloader || glow).module({ - name: "glow.events", - library: ["glow", "@VERSION@"], - depends: [["glow", "@VERSION@", 'glow.dom']], - builder: function(glow) { - +(window.gloader || glow).module({ + name: "glow.events", + library: ["glow", "@VERSION@"], + depends: [["glow", "@VERSION@", 'glow.dom']], + builder: function(glow) { + var $ = glow.dom.get; var r = {}; var eventid = 1; @@ -21,7 +21,7 @@ // object (keyed by ident) containing listeners var listenersByEventId = {}; var domListeners = {}; - var psuedoPrivateEventKey = '__eventId' + glow.UID; + var psuedoPrivateEventKey = '__eventId' + glow.UID; var psuedoPreventDefaultKey = psuedoPrivateEventKey + 'PreventDefault'; var psuedoStopPropagationKey = psuedoPrivateEventKey + 'StopPropagation'; @@ -111,16 +111,16 @@ listeners[listeners.length] = keyListeners[shiftEventType][i]; } } - } + } - if (! listeners) { return; } + if (! listeners) { return; } - for (var i = 0, len = listeners.length; i < len; i++) { - //call listener and look out for preventing the default - if (listeners[i][2].call(listeners[i][3] || this, e) === false) { - e.preventDefault(); + for (var i = 0, len = listeners.length; i < len; i++) { + //call listener and look out for preventing the default + if (listeners[i][2].call(listeners[i][3] || this, e) === false) { + e.preventDefault(); } - } + } return !e.defaultPrevented(); }); } @@ -163,7 +163,7 @@ var callback = function (e) { if (! e) { e = window.event; } - var event = new r.Event(), + var event = new r.Event(), lowerCaseName = name.toLowerCase(); event.nativeEvent = e; event.source = e.target || e.srcElement; @@ -237,8 +237,8 @@ } r.fire(this, name, event); - if (event.defaultPrevented()) { - return false; + if (event.defaultPrevented()) { + return false; } }; @@ -248,11 +248,11 @@ var onName = 'on' + name; var existing = attachTo[onName]; if (existing) { - attachTo[onName] = function () { - // we still need to return false if either the existing or new callback returns false - var existingReturn = existing.apply(this, arguments), - callbackReturn = callback.apply(this, arguments); - + attachTo[onName] = function () { + // we still need to return false if either the existing or new callback returns false + var existingReturn = existing.apply(this, arguments), + callbackReturn = callback.apply(this, arguments); + return (existingReturn !== false) && (callbackReturn !== false); }; } else { @@ -260,58 +260,58 @@ } } attachTo = null; - } - - /** - @name glow.events._copyListeners - @function - @private - @description Maps event listeners from one set of nodes to another in the order they appear in each NodeList. - Note, it doesn't copy events from a node's children. - - - @param {NodeList} from NodeList to copy events from - @param {NodeList} to NodeList to copy events to - - @returns {Boolean} - - @example - var listener = glow.events.addListener(...); - glow.events.removeListener(listener); - */ - r._copyListeners = function(from, to) { - // grab all the elements (including children) - var i = from.length, - // events attached to a particular element - elementEvents, - // name of the current event we're looking at - eventName, - // current listener index - listenerIndex, - // number of listeners to an event - listenersLen, - // listener definition from listenersByObjId - listener; - - // loop through all items - while(i--) { - // has a glow event been assigned to this node? - if ( from[i][psuedoPrivateEventKey] ) { - // get listeners for that event - elementEvents = listenersByObjId[ from[i][psuedoPrivateEventKey] ]; - // loop through event names - for (eventName in elementEvents) { - listenerIndex = 0; - listenersLen = elementEvents[eventName].length; - // loop through listeners to that event - for (; listenerIndex < listenersLen; listenerIndex++) { - listener = elementEvents[eventName][listenerIndex]; - // listen to them on the clone - r.addListener(to[i], eventName, listener[2], listener[3]); - } - } - } - } + } + + /** + @name glow.events._copyListeners + @function + @private + @description Maps event listeners from one set of nodes to another in the order they appear in each NodeList. + Note, it doesn't copy events from a node's children. + + + @param {NodeList} from NodeList to copy events from + @param {NodeList} to NodeList to copy events to + + @returns {Boolean} + + @example + var listener = glow.events.addListener(...); + glow.events.removeListener(listener); + */ + r._copyListeners = function(from, to) { + // grab all the elements (including children) + var i = from.length, + // events attached to a particular element + elementEvents, + // name of the current event we're looking at + eventName, + // current listener index + listenerIndex, + // number of listeners to an event + listenersLen, + // listener definition from listenersByObjId + listener; + + // loop through all items + while(i--) { + // has a glow event been assigned to this node? + if ( from[i][psuedoPrivateEventKey] ) { + // get listeners for that event + elementEvents = listenersByObjId[ from[i][psuedoPrivateEventKey] ]; + // loop through event names + for (eventName in elementEvents) { + listenerIndex = 0; + listenersLen = elementEvents[eventName].length; + // loop through listeners to that event + for (; listenerIndex < listenersLen; listenerIndex++) { + listener = elementEvents[eventName][listenerIndex]; + // listen to them on the clone + r.addListener(to[i], eventName, listener[2], listener[3]); + } + } + } + } } /** @@ -346,30 +346,30 @@ selector that returns no elements is passed, then undefined is returned. @example - glow.events.addListener('#nav', 'click', function () { - alert('nav clicked'); + glow.events.addListener('#nav', 'click', function () { + alert('nav clicked'); }); glow.events.addListener(myLightBox, 'close', this.showSurvey, this); - */ + */ r.addListener = function (attachTo, name, callback, context) { if (! attachTo) { throw 'no attachTo paramter passed to addListener'; } if (typeof attachTo == 'string') { if (! glow.dom) { throw "glow.dom must be loaded to use a selector as the first argument to glow.events.addListener"; } - attachTo = $(attachTo); - } - - if (glow.dom && attachTo instanceof glow.dom.NodeList) { - var listenerIds = [], - i = attachTo.length; - - //attach the event for each element, return an array of listener ids - while (i--) { - listenerIds[i] = r.addListener(attachTo[i], name, callback, context); - } + attachTo = $(attachTo); + } + + if (glow.dom && attachTo instanceof glow.dom.NodeList) { + var listenerIds = [], + i = attachTo.length; + + //attach the event for each element, return an array of listener ids + while (i--) { + listenerIds[i] = r.addListener(attachTo[i], name, callback, context); + } - return listenerIds; + return listenerIds; } var objIdent; @@ -410,13 +410,13 @@ if (ident && ident.toString().indexOf('k:') != -1) { return removeKeyListener(ident); } - if (ident instanceof Array) { - //call removeListener for each array member - var i = ident.length; while(i--) { - r.removeListener(ident[i]); - } - return true; - } + if (ident instanceof Array) { + //call removeListener for each array member + var i = ident.length; while(i--) { + r.removeListener(ident[i]); + } + return true; + } var listener = listenersByEventId[ident]; if (! listener) { return false; } delete listenersByEventId[ident]; @@ -436,63 +436,63 @@ delete listenersByObjId[listener[0]]; } return true; - }; - - /** - @name glow.events.removeAllListeners - @function - @description Removes all listeners attached to a given object - - @param {String | glow.dom.NodeList | Object | Object[] } detachFrom The object(s) to remove listeners from. - - If the parameter is a string, then it is treated as a CSS selector, - listeners are removed from all nodes. - - @returns glow.events - - @example - glow.events.removeAllListeners("#myDiv"); - */ - r.removeAllListeners = function(obj) { - var i, - objId, - listenerIds = [], - listenerIdsLen = 0, - eventName, - events; - - // cater for selector - if (typeof obj == "string") { - // get nodes - obj = $(obj); - } - // cater for arrays & nodelists - if (obj instanceof Array || obj instanceof glow.dom.NodeList) { - //call removeAllListeners for each array member - i = obj.length; while(i--) { - r.removeAllListeners(obj[i]); - } - return r; - } - - // get the objects id - objId = obj[psuedoPrivateEventKey]; - - // if it doesn't have an id it doesn't have events... return - if (!objId) { - return r; - } - events = listenersByObjId[objId]; - for (eventName in events) { - i = events[eventName].length; while(i--) { - listenerIds[listenerIdsLen++] = events[eventName][i][4]; - } - } - // remove listeners for that object - if (listenerIds.length) { - r.removeListener( listenerIds ); - } - return r; + }; + + /** + @name glow.events.removeAllListeners + @function + @description Removes all listeners attached to a given object + + @param {String | glow.dom.NodeList | Object | Object[] } detachFrom The object(s) to remove listeners from. + + If the parameter is a string, then it is treated as a CSS selector, + listeners are removed from all nodes. + + @returns glow.events + + @example + glow.events.removeAllListeners("#myDiv"); + */ + r.removeAllListeners = function(obj) { + var i, + objId, + listenerIds = [], + listenerIdsLen = 0, + eventName, + events; + + // cater for selector + if (typeof obj == "string") { + // get nodes + obj = $(obj); + } + // cater for arrays & nodelists + if (obj instanceof Array || obj instanceof glow.dom.NodeList) { + //call removeAllListeners for each array member + i = obj.length; while(i--) { + r.removeAllListeners(obj[i]); + } + return r; + } + + // get the objects id + objId = obj[psuedoPrivateEventKey]; + + // if it doesn't have an id it doesn't have events... return + if (!objId) { + return r; + } + events = listenersByObjId[objId]; + for (eventName in events) { + i = events[eventName].length; while(i--) { + listenerIds[listenerIdsLen++] = events[eventName][i][4]; + } + } + // remove listeners for that object + if (listenerIds.length) { + r.removeListener( listenerIds ); + } + return r; } /** @@ -508,47 +508,52 @@ @param {Object | glow.events.Event} [event] An event object or properties to add to a default event object - If not specified, a generic event object is created. If you provide a simple + If not specified, a generic event object is created. If you provide a simple object, a default Event will be created with the properties from the provided object. @returns {Object} The event object. - @example + @example // firing a custom event Ball.prototype.move = function () { - // move the ball... - // check its position - if (this._height == 0) { - var event = glow.events.fire(this, 'bounce', { - bounceCount: this._bounceCount - }); - - // handle what to do if a listener returned false - if ( event.defaultPrevented() ) { - this.stopMoving(); - } + // move the ball... + // check its position + if (this._height == 0) { + var event = glow.events.fire(this, 'bounce', { + bounceCount: this._bounceCount + }); + + // handle what to do if a listener returned false + if ( event.defaultPrevented() ) { + this.stopMoving(); + } + } + }; + + @example + // listening to a custom event + var myBall = new Ball(); + + glow.events.addListener(myBall, "bounce", function(event) { + if (event.bounceCount == 3) { + // stop bouncing after 3 bounces + return false; } - }; - - @example - // listening to a custom event - var myBall = new Ball(); - - glow.events.addListener(myBall, "bounce", function(event) { - if (event.bounceCount == 3) { - // stop bouncing after 3 bounces - return false; - } }); */ r.fire = function (attachedTo, name, e) { if (! attachedTo) throw 'glow.events.fire: required parameter attachedTo not passed (name: ' + name + ')'; if (! name) throw 'glow.events.fire: required parameter name not passed'; if (! e) { e = new r.Event(); } - if ( e.constructor === Object ) { e = new r.Event( e ) } - + if ( e.constructor === Object ) { e = new r.Event( e ) } + + if (typeof attachedTo == 'string') { + if (! glow.dom) { throw "glow.dom must be loaded to use a selector as the first argument to glow.events.addListener"; } + attachedTo = $(attachedTo); + } + e.type = name; e.attachedTo = attachedTo; if (! e.source) { e.source = attachedTo; } @@ -556,27 +561,82 @@ var objIdent, objListeners, objEventListeners = objListeners && objListeners[name]; - - // 3 assignments, but stop assigning if any of them are false - (objIdent = attachedTo[psuedoPrivateEventKey]) && - (objListeners = listenersByObjId[objIdent]) && - (objEventListeners = objListeners[name]); - if (! objEventListeners) { return e; } + // 3 assignments, but stop assigning if any of them are false +// (objIdent = attachedTo[psuedoPrivateEventKey]) && +// (objListeners = listenersByObjId[objIdent]) && +// (objEventListeners = objListeners[name]); + +// if (! objEventListeners) { return e; } // Moved this to inside the else below + + + + if (typeof attachedTo.addClass == 'function') { + + attachedTo.each(function(i){ + + (objIdent = attachedTo[i][psuedoPrivateEventKey]) && + (objListeners = listenersByObjId[objIdent]) && + (objEventListeners = objListeners[name]); + + if (! objEventListeners) { return e; } + + checkListeners(attachedTo[i], objEventListeners.slice(0), e); + + }); + + } else { + + (objIdent = attachedTo[psuedoPrivateEventKey]) && + (objListeners = listenersByObjId[objIdent]) && + (objEventListeners = objListeners[name]); + +// console.log(typeof attachedTo); +// console.log(attachedTo); +// console.log(attachedTo[psuedoPrivateEventKey]); +// console.log(psuedoPrivateEventKey); + + //console.log("obj: " + objEventListeners); + + if (! objEventListeners) { return e; } + + checkListeners(attachedTo, objEventListeners.slice(0), e); + + } + + + +// var listener; +// +// // we make a copy of the listeners before calling them, as the event handlers may +// // remove themselves (took me a while to track this one down) +// var listeners = objEventListeners.slice(0); +// for (var i = 0, len = listeners.length; i < len; i++) { +// listener = listeners[i]; +// if ( listener[2].call(listener[3] || attachedTo, e) === false ) { +// e.preventDefault(); +// } +// } + + + + return e; + }; + + function checkListeners(attachedTo, listeners, e) { var listener; // we make a copy of the listeners before calling them, as the event handlers may // remove themselves (took me a while to track this one down) - var listeners = objEventListeners.slice(0); for (var i = 0, len = listeners.length; i < len; i++) { listener = listeners[i]; if ( listener[2].call(listener[3] || attachedTo, e) === false ) { e.preventDefault(); } } - return e; - }; + + } /** @private @@ -707,9 +767,9 @@ /** @name glow.events.Event - @class - @param {Object} [properties] Properties to add to the Event instance. - Each key-value pair in the object will be added to the Event as + @class + @param {Object} [properties] Properties to add to the Event instance. + Each key-value pair in the object will be added to the Event as properties @description Object passed into all events @@ -868,14 +928,14 @@ This will be undefined if the key was not a printable character. */ - - /** - @name glow.events.Event#preventDefault - @function - @description Prevent the default action for events. - - This can also be achieved by returning false from an event callback - + + /** + @name glow.events.Event#preventDefault + @function + @description Prevent the default action for events. + + This can also be achieved by returning false from an event callback + */ r.Event.prototype.preventDefault = function () { if (this[psuedoPreventDefaultKey]) { return; } @@ -885,15 +945,15 @@ this.nativeEvent.returnValue = false; } }; - - /** - @name glow.events.Event#defaultPrevented - @function - @description Test if the default action has been prevented. - - @returns {Boolean} - - True if the default action has been prevented. + + /** + @name glow.events.Event#defaultPrevented + @function + @description Test if the default action has been prevented. + + @returns {Boolean} + + True if the default action has been prevented. */ r.Event.prototype.defaultPrevented = function () { return !! this[psuedoPreventDefaultKey]; @@ -950,6 +1010,7 @@ //cleanup to avoid mem leaks in IE r.addListener(window, "unload", clearEvents); - glow.events = r; + glow.events = r; + glow.events.listenersByObjId = listenersByObjId; } }); diff --git a/test/glow/events/events.js b/test/glow/events/events.js index 12862f3..cb5a42b 100644 --- a/test/glow/events/events.js +++ b/test/glow/events/events.js @@ -79,6 +79,40 @@ t.test("Load DOM", function() { }); }); +t.test("glow.events.fire used with all selector types", function() { + + t.expect(3); + + // Add DOM element to the page + glow.dom.get("body").append(glow.dom.create('
')); + + // Add event listener onto the element + var customEvent = new glow.events.Event(), + listener = glow.events.addListener( + glow.dom.get('div#firingTest'), + 'click', + function (e) { + t.ok(true, e.testMessage); + } + ); + + // Fire event using a DOM node selector + customEvent.testMessage = "events.fire with DOM node: glow.dom.get('div#firingTest')[0]"; + glow.events.fire(glow.dom.get('div#firingTest')[0], 'click', customEvent); + + // Fire event using a nodeList selector + customEvent.testMessage = "events.fire with nodeList: glow.dom.get('div#firingTest')"; + glow.events.fire(glow.dom.get('div#firingTest'), 'click', customEvent); + + // Fire event using a string selector + customEvent.testMessage = "events.fire with string: 'div#firingTest'"; + glow.events.fire('div#firingTest', 'click', customEvent); + + // Remove DOM element from the page + glow.dom.get('div#firingTest').remove(); + +}); + t.test("Removing all events from an object / nodelist", function() { t.expect(5);