diff --git a/src/api/map.js b/src/api/map.js index f6a090fe9b..3c2e1b50bd 100644 --- a/src/api/map.js +++ b/src/api/map.js @@ -7,6 +7,7 @@ goog.require('ol.loc'); goog.require('ol.projection'); goog.require('ol.error'); +goog.require('goog.dispose'); /** * @typedef {ol.Map|{center, zoom, numZoomLevels, projection, userProjection, maxExtent, maxResolution, resolutions, renderTo, layers, controls}|string} @@ -247,3 +248,9 @@ ol.Map.prototype.renderTo = function(arg) { this.setContainer(goog.dom.getElement(arg)); return this; }; + +/** + */ +ol.Map.prototype.destroy = function() { + goog.dispose(this); +}; diff --git a/src/ol.export.js b/src/ol.export.js index 48f5786f8f..f243f1386d 100644 --- a/src/ol.export.js +++ b/src/ol.export.js @@ -130,4 +130,3 @@ goog.exportProperty(ol.renderer.Composite, 'isSupported', ol.renderer.Composite. goog.exportProperty(ol.renderer.TileLayerRenderer, 'isSupported', ol.renderer.TileLayerRenderer.isSupported); goog.exportProperty(ol.renderer.TileLayerRenderer, 'canRender', ol.renderer.TileLayerRenderer.canRender); goog.exportProperty(ol.renderer.TileLayerRenderer, 'getType', ol.renderer.TileLayerRenderer.getType); -ol.control.addControl('navigation', ol.control.Navigation); diff --git a/src/ol.js b/src/ol.js index f85b363567..8c936f0329 100644 --- a/src/ol.js +++ b/src/ol.js @@ -3,11 +3,10 @@ goog.provide("ol"); goog.require('ol.base'); goog.require('ol.bounds'); goog.require('ol.control.Attribution'); -goog.require('ol.control.Navigation'); goog.require('ol.control.Zoom'); -goog.require('ol.event.Drag'); -goog.require('ol.event.Events'); -goog.require('ol.event.Scroll'); +goog.require('ol.handler.Drag'); +goog.require('ol.handler.MouseWheel'); +goog.require('ol.handler.Click'); goog.require("ol.map"); goog.require("ol.loc"); goog.require("ol.feature"); diff --git a/src/ol/Map.js b/src/ol/Map.js index b524710bc8..cadf228e04 100644 --- a/src/ol/Map.js +++ b/src/ol/Map.js @@ -3,25 +3,44 @@ goog.provide('ol.Map'); goog.require('ol.Loc'); goog.require('ol.Bounds'); goog.require('ol.Projection'); -goog.require('ol.event'); -goog.require('ol.event.Events'); goog.require('ol.control.Control'); goog.require('ol.renderer.MapRenderer'); +goog.require('ol.handler.Drag'); +goog.require('ol.handler.MouseWheel'); +goog.require('ol.handler.Click'); goog.require('goog.dom'); goog.require('goog.math'); goog.require('goog.asserts'); +goog.require('goog.events.EventTarget'); +/** + * @define {boolean} Whether to enable the drag handler. + */ +ol.ENABLE_DRAG_HANDLER = true; + +/** + * @define {boolean} Whether to enable the mousewheel handler. + */ +ol.ENABLE_MOUSEWHEEL_HANDLER = true; + +/** + * @define {boolean} Whether to enable the click handler. + */ +ol.ENABLE_CLICK_HANDLER = true; /** * @export * @constructor + * @extends {goog.events.EventTarget} * * @event layeradd Fires when a layer is added to the map. The event object * contains a 'layer' property referencing the added layer. */ ol.Map = function() { + goog.base(this); + /** * @private * @type {ol.Projection} @@ -106,21 +125,13 @@ ol.Map = function() { */ this.staticOverlay_ = null; - /** - * @private - * @type {ol.event.Events} - */ - this.events_ = new ol.event.Events( - this, undefined, false, ['drag', 'scroll'] - ); - /** * @private * @type {Element} */ this.container_ = null; - }; +goog.inherits(ol.Map, goog.events.EventTarget); /** @const @@ -146,7 +157,7 @@ ol.Map.DEFAULT_TILE_SIZE = 256; @const @type {Array.} */ -ol.Map.DEFAULT_CONTROLS = ["attribution", "navigation", "zoom"]; +ol.Map.DEFAULT_CONTROLS = ["attribution", "zoom"]; /** * @return {ol.Loc} Map center in map projection. @@ -426,7 +437,7 @@ ol.Map.prototype.addLayers = function(layers) { for (var i=0, ii=layers.length; i 1); -}; - -/** - * Call preventDefault on the provided event. - * - * @param {!Event} evt - */ -ol.event.preventDefault = function(evt) { - evt.preventDefault(); -}; - -/** - * Call stopPropagation on the provided event. - * - * @param {!Event} evt - */ -ol.event.stopPropagation = function(evt) { - evt.stopPropagation(); -}; - - -/** - * Construct an ol.event.Events instance. - * - * @constructor - * @extends {goog.events.EventTarget} - * @param {Object} object The object we are creating this instance for. - * @param {!Element=} opt_element An optional element that we want to - * listen to browser events on. - * @param {boolean=} opt_includeXY Should the 'xy' property automatically be - * created for browser pointer events? In general, this should be false. If - * it is true, then pointer events will automatically generate an 'xy' - * property on the event object that is passed, which represents the - * relative position of the pointer to the {@code element}. Default is - * false. - * @param {Array.=} opt_sequences Event sequences to register with - * this Events instance. - */ -ol.event.Events = function(object, opt_element, opt_includeXY, opt_sequences) { - - goog.base(this); - - /** - * @private - * @type {Object} - * The object that this instance is bound to. - */ - this.object_ = object; - - /** - * @private - * @type {Element} - * The element that this instance listens to mouse events on. - */ - this.element_ = null; - - /** - * @private - * @type {boolean} - */ - this.includeXY_ = goog.isDef(opt_includeXY) ? opt_includeXY : false; - - /** - * @private - * @type {Array.} - */ - this.sequenceProviders_ = goog.isDef(opt_sequences) ? opt_sequences : []; - - /** - * @private - * @type {Array.} - */ - this.sequences_ = []; - - /** - * @private - * @type {Object} - */ - this.listenerCount_ = {}; - - if (goog.isDef(opt_element)) { - this.setElement(opt_element); - } -}; -goog.inherits(ol.event.Events, goog.events.EventTarget); - -/** - * @return {Object} The object that this instance is bound to. - */ -ol.event.Events.prototype.getObject = function() { - return this.object_; -}; - -/** - * @param {boolean} includeXY - */ -ol.event.Events.prototype.setIncludeXY = function(includeXY) { - this.includeXY_ = includeXY; -}; - -/** - * @return {Element} The element that this instance currently - * listens to browser events on. - */ -ol.event.Events.prototype.getElement = function() { - return this.element_; -}; - -/** - * Attach this instance to a DOM element. When called, all browser events fired - * on the provided element will be relayed by this instance. - * - * @param {Element|Node} element A DOM element to attach - * browser events to. If called without this argument, all browser events - * will be detached from the element they are currently attached to. - */ -ol.event.Events.prototype.setElement = function(element) { - var types = goog.events.EventType, t; - if (this.element_) { - for (t in types) { - goog.events.unlisten( - this.element_, types[t], this.handleBrowserEvent, false, this - ); - } - this.destroySequences(); - delete this.element_; - } - this.element_ = /** @type {Element} */ (element) || null; - if (goog.isDefAndNotNull(element)) { - this.createSequences(); - for (t in types) { - goog.events.listen( - element, types[t], this.handleBrowserEvent, false, this - ); - } - } -}; - -ol.event.Events.prototype.createSequences = function() { - for (var i=0, ii=this.sequenceProviders_.length; i=0; --i) { - this.sequences_[i].destroy(); - } - this.sequences_ = []; -}; - -/** - * Register a listener for an event. - * - * When the event is triggered, the 'listener' function will be called, in the - * context of 'scope'. Imagine we were to register an event, specifying an - * ol.Bounds instance as 'scope'. When the event is triggered, the context in - * the listener function will be our Bounds instance. This means that within - * our listener function, we can access the properties and methods of the - * Bounds instance through the 'this' keyword. So our listener could execute - * something like: - * - * var leftStr = "Left: " + this.minX(); - * - * @param {string} type Name of the event to register. - * @param {Function} listener The callback function. - * @param {Object=} opt_scope The object to bind the context to for the - * listener. If no scope is specified, default is this intance's 'object' - * property. - * @param {boolean=} opt_priority Register the listener as priority listener, - * so it gets executed before other listeners? Default is false. - */ -ol.event.Events.prototype.register = function(type, listener, opt_scope, - opt_priority) { - goog.events.listen( - this, type, listener, opt_priority, opt_scope || this.object_ - ); - this.listenerCount_[type] = (this.listenerCount_[type] || 0) + 1; -}; - -/** - * Unregister a listener for an event - * - * @param {string} type Name of the event to unregister - * @param {Function} listener The callback function. - * @param {Object=} opt_scope The object to bind the context to for the - * listener. If no scope is specified, default is the event's default - * scope. - * @param {boolean=} opt_priority Listener was registered as priority listener, - * so it gets executed before other listeners. Default is false. - */ -ol.event.Events.prototype.unregister = function(type, listener, opt_scope, - opt_priority) { - var removed = goog.events.unlisten( - this, type, listener, opt_priority, opt_scope || this.object_ - ); - if (removed) { - this.listenerCount_[type] = (this.listenerCount_[type] || 1) - 1; - } -}; - -/** - * Trigger a specified registered event. - * - * @param {string} type The type of the event to trigger. - * @param {Object=} opt_evt The event object that will be passed to listeners. - * This object will always have a 'type' property with the event type and - * an 'object' property referencing this Events instance. - * - * @return {boolean} The last listener return. If a listener returns false, - * the chain of listeners will stop getting called. Returns undefined if - * called for an event type that has no listeners. - */ -ol.event.Events.prototype.triggerEvent = function(type, opt_evt) { - var returnValue; - if (this.listenerCount_[type] > 0) { - var listeners = goog.events.getListeners(this, type, true) - .concat(goog.events.getListeners(this, type, false)); - if (arguments.length === 1) { - opt_evt = {'type': type}; - } - opt_evt['object'] = this.object_; - for (var i=0, ii=listeners.length; i 0) { - // add clientX & clientY to all events - corresponds to average x, y - var touches = evt.touches; - if (touches && touches[0]) { - var x = 0; - var y = 0; - var num = touches.length; - var touch; - for (var i=0; i - @@ -88,6 +87,9 @@ + + + diff --git a/test/spec/ol/Events.test.js b/test/spec/ol/Events.test.js deleted file mode 100644 index 604e131708..0000000000 --- a/test/spec/ol/Events.test.js +++ /dev/null @@ -1,175 +0,0 @@ -describe("ol.event.Events", function() { - - var log = [], - logFn = function(e) {log.push({scope: this, evt: e});}; - - - it("constructs instances", function() { - var events, element = document.createElement("div"); - - events = new ol.event.Events("foo"); - expect(events.getObject()).toBe("foo"); - expect(events.getElement()).toBe(null); - events.destroy(); - - events = new ol.event.Events("foo", element, true); - expect(events.getElement()).toBe(element); - expect(events.includeXY_).toBe(true); - events.destroy(); - }); - - it("destroys properly", function() { - var events = new ol.event.Events("foo"); - events.destroy(); - expect(events.getObject()).toBe(undefined); - }); - - it("relays browser events and knows about pointer position", function() { - var element = document.createElement("div"), - events = new ol.event.Events("foo", element); - - //TODO Figure out a good way to deal with fixtures - element.style.position = "absolute"; - element.style.left = "5px"; - element.style.top = "10px"; - document.body.appendChild(element); - // mock dom element so we can trigger events on it - goog.object.extend(element, new goog.events.EventTarget()); - - log = []; - events.register("click", logFn); - element.dispatchEvent("click"); - expect(log.length).toBe(1); - - // detach from the element - events.setElement(null); - element.dispatchEvent("click"); - expect(log.length).toBe(1); - - // attach to the element again - events.setElement(element); - events.setIncludeXY(true); - element.dispatchEvent({ - type: "click", - touches: [{clientX: 9, clientY: 22}, {clientX: 11, clientY: 18}] - }); - expect(log.length).toBe(2); - expect(log[1].evt.xy.x).toBe(5); - expect(log[1].evt.xy.y).toBe(10); - expect(log[1].evt.clientX).toBe(10); - expect(log[1].evt.clientY).toBe(20); - - events.destroy(); - document.body.removeChild(element); - }); - - it("calls listeners with a scope and an event object", function() { - var scope = {}, evt = {}, events = new ol.event.Events("foo"); - - log = []; - events.register("bar", logFn, scope); - events.triggerEvent("bar", evt); - expect(log[0].scope).toBe(scope); - expect(log[0].evt).toBe(evt); - expect(log[0].evt.object).toBe("foo"); - }); - - it("respects event priority", function() { - var log = [], events = new ol.event.Events("foo"); - - // register a normal listener - events.register("bar", function() {log.push("normal");}); - // register a priority listener - events.register( - "bar", function() {log.push("priority");}, undefined, true); - events.triggerEvent("bar"); - expect(log[0]).toBe("priority"); - expect(log[1]).toBe("normal"); - - events.destroy(); - }); - - it("allows to abort the event chain", function() { - var events = new ol.event.Events("foo"); - - log = []; - // register a listener that aborts the event chain - events.register("bar", function(e) {logFn(e); return false;}); - // register a listener that just does something - events.register("bar", logFn); - events.triggerEvent("bar"); - expect(log.length).toBe(1); - - log = []; - // register a priority listener that just does something - events.register("bar", logFn, undefined, true); - events.triggerEvent("bar"); - expect(log.length).toBe(2); - - events.destroy(); - }); - - it("allows to unregister events", function() { - var events = new ol.event.Events("foo"); - - log = []; - events.register("bar", logFn); - expect(events.listenerCount_["bar"]).toBe(1); - - events.triggerEvent("bar"); - expect(log.length).toBe(1); - - events.unregister("bar", logFn); - events.triggerEvent("bar"); - expect(log.length).toBe(1); - - events.unregister("bar", logFn); - expect(events.listenerCount_["bar"]).toBe(0); - - events.destroy(); - }); - - it("can be extended with sequences implementing ol.event.ISequence", function() { - var sequence; - var Sequence = function(target) { - sequence = this; - this.target = target; - }; - Sequence.prototype.fire = function() { - this.target.triggerEvent("myevent"); - }; - Sequence.prototype.destroy = function() { - this.destroyed = true; - }; - ol.event.addSequenceProvider("myseq", Sequence); - - var element = document.createElement("div"); - events = new ol.event.Events("foo", undefined, false, ["myseq"]); - - expect(sequence).toBeUndefined(); - - log = []; - events.setElement(element); - events.register('myevent', logFn); - sequence.fire(); - - expect(log.length).toBe(1); - expect(log[0].evt.type).toBe("myevent"); - - events.destroy(); - expect(sequence.destroyed).toBe(true); - }); - - it("provides an isSingleTouch() function", function() { - expect(ol.event.isSingleTouch({touches: [{}, {}]})).toBe(false); - expect(ol.event.isSingleTouch({touches: [{}]})).toBe(true); - expect(ol.event.isSingleTouch({})).toBe(false); - }); - - it("provides an isMultiTouch() function", function() { - expect(ol.event.isMultiTouch({touches: [{}, {}]})).toBe(true); - expect(ol.event.isMultiTouch({touches: [{}]})).toBe(false); - expect(ol.event.isMultiTouch({})).toBe(false); - }); - -}); diff --git a/test/spec/ol/Tile.test.js b/test/spec/ol/Tile.test.js index 554560f617..527d0ae6cb 100644 --- a/test/spec/ol/Tile.test.js +++ b/test/spec/ol/Tile.test.js @@ -34,7 +34,7 @@ describe("ol.Tile", function() { }); it("fires a load event", function() { var spy = jasmine.createSpy(); - tile.events_.register('load', spy); + goog.events.listen(tile, 'load', spy); tile.handleImageLoad(); expect(spy).toHaveBeenCalled(); }); @@ -57,7 +57,7 @@ describe("ol.Tile", function() { }); it("fires a load event", function() { var spy = jasmine.createSpy(); - tile.events_.register('error', spy); + goog.events.listen(tile, 'error', spy); tile.handleImageError(); expect(spy).toHaveBeenCalled(); }); diff --git a/test/spec/ol/handler/Click.test.js b/test/spec/ol/handler/Click.test.js new file mode 100644 index 0000000000..c559f1f777 --- /dev/null +++ b/test/spec/ol/handler/Click.test.js @@ -0,0 +1,47 @@ +describe('ol.handler.Click', function() { + var map, elt, listener; + + beforeEach(function() { + map = new ol.Map(); + elt = new goog.events.EventTarget(); + map.viewport_ = elt; + listener = {fn: function() {}}; + spyOn(listener, 'fn'); + }); + + describe('creating a drag handler', function() { + + it('returns an ol.handler.Click instance', function() { + var handler = new ol.handler.Click(map, {}); + expect(handler).toBeA(ol.handler.Click); + }); + + }); + + describe('dispatching events', function() { + + it('dispatches a click event which is an ol.events.MapEvent', function() { + new ol.handler.Click(map, {}); + goog.events.listen(map, 'click', listener.fn); + + goog.events.fireListeners(elt, 'click', false, 'foo'); + var evt = listener.fn.calls[0].args[0]; + expect(evt).toBeA(ol.events.MapEvent); + expect(evt.originalEvent).toBe('foo'); + }); + + it('ignores click events when the dragged state is set', function() { + var states = {}; + new ol.handler.Click(map, states); + goog.events.listen(map, 'click', listener.fn); + + goog.events.fireListeners(elt, 'click', false); + expect(listener.fn.calls.length).toBe(1); + + states.dragged = true; + goog.events.fireListeners(elt, 'click', false); + expect(listener.fn.calls.length).toBe(1); + }); + + }); +}); diff --git a/test/spec/ol/handler/Drag.test.js b/test/spec/ol/handler/Drag.test.js new file mode 100644 index 0000000000..6a0fbcddc7 --- /dev/null +++ b/test/spec/ol/handler/Drag.test.js @@ -0,0 +1,77 @@ +describe('ol.handler.Drag', function() { + var map; + + beforeEach(function() { + map = new ol.Map(); + var elt = new goog.events.EventTarget(); + map.viewport_ = elt; + }); + + describe('creating a drag handler', function() { + + it('returns an ol.handler.Drag instance', function() { + var handler = new ol.handler.Drag(map, {}); + expect(handler).toBeA(ol.handler.Drag); + }); + + }); + + describe('dispatching events', function() { + var handler, states; + + beforeEach(function() { + states = {}; + handler = new ol.handler.Drag(map, states); + }); + + it('dragstart, drag and dragend events', function() { + var spy = spyOn(goog.events.Event, 'preventDefault').andCallThrough(); + goog.events.listen(map, ol.events.MapEventType.DRAGSTART, spy); + goog.events.listen(map, ol.events.MapEventType.DRAG, spy); + goog.events.listen(map, ol.events.MapEventType.DRAGEND, spy); + + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.START}); + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.DRAG}); + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.END}); + + expect(spy.callCount).toEqual(3); + expect(spy.argsForCall[0][0].type).toEqual(ol.events.MapEventType.DRAGSTART); + expect(spy.argsForCall[1][0].type).toEqual(ol.events.MapEventType.DRAG); + expect(spy.argsForCall[2][0].type).toEqual(ol.events.MapEventType.DRAGEND); + }); + + it('sets the dragged state during a drag sequence', function() { + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.DRAG}); + expect(states.dragged).toBeTruthy(); + + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.START}); + expect(states.dragged).toBeFalsy(); + }); + + it('sets deltaX and deltaY on the ol.event.MapEvent', function() { + var spy = spyOn(goog.events.Event, 'preventDefault').andCallThrough(); + goog.events.listen(map, ol.events.MapEventType.DRAG, spy); + + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.START, + clientX: 2, clientY: 4}); + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.DRAG, + clientX: 1, clientY: 2}); + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.DRAG, + clientX: 2, clientY: 4}); + + expect(spy.callCount).toEqual(2); + expect(spy.argsForCall[0][0].deltaX).toEqual(-1); + expect(spy.argsForCall[0][0].deltaY).toEqual(-2); + expect(spy.argsForCall[1][0].deltaX).toEqual(1); + expect(spy.argsForCall[1][0].deltaY).toEqual(2); + }); + + it('calls the default action', function() { + var handler = new ol.handler.Drag(map, {}); + var spy spyOn(handler, 'defaultDrag'); + + handler.dragger_.dispatchEvent({type: goog.fx.Dragger.EventType.DRAG}); + expect(spy).toHaveBeenCalled(); + }); + }); +}); diff --git a/test/spec/ol/handler/MouseWheel.test.js b/test/spec/ol/handler/MouseWheel.test.js new file mode 100644 index 0000000000..ccd9e2aa8d --- /dev/null +++ b/test/spec/ol/handler/MouseWheel.test.js @@ -0,0 +1,49 @@ +describe('ol.handler.MouseWheel', function() { + var map; + + beforeEach(function() { + map = new ol.Map(); + var elt = new goog.events.EventTarget(); + map.viewport_ = elt; + }); + + describe('create a mouse wheel handler', function() { + + it('returns an ol.handler.MouseWheel instance', function() { + var handler = new ol.handler.MouseWheel(map, {}); + expect(handler).toBeA(ol.handler.MouseWheel); + }); + + }); + + describe('dispatching events', function() { + + var handler; + + beforeEach(function() { + handler = new ol.handler.MouseWheel(map, {}); + }); + + it('dispatches a mousewheel event', function() { + var spy = spyOn(goog.events.Event, 'preventDefault').andCallThrough(); + goog.events.listen(map, ol.events.MapEventType.MOUSEWHEEL, spy); + + var evt = new goog.events.MouseWheelEvent(1, 'foo', 0, 1); + handler.handler_.dispatchEvent(evt); + + expect(spy).toHaveBeenCalled(); + expect(spy.argsForCall[0][0].type).toEqual(ol.events.MapEventType.MOUSEWHEEL); + }); + + it('calls the default action', function() { + var handler = new ol.handler.MouseWheel(map, {}); + spyOn(handler, 'defaultMouseWheel'); + + var evt = new goog.events.MouseWheelEvent(1, 'foo', 0, 1); + handler.handler_.dispatchEvent(evt); + + expect(handler.defaultMouseWheel).toHaveBeenCalled(); + }); + + }); +});