From fe0620dc7f0d54f707e22864e70d013663f67ef0 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Mon, 27 May 2013 17:17:42 -0700 Subject: [PATCH] Merge pull request #174 from ahocevar/feature-events Turning @tschaub's FeatureAgent into an event extension. r=@bartvde --- examples/feature-events.html | 46 ++++ examples/feature-events.js | 67 ++++++ lib/OpenLayers.js | 1 + lib/OpenLayers/Events/featureclick.js | 321 ++++++++++++++++++++++++++ notes/2.13.md | 5 + tests/Events/featureclick.html | 91 ++++++++ tests/list-tests.html | 1 + 7 files changed, 532 insertions(+) create mode 100644 examples/feature-events.html create mode 100644 examples/feature-events.js create mode 100644 lib/OpenLayers/Events/featureclick.js create mode 100644 tests/Events/featureclick.html diff --git a/examples/feature-events.html b/examples/feature-events.html new file mode 100644 index 0000000000..923e554cae --- /dev/null +++ b/examples/feature-events.html @@ -0,0 +1,46 @@ + + + + + + + OpenLayers Feature Events Example + + + + + +

Feature Events Example

+ +
+ feature, select, hover +
+ +
Feature hover and click events
+ +
+
+

Hover over or click features on the map.

+ +

This example shows how to use the 'featureclick', 'nofeatureclick', + 'featureover' and 'featureout' events to make features interactive. + Look at the feature-events.js source + code to see how this is done.

+ +

Note that these events can be registered both on the map and on + individual layers. If many layers need to be observed, it is + recommended to register listeners once on the map for performance + reasons.

