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. */
6 * @requires OpenLayers/Control.js
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.
16 * To create the LayerSwitcher outside of the map, pass the Id of a html div
17 * as the first argument to the constructor.
20 * - <OpenLayers.Control>
22 OpenLayers.Control.LayerSwitcher =
23 OpenLayers.Class(OpenLayers.Control, {
26 * Property: activeColor
29 activeColor: "darkblue",
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.
49 * Property: baseLayersDiv
55 * Property: baseLayers
56 * {Array(<OpenLayers.Layer>)}
68 * Property: dataLayersDiv
74 * Property: dataLayers
75 * {Array(<OpenLayers.Layer>)}
81 * Property: minimizeDiv
87 * Property: maximizeDiv
93 * APIProperty: ascending
99 * Constructor: OpenLayers.Control.LayerSwitcher
104 initialize: function(options) {
105 OpenLayers.Control.prototype.initialize.apply(this, arguments);
106 this.layerStates = [];
112 destroy: function() {
114 OpenLayers.Event.stopObservingElement(this.div);
116 OpenLayers.Event.stopObservingElement(this.minimizeDiv);
117 OpenLayers.Event.stopObservingElement(this.maximizeDiv);
119 //clear out layers info and unregister their events
120 this.clearLayersArray("base");
121 this.clearLayersArray("data");
124 "addlayer": this.redraw,
125 "changelayer": this.redraw,
126 "removelayer": this.redraw,
127 "changebaselayer": this.redraw,
131 OpenLayers.Control.prototype.destroy.apply(this, arguments);
138 * map - {<OpenLayers.Map>}
140 setMap: function(map) {
141 OpenLayers.Control.prototype.setMap.apply(this, arguments);
144 "addlayer": this.redraw,
145 "changelayer": this.redraw,
146 "removelayer": this.redraw,
147 "changebaselayer": this.redraw,
156 * {DOMElement} A reference to the DIV DOMElement containing the
160 OpenLayers.Control.prototype.draw.apply(this);
162 // create layout divs
165 // set mode to minimize
166 if(!this.outsideViewport) {
167 this.minimizeControl();
170 // populate div with current info
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.
182 * layersType - {String}
184 clearLayersArray: function(layersType) {
185 var layers = this[layersType + "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);
193 this[layersType + "LayersDiv"].innerHTML = "";
194 this[layersType + "Layers"] = [];
199 * Method: checkRedraw
200 * Checks if the layer state has changed since the last redraw() call.
203 * {Boolean} The layer state changed since the last redraw() call.
205 checkRedraw: function() {
207 if ( !this.layerStates.length ||
208 (this.map.layers.length != this.layerStates.length) ) {
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) ) {
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.
233 * {DOMElement} A reference to the DIV DOMElement containing the control
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()) {
242 //clear out previous layers
243 this.clearLayersArray("base");
244 this.clearLayersArray("data");
246 var containsOverlays = false;
247 var containsBaseLayers = false;
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] = {
259 'visibility': layer.visibility,
260 'inRange': layer.inRange,
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;
271 if (layer.displayInLayerSwitcher) {
274 containsBaseLayers = true;
276 containsOverlays = true;
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();
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;
293 if (!baseLayer && !layer.inRange) {
294 inputElem.disabled = true;
297 'inputElem': inputElem,
299 'layerSwitcher': this
301 OpenLayers.Event.observe(inputElem, "mouseup",
302 OpenLayers.Function.bindAsEventListener(this.onInputClick,
307 var labelSpan = document.createElement("span");
308 if (!baseLayer && !layer.inRange) {
309 labelSpan.style.color = "gray";
311 labelSpan.innerHTML = layer.name;
312 labelSpan.style.verticalAlign = (baseLayer) ? "bottom"
314 OpenLayers.Event.observe(labelSpan, "click",
315 OpenLayers.Function.bindAsEventListener(this.onInputClick,
319 var br = document.createElement("br");
322 var groupArray = (baseLayer) ? this.baseLayers
326 'inputElem': inputElem,
327 'labelSpan': labelSpan
331 var groupDiv = (baseLayer) ? this.baseLayersDiv
332 : this.dataLayersDiv;
333 groupDiv.appendChild(inputElem);
334 groupDiv.appendChild(labelSpan);
335 groupDiv.appendChild(br);
339 // if no overlays, dont display the overlay label
340 this.dataLbl.style.display = (containsOverlays) ? "" : "none";
342 // if no baselayers, dont display the baselayer label
343 this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";
350 * A label has been clicked, check or uncheck its corresponding input
356 * - {DOMElement} inputElem
357 * - {<OpenLayers.Control.LayerSwitcher>} layerSwitcher
358 * - {<OpenLayers.Layer>} layer
361 onInputClick: function(e) {
363 if (!this.inputElem.disabled) {
364 if (this.inputElem.type == "radio") {
365 this.inputElem.checked = true;
366 this.layer.map.setBaseLayer(this.layer);
368 this.inputElem.checked = !this.inputElem.checked;
369 this.layerSwitcher.updateMap();
372 OpenLayers.Event.stop(e);
376 * Method: onLayerClick
377 * Need to update the map accordingly whenever user clicks in either of
383 onLayerClick: function(e) {
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
395 updateMap: function() {
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);
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);
414 * Method: maximizeControl
415 * Set up the labels and divs for the control
420 maximizeControl: function(e) {
422 //HACK HACK HACK - find a way to auto-size this layerswitcher
423 this.div.style.width = "20em";
424 this.div.style.height = "";
426 this.showControls(false);
429 OpenLayers.Event.stop(e);
434 * Method: minimizeControl
435 * Hide all the contents of the control, shrink the size,
436 * add the maximize icon
441 minimizeControl: function(e) {
443 this.div.style.width = "0px";
444 this.div.style.height = "0px";
446 this.showControls(true);
449 OpenLayers.Event.stop(e);
454 * Method: showControls
455 * Hide/Show all LayerSwitcher controls depending on whether we are
459 * minimize - {Boolean}
461 showControls: function(minimize) {
463 this.maximizeDiv.style.display = minimize ? "" : "none";
464 this.minimizeDiv.style.display = minimize ? "none" : "";
466 this.layersDiv.style.display = minimize ? "none" : "";
470 * Method: loadContents
471 * Set up the labels and divs for the control
473 loadContents: function() {
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";
489 OpenLayers.Event.observe(this.div, "mouseup",
490 OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
491 OpenLayers.Event.observe(this.div, "click",
493 OpenLayers.Event.observe(this.div, "mousedown",
494 OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
495 OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);
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;
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
510 this.layersDiv.style.width = "100%";
511 this.layersDiv.style.height = "100%";
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";
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));
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";
533 this.dataLayersDiv = document.createElement("div");
534 this.dataLayersDiv.style.paddingLeft = "10px";
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);
542 this.layersDiv.appendChild(this.dataLbl);
543 this.layersDiv.appendChild(this.dataLayersDiv);
544 this.layersDiv.appendChild(this.baseLbl);
545 this.layersDiv.appendChild(this.baseLayersDiv);
548 this.div.appendChild(this.layersDiv);
550 OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
551 bgColor: "transparent",
552 color: this.activeColor,
555 OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);
557 var imgLocation = OpenLayers.Util.getImagesLocation();
558 var sz = new OpenLayers.Size(18,18);
560 // maximize button div
561 var img = imgLocation + 'layer-switcher-maximize.png';
562 this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
563 "OpenLayers_Control_MaximizeDiv",
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)
576 this.div.appendChild(this.maximizeDiv);
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",
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)
595 this.div.appendChild(this.minimizeDiv);
599 * Method: ignoreEvent
604 ignoreEvent: function(evt) {
605 OpenLayers.Event.stop(evt);
610 * Register a local 'mouseDown' flag so that we'll know whether or not
611 * to ignore a mouseUp event
616 mouseDown: function(evt) {
617 this.isMouseDown = true;
618 this.ignoreEvent(evt);
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.
630 mouseUp: function(evt) {
631 if (this.isMouseDown) {
632 this.isMouseDown = false;
633 this.ignoreEvent(evt);
637 CLASS_NAME: "OpenLayers.Control.LayerSwitcher"