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
This commit is contained in:
ahocevar
2009-03-22 14:25:18 +00:00
parent 5c26d578ca
commit 2b1e8641f9
14 changed files with 552 additions and 59 deletions

View File

@@ -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
* {<OpenLayers.Layer.Vector>}
* {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
* root for all layers this control is configured with.
*/
layer: null,
/**
* Property: layers
* {Array(<OpenLayers.Layer.Vector>} 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: <OpenLayers.Control.SelectFeature>
*
* Parameters:
* layer - {<OpenLayers.Layer.Vector>}
* layers - {<OpenLayers.Layer.Vector>}, 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<this.layers.length; ++l) {
layer = this.layers[l];
for(var i=layer.selectedFeatures.length-1; i>=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 - {<OpenLayers.Feature.Vector>}
*/
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<len; ++i) {
var feature = this.layer.features[i];
if (this.geometryTypes == null || OpenLayers.Util.indexOf(
this.geometryTypes, feature.geometry.CLASS_NAME) > -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<this.layers.length; ++l) {
layer = this.layers[l];
for(var i=0, len = layer.features.length; i<len; ++i) {
var feature = layer.features[i];
if (this.geometryTypes == null || OpenLayers.Util.indexOf(
this.geometryTypes, feature.geometry.CLASS_NAME) > -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"
});

View File

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

View File

@@ -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>
*/
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(<OpenLayers.Layer.Vector>)} The layers managed by this
* container
*
* Returns:
* {<OpenLayers.Layer.Vector.RootContainer>} 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:
* {<OpenLayers.Feature.Vector>}
*/
getFeatureFromEvent: function(evt) {
var layers = this.layers;
var feature;
for(var i=0; i<layers.length; i++) {
feature = layers[i].getFeatureFromEvent(evt);
if(feature) {
return feature;
}
}
},
/**
* Method: setMap
*
* Parameters:
* map - {<OpenLayers.Map>}
*/
setMap: function(map) {
OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
this.collectRoots();
map.events.register("changelayer", this, this.handleChangeLayer);
},
/**
* Method: removeMap
*
* Parameters:
* map - {<OpenLayers.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<this.map.layers.length; ++i) {
layer = this.map.layers[i];
if(OpenLayers.Util.indexOf(this.layers, layer) != -1) {
layer.renderer.moveRoot(this.renderer);
}
}
},
/**
* Method: resetRoots
* Resets the root nodes back into the layers they belong to.
*/
resetRoots: function() {
var layer;
for(var i=0; i<this.layers.length; ++i) {
layer = this.layers[i];
if(layer.renderer.getRenderLayer() == this) {
this.renderer.moveRoot(layer.renderer);
}
}
},
/**
* Method: handleChangeLayer
* Event handler for the map's changelayer event. We need to rebuild
* this container's layer dom if order of one of its layers changes.
* This handler is added with the setMap method, and removed with the
* removeMap method.
*
* Parameters:
* evt - {Object}
*/
handleChangeLayer: function(evt) {
var layer = evt.layer;
if(evt.property == "order" &&
OpenLayers.Util.indexOf(this.layers, layer) != -1) {
this.resetRoots();
this.collectRoots();
}
},
CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer"
});

View File

@@ -392,6 +392,20 @@ OpenLayers.Layer.WFS = OpenLayers.Class(
}
},
/**
* Method: display
* Call the display method of the appropriate parent class.
*/
display: function() {
if(this.vectorMode) {
OpenLayers.Layer.Vector.prototype.display.apply(this,
arguments);
} else {
OpenLayers.Layer.Markers.prototype.display.apply(this,
arguments);
}
},
/**
* APIMethod: mergeNewParams
* Modify parameters for the layer and redraw.

View File

@@ -27,6 +27,12 @@ OpenLayers.Renderer = OpenLayers.Class({
*/
container: null,
/**
* Property: root
* {DOMElement}
*/
root: null,
/**
* Property: extent
* {<OpenLayers.Bounds>}
@@ -237,6 +243,32 @@ OpenLayers.Renderer = OpenLayers.Class({
* geometry - {<OpenLayers.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 - {<OpenLayers.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:
* {<OpenLayers.Layer.Vector>} 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"
});

View File

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

View File

@@ -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 - {<OpenLayers.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:
* {<OpenLayers.Layer.Vector>} 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

View File

@@ -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 - {<OpenLayers.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