From 3dc40f8cb6e727f897e1b96ddba108e2bf5294cd Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 5 Dec 2013 12:27:30 -0700 Subject: [PATCH 1/2] Add ol.Observable for on, once, un, and unByKey methods --- src/ol/observable.exports | 4 + src/ol/observable.js | 70 +++++++++++++ test/spec/ol/observable.test.js | 172 ++++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 src/ol/observable.exports create mode 100644 src/ol/observable.js create mode 100644 test/spec/ol/observable.test.js diff --git a/src/ol/observable.exports b/src/ol/observable.exports new file mode 100644 index 0000000000..1d6e1ffcdf --- /dev/null +++ b/src/ol/observable.exports @@ -0,0 +1,4 @@ +@exportProperty ol.Observable.prototype.on +@exportProperty ol.Observable.prototype.once +@exportProperty ol.Observable.prototype.un +@exportProperty ol.Observable.prototype.unByKey diff --git a/src/ol/observable.js b/src/ol/observable.js new file mode 100644 index 0000000000..6becf17b47 --- /dev/null +++ b/src/ol/observable.js @@ -0,0 +1,70 @@ +goog.provide('ol.Observable'); + +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); + + + +/** + * An event target providing convenient methods for listener registration + * and unregistration. + * @constructor + * @extends {goog.events.EventTarget} + * @todo stability experimental + */ +ol.Observable = function() { + goog.base(this); + +}; +goog.inherits(ol.Observable, goog.events.EventTarget); + + +/** + * Listen for a certain type of event. + * @param {string|Array.} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_scope Object is whose scope to call + * the listener. + * @return {goog.events.Key} Unique key for the listener. + * @todo stability experimental + */ +ol.Observable.prototype.on = function(type, listener, opt_scope) { + return goog.events.listen(this, type, listener, false, opt_scope); +}; + + +/** + * Listen once for a certain type of event. + * @param {string|Array.} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_scope Object is whose scope to call + * the listener. + * @return {goog.events.Key} Unique key for the listener. + * @todo stability experimental + */ +ol.Observable.prototype.once = function(type, listener, opt_scope) { + return goog.events.listenOnce(this, type, listener, false, opt_scope); +}; + + +/** + * Unlisten for a certain type of event. + * @param {string|Array.} type The event type or array of event types. + * @param {function(?): ?} listener The listener function. + * @param {Object=} opt_scope Object is whose scope to call + * the listener. + * @todo stability experimental + */ +ol.Observable.prototype.un = function(type, listener, opt_scope) { + goog.events.unlisten(this, type, listener, false, opt_scope); +}; + + +/** + * Removes an event listener using the key returned by `on()` or `once()`. + * @param {goog.events.Key} key Key. + * @todo stability experimental + */ +ol.Observable.prototype.unByKey = function(key) { + goog.events.unlistenByKey(key); +}; diff --git a/test/spec/ol/observable.test.js b/test/spec/ol/observable.test.js new file mode 100644 index 0000000000..7d7e93cbc9 --- /dev/null +++ b/test/spec/ol/observable.test.js @@ -0,0 +1,172 @@ +goog.provide('ol.test.Observable'); + +describe('ol.Observable', function() { + + describe('constructor', function() { + + it('creates a new observable', function() { + var observable = new ol.Observable(); + expect(observable).to.be.a(ol.Observable); + expect(observable).to.be.a(goog.events.EventTarget); + }); + + }); + + describe('#on()', function() { + var observable, listener; + beforeEach(function() { + observable = new ol.Observable(); + listener = sinon.spy(); + }); + + it('registers a listener for events of the given type', function() { + observable.on('foo', listener); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + + observable.dispatchEvent('foo'); + expect(listener.callCount).to.be(2); + }); + + it('accepts an array of event types', function() { + observable.on(['foo', 'bar'], listener); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + + observable.dispatchEvent('bar'); + expect(listener.callCount).to.be(2); + }); + + it('accepts an optional `this` arg for the listener', function() { + var thisArg = {}; + observable.on('foo', listener, thisArg); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + expect(listener.calledOn(thisArg)).to.be(true); + }); + + it('returns a listener key', function() { + var key = observable.on('foo', listener); + + expect(key).to.be.a(goog.events.Listener); + }); + + }); + + describe('#once()', function() { + var observable, listener; + beforeEach(function() { + observable = new ol.Observable(); + listener = sinon.spy(); + }); + + it('registers a listener that is only called once', function() { + observable.once('foo', listener); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + + observable.dispatchEvent('foo'); + expect(listener.callCount).to.be(1); + }); + + it('accepts an array of event types (called once for each)', function() { + observable.once(['foo', 'bar'], listener); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + + observable.dispatchEvent('foo'); + expect(listener.callCount).to.be(1); + + observable.dispatchEvent('bar'); + expect(listener.callCount).to.be(2); + + observable.dispatchEvent('bar'); + expect(listener.callCount).to.be(2); + }); + + it('accepts an optional `this` arg for the listener', function() { + var thisArg = {}; + observable.once('foo', listener, thisArg); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + expect(listener.calledOn(thisArg)).to.be(true); + }); + + it('returns a listener key', function() { + var key = observable.once('foo', listener); + + expect(key).to.be.a(goog.events.Listener); + }); + + }); + + describe('#un()', function() { + var observable, listener; + beforeEach(function() { + observable = new ol.Observable(); + listener = sinon.spy(); + }); + + it('unregisters a previously registered listener', function() { + observable.on('foo', listener); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + + observable.un('foo', listener); + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + }); + + it('accepts a `this` arg', function() { + var thisArg = {}; + observable.on('foo', listener, thisArg); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + + // will not unregister without the same thisArg + observable.un('foo', listener); + observable.dispatchEvent('foo'); + expect(listener.callCount).to.be(2); + + // properly unregister by providing the same thisArg + observable.un('foo', listener, thisArg); + observable.dispatchEvent('foo'); + expect(listener.callCount).to.be(2); + }); + + }); + + describe('#unByKey()', function() { + var observable, listener; + beforeEach(function() { + observable = new ol.Observable(); + listener = sinon.spy(); + }); + + it('unregisters a listener given the key returned by `on`', function() { + var key = observable.on('foo', listener); + + observable.dispatchEvent('foo'); + expect(listener.calledOnce).to.be(true); + + observable.unByKey(key); + observable.dispatchEvent('foo'); + expect(listener.callCount).to.be(1); + }); + + }); + +}); + + +goog.require('goog.events.EventTarget'); +goog.require('goog.events.Listener'); +goog.require('ol.Observable'); From 162b245d7c61e9491da0f8d4464d3f4a224e3ac3 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 5 Dec 2013 12:28:33 -0700 Subject: [PATCH 2/2] ol.Object extends ol.Observable --- src/ol/object.exports | 4 --- src/ol/object.js | 58 +++---------------------------------------- 2 files changed, 3 insertions(+), 59 deletions(-) diff --git a/src/ol/object.exports b/src/ol/object.exports index c824a78609..89426170a5 100644 --- a/src/ol/object.exports +++ b/src/ol/object.exports @@ -3,11 +3,7 @@ @exportProperty ol.Object.prototype.get @exportProperty ol.Object.prototype.getProperties @exportProperty ol.Object.prototype.notify -@exportProperty ol.Object.prototype.on -@exportProperty ol.Object.prototype.once @exportProperty ol.Object.prototype.set @exportProperty ol.Object.prototype.setValues -@exportProperty ol.Object.prototype.un -@exportProperty ol.Object.prototype.unByKey @exportProperty ol.Object.prototype.unbind @exportProperty ol.Object.prototype.unbindAll diff --git a/src/ol/object.js b/src/ol/object.js index 1a14e785af..4bca4e75ca 100644 --- a/src/ol/object.js +++ b/src/ol/object.js @@ -10,9 +10,9 @@ goog.provide('ol.ObjectEventType'); goog.require('goog.array'); goog.require('goog.events'); -goog.require('goog.events.EventTarget'); goog.require('goog.functions'); goog.require('goog.object'); +goog.require('ol.Observable'); /** @@ -81,7 +81,7 @@ ol.ObjectProperty = { /** * Base class implementing KVO (Key Value Observing). * @constructor - * @extends {goog.events.EventTarget} + * @extends {ol.Observable} * @param {Object.=} opt_values Values. * @todo stability experimental */ @@ -98,7 +98,7 @@ ol.Object = function(opt_values) { this.setValues(opt_values); } }; -goog.inherits(ol.Object, goog.events.EventTarget); +goog.inherits(ol.Object, ol.Observable); /** @@ -335,34 +335,6 @@ ol.Object.prototype.notifyInternal_ = function(key) { }; -/** - * Listen for a certain type of event. - * @param {string|Array.} type The event type or array of event types. - * @param {function(?): ?} listener The listener function. - * @param {Object=} opt_scope Object is whose scope to call - * the listener. - * @return {goog.events.Key} Unique key for the listener. - * @todo stability experimental - */ -ol.Object.prototype.on = function(type, listener, opt_scope) { - return goog.events.listen(this, type, listener, false, opt_scope); -}; - - -/** - * Listen once for a certain type of event. - * @param {string|Array.} type The event type or array of event types. - * @param {function(?): ?} listener The listener function. - * @param {Object=} opt_scope Object is whose scope to call - * the listener. - * @return {goog.events.Key} Unique key for the listener. - * @todo stability experimental - */ -ol.Object.prototype.once = function(type, listener, opt_scope) { - return goog.events.listenOnce(this, type, listener, false, opt_scope); -}; - - /** * Sets a value. * @param {string} key Key name. @@ -428,30 +400,6 @@ ol.Object.prototype.unbind = function(key) { }; -/** - * Unlisten for a certain type of event. - * @param {string|Array.} type The event type or array of event types. - * @param {function(?): ?} listener The listener function. - * @param {Object=} opt_scope Object is whose scope to call - * the listener. - * @todo stability experimental - */ -ol.Object.prototype.un = function(type, listener, opt_scope) { - goog.events.unlisten(this, type, listener, false, opt_scope); -}; - - -/** - * Removes an event listener which was added with `listen()` by the key returned - * by `on()` or `once()`. - * @param {goog.events.Key} key Key. - * @todo stability experimental - */ -ol.Object.prototype.unByKey = function(key) { - goog.events.unlistenByKey(key); -}; - - /** * Removes all bindings. * @todo stability experimental