LayerSwitcher.js 19 KB

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