diff --git a/src/pixi/utils/EventTarget.js b/src/pixi/utils/EventTarget.js index 7893997..c85b039 100644 --- a/src/pixi/utils/EventTarget.js +++ b/src/pixi/utils/EventTarget.js @@ -1,14 +1,15 @@ /** * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** - * https://github.com/mrdoob/eventtarget.js/ - * THankS mr DOob! + * @author Chad Engler https://github.com/englercj @Rolnaaba */ /** - * Adds event emitter functionality to a class + * Originally based on https://github.com/mrdoob/eventtarget.js/ from mr DOob. + * Currently takes inspiration from the nodejs EventEmitter, and EventEmitter3 + */ + +/** + * Mixins event emitter functionality to a class * * @class EventTarget * @example @@ -17,91 +18,152 @@ * } * * var em = new MyEmitter(); - * em.emit({ type: 'eventName', data: 'some data' }); + * em.emit('eventName', 'some data', 'some moar data', {}, null, ...); */ -PIXI.EventTarget = function () { +PIXI.EventTarget = function() { + this._listeners = this._listeners || {}; /** - * Holds all the listeners + * Return a list of assigned event listeners. * - * @property listeners - * @type Object + * @param eventName {String} The events that should be listed. + * @returns {Array} + * @api public */ - var listeners = {}; - - /** - * Adds a listener for a specific event - * - * @method addEventListener - * @param type {string} A string representing the event type to listen for. - * @param listener {function} The callback function that will be fired when the event occurs - */ - this.addEventListener = this.on = function ( type, listener ) { - - - if ( listeners[ type ] === undefined ) { - - listeners[ type ] = []; - - } - - if ( listeners[ type ].indexOf( listener ) === - 1 ) { - - listeners[ type ].push( listener ); - } - + this.listeners = function listeners(eventName) { + return Array.apply(this, this._listeners[eventName] || []); }; /** - * Fires the event, ie pretends that the event has happened + * Emit an event to all registered event listeners. * - * @method dispatchEvent - * @param event {Event} the event object + * @param eventName {String} The name of the event. + * @returns {Boolean} Indication if we've emitted an event. + * @api public */ - this.dispatchEvent = this.emit = function ( event ) { + this.emit = function emit(eventName, data) { + if(!data || data.__isEventObject !== true) + data = new PIXI.Event(this, eventName, data); - if ( !listeners[ event.type ] || !listeners[ event.type ].length ) { + if(this._listeners && this._listeners[eventName]) { + var listeners = this._listeners[eventName], + length = listeners.length, + fn = listeners[0], + i; - return; + for(i = 0; i < length; fn = listeners[++i]) { + //call the event listener + fn.call(this, data); + //remove the listener if this is a "once" event + if(fn.__isOnce) + this.off(eventName, fn); + + //if "stopImmediatePropagation" is called, stop calling all events + if(data.stoppedImmediate) + return; + } + + //if "stopPropagation" is called then don't bubble the event + if(data.stopped) + return; } - for(var i = 0, l = listeners[ event.type ].length; i < l; i++) { - - listeners[ event.type ][ i ]( event ); - + if(this.parent && this.parent.emit) { + this.parent.emit.call(this.parent, eventName, data); } + return true; }; /** - * Removes the specified listener that was assigned to the specified event type + * Register a new EventListener for the given event. * - * @method removeEventListener - * @param type {string} A string representing the event type which will have its listener removed - * @param listener {function} The callback function that was be fired when the event occured + * @param eventName {String} Name of the event. + * @param callback {Functon} fn Callback function. + * @api public */ - this.removeEventListener = this.off = function ( type, listener ) { + this.on = function on(eventName, fn) { + if(!this._listeners[eventName]) + this._listeners[eventName] = []; - var index = listeners[ type ].indexOf( listener ); + this._listeners[eventName].push(fn); - if ( index !== - 1 ) { - - listeners[ type ].splice( index, 1 ); - - } - + return this; }; /** - * Removes all the listeners that were active for the specified event type + * Add an EventListener that's only called once. * - * @method removeAllEventListeners - * @param type {string} A string representing the event type which will have all its listeners removed + * @param eventName {String} Name of the event. + * @param callback {Function} Callback function. + * @api public */ - this.removeAllEventListeners = function( type ) { - var a = listeners[type]; - if (a) - a.length = 0; - }; + this.once = function once(eventName, fn) { + fn.__isOnce = true; + return this.on(eventName, fn); + }; + + /** + * Remove event listeners. + * + * @param eventName {String} The event we want to remove. + * @param callback {Function} The listener that we need to find. + * @api public + */ + this.off = function off(eventName, fn) { + if(!this._listeners[eventName]) + return this; + + var index = this._listeners[eventName].indexOf(fn); + + if(index !== -1) { + this._listeners[eventName].splice(index, 1); + } + + return this; + }; + + /** + * Remove all listeners or only the listeners for the specified event. + * + * @param eventName {String} The event want to remove all listeners for. + * @api public + */ + this.removeAllListeners = function removeAllListeners(eventName) { + if(!this._listeners[eventName]) + return this; + + this._listeners[eventName].length = 0; + + return this; + }; + + /** + * Alias methods names because people roll like that. + */ + this.removeEventListener = this.off; + this.addEventListener = this.on; + this.dispatchEvent = this.emit; +}; + +PIXI.Event = function(target, name, data) { + this.__isEventObject = true; + + this.stopped = false; + this.stoppedImmediate = false; + + this.target = target; + this.type = name; + this.data = data; + + this.timeStamp = Date.now(); +}; + +PIXI.Event.prototype.stopPropagation = function() { + this.stopped = true; +}; + +PIXI.Event.prototype.stopImmediatePropagation = function() { + this.stoppedImmediate = true; }; diff --git a/src/pixi/utils/EventTarget.js b/src/pixi/utils/EventTarget.js index 7893997..c85b039 100644 --- a/src/pixi/utils/EventTarget.js +++ b/src/pixi/utils/EventTarget.js @@ -1,14 +1,15 @@ /** * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** - * https://github.com/mrdoob/eventtarget.js/ - * THankS mr DOob! + * @author Chad Engler https://github.com/englercj @Rolnaaba */ /** - * Adds event emitter functionality to a class + * Originally based on https://github.com/mrdoob/eventtarget.js/ from mr DOob. + * Currently takes inspiration from the nodejs EventEmitter, and EventEmitter3 + */ + +/** + * Mixins event emitter functionality to a class * * @class EventTarget * @example @@ -17,91 +18,152 @@ * } * * var em = new MyEmitter(); - * em.emit({ type: 'eventName', data: 'some data' }); + * em.emit('eventName', 'some data', 'some moar data', {}, null, ...); */ -PIXI.EventTarget = function () { +PIXI.EventTarget = function() { + this._listeners = this._listeners || {}; /** - * Holds all the listeners + * Return a list of assigned event listeners. * - * @property listeners - * @type Object + * @param eventName {String} The events that should be listed. + * @returns {Array} + * @api public */ - var listeners = {}; - - /** - * Adds a listener for a specific event - * - * @method addEventListener - * @param type {string} A string representing the event type to listen for. - * @param listener {function} The callback function that will be fired when the event occurs - */ - this.addEventListener = this.on = function ( type, listener ) { - - - if ( listeners[ type ] === undefined ) { - - listeners[ type ] = []; - - } - - if ( listeners[ type ].indexOf( listener ) === - 1 ) { - - listeners[ type ].push( listener ); - } - + this.listeners = function listeners(eventName) { + return Array.apply(this, this._listeners[eventName] || []); }; /** - * Fires the event, ie pretends that the event has happened + * Emit an event to all registered event listeners. * - * @method dispatchEvent - * @param event {Event} the event object + * @param eventName {String} The name of the event. + * @returns {Boolean} Indication if we've emitted an event. + * @api public */ - this.dispatchEvent = this.emit = function ( event ) { + this.emit = function emit(eventName, data) { + if(!data || data.__isEventObject !== true) + data = new PIXI.Event(this, eventName, data); - if ( !listeners[ event.type ] || !listeners[ event.type ].length ) { + if(this._listeners && this._listeners[eventName]) { + var listeners = this._listeners[eventName], + length = listeners.length, + fn = listeners[0], + i; - return; + for(i = 0; i < length; fn = listeners[++i]) { + //call the event listener + fn.call(this, data); + //remove the listener if this is a "once" event + if(fn.__isOnce) + this.off(eventName, fn); + + //if "stopImmediatePropagation" is called, stop calling all events + if(data.stoppedImmediate) + return; + } + + //if "stopPropagation" is called then don't bubble the event + if(data.stopped) + return; } - for(var i = 0, l = listeners[ event.type ].length; i < l; i++) { - - listeners[ event.type ][ i ]( event ); - + if(this.parent && this.parent.emit) { + this.parent.emit.call(this.parent, eventName, data); } + return true; }; /** - * Removes the specified listener that was assigned to the specified event type + * Register a new EventListener for the given event. * - * @method removeEventListener - * @param type {string} A string representing the event type which will have its listener removed - * @param listener {function} The callback function that was be fired when the event occured + * @param eventName {String} Name of the event. + * @param callback {Functon} fn Callback function. + * @api public */ - this.removeEventListener = this.off = function ( type, listener ) { + this.on = function on(eventName, fn) { + if(!this._listeners[eventName]) + this._listeners[eventName] = []; - var index = listeners[ type ].indexOf( listener ); + this._listeners[eventName].push(fn); - if ( index !== - 1 ) { - - listeners[ type ].splice( index, 1 ); - - } - + return this; }; /** - * Removes all the listeners that were active for the specified event type + * Add an EventListener that's only called once. * - * @method removeAllEventListeners - * @param type {string} A string representing the event type which will have all its listeners removed + * @param eventName {String} Name of the event. + * @param callback {Function} Callback function. + * @api public */ - this.removeAllEventListeners = function( type ) { - var a = listeners[type]; - if (a) - a.length = 0; - }; + this.once = function once(eventName, fn) { + fn.__isOnce = true; + return this.on(eventName, fn); + }; + + /** + * Remove event listeners. + * + * @param eventName {String} The event we want to remove. + * @param callback {Function} The listener that we need to find. + * @api public + */ + this.off = function off(eventName, fn) { + if(!this._listeners[eventName]) + return this; + + var index = this._listeners[eventName].indexOf(fn); + + if(index !== -1) { + this._listeners[eventName].splice(index, 1); + } + + return this; + }; + + /** + * Remove all listeners or only the listeners for the specified event. + * + * @param eventName {String} The event want to remove all listeners for. + * @api public + */ + this.removeAllListeners = function removeAllListeners(eventName) { + if(!this._listeners[eventName]) + return this; + + this._listeners[eventName].length = 0; + + return this; + }; + + /** + * Alias methods names because people roll like that. + */ + this.removeEventListener = this.off; + this.addEventListener = this.on; + this.dispatchEvent = this.emit; +}; + +PIXI.Event = function(target, name, data) { + this.__isEventObject = true; + + this.stopped = false; + this.stoppedImmediate = false; + + this.target = target; + this.type = name; + this.data = data; + + this.timeStamp = Date.now(); +}; + +PIXI.Event.prototype.stopPropagation = function() { + this.stopped = true; +}; + +PIXI.Event.prototype.stopImmediatePropagation = function() { + this.stoppedImmediate = true; }; diff --git a/test/lib/pixi/utils/EventTarget.js b/test/lib/pixi/utils/EventTarget.js index 7b1dcfa..40bb727 100644 --- a/test/lib/pixi/utils/EventTarget.js +++ b/test/lib/pixi/utils/EventTarget.js @@ -2,7 +2,16 @@ function pixi_utils_EventTarget_like(obj) { var expect = chai.expect; + //public API + expect(obj).to.respondTo('listeners'); + expect(obj).to.respondTo('emit'); + expect(obj).to.respondTo('on'); + expect(obj).to.respondTo('once'); + expect(obj).to.respondTo('off'); + expect(obj).to.respondTo('removeAllListeners'); + + //Aliased names + expect(obj).to.respondTo('removeEventListener'); expect(obj).to.respondTo('addEventListener'); expect(obj).to.respondTo('dispatchEvent'); - expect(obj).to.respondTo('removeEventListener'); } diff --git a/src/pixi/utils/EventTarget.js b/src/pixi/utils/EventTarget.js index 7893997..c85b039 100644 --- a/src/pixi/utils/EventTarget.js +++ b/src/pixi/utils/EventTarget.js @@ -1,14 +1,15 @@ /** * @author Mat Groves http://matgroves.com/ @Doormat23 - */ - -/** - * https://github.com/mrdoob/eventtarget.js/ - * THankS mr DOob! + * @author Chad Engler https://github.com/englercj @Rolnaaba */ /** - * Adds event emitter functionality to a class + * Originally based on https://github.com/mrdoob/eventtarget.js/ from mr DOob. + * Currently takes inspiration from the nodejs EventEmitter, and EventEmitter3 + */ + +/** + * Mixins event emitter functionality to a class * * @class EventTarget * @example @@ -17,91 +18,152 @@ * } * * var em = new MyEmitter(); - * em.emit({ type: 'eventName', data: 'some data' }); + * em.emit('eventName', 'some data', 'some moar data', {}, null, ...); */ -PIXI.EventTarget = function () { +PIXI.EventTarget = function() { + this._listeners = this._listeners || {}; /** - * Holds all the listeners + * Return a list of assigned event listeners. * - * @property listeners - * @type Object + * @param eventName {String} The events that should be listed. + * @returns {Array} + * @api public */ - var listeners = {}; - - /** - * Adds a listener for a specific event - * - * @method addEventListener - * @param type {string} A string representing the event type to listen for. - * @param listener {function} The callback function that will be fired when the event occurs - */ - this.addEventListener = this.on = function ( type, listener ) { - - - if ( listeners[ type ] === undefined ) { - - listeners[ type ] = []; - - } - - if ( listeners[ type ].indexOf( listener ) === - 1 ) { - - listeners[ type ].push( listener ); - } - + this.listeners = function listeners(eventName) { + return Array.apply(this, this._listeners[eventName] || []); }; /** - * Fires the event, ie pretends that the event has happened + * Emit an event to all registered event listeners. * - * @method dispatchEvent - * @param event {Event} the event object + * @param eventName {String} The name of the event. + * @returns {Boolean} Indication if we've emitted an event. + * @api public */ - this.dispatchEvent = this.emit = function ( event ) { + this.emit = function emit(eventName, data) { + if(!data || data.__isEventObject !== true) + data = new PIXI.Event(this, eventName, data); - if ( !listeners[ event.type ] || !listeners[ event.type ].length ) { + if(this._listeners && this._listeners[eventName]) { + var listeners = this._listeners[eventName], + length = listeners.length, + fn = listeners[0], + i; - return; + for(i = 0; i < length; fn = listeners[++i]) { + //call the event listener + fn.call(this, data); + //remove the listener if this is a "once" event + if(fn.__isOnce) + this.off(eventName, fn); + + //if "stopImmediatePropagation" is called, stop calling all events + if(data.stoppedImmediate) + return; + } + + //if "stopPropagation" is called then don't bubble the event + if(data.stopped) + return; } - for(var i = 0, l = listeners[ event.type ].length; i < l; i++) { - - listeners[ event.type ][ i ]( event ); - + if(this.parent && this.parent.emit) { + this.parent.emit.call(this.parent, eventName, data); } + return true; }; /** - * Removes the specified listener that was assigned to the specified event type + * Register a new EventListener for the given event. * - * @method removeEventListener - * @param type {string} A string representing the event type which will have its listener removed - * @param listener {function} The callback function that was be fired when the event occured + * @param eventName {String} Name of the event. + * @param callback {Functon} fn Callback function. + * @api public */ - this.removeEventListener = this.off = function ( type, listener ) { + this.on = function on(eventName, fn) { + if(!this._listeners[eventName]) + this._listeners[eventName] = []; - var index = listeners[ type ].indexOf( listener ); + this._listeners[eventName].push(fn); - if ( index !== - 1 ) { - - listeners[ type ].splice( index, 1 ); - - } - + return this; }; /** - * Removes all the listeners that were active for the specified event type + * Add an EventListener that's only called once. * - * @method removeAllEventListeners - * @param type {string} A string representing the event type which will have all its listeners removed + * @param eventName {String} Name of the event. + * @param callback {Function} Callback function. + * @api public */ - this.removeAllEventListeners = function( type ) { - var a = listeners[type]; - if (a) - a.length = 0; - }; + this.once = function once(eventName, fn) { + fn.__isOnce = true; + return this.on(eventName, fn); + }; + + /** + * Remove event listeners. + * + * @param eventName {String} The event we want to remove. + * @param callback {Function} The listener that we need to find. + * @api public + */ + this.off = function off(eventName, fn) { + if(!this._listeners[eventName]) + return this; + + var index = this._listeners[eventName].indexOf(fn); + + if(index !== -1) { + this._listeners[eventName].splice(index, 1); + } + + return this; + }; + + /** + * Remove all listeners or only the listeners for the specified event. + * + * @param eventName {String} The event want to remove all listeners for. + * @api public + */ + this.removeAllListeners = function removeAllListeners(eventName) { + if(!this._listeners[eventName]) + return this; + + this._listeners[eventName].length = 0; + + return this; + }; + + /** + * Alias methods names because people roll like that. + */ + this.removeEventListener = this.off; + this.addEventListener = this.on; + this.dispatchEvent = this.emit; +}; + +PIXI.Event = function(target, name, data) { + this.__isEventObject = true; + + this.stopped = false; + this.stoppedImmediate = false; + + this.target = target; + this.type = name; + this.data = data; + + this.timeStamp = Date.now(); +}; + +PIXI.Event.prototype.stopPropagation = function() { + this.stopped = true; +}; + +PIXI.Event.prototype.stopImmediatePropagation = function() { + this.stoppedImmediate = true; }; diff --git a/test/lib/pixi/utils/EventTarget.js b/test/lib/pixi/utils/EventTarget.js index 7b1dcfa..40bb727 100644 --- a/test/lib/pixi/utils/EventTarget.js +++ b/test/lib/pixi/utils/EventTarget.js @@ -2,7 +2,16 @@ function pixi_utils_EventTarget_like(obj) { var expect = chai.expect; + //public API + expect(obj).to.respondTo('listeners'); + expect(obj).to.respondTo('emit'); + expect(obj).to.respondTo('on'); + expect(obj).to.respondTo('once'); + expect(obj).to.respondTo('off'); + expect(obj).to.respondTo('removeAllListeners'); + + //Aliased names + expect(obj).to.respondTo('removeEventListener'); expect(obj).to.respondTo('addEventListener'); expect(obj).to.respondTo('dispatchEvent'); - expect(obj).to.respondTo('removeEventListener'); } diff --git a/test/unit/pixi/utils/EventTarget.js b/test/unit/pixi/utils/EventTarget.js index 67c6ad1..c958e19 100644 --- a/test/unit/pixi/utils/EventTarget.js +++ b/test/unit/pixi/utils/EventTarget.js @@ -15,38 +15,126 @@ pixi_utils_EventTarget_like(obj); }); - it('addEventListener and dispatchEvent works', function (done) { + it('simple on/emit case works', function (done) { var myData = {}, obj = {}; EventTarget.call(obj); - obj.addEventListener('myevent', function (event) { - expect(event).to.be.an('object'); + obj.on('myevent', function (event) { + expect(event).to.be.an.instanceOf(PIXI.Event); + + expect(event).to.have.property('stopped', false); + expect(event).to.have.property('stoppedImmediate', false); + + expect(event).to.have.property('target', obj); expect(event).to.have.property('type', 'myevent'); expect(event).to.have.property('data', myData); + + expect(event).to.respondTo('stopPropagation'); + expect(event).to.respondTo('stopImmediatePropagation'); + done(); }); - obj.dispatchEvent({type: 'myevent', data: myData}); + obj.emit('myevent', myData); }); - it('removeEventListener works', function (done) { + it('simple once case works', function () { + var called = 0, + obj = {}; + + EventTarget.call(obj); + + obj.once('myevent', function() { called++; }); + + obj.emit('myevent'); + obj.emit('myevent'); + obj.emit('myevent'); + obj.emit('myevent'); + obj.emit('myevent'); + + expect(called).to.equal(1); + }); + + it('simple off case works', function (done) { var obj = {}; EventTarget.call(obj); function onMyEvent() { - done(new Error('addEventListener should not have been called')); + done(new Error('Event listener should not have been called')); } - obj.addEventListener('myevent', onMyEvent); - obj.removeEventListener('myevent', onMyEvent); - obj.dispatchEvent({type: 'myevent'}); + obj.on('myevent', onMyEvent); + obj.off('myevent', onMyEvent); + obj.emit('myevent'); + done(); }); - it('multiple dispatches', function () { + it('simple propagation case works', function (done) { + var myData = {}, + pobj = {}, + obj = { parent: pobj }; + + EventTarget.call(pobj); + EventTarget.call(obj); + + pobj.on('myevent', function () { + done(); + }); + + obj.emit('myevent'); + }); + + it('simple stopPropagation case works', function (done) { + var myData = {}, + pobj = {}, + obj = { parent: pobj }; + + EventTarget.call(pobj); + EventTarget.call(obj); + + pobj.on('myevent', function () { + done(new Error('Event listener should not have been called on the parent element')); + }); + + obj.on('myevent', function (evt) { + evt.stopPropagation(); + }); + + obj.emit('myevent'); + + done(); + }); + + it('simple stopImmediatePropagation case works', function (done) { + var myData = {}, + pobj = {}, + obj = { parent: pobj }; + + EventTarget.call(pobj); + EventTarget.call(obj); + + pobj.on('myevent', function () { + done(new Error('Event listener should not have been called on the parent')); + }); + + obj.on('myevent', function (evt) { + evt.stopImmediatePropagation(); + }); + + obj.on('myevent', function () { + done(new Error('Event listener should not have been called on the second')); + }); + + obj.emit('myevent'); + + done(); + }); + + it('multiple dispatches work properly', function () { var called = 0, obj = {}; @@ -56,15 +144,15 @@ called++; } - obj.addEventListener('myevent', onMyEvent); - obj.dispatchEvent({type: 'myevent'}); - obj.dispatchEvent({type: 'myevent'}); - obj.dispatchEvent({type: 'myevent'}); - obj.dispatchEvent({type: 'myevent'}); + obj.on('myevent', onMyEvent); + obj.emit('myevent'); + obj.emit('myevent'); + obj.emit('myevent'); + obj.emit('myevent'); expect(called).to.equal(4); }); - it('multiple events', function () { + it('multiple events work properly', function () { var called = 0, obj = {}; @@ -74,16 +162,16 @@ called++; } - obj.addEventListener('myevent1', onMyEvent); - obj.addEventListener('myevent2', onMyEvent); - obj.addEventListener('myevent3', onMyEvent); - obj.dispatchEvent({type: 'myevent1'}); - obj.dispatchEvent({type: 'myevent2'}); - obj.dispatchEvent({type: 'myevent3'}); + obj.on('myevent1', onMyEvent); + obj.on('myevent2', onMyEvent); + obj.on('myevent3', onMyEvent); + obj.emit('myevent1'); + obj.emit('myevent2'); + obj.emit('myevent3'); expect(called).to.equal(3); }); - it('multiple events one removed', function () { + it('multiple events one removed works properly', function () { var called = 0, obj = {}; @@ -93,16 +181,20 @@ called++; } - obj.addEventListener('myevent1', onMyEvent); - obj.addEventListener('myevent2', onMyEvent); - obj.addEventListener('myevent3', onMyEvent); - obj.dispatchEvent({type: 'myevent1'}); - obj.dispatchEvent({type: 'myevent2'}); - obj.dispatchEvent({type: 'myevent3'}); - obj.removeEventListener('myevent2', onMyEvent); - obj.dispatchEvent({type: 'myevent1'}); - obj.dispatchEvent({type: 'myevent2'}); - obj.dispatchEvent({type: 'myevent3'}); + obj.on('myevent1', onMyEvent); + obj.on('myevent2', onMyEvent); + obj.on('myevent3', onMyEvent); + + obj.emit('myevent1'); + obj.emit('myevent2'); + obj.emit('myevent3'); + + obj.off('myevent2', onMyEvent); + + obj.emit('myevent1'); + obj.emit('myevent2'); + obj.emit('myevent3'); + expect(called).to.equal(5); }); });