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 @@