From 2b1e8641f949a3bd651e5da70c6bd912e5ce9367 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 22 Mar 2009 14:25:18 +0000 Subject: [PATCH] SelectFeature control can now select across multiple vector layers when passed an array of layers instead of a single layer with the constructor. This changeset also introduces a new layer type, Layer.Vector.RootContainer, which will be set as the topmost layer by the SelectFeature control and collect the svg/vml/canvas roots of multiple vector layers. r=crschmidt (closes #1666) git-svn-id: http://svn.openlayers.org/trunk/openlayers@9116 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/select-feature-multilayer.html | 116 ++++++++++++++ lib/OpenLayers.js | 1 + lib/OpenLayers/Control/SelectFeature.js | 97 ++++++++---- lib/OpenLayers/Layer/Vector.js | 17 ++ lib/OpenLayers/Layer/Vector/RootContainer.js | 156 +++++++++++++++++++ lib/OpenLayers/Layer/WFS.js | 14 ++ lib/OpenLayers/Renderer.js | 32 ++++ lib/OpenLayers/Renderer/Canvas.js | 6 - lib/OpenLayers/Renderer/Elements.js | 36 ++++- lib/OpenLayers/Renderer/VML.js | 20 +++ tests/Control/SelectFeature.html | 45 ++++-- tests/Layer/Vector/RootContainer.html | 55 +++++++ tests/Renderer/Elements.html | 15 +- tests/list-tests.html | 1 + 14 files changed, 552 insertions(+), 59 deletions(-) create mode 100644 examples/select-feature-multilayer.html create mode 100644 lib/OpenLayers/Layer/Vector/RootContainer.js create mode 100644 tests/Layer/Vector/RootContainer.html diff --git a/examples/select-feature-multilayer.html b/examples/select-feature-multilayer.html new file mode 100644 index 0000000000..90c9d635af --- /dev/null +++ b/examples/select-feature-multilayer.html @@ -0,0 +1,116 @@ + + + SelectFeature Control on multiple vector layers + + + + + + + +

OpenLayers Select Feature on Multiple Layers Example

+

+ Select a feature on click with the Control.SelectFeature on multiple + vector layers. +

+
+
+ + diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 29f964cf22..20395f8cdb 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -181,6 +181,7 @@ "OpenLayers/Renderer/Canvas.js", "OpenLayers/Renderer/VML.js", "OpenLayers/Layer/Vector.js", + "OpenLayers/Layer/Vector/RootContainer.js", "OpenLayers/Strategy.js", "OpenLayers/Strategy/Fixed.js", "OpenLayers/Strategy/Cluster.js", diff --git a/lib/OpenLayers/Control/SelectFeature.js b/lib/OpenLayers/Control/SelectFeature.js index a982a2f30a..239da12635 100644 --- a/lib/OpenLayers/Control/SelectFeature.js +++ b/lib/OpenLayers/Control/SelectFeature.js @@ -7,6 +7,7 @@ * @requires OpenLayers/Control.js * @requires OpenLayers/Feature/Vector.js * @requires OpenLayers/Handler/Feature.js + * @requires OpenLayers/Layer/Vector/RootContainer.js */ /** @@ -103,10 +104,17 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { /** * Property: layer - * {} + * {} The vector layer with a common renderer + * root for all layers this control is configured with. */ layer: null, + /** + * Property: layers + * {Array(} The layers this control will work on. + */ + layers: null, + /** * APIProperty: callbacks * {Object} The functions that are sent to the handlers.feature for callback @@ -137,12 +145,20 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { * Constructor: * * Parameters: - * layer - {} + * layers - {}, or an array of vector layers * options - {Object} */ - initialize: function(layer, options) { + initialize: function(layers, options) { OpenLayers.Control.prototype.initialize.apply(this, [options]); - this.layer = layer; + if(!(layers instanceof Array)) { + layers = [layers]; + } + this.layers = layers; + this.layer = new OpenLayers.Layer.Vector.RootContainer( + this.id + "_container", { + layers: layers + } + ); var callbacks = { click: this.clickFeature, clickout: this.clickoutFeature @@ -155,7 +171,8 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks); this.handlers = { feature: new OpenLayers.Handler.Feature( - this, layer, this.callbacks, {geometryTypes: this.geometryTypes} + this, this.layer, this.callbacks, + {geometryTypes: this.geometryTypes} ) }; @@ -166,6 +183,14 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { ); } }, + + /** + * Method: destroy + */ + destroy: function() { + OpenLayers.Control.prototype.destroy.apply(this, arguments); + this.layer.destroy(); + }, /** * Method: activate @@ -176,6 +201,7 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { */ activate: function () { if (!this.active) { + this.map.addLayer(this.layer); this.handlers.feature.activate(); if(this.box && this.handlers.box) { this.handlers.box.activate(); @@ -199,6 +225,9 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { if(this.handlers.box) { this.handlers.box.deactivate(); } + this.map.events.unregister("changelayer", this.layer, + this.layer.handleChangeLayer); + this.map.removeLayer(this.layer); } return OpenLayers.Control.prototype.deactivate.apply( this, arguments @@ -215,11 +244,14 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { */ unselectAll: function(options) { // we'll want an option to supress notification here - var feature; - for(var i=this.layer.selectedFeatures.length-1; i>=0; --i) { - feature = this.layer.selectedFeatures[i]; - if(!options || options.except != feature) { - this.unselect(feature); + var layer, feature; + for(var l=0; l=0; --i) { + feature = layer.selectedFeatures[i]; + if(!options || options.except != feature) { + this.unselect(feature); + } } } }, @@ -234,8 +266,8 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { */ clickFeature: function(feature) { if(!this.hover) { - var selected = (OpenLayers.Util.indexOf(this.layer.selectedFeatures, - feature) > -1); + var selected = (OpenLayers.Util.indexOf( + feature.layer.selectedFeatures, feature) > -1); if(selected) { if(this.toggleSelect()) { this.unselect(feature); @@ -301,7 +333,7 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { */ overFeature: function(feature) { if(this.hover && - (OpenLayers.Util.indexOf(this.layer.selectedFeatures, feature) == -1)) { + (OpenLayers.Util.indexOf(feature.layer.selectedFeatures, feature) == -1)) { this.select(feature); } }, @@ -330,17 +362,19 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { */ select: function(feature) { var cont = this.onBeforeSelect.call(this.scope, feature); + var layer = feature.layer; if(cont !== false) { - cont = this.layer.events.triggerEvent("beforefeatureselected", { + cont = layer.events.triggerEvent("beforefeatureselected", { feature: feature }); if(cont !== false) { - this.layer.selectedFeatures.push(feature); - + layer.selectedFeatures.push(feature); + this.layerData = {}; + var selectStyle = this.selectStyle || this.renderIntent; - this.layer.drawFeature(feature, selectStyle); - this.layer.events.triggerEvent("featureselected", {feature: feature}); + layer.drawFeature(feature, selectStyle); + layer.events.triggerEvent("featureselected", {feature: feature}); this.onSelect.call(this.scope, feature); } } @@ -355,10 +389,11 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { * feature - {} */ unselect: function(feature) { + var layer = feature.layer; // Store feature style for restoration later - this.layer.drawFeature(feature, "default"); - OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature); - this.layer.events.triggerEvent("featureunselected", {feature: feature}); + layer.drawFeature(feature, "default"); + OpenLayers.Util.removeItem(layer.selectedFeatures, feature); + layer.events.triggerEvent("featureunselected", {feature: feature}); this.onUnselect.call(this.scope, feature); }, @@ -390,13 +425,17 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { // because we're using a box, we consider we want multiple selection var prevMultiple = this.multiple; this.multiple = true; - for(var i=0, len = this.layer.features.length; i -1) { - if (bounds.toGeometry().intersects(feature.geometry)) { - if (OpenLayers.Util.indexOf(this.layer.selectedFeatures, feature) == -1) { - this.select(feature); + var layer; + for(var l=0; l -1) { + if (bounds.toGeometry().intersects(feature.geometry)) { + if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) { + this.select(feature); + } } } } @@ -419,6 +458,6 @@ OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { } OpenLayers.Control.prototype.setMap.apply(this, arguments); }, - + CLASS_NAME: "OpenLayers.Control.SelectFeature" }); diff --git a/lib/OpenLayers/Layer/Vector.js b/lib/OpenLayers/Layer/Vector.js index bcc965665c..36ee4c8a91 100644 --- a/lib/OpenLayers/Layer/Vector.js +++ b/lib/OpenLayers/Layer/Vector.js @@ -445,6 +445,23 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { } } }, + + /** + * APIMethod: display + * Hide or show the Layer + * + * Parameters: + * display - {Boolean} + */ + display: function(display) { + OpenLayers.Layer.prototype.display.apply(this, arguments); + // we need to set the display style of the root in case it is attached + // to a foreign layer + var currentDisplay = this.div.style.display; + if(currentDisplay != this.renderer.root.style.display) { + this.renderer.root.style.display = currentDisplay; + } + }, /** * APIMethod: addFeatures diff --git a/lib/OpenLayers/Layer/Vector/RootContainer.js b/lib/OpenLayers/Layer/Vector/RootContainer.js new file mode 100644 index 0000000000..f2c830b340 --- /dev/null +++ b/lib/OpenLayers/Layer/Vector/RootContainer.js @@ -0,0 +1,156 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Layer/Vector.js + */ + +/** + * Class: OpenLayers.Layer.Vector.RootContainer + * A special layer type to combine multiple vector layers inside a single + * renderer root container. This class is not supposed to be instantiated + * from user space, it is a helper class for controls that require event + * processing for multiple vector layers. + * + * Inherits from: + * - + */ +OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, { + + /** + * Property: displayInLayerSwitcher + * Set to false for this layer type + */ + displayInLayerSwitcher: false, + + /** + * APIProperty: layers + * Layers that are attached to this container. Required config option. + */ + layers: null, + + /** + * Constructor: OpenLayers.Layer.Vector.RootContainer + * Create a new root container for multiple vector layer. This constructor + * is not supposed to be used from user space, it is only to be used by + * controls that need feature selection across multiple vector layers. + * + * Parameters: + * name - {String} A name for the layer + * options - {Object} Optional object with non-default properties to set on + * the layer. + * + * Required options properties: + * layers - {Array()} The layers managed by this + * container + * + * Returns: + * {} A new vector layer root + * container + */ + initialize: function(name, options) { + OpenLayers.Layer.Vector.prototype.initialize.apply(this, arguments); + }, + + /** + * Method: display + */ + display: function() {}, + + /** + * Method: getFeatureFromEvent + * walk through the layers to find the feature returned by the event + * + * Parameters: + * evt - {Object} event object with a feature property + * + * Returns: + * {} + */ + getFeatureFromEvent: function(evt) { + var layers = this.layers; + var feature; + for(var i=0; i} + */ + setMap: function(map) { + OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments); + this.collectRoots(); + map.events.register("changelayer", this, this.handleChangeLayer); + }, + + /** + * Method: removeMap + * + * Parameters: + * map - {} + */ + removeMap: function(map) { + map.events.unregister("changelayer", this, this.handleChangeLayer); + this.resetRoots(); + OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments); + }, + + /** + * Method: collectRoots + * Collects the root nodes of all layers this control is configured with + * and moveswien the nodes to this control's layer + */ + collectRoots: function() { + var layer; + // walk through all map layers, because we want to keep the order + for(var i=0; i} @@ -237,6 +243,32 @@ OpenLayers.Renderer = OpenLayers.Class({ * geometry - {} */ eraseGeometry: function(geometry) {}, + + /** + * Method: moveRoot + * moves this renderer's root to a (different) renderer. + * To be implemented by subclasses that require a common renderer root for + * feature selection. + * + * Parameters: + * renderer - {} target renderer for the moved root + */ + moveRoot: function(renderer) {}, + + /** + * Method: getRenderLayer + * Gets the layer that this renderer's output appears on. If moveRoot was + * used, this will be different from the layer containing the features + * rendered by this renderer. + * To be overridden by subclasses that implement moveRoot. + * + * Returns: + * {} the output layer, if any (i.e. this method + * will not return a layer if the layer is not added to a map). + */ + getRenderLayer: function() { + return this.map.getLayer(this.container.id); + }, CLASS_NAME: "OpenLayers.Renderer" }); \ No newline at end of file diff --git a/lib/OpenLayers/Renderer/Canvas.js b/lib/OpenLayers/Renderer/Canvas.js index 8f74bca196..4b799768c1 100644 --- a/lib/OpenLayers/Renderer/Canvas.js +++ b/lib/OpenLayers/Renderer/Canvas.js @@ -15,12 +15,6 @@ */ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, { - /** - * Property: root - * {DOMElement} root element of canvas. - */ - root: null, - /** * Property: canvas * {Canvas} The canvas context object. diff --git a/lib/OpenLayers/Renderer/Elements.js b/lib/OpenLayers/Renderer/Elements.js index 2914b5cc0d..f6c5b94f6e 100644 --- a/lib/OpenLayers/Renderer/Elements.js +++ b/lib/OpenLayers/Renderer/Elements.js @@ -331,12 +331,6 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, { */ rendererRoot: null, - /** - * Property: root - * {DOMElement} - */ - root: null, - /** * Property: xmlns * {String} @@ -887,6 +881,36 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, { */ createNode: function(type, id) {}, + /** + * Method: moveRoot + * moves this renderer's root to a different renderer. + * + * Parameters: + * renderer - {} target renderer for the moved root + */ + moveRoot: function(renderer) { + var root = this.root; + if(renderer.root.parentNode == this.rendererRoot) { + root = renderer.root; + } + root.parentNode.removeChild(root); + renderer.rendererRoot.appendChild(root); + }, + + /** + * Method: getRenderLayer + * Gets the layer that this renderer's output appears on. If moveRoot was + * used, this will be different from the layer containing the features + * rendered by this renderer. + * + * Returns: + * {} the output layer, if any (i.e. this method + * will not return a layer if it is not added to a map). + */ + getRenderLayer: function() { + return this.map.getLayer(this.root.parentNode.parentNode.id); + }, + /** * Method: isComplexSymbol * Determines if a symbol cannot be rendered using drawCircle diff --git a/lib/OpenLayers/Renderer/VML.js b/lib/OpenLayers/Renderer/VML.js index b4d01bdae3..60ab01612a 100644 --- a/lib/OpenLayers/Renderer/VML.js +++ b/lib/OpenLayers/Renderer/VML.js @@ -810,6 +810,26 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { return node; }, + /** + * Method: moveRoot + * moves this renderer's root to a different renderer. + * + * Parameters: + * renderer - {} target renderer for the moved root + * root - {DOMElement} optional root node. To be used when this renderer + * holds roots from multiple layers to tell this method which one to + * detach + * + * Returns: + * {Boolean} true if successful, false otherwise + */ + moveRoot: function(renderer) { + var layer = this.map.getLayer(this.container.id); + layer && this.clear(); + OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments); + layer && layer.redraw(); + }, + /** * Method: importSymbol * add a new symbol definition from the rendererer's symbol hash diff --git a/tests/Control/SelectFeature.html b/tests/Control/SelectFeature.html index 580327c6ea..e6adc0ed2c 100644 --- a/tests/Control/SelectFeature.html +++ b/tests/Control/SelectFeature.html @@ -3,7 +3,7 @@ + + + +
+ + diff --git a/tests/Renderer/Elements.html b/tests/Renderer/Elements.html index e58ccdb72b..67a3670903 100644 --- a/tests/Renderer/Elements.html +++ b/tests/Renderer/Elements.html @@ -466,7 +466,7 @@ OpenLayers.Util.getElement = OpenLayers.Util._getElement; } - + function test_Elements_drawAndErase(t) { t.plan(20); @@ -543,6 +543,19 @@ tearDown(); } + function test_Elements_moveRoot(t) { + t.plan(2); + setUp(); + var r1 = create_renderer(); + var r2 = create_renderer(); + r1.moveRoot(r2); + t.xml_eq(r1.root.parentNode, r2.root.parentNode, "root moved successfully"); + r1.moveRoot(r1); + t.xml_eq(r1.root.parentNode, r1.rendererRoot, "root moved back successfully"); + tearDown(); + } + + diff --git a/tests/list-tests.html b/tests/list-tests.html index 11b8a53456..5b5feaa978 100644 --- a/tests/list-tests.html +++ b/tests/list-tests.html @@ -115,6 +115,7 @@
  • Layer/TileCache.html
  • Layer/TMS.html
  • Layer/Vector.html
  • +
  • Layer/Vector/RootContainer.html
  • Layer/WFS.html
  • Layer/WMS.html
  • Layer/WrapDateLine.html