]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Control/LayerSwitcher.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Control / LayerSwitcher.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  * @requires OpenLayers/Control.js
7  */
8
9 /**
10  * Class: OpenLayers.Control.LayerSwitcher
11  * The LayerSwitcher control displays a table of contents for the map. This 
12  * allows the user interface to switch between BaseLasyers and to show or hide
13  * Overlays. By default the switcher is shown minimized on the right edge of 
14  * the map, the user may expand it by clicking on the handle.
15  *
16  * To create the LayerSwitcher outside of the map, pass the Id of a html div 
17  * as the first argument to the constructor.
18  * 
19  * Inherits from:
20  *  - <OpenLayers.Control>
21  */
22 OpenLayers.Control.LayerSwitcher = 
23   OpenLayers.Class(OpenLayers.Control, {
24
25     /**  
26      * Property: activeColor
27      * {String}
28      */
29     activeColor: "darkblue",
30     
31     /**  
32      * Property: layerStates 
33      * {Array(Object)} Basically a copy of the "state" of the map's layers 
34      *     the last time the control was drawn. We have this in order to avoid
35      *     unnecessarily redrawing the control.
36      */
37     layerStates: null,
38     
39
40   // DOM Elements
41   
42     /**
43      * Property: layersDiv
44      * {DOMElement} 
45      */
46     layersDiv: null,
47     
48     /** 
49      * Property: baseLayersDiv
50      * {DOMElement}
51      */
52     baseLayersDiv: null,
53
54     /** 
55      * Property: baseLayers
56      * {Array(<OpenLayers.Layer>)}
57      */
58     baseLayers: null,
59     
60     
61     /** 
62      * Property: dataLbl
63      * {DOMElement} 
64      */
65     dataLbl: null,
66     
67     /** 
68      * Property: dataLayersDiv
69      * {DOMElement} 
70      */
71     dataLayersDiv: null,
72
73     /** 
74      * Property: dataLayers
75      * {Array(<OpenLayers.Layer>)} 
76      */
77     dataLayers: null,
78
79
80     /** 
81      * Property: minimizeDiv
82      * {DOMElement} 
83      */
84     minimizeDiv: null,
85
86     /** 
87      * Property: maximizeDiv
88      * {DOMElement} 
89      */
90     maximizeDiv: null,
91     
92     /**
93      * APIProperty: ascending
94      * {Boolean} 
95      */
96     ascending: true,
97  
98     /**
99      * Constructor: OpenLayers.Control.LayerSwitcher
100      * 
101      * Parameters:
102      * options - {Object}
103      */
104     initialize: function(options) {
105         OpenLayers.Control.prototype.initialize.apply(this, arguments);
106         this.layerStates = [];
107     },
108
109     /**
110      * APIMethod: destroy 
111      */    
112     destroy: function() {
113         
114         OpenLayers.Event.stopObservingElement(this.div);
115
116         OpenLayers.Event.stopObservingElement(this.minimizeDiv);
117         OpenLayers.Event.stopObservingElement(this.maximizeDiv);
118
119         //clear out layers info and unregister their events 
120         this.clearLayersArray("base");
121         this.clearLayersArray("data");
122         
123         this.map.events.un({
124             "addlayer": this.redraw,
125             "changelayer": this.redraw,
126             "removelayer": this.redraw,
127             "changebaselayer": this.redraw,
128             scope: this
129         });
130         
131         OpenLayers.Control.prototype.destroy.apply(this, arguments);
132     },
133
134     /** 
135      * Method: setMap
136      *
137      * Properties:
138      * map - {<OpenLayers.Map>} 
139      */
140     setMap: function(map) {
141         OpenLayers.Control.prototype.setMap.apply(this, arguments);
142
143         this.map.events.on({
144             "addlayer": this.redraw,
145             "changelayer": this.redraw,
146             "removelayer": this.redraw,
147             "changebaselayer": this.redraw,
148             scope: this
149         });
150     },
151
152     /**
153      * Method: draw
154      *
155      * Returns:
156      * {DOMElement} A reference to the DIV DOMElement containing the 
157      *     switcher tabs.
158      */  
159     draw: function() {
160         OpenLayers.Control.prototype.draw.apply(this);
161
162         // create layout divs
163         this.loadContents();
164
165         // set mode to minimize
166         if(!this.outsideViewport) {
167             this.minimizeControl();
168         }
169
170         // populate div with current info
171         this.redraw();    
172
173         return this.div;
174     },
175
176     /** 
177      * Method: clearLayersArray
178      * User specifies either "base" or "data". we then clear all the
179      *     corresponding listeners, the div, and reinitialize a new array.
180      * 
181      * Parameters:
182      * layersType - {String}  
183      */
184     clearLayersArray: function(layersType) {
185         var layers = this[layersType + "Layers"];
186         if (layers) {
187             for(var i=0, len=layers.length; i<len ; i++) {
188                 var layer = layers[i];
189                 OpenLayers.Event.stopObservingElement(layer.inputElem);
190                 OpenLayers.Event.stopObservingElement(layer.labelSpan);
191             }
192         }
193         this[layersType + "LayersDiv"].innerHTML = "";
194         this[layersType + "Layers"] = [];
195     },
196
197
198     /**
199      * Method: checkRedraw
200      * Checks if the layer state has changed since the last redraw() call.
201      * 
202      * Returns:
203      * {Boolean} The layer state changed since the last redraw() call. 
204      */
205     checkRedraw: function() {
206         var redraw = false;
207         if ( !this.layerStates.length ||
208              (this.map.layers.length != this.layerStates.length) ) {
209             redraw = true;
210         } else {
211             for (var i=0, len=this.layerStates.length; i<len; i++) {
212                 var layerState = this.layerStates[i];
213                 var layer = this.map.layers[i];
214                 if ( (layerState.name != layer.name) || 
215                      (layerState.inRange != layer.inRange) || 
216                      (layerState.id != layer.id) || 
217                      (layerState.visibility != layer.visibility) ) {
218                     redraw = true;
219                     break;
220                 }    
221             }
222         }    
223         return redraw;
224     },
225     
226     /** 
227      * Method: redraw
228      * Goes through and takes the current state of the Map and rebuilds the
229      *     control to display that state. Groups base layers into a 
230      *     radio-button group and lists each data layer with a checkbox.
231      *
232      * Returns: 
233      * {DOMElement} A reference to the DIV DOMElement containing the control
234      */  
235     redraw: function() {
236         //if the state hasn't changed since last redraw, no need 
237         // to do anything. Just return the existing div.
238         if (!this.checkRedraw()) { 
239             return this.div; 
240         } 
241
242         //clear out previous layers 
243         this.clearLayersArray("base");
244         this.clearLayersArray("data");
245         
246         var containsOverlays = false;
247         var containsBaseLayers = false;
248         
249         // Save state -- for checking layer if the map state changed.
250         // We save this before redrawing, because in the process of redrawing
251         // we will trigger more visibility changes, and we want to not redraw
252         // and enter an infinite loop.
253         var len = this.map.layers.length;
254         this.layerStates = new Array(len);
255         for (var i=0; i <len; i++) {
256             var layer = this.map.layers[i];
257             this.layerStates[i] = {
258                 'name': layer.name, 
259                 'visibility': layer.visibility,
260                 'inRange': layer.inRange,
261                 'id': layer.id
262             };
263         }    
264
265         var layers = this.map.layers.slice();
266         if (!this.ascending) { layers.reverse(); }
267         for(var i=0, len=layers.length; i<len; i++) {
268             var layer = layers[i];
269             var baseLayer = layer.isBaseLayer;
270
271             if (layer.displayInLayerSwitcher) {
272
273                 if (baseLayer) {
274                     containsBaseLayers = true;
275                 } else {
276                     containsOverlays = true;
277                 }    
278
279                 // only check a baselayer if it is *the* baselayer, check data
280                 //  layers if they are visible
281                 var checked = (baseLayer) ? (layer == this.map.baseLayer)
282                                           : layer.getVisibility();
283     
284                 // create input element
285                 var inputElem = document.createElement("input");
286                 inputElem.id = this.id + "_input_" + layer.name;
287                 inputElem.name = (baseLayer) ? "baseLayers" : layer.name;
288                 inputElem.type = (baseLayer) ? "radio" : "checkbox";
289                 inputElem.value = layer.name;
290                 inputElem.checked = checked;
291                 inputElem.defaultChecked = checked;
292
293                 if (!baseLayer && !layer.inRange) {
294                     inputElem.disabled = true;
295                 }
296                 var context = {
297                     'inputElem': inputElem,
298                     'layer': layer,
299                     'layerSwitcher': this
300                 };
301                 OpenLayers.Event.observe(inputElem, "mouseup", 
302                     OpenLayers.Function.bindAsEventListener(this.onInputClick,
303                                                             context)
304                 );
305                 
306                 // create span
307                 var labelSpan = document.createElement("span");
308                 if (!baseLayer && !layer.inRange) {
309                     labelSpan.style.color = "gray";
310                 }
311                 labelSpan.innerHTML = layer.name;
312                 labelSpan.style.verticalAlign = (baseLayer) ? "bottom" 
313                                                             : "baseline";
314                 OpenLayers.Event.observe(labelSpan, "click", 
315                     OpenLayers.Function.bindAsEventListener(this.onInputClick,
316                                                             context)
317                 );
318                 // create line break
319                 var br = document.createElement("br");
320     
321                 
322                 var groupArray = (baseLayer) ? this.baseLayers
323                                              : this.dataLayers;
324                 groupArray.push({
325                     'layer': layer,
326                     'inputElem': inputElem,
327                     'labelSpan': labelSpan
328                 });
329                                                      
330     
331                 var groupDiv = (baseLayer) ? this.baseLayersDiv
332                                            : this.dataLayersDiv;
333                 groupDiv.appendChild(inputElem);
334                 groupDiv.appendChild(labelSpan);
335                 groupDiv.appendChild(br);
336             }
337         }
338
339         // if no overlays, dont display the overlay label
340         this.dataLbl.style.display = (containsOverlays) ? "" : "none";        
341         
342         // if no baselayers, dont display the baselayer label
343         this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";        
344
345         return this.div;
346     },
347
348     /** 
349      * Method:
350      * A label has been clicked, check or uncheck its corresponding input
351      * 
352      * Parameters:
353      * e - {Event} 
354      *
355      * Context:  
356      *  - {DOMElement} inputElem
357      *  - {<OpenLayers.Control.LayerSwitcher>} layerSwitcher
358      *  - {<OpenLayers.Layer>} layer
359      */
360
361     onInputClick: function(e) {
362
363         if (!this.inputElem.disabled) {
364             if (this.inputElem.type == "radio") {
365                 this.inputElem.checked = true;
366                 this.layer.map.setBaseLayer(this.layer);
367             } else {
368                 this.inputElem.checked = !this.inputElem.checked;
369                 this.layerSwitcher.updateMap();
370             }
371         }
372         OpenLayers.Event.stop(e);
373     },
374     
375     /**
376      * Method: onLayerClick
377      * Need to update the map accordingly whenever user clicks in either of
378      *     the layers.
379      * 
380      * Parameters: 
381      * e - {Event} 
382      */
383     onLayerClick: function(e) {
384         this.updateMap();
385     },
386
387
388     /** 
389      * Method: updateMap
390      * Cycles through the loaded data and base layer input arrays and makes
391      *     the necessary calls to the Map object such that that the map's 
392      *     visual state corresponds to what the user has selected in 
393      *     the control.
394      */
395     updateMap: function() {
396
397         // set the newly selected base layer        
398         for(var i=0, len=this.baseLayers.length; i<len; i++) {
399             var layerEntry = this.baseLayers[i];
400             if (layerEntry.inputElem.checked) {
401                 this.map.setBaseLayer(layerEntry.layer, false);
402             }
403         }
404
405         // set the correct visibilities for the overlays
406         for(var i=0, len=this.dataLayers.length; i<len; i++) {
407             var layerEntry = this.dataLayers[i];   
408             layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
409         }
410
411     },
412
413     /** 
414      * Method: maximizeControl
415      * Set up the labels and divs for the control
416      * 
417      * Parameters:
418      * e - {Event} 
419      */
420     maximizeControl: function(e) {
421
422         //HACK HACK HACK - find a way to auto-size this layerswitcher
423         this.div.style.width = "20em";
424         this.div.style.height = "";
425
426         this.showControls(false);
427
428         if (e != null) {
429             OpenLayers.Event.stop(e);                                            
430         }
431     },
432     
433     /** 
434      * Method: minimizeControl
435      * Hide all the contents of the control, shrink the size, 
436      *     add the maximize icon
437      *
438      * Parameters:
439      * e - {Event} 
440      */
441     minimizeControl: function(e) {
442
443         this.div.style.width = "0px";
444         this.div.style.height = "0px";
445
446         this.showControls(true);
447
448         if (e != null) {
449             OpenLayers.Event.stop(e);                                            
450         }
451     },
452
453     /**
454      * Method: showControls
455      * Hide/Show all LayerSwitcher controls depending on whether we are
456      *     minimized or not
457      * 
458      * Parameters:
459      * minimize - {Boolean}
460      */
461     showControls: function(minimize) {
462
463         this.maximizeDiv.style.display = minimize ? "" : "none";
464         this.minimizeDiv.style.display = minimize ? "none" : "";
465
466         this.layersDiv.style.display = minimize ? "none" : "";
467     },
468     
469     /** 
470      * Method: loadContents
471      * Set up the labels and divs for the control
472      */
473     loadContents: function() {
474
475         //configure main div
476         this.div.style.position = "absolute";
477         this.div.style.top = "25px";
478         this.div.style.right = "0px";
479         this.div.style.left = "";
480         this.div.style.fontFamily = "sans-serif";
481         this.div.style.fontWeight = "bold";
482         this.div.style.marginTop = "3px";
483         this.div.style.marginLeft = "3px";
484         this.div.style.marginBottom = "3px";
485         this.div.style.fontSize = "smaller";   
486         this.div.style.color = "white";   
487         this.div.style.backgroundColor = "transparent";
488     
489         OpenLayers.Event.observe(this.div, "mouseup", 
490             OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
491         OpenLayers.Event.observe(this.div, "click",
492                       this.ignoreEvent);
493         OpenLayers.Event.observe(this.div, "mousedown",
494             OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
495         OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);
496
497
498         // layers list div        
499         this.layersDiv = document.createElement("div");
500         this.layersDiv.id = this.id + "_layersDiv";
501         this.layersDiv.style.paddingTop = "5px";
502         this.layersDiv.style.paddingLeft = "10px";
503         this.layersDiv.style.paddingBottom = "5px";
504         this.layersDiv.style.paddingRight = "75px";
505         this.layersDiv.style.backgroundColor = this.activeColor;        
506
507         // had to set width/height to get transparency in IE to work.
508         // thanks -- http://jszen.blogspot.com/2005/04/ie6-opacity-filter-caveat.html
509         //
510         this.layersDiv.style.width = "100%";
511         this.layersDiv.style.height = "100%";
512
513
514         this.baseLbl = document.createElement("div");
515         this.baseLbl.innerHTML = OpenLayers.i18n("baseLayer");
516         this.baseLbl.style.marginTop = "3px";
517         this.baseLbl.style.marginLeft = "3px";
518         this.baseLbl.style.marginBottom = "3px";
519         
520         this.baseLayersDiv = document.createElement("div");
521         this.baseLayersDiv.style.paddingLeft = "10px";
522         /*OpenLayers.Event.observe(this.baseLayersDiv, "click", 
523             OpenLayers.Function.bindAsEventListener(this.onLayerClick, this));
524         */
525                      
526
527         this.dataLbl = document.createElement("div");
528         this.dataLbl.innerHTML = OpenLayers.i18n("overlays");
529         this.dataLbl.style.marginTop = "3px";
530         this.dataLbl.style.marginLeft = "3px";
531         this.dataLbl.style.marginBottom = "3px";
532         
533         this.dataLayersDiv = document.createElement("div");
534         this.dataLayersDiv.style.paddingLeft = "10px";
535
536         if (this.ascending) {
537             this.layersDiv.appendChild(this.baseLbl);
538             this.layersDiv.appendChild(this.baseLayersDiv);
539             this.layersDiv.appendChild(this.dataLbl);
540             this.layersDiv.appendChild(this.dataLayersDiv);
541         } else {
542             this.layersDiv.appendChild(this.dataLbl);
543             this.layersDiv.appendChild(this.dataLayersDiv);
544             this.layersDiv.appendChild(this.baseLbl);
545             this.layersDiv.appendChild(this.baseLayersDiv);
546         }    
547  
548         this.div.appendChild(this.layersDiv);
549
550         OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
551                                         bgColor: "transparent",
552                                         color: this.activeColor,
553                                         blend: false});
554
555         OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);
556
557         var imgLocation = OpenLayers.Util.getImagesLocation();
558         var sz = new OpenLayers.Size(18,18);        
559
560         // maximize button div
561         var img = imgLocation + 'layer-switcher-maximize.png';
562         this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
563                                     "OpenLayers_Control_MaximizeDiv", 
564                                     null, 
565                                     sz, 
566                                     img, 
567                                     "absolute");
568         this.maximizeDiv.style.top = "5px";
569         this.maximizeDiv.style.right = "0px";
570         this.maximizeDiv.style.left = "";
571         this.maximizeDiv.style.display = "none";
572         OpenLayers.Event.observe(this.maximizeDiv, "click", 
573             OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
574         );
575         
576         this.div.appendChild(this.maximizeDiv);
577
578         // minimize button div
579         var img = imgLocation + 'layer-switcher-minimize.png';
580         var sz = new OpenLayers.Size(18,18);        
581         this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
582                                     "OpenLayers_Control_MinimizeDiv", 
583                                     null, 
584                                     sz, 
585                                     img, 
586                                     "absolute");
587         this.minimizeDiv.style.top = "5px";
588         this.minimizeDiv.style.right = "0px";
589         this.minimizeDiv.style.left = "";
590         this.minimizeDiv.style.display = "none";
591         OpenLayers.Event.observe(this.minimizeDiv, "click", 
592             OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
593         );
594
595         this.div.appendChild(this.minimizeDiv);
596     },
597     
598     /** 
599      * Method: ignoreEvent
600      * 
601      * Parameters:
602      * evt - {Event} 
603      */
604     ignoreEvent: function(evt) {
605         OpenLayers.Event.stop(evt);
606     },
607
608     /** 
609      * Method: mouseDown
610      * Register a local 'mouseDown' flag so that we'll know whether or not
611      *     to ignore a mouseUp event
612      * 
613      * Parameters:
614      * evt - {Event}
615      */
616     mouseDown: function(evt) {
617         this.isMouseDown = true;
618         this.ignoreEvent(evt);
619     },
620
621     /** 
622      * Method: mouseUp
623      * If the 'isMouseDown' flag has been set, that means that the drag was 
624      *     started from within the LayerSwitcher control, and thus we can 
625      *     ignore the mouseup. Otherwise, let the Event continue.
626      *  
627      * Parameters:
628      * evt - {Event} 
629      */
630     mouseUp: function(evt) {
631         if (this.isMouseDown) {
632             this.isMouseDown = false;
633             this.ignoreEvent(evt);
634         }
635     },
636
637     CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
638 });