]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Control/SelectFeature.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Control / SelectFeature.js
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. */
4
5
6 /**
7  * @requires OpenLayers/Control.js
8  * @requires OpenLayers/Feature/Vector.js
9  * @requires OpenLayers/Handler/Feature.js
10  * @requires OpenLayers/Layer/Vector/RootContainer.js
11  */
12
13 /**
14  * Class: OpenLayers.Control.SelectFeature
15  * The SelectFeature control selects vector features from a given layer on 
16  * click or hover. 
17  *
18  * Inherits from:
19  *  - <OpenLayers.Control>
20  */
21 OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
22
23     /**
24      * Constant: EVENT_TYPES
25      *
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
30      */
31     EVENT_TYPES: ["beforefeaturehighlighted", "featurehighlighted", "featureunhighlighted"],
32     
33     /**
34      * Property: multipleKey
35      * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
36      *     the <multiple> property to true.  Default is null.
37      */
38     multipleKey: null,
39     
40     /**
41      * Property: toggleKey
42      * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
43      *     the <toggle> property to true.  Default is null.
44      */
45     toggleKey: null,
46     
47     /**
48      * APIProperty: multiple
49      * {Boolean} Allow selection of multiple geometries.  Default is false.
50      */
51     multiple: false, 
52
53     /**
54      * APIProperty: clickout
55      * {Boolean} Unselect features when clicking outside any feature.
56      *     Default is true.
57      */
58     clickout: true,
59
60     /**
61      * APIProperty: toggle
62      * {Boolean} Unselect a selected feature on click.  Default is false.  Only
63      *     has meaning if hover is false.
64      */
65     toggle: false,
66
67     /**
68      * APIProperty: hover
69      * {Boolean} Select on mouse over and deselect on mouse out.  If true, this
70      * ignores clicks and only listens to mouse moves.
71      */
72     hover: false,
73
74     /**
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.
79      */
80     highlightOnly: false,
81     
82     /**
83      * APIProperty: box
84      * {Boolean} Allow feature selection by drawing a box.
85      */
86     box: false,
87     
88     /**
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.
92      */
93     onBeforeSelect: function() {},
94     
95     /**
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.
99      */
100     onSelect: function() {},
101
102     /**
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.
106      */
107     onUnselect: function() {},
108     
109     /**
110      * Property: scope
111      * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
112      *     callbacks. If null the scope will be this control.
113      */
114     scope: null,
115
116     /**
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.
120      */
121     geometryTypes: null,
122
123     /**
124      * Property: layer
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).
129      */
130     layer: null,
131     
132     /**
133      * Property: layers
134      * {Array(<OpenLayers.Layer.Vector>} The layers this control will work on,
135      * or null if the control was configured with a single layer
136      */
137     layers: null,
138     
139     /**
140      * APIProperty: callbacks
141      * {Object} The functions that are sent to the handlers.feature for callback
142      */
143     callbacks: null,
144     
145     /**
146      * APIProperty: selectStyle 
147      * {Object} Hash of styles
148      */
149     selectStyle: null,
150     
151     /**
152      * Property: renderIntent
153      * {String} key used to retrieve the select style from the layer's
154      * style map.
155      */
156     renderIntent: "select",
157
158     /**
159      * Property: handlers
160      * {Object} Object with references to multiple <OpenLayers.Handler>
161      *     instances.
162      */
163     handlers: null,
164
165     /**
166      * Constructor: OpenLayers.Control.SelectFeature
167      * Create a new control for selecting features.
168      *
169      * Parameters:
170      * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
171      *     layer(s) this control will select features from.
172      * options - {Object} 
173      */
174     initialize: function(layers, options) {
175         // concatenate events specific to this control with those from the base
176         this.EVENT_TYPES =
177             OpenLayers.Control.SelectFeature.prototype.EVENT_TYPES.concat(
178             OpenLayers.Control.prototype.EVENT_TYPES
179         );
180         OpenLayers.Control.prototype.initialize.apply(this, [options]);
181         
182         if(this.scope === null) {
183             this.scope = this;
184         }
185         if(layers instanceof Array) {
186             this.layers = layers;
187             this.layer = new OpenLayers.Layer.Vector.RootContainer(
188                 this.id + "_container", {
189                     layers: layers
190                 }
191             );
192         } else {
193             this.layer = layers;
194         }
195         var callbacks = {
196             click: this.clickFeature,
197             clickout: this.clickoutFeature
198         };
199         if (this.hover) {
200             callbacks.over = this.overFeature;
201             callbacks.out = this.outFeature;
202         }
203              
204         this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
205         this.handlers = {
206             feature: new OpenLayers.Handler.Feature(
207                 this, this.layer, this.callbacks,
208                 {geometryTypes: this.geometryTypes}
209             )
210         };
211
212         if (this.box) {
213             this.handlers.box = new OpenLayers.Handler.Box(
214                 this, {done: this.selectBox},
215                 {boxDivClassName: "olHandlerBoxSelectFeature"}
216             ); 
217         }
218     },
219     
220     /**
221      * Method: destroy
222      */
223     destroy: function() {
224         OpenLayers.Control.prototype.destroy.apply(this, arguments);
225         if(this.layers) {
226             this.layer.destroy();
227         }
228     },
229
230     /**
231      * Method: activate
232      * Activates the control.
233      * 
234      * Returns:
235      * {Boolean} The control was effectively activated.
236      */
237     activate: function () {
238         if (!this.active) {
239             if(this.layers) {
240                 this.map.addLayer(this.layer);
241             }
242             this.handlers.feature.activate();
243             if(this.box && this.handlers.box) {
244                 this.handlers.box.activate();
245             }
246         }
247         return OpenLayers.Control.prototype.activate.apply(
248             this, arguments
249         );
250     },
251
252     /**
253      * Method: deactivate
254      * Deactivates the control.
255      * 
256      * Returns:
257      * {Boolean} The control was effectively deactivated.
258      */
259     deactivate: function () {
260         if (this.active) {
261             this.handlers.feature.deactivate();
262             if(this.handlers.box) {
263                 this.handlers.box.deactivate();
264             }
265             if(this.layers) {
266                 this.map.removeLayer(this.layer);
267             }
268         }
269         return OpenLayers.Control.prototype.deactivate.apply(
270             this, arguments
271         );
272     },
273
274     /**
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.
278      *
279      * Parameters:
280      * options - {Object} Optional configuration object.
281      */
282     unselectAll: function(options) {
283         // we'll want an option to supress notification here
284         var layers = this.layers || [this.layer];
285         var layer, feature;
286         for(var l=0; l<layers.length; ++l) {
287             layer = layers[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);
292                 }
293             }
294         }
295     },
296
297     /**
298      * Method: clickFeature
299      * Called on click in a feature
300      * Only responds if this.hover is false.
301      *
302      * Parameters:
303      * feature - {<OpenLayers.Feature.Vector>} 
304      */
305     clickFeature: function(feature) {
306         if(!this.hover) {
307             var selected = (OpenLayers.Util.indexOf(
308                 feature.layer.selectedFeatures, feature) > -1);
309             if(selected) {
310                 if(this.toggleSelect()) {
311                     this.unselect(feature);
312                 } else if(!this.multipleSelect()) {
313                     this.unselectAll({except: feature});
314                 }
315             } else {
316                 if(!this.multipleSelect()) {
317                     this.unselectAll({except: feature});
318                 }
319                 this.select(feature);
320             }
321         }
322     },
323
324     /**
325      * Method: multipleSelect
326      * Allow for multiple selected features based on <multiple> property and
327      *     <multipleKey> event modifier.
328      *
329      * Returns:
330      * {Boolean} Allow for multiple selected features.
331      */
332     multipleSelect: function() {
333         return this.multiple || (this.handlers.feature.evt &&
334                                  this.handlers.feature.evt[this.multipleKey]);
335     },
336     
337     /**
338      * Method: toggleSelect
339      * Event should toggle the selected state of a feature based on <toggle>
340      *     property and <toggleKey> event modifier.
341      *
342      * Returns:
343      * {Boolean} Toggle the selected state of a feature.
344      */
345     toggleSelect: function() {
346         return this.toggle || (this.handlers.feature.evt &&
347                                this.handlers.feature.evt[this.toggleKey]);
348     },
349
350     /**
351      * Method: clickoutFeature
352      * Called on click outside a previously clicked (selected) feature.
353      * Only responds if this.hover is false.
354      *
355      * Parameters:
356      * feature - {<OpenLayers.Vector.Feature>} 
357      */
358     clickoutFeature: function(feature) {
359         if(!this.hover && this.clickout) {
360             this.unselectAll();
361         }
362     },
363
364     /**
365      * Method: overFeature
366      * Called on over a feature.
367      * Only responds if this.hover is true.
368      *
369      * Parameters:
370      * feature - {<OpenLayers.Feature.Vector>} 
371      */
372     overFeature: function(feature) {
373         var layer = feature.layer;
374         if(this.hover) {
375             if(this.highlightOnly) {
376                 this.highlight(feature);
377             } else if(OpenLayers.Util.indexOf(
378                 layer.selectedFeatures, feature) == -1) {
379                 this.select(feature);
380             }
381         }
382     },
383
384     /**
385      * Method: outFeature
386      * Called on out of a selected feature.
387      * Only responds if this.hover is true.
388      *
389      * Parameters:
390      * feature - {<OpenLayers.Feature.Vector>} 
391      */
392     outFeature: function(feature) {
393         if(this.hover) {
394             if(this.highlightOnly) {
395                 // we do nothing if we're not the last highlighter of the
396                 // feature
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
401                     // unhighlight it
402                     if(feature._prevHighlighter &&
403                        feature._prevHighlighter != this.id) {
404                         delete feature._lastHighlighter;
405                         var control = this.map.getControl(
406                             feature._prevHighlighter);
407                         if(control) {
408                             control.highlight(feature);
409                         }
410                     } else {
411                         this.unhighlight(feature);
412                     }
413                 }
414             } else {
415                 this.unselect(feature);
416             }
417         }
418     },
419
420     /**
421      * Method: highlight
422      * Redraw feature with the select style.
423      *
424      * Parameters:
425      * feature - {<OpenLayers.Feature.Vector>} 
426      */
427     highlight: function(feature) {
428         var layer = feature.layer;
429         var cont = this.events.triggerEvent("beforefeaturehighlighted", {
430             feature : feature
431         });
432         if(cont !== false) {
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});
438         }
439     },
440
441     /**
442      * Method: unhighlight
443      * Redraw feature with the "default" style
444      *
445      * Parameters:
446      * feature - {<OpenLayers.Feature.Vector>} 
447      */
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 ||
453             "default");
454         this.events.triggerEvent("featureunhighlighted", {feature : feature});
455     },
456     
457     /**
458      * Method: select
459      * Add feature to the layer's selectedFeature array, render the feature as
460      * selected, and call the onSelect function.
461      * 
462      * Parameters:
463      * feature - {<OpenLayers.Feature.Vector>} 
464      */
465     select: function(feature) {
466         var cont = this.onBeforeSelect.call(this.scope, feature);
467         var layer = feature.layer;
468         if(cont !== false) {
469             cont = layer.events.triggerEvent("beforefeatureselected", {
470                 feature: feature
471             });
472             if(cont !== false) {
473                 layer.selectedFeatures.push(feature);
474                 this.highlight(feature);
475                 layer.events.triggerEvent("featureselected", {feature: feature});
476                 this.onSelect.call(this.scope, feature);
477             }
478         }
479     },
480
481     /**
482      * Method: unselect
483      * Remove feature from the layer's selectedFeature array, render the feature as
484      * normal, and call the onUnselect function.
485      *
486      * Parameters:
487      * feature - {<OpenLayers.Feature.Vector>}
488      */
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);
496     },
497     
498     /**
499      * Method: selectBox
500      * Callback from the handlers.box set up when <box> selection is true
501      *     on.
502      *
503      * Parameters:
504      * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }  
505      */
506     selectBox: function(position) {
507         if (position instanceof OpenLayers.Bounds) {
508             var minXY = this.map.getLonLatFromPixel(
509                 new OpenLayers.Pixel(position.left, position.bottom)
510             );
511             var maxXY = this.map.getLonLatFromPixel(
512                 new OpenLayers.Pixel(position.right, position.top)
513             );
514             var bounds = new OpenLayers.Bounds(
515                 minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
516             );
517             
518             // if multiple is false, first deselect currently selected features
519             if (!this.multipleSelect()) {
520                 this.unselectAll();
521             }
522             
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];
527             var layer;
528             for(var l=0; l<layers.length; ++l) {
529                 layer = layers[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);
537                             }
538                         }
539                     }
540                 }
541             }
542             this.multiple = prevMultiple;
543         }
544     },
545
546     /** 
547      * Method: setMap
548      * Set the map property for the control. 
549      * 
550      * Parameters:
551      * map - {<OpenLayers.Map>} 
552      */
553     setMap: function(map) {
554         this.handlers.feature.setMap(map);
555         if (this.box) {
556             this.handlers.box.setMap(map);
557         }
558         OpenLayers.Control.prototype.setMap.apply(this, arguments);
559     },
560     
561     CLASS_NAME: "OpenLayers.Control.SelectFeature"
562 });