1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
7 * @requires OpenLayers/Control.js
8 * @requires OpenLayers/Feature/Vector.js
9 * @requires OpenLayers/Handler/Feature.js
10 * @requires OpenLayers/Layer/Vector/RootContainer.js
14 * Class: OpenLayers.Control.SelectFeature
15 * The SelectFeature control selects vector features from a given layer on
19 * - <OpenLayers.Control>
21 OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
24 * Constant: EVENT_TYPES
26 * Supported event types:
27 * - *beforefeaturehighlighted* Triggered before a feature is highlighted
28 * - *featurehighlighted* Triggered when a feature is highlighted
29 * - *featureunhighlighted* Triggered when a feature is unhighlighted
31 EVENT_TYPES: ["beforefeaturehighlighted", "featurehighlighted", "featureunhighlighted"],
34 * Property: multipleKey
35 * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
36 * the <multiple> property to true. Default is null.
42 * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
43 * the <toggle> property to true. Default is null.
48 * APIProperty: multiple
49 * {Boolean} Allow selection of multiple geometries. Default is false.
54 * APIProperty: clickout
55 * {Boolean} Unselect features when clicking outside any feature.
62 * {Boolean} Unselect a selected feature on click. Default is false. Only
63 * has meaning if hover is false.
69 * {Boolean} Select on mouse over and deselect on mouse out. If true, this
70 * ignores clicks and only listens to mouse moves.
75 * APIProperty: highlightOnly
76 * {Boolean} If true do not actually select features (i.e. place them in the
77 * layer's selected features array), just highlight them. This property has
78 * no effect if hover is false. Defaults to false.
84 * {Boolean} Allow feature selection by drawing a box.
89 * Property: onBeforeSelect
90 * {Function} Optional function to be called before a feature is selected.
91 * The function should expect to be called with a feature.
93 onBeforeSelect: function() {},
96 * APIProperty: onSelect
97 * {Function} Optional function to be called when a feature is selected.
98 * The function should expect to be called with a feature.
100 onSelect: function() {},
103 * APIProperty: onUnselect
104 * {Function} Optional function to be called when a feature is unselected.
105 * The function should expect to be called with a feature.
107 onUnselect: function() {},
111 * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
112 * callbacks. If null the scope will be this control.
117 * APIProperty: geometryTypes
118 * {Array(String)} To restrict selecting to a limited set of geometry types,
119 * send a list of strings corresponding to the geometry class names.
125 * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
126 * root for all layers this control is configured with (if an array of
127 * layers was passed to the constructor), or the vector layer the control
128 * was configured with (if a single layer was passed to the constructor).
134 * {Array(<OpenLayers.Layer.Vector>} The layers this control will work on,
135 * or null if the control was configured with a single layer
140 * APIProperty: callbacks
141 * {Object} The functions that are sent to the handlers.feature for callback
146 * APIProperty: selectStyle
147 * {Object} Hash of styles
152 * Property: renderIntent
153 * {String} key used to retrieve the select style from the layer's
156 renderIntent: "select",
160 * {Object} Object with references to multiple <OpenLayers.Handler>
166 * Constructor: OpenLayers.Control.SelectFeature
167 * Create a new control for selecting features.
170 * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
171 * layer(s) this control will select features from.
174 initialize: function(layers, options) {
175 // concatenate events specific to this control with those from the base
177 OpenLayers.Control.SelectFeature.prototype.EVENT_TYPES.concat(
178 OpenLayers.Control.prototype.EVENT_TYPES
180 OpenLayers.Control.prototype.initialize.apply(this, [options]);
182 if(this.scope === null) {
185 if(layers instanceof Array) {
186 this.layers = layers;
187 this.layer = new OpenLayers.Layer.Vector.RootContainer(
188 this.id + "_container", {
196 click: this.clickFeature,
197 clickout: this.clickoutFeature
200 callbacks.over = this.overFeature;
201 callbacks.out = this.outFeature;
204 this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
206 feature: new OpenLayers.Handler.Feature(
207 this, this.layer, this.callbacks,
208 {geometryTypes: this.geometryTypes}
213 this.handlers.box = new OpenLayers.Handler.Box(
214 this, {done: this.selectBox},
215 {boxDivClassName: "olHandlerBoxSelectFeature"}
223 destroy: function() {
224 OpenLayers.Control.prototype.destroy.apply(this, arguments);
226 this.layer.destroy();
232 * Activates the control.
235 * {Boolean} The control was effectively activated.
237 activate: function () {
240 this.map.addLayer(this.layer);
242 this.handlers.feature.activate();
243 if(this.box && this.handlers.box) {
244 this.handlers.box.activate();
247 return OpenLayers.Control.prototype.activate.apply(
254 * Deactivates the control.
257 * {Boolean} The control was effectively deactivated.
259 deactivate: function () {
261 this.handlers.feature.deactivate();
262 if(this.handlers.box) {
263 this.handlers.box.deactivate();
266 this.map.removeLayer(this.layer);
269 return OpenLayers.Control.prototype.deactivate.apply(
275 * Method: unselectAll
276 * Unselect all selected features. To unselect all except for a single
277 * feature, set the options.except property to the feature.
280 * options - {Object} Optional configuration object.
282 unselectAll: function(options) {
283 // we'll want an option to supress notification here
284 var layers = this.layers || [this.layer];
286 for(var l=0; l<layers.length; ++l) {
288 for(var i=layer.selectedFeatures.length-1; i>=0; --i) {
289 feature = layer.selectedFeatures[i];
290 if(!options || options.except != feature) {
291 this.unselect(feature);
298 * Method: clickFeature
299 * Called on click in a feature
300 * Only responds if this.hover is false.
303 * feature - {<OpenLayers.Feature.Vector>}
305 clickFeature: function(feature) {
307 var selected = (OpenLayers.Util.indexOf(
308 feature.layer.selectedFeatures, feature) > -1);
310 if(this.toggleSelect()) {
311 this.unselect(feature);
312 } else if(!this.multipleSelect()) {
313 this.unselectAll({except: feature});
316 if(!this.multipleSelect()) {
317 this.unselectAll({except: feature});
319 this.select(feature);
325 * Method: multipleSelect
326 * Allow for multiple selected features based on <multiple> property and
327 * <multipleKey> event modifier.
330 * {Boolean} Allow for multiple selected features.
332 multipleSelect: function() {
333 return this.multiple || (this.handlers.feature.evt &&
334 this.handlers.feature.evt[this.multipleKey]);
338 * Method: toggleSelect
339 * Event should toggle the selected state of a feature based on <toggle>
340 * property and <toggleKey> event modifier.
343 * {Boolean} Toggle the selected state of a feature.
345 toggleSelect: function() {
346 return this.toggle || (this.handlers.feature.evt &&
347 this.handlers.feature.evt[this.toggleKey]);
351 * Method: clickoutFeature
352 * Called on click outside a previously clicked (selected) feature.
353 * Only responds if this.hover is false.
356 * feature - {<OpenLayers.Vector.Feature>}
358 clickoutFeature: function(feature) {
359 if(!this.hover && this.clickout) {
365 * Method: overFeature
366 * Called on over a feature.
367 * Only responds if this.hover is true.
370 * feature - {<OpenLayers.Feature.Vector>}
372 overFeature: function(feature) {
373 var layer = feature.layer;
375 if(this.highlightOnly) {
376 this.highlight(feature);
377 } else if(OpenLayers.Util.indexOf(
378 layer.selectedFeatures, feature) == -1) {
379 this.select(feature);
386 * Called on out of a selected feature.
387 * Only responds if this.hover is true.
390 * feature - {<OpenLayers.Feature.Vector>}
392 outFeature: function(feature) {
394 if(this.highlightOnly) {
395 // we do nothing if we're not the last highlighter of the
397 if(feature._lastHighlighter == this.id) {
398 // if another select control had highlighted the feature before
399 // we did it ourself then we use that control to highlight the
400 // feature as it was before we highlighted it, else we just
402 if(feature._prevHighlighter &&
403 feature._prevHighlighter != this.id) {
404 delete feature._lastHighlighter;
405 var control = this.map.getControl(
406 feature._prevHighlighter);
408 control.highlight(feature);
411 this.unhighlight(feature);
415 this.unselect(feature);
422 * Redraw feature with the select style.
425 * feature - {<OpenLayers.Feature.Vector>}
427 highlight: function(feature) {
428 var layer = feature.layer;
429 var cont = this.events.triggerEvent("beforefeaturehighlighted", {
433 feature._prevHighlighter = feature._lastHighlighter;
434 feature._lastHighlighter = this.id;
435 var style = this.selectStyle || this.renderIntent;
436 layer.drawFeature(feature, style);
437 this.events.triggerEvent("featurehighlighted", {feature : feature});
442 * Method: unhighlight
443 * Redraw feature with the "default" style
446 * feature - {<OpenLayers.Feature.Vector>}
448 unhighlight: function(feature) {
449 var layer = feature.layer;
450 feature._lastHighlighter = feature._prevHighlighter;
451 delete feature._prevHighlighter;
452 layer.drawFeature(feature, feature.style || feature.layer.style ||
454 this.events.triggerEvent("featureunhighlighted", {feature : feature});
459 * Add feature to the layer's selectedFeature array, render the feature as
460 * selected, and call the onSelect function.
463 * feature - {<OpenLayers.Feature.Vector>}
465 select: function(feature) {
466 var cont = this.onBeforeSelect.call(this.scope, feature);
467 var layer = feature.layer;
469 cont = layer.events.triggerEvent("beforefeatureselected", {
473 layer.selectedFeatures.push(feature);
474 this.highlight(feature);
475 layer.events.triggerEvent("featureselected", {feature: feature});
476 this.onSelect.call(this.scope, feature);
483 * Remove feature from the layer's selectedFeature array, render the feature as
484 * normal, and call the onUnselect function.
487 * feature - {<OpenLayers.Feature.Vector>}
489 unselect: function(feature) {
490 var layer = feature.layer;
491 // Store feature style for restoration later
492 this.unhighlight(feature);
493 OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
494 layer.events.triggerEvent("featureunselected", {feature: feature});
495 this.onUnselect.call(this.scope, feature);
500 * Callback from the handlers.box set up when <box> selection is true
504 * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
506 selectBox: function(position) {
507 if (position instanceof OpenLayers.Bounds) {
508 var minXY = this.map.getLonLatFromPixel(
509 new OpenLayers.Pixel(position.left, position.bottom)
511 var maxXY = this.map.getLonLatFromPixel(
512 new OpenLayers.Pixel(position.right, position.top)
514 var bounds = new OpenLayers.Bounds(
515 minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
518 // if multiple is false, first deselect currently selected features
519 if (!this.multipleSelect()) {
523 // because we're using a box, we consider we want multiple selection
524 var prevMultiple = this.multiple;
525 this.multiple = true;
526 var layers = this.layers || [this.layer];
528 for(var l=0; l<layers.length; ++l) {
530 for(var i=0, len = layer.features.length; i<len; ++i) {
531 var feature = layer.features[i];
532 if (this.geometryTypes == null || OpenLayers.Util.indexOf(
533 this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
534 if (bounds.toGeometry().intersects(feature.geometry)) {
535 if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
536 this.select(feature);
542 this.multiple = prevMultiple;
548 * Set the map property for the control.
551 * map - {<OpenLayers.Map>}
553 setMap: function(map) {
554 this.handlers.feature.setMap(map);
556 this.handlers.box.setMap(map);
558 OpenLayers.Control.prototype.setMap.apply(this, arguments);
561 CLASS_NAME: "OpenLayers.Control.SelectFeature"