/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/Handler.js */ /** * Class: OpenLayers.Handler.Feature * Handler to respond to mouse events related to a drawn feature. Callbacks * with the following keys will be notified of the following events * associated with features: click, clickout, over, out, and dblclick. * * This handler stops event propagation for mousedown and mouseup if those * browser events target features that can be selected. * * Inherits from: * - */ OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, { /** * Property: EVENTMAP * {Object} A object mapping the browser events to objects with callback * keys for in and out. */ EVENTMAP: { 'click': {'in': 'click', 'out': 'clickout'}, 'mousemove': {'in': 'over', 'out': 'out'}, 'dblclick': {'in': 'dblclick', 'out': null}, 'mousedown': {'in': null, 'out': null}, 'mouseup': {'in': null, 'out': null}, 'touchstart': {'in': 'click', 'out': 'clickout'} }, /** * Property: feature * {} The last feature that was hovered. */ feature: null, /** * Property: lastFeature * {} The last feature that was handled. */ lastFeature: null, /** * Property: down * {} The location of the last mousedown. */ down: null, /** * Property: up * {} The location of the last mouseup. */ up: null, /** * Property: clickTolerance * {Number} The number of pixels the mouse can move between mousedown * and mouseup for the event to still be considered a click. * Dragging the map should not trigger the click and clickout callbacks * unless the map is moved by less than this tolerance. Defaults to 4. */ clickTolerance: 4, /** * Property: geometryTypes * To restrict dragging to a limited set of geometry types, send a list * of strings corresponding to the geometry class names. * * @type Array(String) */ geometryTypes: null, /** * Property: stopClick * {Boolean} If stopClick is set to true, handled clicks do not * propagate to other click listeners. Otherwise, handled clicks * do propagate. Unhandled clicks always propagate, whatever the * value of stopClick. Defaults to true. */ stopClick: true, /** * Property: stopDown * {Boolean} If stopDown is set to true, handled mousedowns do not * propagate to other mousedown listeners. Otherwise, handled * mousedowns do propagate. Unhandled mousedowns always propagate, * whatever the value of stopDown. Defaults to true. */ stopDown: true, /** * Property: stopUp * {Boolean} If stopUp is set to true, handled mouseups do not * propagate to other mouseup listeners. Otherwise, handled mouseups * do propagate. Unhandled mouseups always propagate, whatever the * value of stopUp. Defaults to false. */ stopUp: false, /** * Constructor: OpenLayers.Handler.Feature * * Parameters: * control - {} * layer - {} * callbacks - {Object} An object with a 'over' property whos value is * a function to be called when the mouse is over a feature. The * callback should expect to recieve a single argument, the feature. * options - {Object} */ initialize: function(control, layer, callbacks, options) { OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]); this.layer = layer; }, /** * Method: touchstart * Handle touchstart events * * Parameters: * evt - {Event} * * Returns: * {Boolean} Let the event propagate. */ touchstart: function(evt) { this.startTouch(); return OpenLayers.Event.isMultiTouch(evt) ? true : this.mousedown(evt); }, /** * Method: touchmove * Handle touchmove events. We just prevent the browser default behavior, * for Android Webkit not to select text when moving the finger after * selecting a feature. * * Parameters: * evt - {Event} */ touchmove: function(evt) { OpenLayers.Event.preventDefault(evt); }, /** * Method: mousedown * Handle mouse down. Stop propagation if a feature is targeted by this * event (stops map dragging during feature selection). * * Parameters: * evt - {Event} */ mousedown: function(evt) { // Feature selection is only done with a left click. Other handlers may stop the // propagation of left-click mousedown events but not right-click mousedown events. // This mismatch causes problems when comparing the location of the down and up // events in the click function so it is important ignore right-clicks. if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) { this.down = evt.xy; } return this.handle(evt) ? !this.stopDown : true; }, /** * Method: mouseup * Handle mouse up. Stop propagation if a feature is targeted by this * event. * * Parameters: * evt - {Event} */ mouseup: function(evt) { this.up = evt.xy; return this.handle(evt) ? !this.stopUp : true; }, /** * Method: click * Handle click. Call the "click" callback if click on a feature, * or the "clickout" callback if click outside any feature. * * Parameters: * evt - {Event} * * Returns: * {Boolean} */ click: function(evt) { return this.handle(evt) ? !this.stopClick : true; }, /** * Method: mousemove * Handle mouse moves. Call the "over" callback if moving in to a feature, * or the "out" callback if moving out of a feature. * * Parameters: * evt - {Event} * * Returns: * {Boolean} */ mousemove: function(evt) { if (!this.callbacks['over'] && !this.callbacks['out']) { return true; } this.handle(evt); return true; }, /** * Method: dblclick * Handle dblclick. Call the "dblclick" callback if dblclick on a feature. * * Parameters: * evt - {Event} * * Returns: * {Boolean} */ dblclick: function(evt) { return !this.handle(evt); }, /** * Method: geometryTypeMatches * Return true if the geometry type of the passed feature matches * one of the geometry types in the geometryTypes array. * * Parameters: * feature - {} * * Returns: * {Boolean} */ geometryTypeMatches: function(feature) { return this.geometryTypes == null || OpenLayers.Util.indexOf(this.geometryTypes, feature.geometry.CLASS_NAME) > -1; }, /** * Method: handle * * Parameters: * evt - {Event} * * Returns: * {Boolean} The event occurred over a relevant feature. */ handle: function(evt) { if(this.feature && !this.feature.layer) { // feature has been destroyed this.feature = null; } var type = evt.type; var handled = false; var previouslyIn = !!(this.feature); // previously in a feature var click = (type == "click" || type == "dblclick" || type == "touchstart"); this.feature = this.layer.getFeatureFromEvent(evt); if(this.feature && !this.feature.layer) { // feature has been destroyed this.feature = null; } if(this.lastFeature && !this.lastFeature.layer) { // last feature has been destroyed this.lastFeature = null; } if(this.feature) { if(type === "touchstart") { // stop the event to prevent Android Webkit from // "flashing" the map div OpenLayers.Event.preventDefault(evt); } var inNew = (this.feature != this.lastFeature); if(this.geometryTypeMatches(this.feature)) { // in to a feature if(previouslyIn && inNew) { // out of last feature and in to another if(this.lastFeature) { this.triggerCallback(type, 'out', [this.lastFeature]); } this.triggerCallback(type, 'in', [this.feature]); } else if(!previouslyIn || click) { // in feature for the first time this.triggerCallback(type, 'in', [this.feature]); } this.lastFeature = this.feature; handled = true; } else { // not in to a feature if(this.lastFeature && (previouslyIn && inNew || click)) { // out of last feature for the first time this.triggerCallback(type, 'out', [this.lastFeature]); } // next time the mouse goes in a feature whose geometry type // doesn't match we don't want to call the 'out' callback // again, so let's set this.feature to null so that // previouslyIn will evaluate to false the next time // we enter handle. Yes, a bit hackish... this.feature = null; } } else if(this.lastFeature && (previouslyIn || click)) { this.triggerCallback(type, 'out', [this.lastFeature]); } return handled; }, /** * Method: triggerCallback * Call the callback keyed in the event map with the supplied arguments. * For click and clickout, the is checked first. * * Parameters: * type - {String} */ triggerCallback: function(type, mode, args) { var key = this.EVENTMAP[type][mode]; if(key) { if(type == 'click' && this.up && this.down) { // for click/clickout, only trigger callback if tolerance is met var dpx = Math.sqrt( Math.pow(this.up.x - this.down.x, 2) + Math.pow(this.up.y - this.down.y, 2) ); if(dpx <= this.clickTolerance) { this.callback(key, args); } // we're done with this set of events now: clear the cached // positions so we can't trip over them later (this can occur // if one of the up/down events gets eaten before it gets to us // but we still get the click) this.up = this.down = null; } else { this.callback(key, args); } } }, /** * Method: activate * Turn on the handler. Returns false if the handler was already active. * * Returns: * {Boolean} */ activate: function() { var activated = false; if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) { this.moveLayerToTop(); this.map.events.on({ "removelayer": this.handleMapEvents, "changelayer": this.handleMapEvents, scope: this }); activated = true; } return activated; }, /** * Method: deactivate * Turn off the handler. Returns false if the handler was already active. * * Returns: * {Boolean} */ deactivate: function() { var deactivated = false; if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) { this.moveLayerBack(); this.feature = null; this.lastFeature = null; this.down = null; this.up = null; this.map.events.un({ "removelayer": this.handleMapEvents, "changelayer": this.handleMapEvents, scope: this }); deactivated = true; } return deactivated; }, /** * Method: handleMapEvents * * Parameters: * evt - {Object} */ handleMapEvents: function(evt) { if (evt.type == "removelayer" || evt.property == "order") { this.moveLayerToTop(); } }, /** * Method: moveLayerToTop * Moves the layer for this handler to the top, so mouse events can reach * it. */ moveLayerToTop: function() { var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1, this.layer.getZIndex()) + 1; this.layer.setZIndex(index); }, /** * Method: moveLayerBack * Moves the layer back to the position determined by the map's layers * array. */ moveLayerBack: function() { var index = this.layer.getZIndex() - 1; if (index >= this.map.Z_INDEX_BASE['Feature']) { this.layer.setZIndex(index); } else { this.map.setLayerZIndex(this.layer, this.map.getLayerIndex(this.layer)); } }, CLASS_NAME: "OpenLayers.Handler.Feature" });