+
+ + + + diff --git a/examples/feature-events.js b/examples/feature-events.js new file mode 100644 index 0000000000..8a6fe28ebb --- /dev/null +++ b/examples/feature-events.js @@ -0,0 +1,67 @@ +var layerListeners = { + featureclick: function(e) { + log(e.object.name + " says: " + e.feature.id + " clicked."); + return false; + }, + nofeatureclick: function(e) { + log(e.object.name + " says: No feature clicked."); + } +}; + +var style = new OpenLayers.StyleMap({ + 'default': OpenLayers.Util.applyDefaults( + {label: "${l}", pointRadius: 10}, + OpenLayers.Feature.Vector.style["default"] + ), + 'select': OpenLayers.Util.applyDefaults( + {pointRadius: 10}, + OpenLayers.Feature.Vector.style.select + ) +}); +var layer1 = new OpenLayers.Layer.Vector("Layer 1", { + styleMap: style, + eventListeners: layerListeners +}); +layer1.addFeatures([ + new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(-1 -1)"), {l:1}), + new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(1 1)"), {l:1}) +]); +var layer2 = new OpenLayers.Layer.Vector("Layer 2", { + styleMap: style, + eventListeners: layerListeners +}); +layer2.addFeatures([ + new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(-1 1)"), {l:2}), + new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(1 -1)"), {l:2}) +]); + +var map = new OpenLayers.Map({ + div: "map", + allOverlays: true, + layers: [layer1, layer2], + zoom: 6, + center: [0, 0], + eventListeners: { + featureover: function(e) { + e.feature.renderIntent = "select"; + e.feature.layer.drawFeature(e.feature); + log("Map says: Pointer entered " + e.feature.id + " on " + e.feature.layer.name); + }, + featureout: function(e) { + e.feature.renderIntent = "default"; + e.feature.layer.drawFeature(e.feature); + log("Map says: Pointer left " + e.feature.id + " on " + e.feature.layer.name); + }, + featureclick: function(e) { + log("Map says: " + e.feature.id + " clicked on " + e.feature.layer.name); + } + } +}); + +function log(msg) { + if (!log.timer) { + result.innerHTML = ""; + log.timer = window.setTimeout(function() {delete log.timer;}, 100); + } + result.innerHTML += msg + "
"; +} diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 8db07a8d13..58362c8ed3 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -146,6 +146,7 @@ "OpenLayers/Kinetic.js", "OpenLayers/Events.js", "OpenLayers/Events/buttonclick.js", + "OpenLayers/Events/featureclick.js", "OpenLayers/Request.js", "OpenLayers/Request/XMLHttpRequest.js", "OpenLayers/Projection.js", diff --git a/lib/OpenLayers/Events/featureclick.js b/lib/OpenLayers/Events/featureclick.js new file mode 100644 index 0000000000..9ae6ec53fe --- /dev/null +++ b/lib/OpenLayers/Events/featureclick.js @@ -0,0 +1,321 @@ +/* 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/Events.js + */ + +/** + * Class: OpenLayers.Events.featureclick + * + * Extension event type for handling feature click events, including overlapping + * features. + * + * Event types provided by this extension: + * - featureclick + */ +OpenLayers.Events.featureclick = OpenLayers.Class({ + + /** + * Property: cache + * {Object} A cache of features under the mouse. + */ + cache: null, + + /** + * Property: map + * {} The map to register browser events on. + */ + map: null, + + /** + * Property: provides + * {Array(String)} The event types provided by this extension. + */ + provides: ["featureclick", "nofeatureclick", "featureover", "featureout"], + + /** + * Constructor: OpenLayers.Events.featureclick + * Create a new featureclick event type. + * + * Parameters: + * target - {} The events instance to create the events + * for. + */ + initialize: function(target) { + this.target = target; + if (target.object instanceof OpenLayers.Map) { + this.setMap(target.object); + } else if (target.object instanceof OpenLayers.Layer.Vector) { + if (target.object.map) { + this.setMap(target.object.map); + } else { + target.object.events.register("added", this, function(evt) { + this.setMap(target.object.map); + }); + } + } else { + throw("Listeners for '" + this.provides.join("', '") + + "' events can only be registered for OpenLayers.Layer.Vector " + + "or OpenLayers.Map instances"); + } + for (var i=this.provides.length-1; i>=0; --i) { + target.extensions[this.provides[i]] = true; + } + }, + + /** + * Method: setMap + * + * Parameters: + * map - {} The map to register browser events on. + */ + setMap: function(map) { + this.map = map; + this.cache = {}; + map.events.register("mousedown", this, this.start, {extension: true}); + map.events.register("mouseup", this, this.onClick, {extension: true}); + map.events.register("touchstart", this, this.start, {extension: true}); + map.events.register("touchmove", this, this.cancel, {extension: true}); + map.events.register("touchend", this, this.onClick, {extension: true}); + map.events.register("mousemove", this, this.onMousemove, {extension: true}); + }, + + /** + * Method: start + * Sets startEvt = evt. + * + * Parameters: + * evt - {} + */ + start: function(evt) { + this.startEvt = evt; + }, + + /** + * Method: cancel + * Deletes the start event. + * + * Parameters: + * evt - {} + */ + cancel: function(evt) { + delete this.startEvt; + }, + + /** + * Method: onClick + * Listener for the click event. + * + * Parameters: + * evt - {} + */ + onClick: function(evt) { + if (!this.startEvt || evt.type !== "touchend" && + !OpenLayers.Event.isLeftClick(evt)) { + return; + } + var features = this.getFeatures(this.startEvt); + delete this.startEvt; + // fire featureclick events + var feature, layer, more, clicked = {}; + for (var i=0, len=features.length; i} + */ + onMousemove: function(evt) { + delete this.startEvt; + var features = this.getFeatures(evt); + var over = {}, newly = [], feature; + for (var i=0, len=features.length; i)} List of features at the given point. + */ + getFeatures: function(evt) { + var x = evt.clientX, y = evt.clientY, + features = [], targets = [], layers = [], + layer, target, feature, i, len; + // go through all layers looking for targets + for (i=this.map.layers.length-1; i>=0; --i) { + layer = this.map.layers[i]; + if (layer.div.style.display !== "none") { + if (layer.renderer instanceof OpenLayers.Renderer.Elements) { + if (layer instanceof OpenLayers.Layer.Vector) { + target = document.elementFromPoint(x, y); + while (target && target._featureId) { + feature = layer.getFeatureById(target._featureId); + if (feature) { + features.push(feature); + target.style.display = "none"; + targets.push(target); + target = document.elementFromPoint(x, y); + } else { + // sketch, all bets off + target = false; + } + } + } + layers.push(layer); + layer.div.style.display = "none"; + } else if (layer.renderer instanceof OpenLayers.Renderer.Canvas) { + feature = layer.renderer.getFeatureIdFromEvent(evt); + if (feature) { + features.push(feature); + layers.push(layer); + } + } + } + } + // restore feature visibility + for (i=0, len=targets.length; i=0; --i) { + layers[i].div.style.display = "block"; + } + return features; + }, + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { + for (var i=this.provides.length-1; i>=0; --i) { + delete this.target.extensions[this.provides[i]]; + } + this.map.events.un({ + mousemove: this.onMousemove, + mousedown: this.start, + mouseup: this.onClick, + touchstart: this.start, + touchmove: this.cancel, + touchend: this.onClick, + scope: this + }); + delete this.cache; + delete this.map; + delete this.target; + } + +}); + +/** + * Class: OpenLayers.Events.nofeatureclick + * + * Extension event type for handling click events that do not hit a feature. + * + * Event types provided by this extension: + * - nofeatureclick + */ +OpenLayers.Events.nofeatureclick = OpenLayers.Events.featureclick; + +/** + * Class: OpenLayers.Events.featureover + * + * Extension event type for handling hovering over a feature. + * + * Event types provided by this extension: + * - featureover + */ +OpenLayers.Events.featureover = OpenLayers.Events.featureclick; + +/** + * Class: OpenLayers.Events.featureout + * + * Extension event type for handling leaving a feature. + * + * Event types provided by this extension: + * - featureout + */ +OpenLayers.Events.featureout = OpenLayers.Events.featureclick; diff --git a/notes/2.13.md b/notes/2.13.md index 9425928810..2dd9ecc2e3 100644 --- a/notes/2.13.md +++ b/notes/2.13.md @@ -35,6 +35,11 @@ Corresponding issues/pull requests: * http://github.com/openlayers/openlayers/pull/700 +## New Map and Vector Layer Events for Feature Interaction + +The featureclick events extension (`lib/Events/featureclick.js`) provides four new events ("featureclick", "nofeatureclick", "featureover", "featureout") that can be used as an alternative to the Feature handler or the +SelectFeature control. It works with multiple layers out of the box and can detect hits on multiple features (except when using the Canvas renderer). See `examples/feature-events.html` for an implementation example. + # Behavior Changes from Past Releases ## Control.DragPan: Kinetic by default diff --git a/tests/Events/featureclick.html b/tests/Events/featureclick.html new file mode 100644 index 0000000000..ae111b7960 --- /dev/null +++ b/tests/Events/featureclick.html @@ -0,0 +1,91 @@ + + + + + + +
+ + diff --git a/tests/list-tests.html b/tests/list-tests.html index f79076c165..f59c477b40 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -52,6 +52,7 @@
  • Control/ZoomBox.html
  • Events.html
  • Events/buttonclick.html
  • +
  • Events/featureclick.html?visible
  • Extras.html
  • Feature.html
  • Feature/Vector.html