82 |
94 |
95 |
223 |
224 |
225 |
--------------------------------------------------------------------------------
/js/LayerList.js:
--------------------------------------------------------------------------------
1 | define([
2 | "dojo/_base/array",
3 | "dojo/_base/declare",
4 | "dojo/_base/lang",
5 |
6 | "esri/kernel",
7 | "dojo/uacss",
8 |
9 | "dojo/Deferred",
10 | "dojo/on",
11 |
12 | "dojo/dom-class",
13 | "dojo/dom-style",
14 | "dojo/dom-construct",
15 | "dojo/dom-attr",
16 |
17 | "dojo/i18n!./LayerList/nls/LayerList",
18 |
19 | "dijit/_WidgetBase",
20 | "dijit/_TemplatedMixin",
21 |
22 | "esri/promiseList",
23 |
24 | "esri/layerUtils",
25 |
26 | "dojo/text!./LayerList/templates/LayerList.html"
27 | ],
28 | function (
29 | array, declare, lang,
30 | esriNS, has,
31 | Deferred, on,
32 | domClass, domStyle, domConstruct, domAttr,
33 | i18n,
34 | _WidgetBase, _TemplatedMixin,
35 | promiseList,
36 | layerUtils,
37 | dijitTemplate
38 | ) {
39 |
40 | var Widget = declare([_WidgetBase, _TemplatedMixin], {
41 |
42 | templateString: dijitTemplate,
43 |
44 | defaults: {
45 | theme: "esriLayerList",
46 | map: null,
47 | layers: null,
48 | subLayers: true,
49 | showOpacitySlider: false,
50 | showLegend: false,
51 | removeUnderscores: true,
52 | visible: true
53 | },
54 |
55 | // lifecycle: 1
56 | constructor: function (options) {
57 | // mix properties
58 | var properties = lang.mixin({}, this.defaults, options);
59 | this.set(properties);
60 | // classes
61 | this.css = {
62 | container: "esriContainer",
63 | noLayers: "esriNoLayers",
64 | noLayersText: "esriNoLayersText",
65 | slider: "esriSlider",
66 | legend: "esriLegend",
67 | list: "esriList",
68 | listExpand: "esriListExpand",
69 | listVisible: "esriListVisible",
70 | subList: "esriSubList",
71 | hasSubList: "esriHasSubList",
72 | subListLayer: "esriSubListLayer",
73 | layer: "esriLayer",
74 | layerScaleInvisible: "esriScaleInvisible",
75 | title: "esriTitle",
76 | titleContainer: "esriTitleContainer",
77 | checkbox: "esriCheckbox",
78 | label: "esriLabel",
79 | button: "esriButton",
80 | content: "esriContent",
81 | clear: "esriClear"
82 | };
83 | },
84 |
85 | postCreate: function () {
86 | this.inherited(arguments);
87 | var _self = this;
88 | // when checkbox is clicked
89 | this.own(on(this._layersNode, "." + this.css.checkbox + ":click", function () {
90 | var data, subData;
91 | // layer index
92 | data = domAttr.get(this, "data-layer-index");
93 | // subLayer index
94 | subData = domAttr.get(this, "data-sublayer-index");
95 | // expand/collapse if necessary
96 | _self._toggleState(data, subData);
97 | // toggle layer visibility
98 | _self._toggleLayer(data, subData);
99 | }));
100 | },
101 |
102 | // start widget. called by user
103 | startup: function () {
104 | this.inherited(arguments);
105 | this._mapLoaded(this.map).then(lang.hitch(this, this._init));
106 | },
107 |
108 | // connections/subscriptions will be cleaned up during the destroy() lifecycle phase
109 | destroy: function () {
110 | this._removeEvents();
111 | this.inherited(arguments);
112 | },
113 |
114 | /* ---------------- */
115 | /* Public Functions */
116 | /* ---------------- */
117 |
118 | refresh: function () {
119 | // all layer info
120 | var layers = this.layers;
121 | // store nodes here
122 | this._nodes = [];
123 | var promises = [];
124 | // if we got layers
125 | if (layers && layers.length) {
126 | for (var i = 0; i < layers.length; i++) {
127 | promises.push(this._layerLoaded(i));
128 | }
129 | }
130 | // wait for layers to load or fail
131 | return promiseList(promises).always(lang.hitch(this, function (response) {
132 | this._loadedLayers = response;
133 | this._removeEvents();
134 | this._createLayerNodes();
135 | this._setLayerEvents();
136 | this.emit("refresh");
137 | }));
138 | },
139 |
140 | /* ---------------- */
141 | /* Private Functions */
142 | /* ---------------- */
143 |
144 | _mapLoaded: function (map) {
145 | var def = new Deferred();
146 | if (map) {
147 | // when map is loaded
148 | if (map.loaded) {
149 | def.resolve();
150 | } else {
151 | on.once(map, "load", lang.hitch(this, function () {
152 | def.resolve();
153 | }));
154 | }
155 | } else {
156 | def.resolve();
157 | }
158 | return def.promise;
159 | },
160 |
161 | _layerLoaded: function (layerIndex) {
162 | var layers = this.layers;
163 | var layerInfo = layers[layerIndex];
164 | var layer = layerInfo.layer;
165 | // returned event
166 | var evt = {
167 | layer: layer,
168 | layerInfo: layerInfo,
169 | layerIndex: layerIndex
170 | };
171 | var def = new Deferred();
172 | if (layer) {
173 | if (layer.loaded) {
174 | // nothing to do
175 | def.resolve(evt);
176 | } else if (layer.loadError) {
177 | def.reject(layer.loadError);
178 | } else {
179 | var loadedEvent, errorEvent;
180 | // once layer is loaded
181 | loadedEvent = on.once(layer, "load", lang.hitch(this, function () {
182 | errorEvent.remove();
183 | def.resolve(evt);
184 | }));
185 | // error occurred loading layer
186 | errorEvent = on.once(layer, "error", lang.hitch(this, function (error) {
187 | loadedEvent.remove();
188 | def.reject(error);
189 | }));
190 | }
191 | } else {
192 | def.resolve(evt);
193 | }
194 | return def.promise;
195 | },
196 |
197 | _checkboxStatus: function (layerInfo) {
198 | return !!layerInfo.visibility;
199 | },
200 |
201 | _WMSVisible: function (layerInfo, subLayerInfo) {
202 | var visibleLayers = [];
203 | if (layerInfo && layerInfo.layer) {
204 | visibleLayers = layerInfo.layer.visibleLayers;
205 | }
206 | return array.indexOf(visibleLayers, subLayerInfo.name) > -1;
207 | },
208 |
209 | _subCheckboxStatus: function (layerInfo, subLayerInfo) {
210 | var checked;
211 | var layerType = layerInfo.layer.declaredClass;
212 | switch (layerType) {
213 | case "esri.layers.KMLLayer":
214 | checked = subLayerInfo.visible;
215 | break;
216 | case "esri.layers.WMSLayer":
217 | checked = this._WMSVisible(layerInfo, subLayerInfo);
218 | break;
219 | default:
220 | checked = subLayerInfo.defaultVisibility;
221 | }
222 | return checked;
223 | },
224 |
225 | _getLayerTitle: function (e) {
226 | var title = "",
227 | layer = e.layer,
228 | layerInfo = e.layerInfo;
229 | // get best title
230 | if (layerInfo && layerInfo.title) {
231 | title = layerInfo.title;
232 | } else if (layer && layer.arcgisProps && layer.arcgisProps.title) {
233 | title = layer.arcgisProps.title;
234 | } else if (layer && layer.name) {
235 | title = layer.name;
236 | } else if (layerInfo && layerInfo.id) {
237 | title = layerInfo.id;
238 | } else if (layer && layer.id) {
239 | title = layer.id;
240 | }
241 | // optionally remove underscores
242 | return this.removeUnderscores ? title.replace(/_/g, " ") : title;
243 | },
244 |
245 | _showSublayers: function (layerInfo) {
246 | return layerInfo.hasOwnProperty("subLayers") ? layerInfo.subLayers : this.subLayers;
247 | },
248 |
249 | _opacityChange: function (value) {
250 | if (this.layer) {
251 | this.layer.setOpacity(value);
252 | } else if (this.layers) {
253 | for (var i = 0; i < this.layers.length; i++) {
254 | if (this.layers[i].layerObject) {
255 | this.layers[i].layerObject.setOpacity(value);
256 | }
257 | }
258 | }
259 | },
260 |
261 | _legend: function (titleNode, layerInfo) {
262 | require(["esri/dijit/Legend"], lang.hitch(this, function (Legend) {
263 | var legendInfo = [layerInfo];
264 | if (layerInfo && layerInfo.featureCollection && layerInfo.featureCollection.layers) {
265 | legendInfo = layerInfo.featureCollection.layers;
266 | for (var i = 0; i < legendInfo.length; i++) {
267 | legendInfo[i].layer = legendInfo[i].layerObject;
268 | }
269 | }
270 | var legendDiv = domConstruct.create("div", {
271 | className: this.css.legend
272 | }, titleNode);
273 | var legend = new Legend({
274 | map: this.map,
275 | layerInfos: legendInfo
276 | }, domConstruct.create("div"));
277 | domConstruct.place(legend.domNode, legendDiv);
278 | legend.startup();
279 | }));
280 | },
281 |
282 | _slider: function (titleNode, layer, layers, opacity) {
283 | require(["dijit/form/HorizontalSlider"], lang.hitch(this, function (HorizontalSlider) {
284 | var sliderDiv = domConstruct.create("div", {
285 | className: this.css.slider
286 | }, titleNode);
287 | var slider = new HorizontalSlider({
288 | showButtons: false,
289 | minimum: 0.1,
290 | maximum: 1,
291 | layer: layer,
292 | layers: layers,
293 | discreteValues: 0.1,
294 | value: opacity,
295 | onChange: this._opacityChange
296 | }, domConstruct.create("div"));
297 | domConstruct.place(slider.domNode, sliderDiv);
298 | slider.startup();
299 | }));
300 | },
301 |
302 | _createLayerNodes: function () {
303 | // clear node
304 | this._layersNode.innerHTML = "";
305 | this._noLayersNode.innerHTML = "";
306 | domClass.remove(this._container, this.css.noLayers);
307 | var loadedLayers = this._loadedLayers;
308 | if (loadedLayers && loadedLayers.length) {
309 | // create nodes for each layer
310 | for (var i = 0; i < loadedLayers.length; i++) {
311 | var response = loadedLayers[i];
312 | if (response) {
313 | var layer = response.layer;
314 | var layerIndex = response.layerIndex;
315 | var layerInfo = response.layerInfo;
316 | if (layerInfo) {
317 | if (layerInfo.featureCollection && !layerInfo.hasOwnProperty("visibility")) {
318 | var firstLayer = layerInfo.featureCollection.layers[0];
319 | if (firstLayer && firstLayer.layerObject) {
320 | layerInfo.visibility = firstLayer.layerObject.visible;
321 | }
322 | }
323 | // set visibility on layer info if not set
324 | if (layer && !layerInfo.hasOwnProperty("visibility")) {
325 | layerInfo.visibility = layerInfo.layer.visible;
326 | }
327 | // set layer info id
328 | if (layer && !layerInfo.hasOwnProperty("id")) {
329 | layerInfo.id = layerInfo.layer.id;
330 | }
331 | var subLayers;
332 | // layer node
333 | var layerNode = domConstruct.create("li", {
334 | className: this.css.layer
335 | });
336 | // currently visible layer
337 | if (layer && !layer.visibleAtMapScale) {
338 | domClass.add(layerNode, this.css.layerScaleInvisible);
339 | }
340 | domConstruct.place(layerNode, this._layersNode, "first");
341 | // title of layer
342 | var titleNode = domConstruct.create("div", {
343 | className: this.css.title
344 | }, layerNode);
345 | // nodes for subLayers
346 | var subNodes = [];
347 | var layerType;
348 | if (layer) {
349 | layerType = layer.declaredClass;
350 | }
351 | // get parent layer checkbox status
352 | var status = this._checkboxStatus(layerInfo);
353 | // title container
354 | var titleContainerNode = domConstruct.create("div", {
355 | className: this.css.titleContainer
356 | }, titleNode);
357 | var id = this.id + "_checkbox_" + layerIndex;
358 | // Title checkbox
359 | var checkboxNode = domConstruct.create("input", {
360 | type: "checkbox",
361 | id: id,
362 | "data-layer-index": layerIndex,
363 | className: this.css.checkbox
364 | }, titleContainerNode);
365 | domAttr.set(checkboxNode, "checked", status);
366 | // optional button icon
367 | var buttonNode;
368 | if (layerInfo.button) {
369 | buttonNode = domConstruct.create("div", {
370 | className: this.css.button
371 | }, titleContainerNode);
372 | domConstruct.place(layerInfo.button, buttonNode);
373 | }
374 | // Title text
375 | var title = this._getLayerTitle(response);
376 | var labelNode = domConstruct.create("label", {
377 | className: this.css.label,
378 | textContent: title
379 | }, titleContainerNode);
380 | domAttr.set(labelNode, "for", id);
381 | // clear css
382 | var clearNode = domConstruct.create("div", {
383 | className: this.css.clear
384 | }, titleContainerNode);
385 | // opacity slider
386 | if (layerInfo.hasOwnProperty("showOpacitySlider") ? layerInfo.showOpacitySlider : this.showOpacitySlider) {
387 | var layers, opacity;
388 | if (!layer && layerInfo.featureCollection) {
389 | layers = layerInfo.featureCollection.layers;
390 | opacity = layerInfo.featureCollection.layers[0].opacity;
391 | } else {
392 | opacity = layer.opacity;
393 | }
394 | this._slider(titleNode, layer, layers, opacity);
395 | }
396 | // legend
397 | if (layerInfo.hasOwnProperty("showLegend") ? layerInfo.showLegend : this.showLegend) {
398 | this._legend(titleNode, layerInfo);
399 | }
400 | // optional custom content
401 | var contentNode;
402 | if (layerInfo.content) {
403 | contentNode = domConstruct.create("div", {
404 | className: this.css.content
405 | }, titleNode);
406 | domConstruct.place(layerInfo.content, contentNode);
407 | }
408 | // lets save all the nodes for events
409 | var nodesObj = {
410 | checkbox: checkboxNode,
411 | title: titleNode,
412 | titleContainer: titleContainerNode,
413 | label: labelNode,
414 | layer: layerNode,
415 | clear: clearNode,
416 | button: buttonNode,
417 | content: contentNode,
418 | subNodes: subNodes
419 | };
420 | this._nodes[layerIndex] = nodesObj;
421 | domClass.toggle(layerNode, this.css.listVisible, status);
422 | if (layer) {
423 | // subLayers from thier info. Also WMS layers
424 | subLayers = layer.layerInfos;
425 | // KML subLayers
426 | if (layerType === "esri.layers.KMLLayer") {
427 | subLayers = layer.folders;
428 | }
429 | // if we have more than one subLayer and layer is of valid type for subLayers
430 | if (this._showSublayers(layerInfo) && layerType !== "esri.layers.ArcGISTiledMapServiceLayer" && subLayers && subLayers.length) {
431 | domClass.add(layerNode, this.css.hasSubList);
432 | domClass.toggle(layerNode, this.css.listExpand, status);
433 | // create subLayer list
434 | var subListNode = domConstruct.create("ul", {
435 | className: this.css.subList
436 | }, layerNode);
437 | var oneSubLayerOn;
438 | var subSubLists = [];
439 | // create each subLayer item
440 | for (var j = 0; j < subLayers.length; j++) {
441 | // subLayer info
442 | var subLayer = subLayers[j];
443 | var subLayerIndex;
444 | var parentId = -1;
445 | var subSubListNode = null;
446 | // Dynamic Map Service
447 | if (layerType === "esri.layers.ArcGISDynamicMapServiceLayer") {
448 | subLayerIndex = subLayer.id;
449 | parentId = subLayer.parentLayerId;
450 | }
451 | // KML
452 | else if (layerType === "esri.layers.KMLLayer") {
453 | subLayerIndex = subLayer.id;
454 | parentId = subLayer.parentFolderId;
455 | }
456 | // WMS
457 | else if (layerType === "esri.layers.WMSLayer") {
458 | subLayerIndex = subLayer.name;
459 | parentId = -1;
460 | }
461 | // place subLayers not in the root
462 | if (parentId !== -1) {
463 | var parent = this._nodes[layerIndex].subNodes[parentId];
464 | if (subSubLists[parentId]) {
465 | subSubListNode = subSubLists[parentId];
466 | } else {
467 | var parentLayer = parent.subLayer;
468 | subSubListNode = domConstruct.create("ul", {
469 | className: this.css.subList
470 | }, parentLayer);
471 | domClass.add(parentLayer, this.css.hasSubList);
472 | domClass.toggle(parentLayer, [this.css.listVisible, this.css.listExpand], subChecked);
473 | subSubLists[parentId] = subSubListNode;
474 | }
475 | }
476 | // default checked state
477 | var subChecked = this._subCheckboxStatus(layerInfo, subLayer);
478 | if (subChecked && !oneSubLayerOn) {
479 | oneSubLayerOn = true;
480 | }
481 | var subId = this.id + "_checkbox_sub_" + layerIndex + "_" + subLayerIndex;
482 | // list item node
483 | var subLayerNode = domConstruct.create("li", {
484 | className: this.css.subListLayer
485 | }, subSubListNode || subListNode);
486 | // title of subLayer layer
487 | var subTitleNode = domConstruct.create("div", {
488 | className: this.css.title
489 | }, subLayerNode);
490 | // subLayer title container
491 | var subTitleContainerNode = domConstruct.create("div", {
492 | className: this.css.titleContainer
493 | }, subTitleNode);
494 | // subLayer checkbox
495 | var subCheckboxNode = domConstruct.create("input", {
496 | type: "checkbox",
497 | id: subId,
498 | "data-layer-index": layerIndex,
499 | "data-sublayer-index": subLayerIndex,
500 | className: this.css.checkbox
501 | }, subTitleContainerNode);
502 | domAttr.set(subCheckboxNode, "checked", subChecked);
503 | // subLayer Title text
504 | var subTitle = subLayer.title || subLayer.name || "";
505 | var subLabelNode = domConstruct.create("label", {
506 | className: this.css.label,
507 | textContent: subTitle
508 | }, subTitleContainerNode);
509 | domAttr.set(subLabelNode, "for", subId);
510 | // subLayer clear css
511 | var subClearNode = domConstruct.create("div", {
512 | className: this.css.clear
513 | }, subTitleContainerNode);
514 | // object of subLayer nodes
515 | var subNode = {
516 | subList: subListNode,
517 | subSubList: subSubListNode,
518 | subLayer: subLayerNode,
519 | subTitle: subTitleNode,
520 | subTitleContainer: subTitleContainerNode,
521 | subCheckbox: subCheckboxNode,
522 | subLabel: subLabelNode,
523 | subClear: subClearNode
524 | };
525 | // add node to array
526 | subNodes[subLayerIndex] = subNode;
527 | }
528 | }
529 | }
530 | }
531 | }
532 | }
533 | } else {
534 | domClass.add(this._container, this.css.noLayers);
535 | domAttr.set(this._noLayersNode, "textContent", i18n.widgets.layerList.noLayers);
536 | }
537 | },
538 |
539 | _removeEvents: function () {
540 | // layer visibility events
541 | if (this._layerEvents && this._layerEvents.length) {
542 | for (var i = 0; i < this._layerEvents.length; i++) {
543 | this._layerEvents[i].remove();
544 | }
545 | }
546 | this._layerEvents = [];
547 | },
548 |
549 | _emitToggle: function (layerIndex, subLayerIndex, visible) {
550 | // emit event
551 | this.emit("toggle", {
552 | layerIndex: layerIndex,
553 | subLayerIndex: subLayerIndex,
554 | visible: visible
555 | });
556 | },
557 |
558 | _toggleVisible: function (index, visible) {
559 | var node = this._nodes[index].checkbox;
560 | domClass.toggle(this._nodes[index].layer, this.css.listVisible, visible);
561 | var checked = domAttr.get(node, "checked");
562 | if (domClass.contains(this._nodes[index].layer, this.css.hasSubList)) {
563 | domClass.toggle(this._nodes[index].layer, this.css.listExpand, checked);
564 | }
565 | if (checked !== visible) {
566 | // update checkbox and layer visibility classes
567 | domAttr.set(node, "checked", visible);
568 | this._emitToggle(index, null, visible);
569 | }
570 | },
571 |
572 | _layerVisChangeEvent: function (response, featureCollection, subLayerIndex) {
573 | var layer;
574 | // layer is a feature collection
575 | if (featureCollection) {
576 | // all subLayers
577 | var fcLayers = response.layerInfo.featureCollection.layers;
578 | // current layer object to setup event for
579 | layer = fcLayers[subLayerIndex].layer;
580 | } else {
581 | // layer object for event
582 | layer = response.layer;
583 | }
584 | // layer visibility changes
585 | var visChange = on(layer, "visibility-change", lang.hitch(this, function (evt) {
586 | if (featureCollection) {
587 | this._featureCollectionVisible(response.layerIndex, evt.visible);
588 | } else {
589 | // update checkbox and layer visibility classes
590 | this._toggleVisible(response.layerIndex, evt.visible);
591 | }
592 | }));
593 | this._layerEvents.push(visChange);
594 | if (!featureCollection) {
595 | // scale visibility changes
596 | var scaleVisChange = on(layer, "scale-visibility-change", lang.hitch(this, function (evt) {
597 | var visible = evt.target.visibleAtMapScale;
598 | domClass.toggle(this._nodes[response.layerIndex].layer, this.css.layerScaleInvisible, !visible);
599 | }));
600 | this._layerEvents.push(scaleVisChange);
601 | // show out of scale range for sublayers if its a map service
602 | if (layer.declaredClass === "esri.layers.ArcGISDynamicMapServiceLayer") {
603 | // on map LOD change
604 | var zoomChange = on(this.map, "zoom-end", lang.hitch(this, function () {
605 | // gray out invisible sublayers
606 | this._subLayerScale(response);
607 | }));
608 | this._layerEvents.push(zoomChange);
609 | // gray out invisible sublayers
610 | this._subLayerScale(response);
611 | }
612 | }
613 | },
614 |
615 | // gray out invisible sublayers
616 | _subLayerScale: function (response) {
617 | var layer = response.layer;
618 | var dynLayerInfos = layer.createDynamicLayerInfosFromLayerInfos();
619 | var layersInScale = layerUtils._getLayersForScale(this.map.getScale(), dynLayerInfos);
620 | array.forEach(dynLayerInfos, lang.hitch(this, function (layerInfo) {
621 | if (!layerInfo.subLayerIds) { // skip group layers
622 | var subLayerId = layerInfo.id;
623 | var subLayerNode = this._nodes[response.layerIndex].subNodes[subLayerId].subLayer;
624 | var scaleInvisible = false;
625 | // if visible and in scale
626 | if (array.indexOf(layersInScale, subLayerId) === -1) {
627 | scaleInvisible = true;
628 | }
629 | domClass.toggle(subLayerNode, this.css.layerScaleInvisible, scaleInvisible);
630 | }
631 | }));
632 | },
633 |
634 | _layerEvent: function (response) {
635 | var layerInfo = response.layerInfo;
636 | // feature collection layer
637 | if (layerInfo.featureCollection && layerInfo.featureCollection.layers && layerInfo.featureCollection.layers.length) {
638 | // feature collection layers
639 | var fsLayers = layerInfo.featureCollection.layers;
640 | if (fsLayers && fsLayers.length) {
641 | // make event for each layer
642 | for (var i = 0; i < fsLayers.length; i++) {
643 | // layer visibility changes
644 | this._layerVisChangeEvent(response, true, i);
645 | }
646 | }
647 | } else {
648 | // layer visibility changes
649 | this._layerVisChangeEvent(response);
650 | // todo: need to figure out way to support visibility change of map service, WMS, & KML sublayers outside of widget
651 | }
652 | },
653 |
654 | _getVisibleLayers: function (layer, subLayerIndex) {
655 | var layerInfos = layer.layerInfos;
656 | var i;
657 | // array for setting visible layers
658 | var visibleLayers = [-1];
659 |
660 | if (typeof subLayerIndex !== "undefined") {
661 | var newVis = !layerInfos[subLayerIndex].defaultVisibility;
662 | // reverse current visibility of sublayer
663 | layerInfos[subLayerIndex].defaultVisibility = newVis;
664 | }
665 |
666 | // for each sublayer
667 | for (i = 0; i < layerInfos.length; i++) {
668 | var info = layerInfos[i];
669 | // push to visible layers if it's visible
670 | if (info.defaultVisibility) {
671 | visibleLayers.push(info.id);
672 | var negative = array.lastIndexOf(visibleLayers, -1);
673 | if (negative !== -1) {
674 | visibleLayers.splice(negative, 1);
675 | }
676 | }
677 | }
678 | //Now that the array of visibleLayer Ids is assembled,
679 | //strip off Ids of invisible child layers, and
680 | //Ids of group layers (group layer Ids should not be submitted
681 | //in .setVisible() or loss of toggle control madness ensues.
682 | //Remove layers whos parents are not visible:
683 | var noInvisibleParents = [];
684 | for (i = 0; i < visibleLayers.length; i++) {
685 | var id = visibleLayers[i];
686 | var hasParentsInVisibleArray = this._allIdsPresent(layer, id, visibleLayers);
687 | if (hasParentsInVisibleArray) {
688 | noInvisibleParents.push(id);
689 | }
690 | }
691 | var noGroups = [];
692 | for (var j = 0; j < noInvisibleParents.length; j++) {
693 | var lyrInfo = this._getLayerInfo(layer, noInvisibleParents[j]);
694 | if (lyrInfo && lyrInfo.subLayerIds === null) {
695 | noGroups.push(noInvisibleParents[j]);
696 | }
697 | }
698 | // note: set -1 if array is empty.
699 | if (!noGroups.length) {
700 | noGroups = [-1];
701 | }
702 | return noGroups;
703 | },
704 |
705 | _toggleState: function (layerIndex, subLayerIndex) {
706 | var layerNode, checkboxNode;
707 | layerIndex = parseInt(layerIndex, 10);
708 | var layerNodes = this._nodes[layerIndex];
709 | if (subLayerIndex !== null) {
710 | subLayerIndex = parseInt(subLayerIndex, 10);
711 | layerNode = layerNodes.subNodes[subLayerIndex].subLayer;
712 | checkboxNode = layerNodes.subNodes[subLayerIndex].subCheckbox;
713 | } else {
714 | layerNode = layerNodes.layer;
715 | checkboxNode = layerNodes.checkbox;
716 | }
717 | var status = domAttr.get(checkboxNode, "checked");
718 | if (domClass.contains(layerNode, this.css.hasSubList)) {
719 | domClass.toggle(layerNode, this.css.listExpand, status);
720 | }
721 | domClass.toggle(layerNode, this.css.listVisible, status);
722 | },
723 |
724 | _toggleLayer: function (layerIndex, subLayerIndex) {
725 | // all layers
726 | if (this.layers && this.layers.length) {
727 | var newVis;
728 | layerIndex = parseInt(layerIndex, 10);
729 | var layerInfo = this.layers[layerIndex];
730 | var layer = layerInfo.layer;
731 | var layerType;
732 | if (layer) {
733 | layerType = layer.declaredClass;
734 | }
735 | var featureCollection = layerInfo.featureCollection;
736 | var visibleLayers;
737 | var i;
738 | // feature collection layer
739 | if (featureCollection) {
740 | // new visibility
741 | newVis = !layerInfo.visibility;
742 | // set visibility for layer reference
743 | layerInfo.visibility = newVis;
744 | // toggle all sub layers
745 | for (i = 0; i < featureCollection.layers.length; i++) {
746 | var fcLayer = featureCollection.layers[i].layerObject;
747 | // toggle to new visibility
748 | fcLayer.setVisibility(newVis);
749 | }
750 | }
751 | // layer
752 | else if (layer) {
753 | // we're toggling a sublayer
754 | if (subLayerIndex !== null) {
755 | // Map Service Layer
756 | if (layerType === "esri.layers.ArcGISDynamicMapServiceLayer") {
757 | subLayerIndex = parseInt(subLayerIndex, 10);
758 | visibleLayers = this._getVisibleLayers(layer, subLayerIndex);
759 | // set visible sublayers which are not grouped
760 | layer.setVisibleLayers(visibleLayers);
761 | }
762 | // KML Layer
763 | else if (layerType === "esri.layers.KMLLayer") {
764 | subLayerIndex = parseInt(subLayerIndex, 10);
765 | var folders = layer.folders;
766 | // for each sublayer
767 | for (i = 0; i < folders.length; i++) {
768 | var folder = folders[i];
769 | if (folder.id === subLayerIndex) {
770 | layer.setFolderVisibility(folder, !folder.visible);
771 | break;
772 | }
773 | }
774 | } else if (layerType === "esri.layers.WMSLayer") {
775 | visibleLayers = layer.visibleLayers;
776 | var found = array.indexOf(visibleLayers, subLayerIndex);
777 | if (found === -1) {
778 | visibleLayers.push(subLayerIndex);
779 | } else {
780 | visibleLayers.splice(found, 1);
781 | }
782 | layer.setVisibleLayers(visibleLayers);
783 | }
784 | }
785 | // parent map layer
786 | else {
787 | if (layerType === "esri.layers.ArcGISDynamicMapServiceLayer") {
788 | visibleLayers = this._getVisibleLayers(layer);
789 | layer.setVisibleLayers(visibleLayers);
790 | }
791 | // reverse current visibility of parent layer
792 | newVis = !layer.visible;
793 | // new visibility of parent layer
794 | layerInfo.visibility = newVis;
795 | layer.setVisibility(newVis);
796 | }
797 | }
798 | // Just layer object
799 | else {
800 | newVis = !layerInfo.visible;
801 | layerInfo.setVisibility(newVis);
802 | }
803 | // emit event
804 | this._emitToggle(layerIndex, subLayerIndex, newVis);
805 | }
806 | },
807 |
808 | _featureCollectionVisible: function (index, visible) {
809 | var layer = this.layers[index];
810 | // all layers either visible or not
811 | var equal;
812 | // feature collection layers turned on by default
813 | var visibleLayers = layer.visibleLayers;
814 | // feature collection layers
815 | var layers = layer.featureCollection.layers;
816 | // if we have layers set
817 | if (visibleLayers && visibleLayers.length) {
818 | // check if all layers have same visibility
819 | equal = array.every(visibleLayers, function (item) {
820 | // check if current layer has same as first layer
821 | return layers[item].layer.visible === visible;
822 | });
823 | } else {
824 | // check if all layers have same visibility
825 | equal = array.every(layers, function (item) {
826 | // check if current layer has same as first layer
827 | return item.layer.visible === visible;
828 | });
829 | }
830 | // all are the same
831 | if (equal) {
832 | this._toggleVisible(index, visible);
833 | }
834 | },
835 |
836 | _setLayerEvents: function () {
837 | // this function sets up all the events for layers
838 | var layers = this._loadedLayers;
839 | if (layers && layers.length) {
840 | // get all layers
841 | for (var i = 0; i < layers.length; i++) {
842 | var response = layers[i];
843 | // if we have a layer
844 | if (response.layer) {
845 | // create necessary events
846 | this._layerEvent(response);
847 | }
848 | }
849 | }
850 | },
851 |
852 | _allIdsPresent: function (layer, layerId, arrayOfIds) {
853 | //Returns false if any Ids are not present in the supplied array of Ids.
854 | var parentIds = this._walkUpLayerIds(layer, layerId);
855 | //If any of the parentIds are NOT in the arrayOfIds return false:
856 | return array.every(parentIds, function (id) {
857 | return array.indexOf(arrayOfIds, id) > -1;
858 | });
859 | },
860 |
861 | _walkUpLayerIds: function (layer, layerId) {
862 | //returns array of layerIds of all parents of layerId
863 | var layerInfo = this._getLayerInfo(layer, layerId);
864 | var parentLayerInfo;
865 | var parentLayerIds = [];
866 | if (layerInfo) {
867 | //If the current layerInfo layerInfo doesn't have a parent,
868 | //then we're at the top of the hierarchy and should return the result.
869 | while (layerInfo.parentLayerId !== -1) {
870 | //A parent exists, save the info and add to the array:
871 | parentLayerInfo = this._getLayerInfo(layer, layerInfo.parentLayerId);
872 | if (parentLayerInfo) {
873 | parentLayerIds.push(parentLayerInfo.id);
874 | }
875 | //Move up hierarchy: reassign the layerInfo to the parent. Loop.
876 | layerInfo = parentLayerInfo;
877 | }
878 | }
879 | return parentLayerIds;
880 | },
881 |
882 | _getLayerInfo: function (layer, layerId) {
883 | //Get the layerInfo for layerId from the layer:
884 | var info;
885 | for (var i = 0; i < layer.layerInfos.length; i++) {
886 | var layerInfo = layer.layerInfos[i];
887 | if (layerInfo.id === layerId) {
888 | //we have our desired layerInfo.
889 | info = layerInfo;
890 | break;
891 | }
892 | }
893 | return info;
894 | },
895 |
896 | _isSupportedLayerType: function (layer) {
897 | return layer && !layer._basemapGalleryLayerType || layer && layer._basemapGalleryLayerType !== "basemap";
898 | },
899 |
900 | _createLayerInfo: function (layer) {
901 | return {
902 | layer: layer
903 | };
904 | },
905 |
906 | _updateAllMapLayers: function () {
907 | if (this.map && (!this.layers || !this.layers.length)) {
908 | var layers = [];
909 | // get all non graphic layers
910 | array.forEach(this.map.layerIds, function (layerId) {
911 | var layer = this.map.getLayer(layerId);
912 | if (this._isSupportedLayerType(layer)) {
913 | layers.push(this._createLayerInfo(layer));
914 | }
915 | }, this);
916 | // get all graphic layers
917 | array.forEach(this.map.graphicsLayerIds, function (layerId) {
918 | var layer = this.map.getLayer(layerId);
919 | // check drawMode so we don't include layers created for pop-ups
920 | if (this._isSupportedLayerType(layer) && layer._params && layer._params.drawMode) {
921 | layers.push(this._createLayerInfo(layer));
922 | }
923 | }, this);
924 | this._set("layers", layers);
925 | }
926 | },
927 |
928 | _init: function () {
929 | this._visible();
930 | this._updateAllMapLayers();
931 | this.refresh().always(lang.hitch(this, function () {
932 | this.set("loaded", true);
933 | this.emit("load");
934 | }));
935 | },
936 |
937 | _visible: function () {
938 | if (this.visible) {
939 | domStyle.set(this.domNode, "display", "block");
940 | } else {
941 | domStyle.set(this.domNode, "display", "none");
942 | }
943 | },
944 |
945 | /* stateful properties */
946 |
947 | _setThemeAttr: function (newVal) {
948 | if (this.domNode) {
949 | domClass.remove(this.domNode, this.theme);
950 | domClass.add(this.domNode, newVal);
951 | }
952 | this._set("theme", newVal);
953 | },
954 |
955 | _setMapAttr: function (newVal) {
956 | this._set("map", newVal);
957 | if (this._created) {
958 | this._mapLoaded(this.map).then(lang.hitch(this, function () {
959 | this._updateAllMapLayers();
960 | this.refresh();
961 | }));
962 | }
963 | },
964 |
965 | _setLayersAttr: function (newVal) {
966 | this._set("layers", newVal);
967 | if (this._created) {
968 | this.refresh();
969 | }
970 | },
971 |
972 | _setRemoveUnderscoresAttr: function (newVal) {
973 | this._set("removeUnderscores", newVal);
974 | if (this._created) {
975 | this.refresh();
976 | }
977 | },
978 |
979 | _setSubLayersAttr: function (newVal) {
980 | this._set("subLayers", newVal);
981 | if (this._created) {
982 | this.refresh();
983 | }
984 | },
985 |
986 | _setShowOpacitySliderAttr: function (newVal) {
987 | this._set("showOpacitySlider", newVal);
988 | if (this._created) {
989 | this.refresh();
990 | }
991 | },
992 |
993 | _setShowLegendAttr: function (newVal) {
994 | this._set("showLegend", newVal);
995 | if (this._created) {
996 | this.refresh();
997 | }
998 | },
999 |
1000 | _setVisibleAttr: function (newVal) {
1001 | this._set("visible", newVal);
1002 | if (this._created) {
1003 | this._visible();
1004 | }
1005 | }
1006 |
1007 | });
1008 | if (has("extend-esri")) {
1009 | lang.setObject("dijit.LayerList", Widget, esriNS);
1010 | }
1011 | return Widget;
1012 | });
--------------------------------------------------------------------------------
/js/LayerList/css/LayerList.css:
--------------------------------------------------------------------------------
1 | .esriLayerList .esriList {
2 | background-color: #ededed;
3 | border-top: 1px solid #e0e0e0;
4 | }
5 |
6 | .esriLayerList .esriNoLayers .esriList {
7 | display: none;
8 | }
9 |
10 | .esriLayerList .esriNoLayersText {
11 | display: none;
12 | }
13 |
14 | .esriLayerList .esriNoLayers .esriNoLayersText {
15 | display: block;
16 | padding: 10px;
17 | }
18 |
19 | .esriLayerList .esriList,
20 | .esriLayerList .esriLayer,
21 | .esriLayerList .esriSubList,
22 | .esriLayerList .esriSubListLayer {
23 | list-style: none;
24 | margin: 0;
25 | padding: 0;
26 | }
27 |
28 | .esriLayerList .esriSubList {
29 | margin: 0 0 0 12px;
30 | display: none;
31 | }
32 |
33 | .dj_rtl .esriLayerList .esriSubList {
34 | margin: 0 20px 0 0;
35 | }
36 |
37 | .esriLayerList .esriListExpand > .esriSubList {
38 | display: block;
39 | }
40 |
41 | .esriLayerList .esriLegend {
42 | padding: 0 20px;
43 | display: none;
44 | }
45 |
46 | .esriLayerList .esriLegend .esriLegendService > table,
47 | .esriLayerList .esriLegend .esriLegendMsg {
48 | display: none;
49 | }
50 |
51 | .esriLayerList .esriListVisible .esriLegend {
52 | display: block;
53 | }
54 |
55 | .esriLayerList .esriSlider {
56 | padding: 5px 20px 15px 20px;
57 | display: none;
58 | }
59 |
60 | .esriLayerList .esriListVisible .esriSlider {
61 | display: block;
62 | }
63 |
64 | .esriLayerList .esriTitle {
65 | border-bottom: 1px solid #e0e0e0;
66 | font-size: 16px;
67 | line-height: 20px;
68 | background-color: #f8f8f8;
69 | color: #555;
70 | }
71 |
72 | .esriLayerList .esriSubList .esriTitle {
73 | border-left: 1px solid #e0e0e0;
74 | }
75 |
76 | .dj_rtl .esriLayerList .esriSubList .esriTitle {
77 | border-left: 0;
78 | border-right: 1px solid #e0e0e0;
79 | }
80 |
81 | .esriLayerList .esriLabel {
82 | display: block;
83 | padding: 10px 20px 10px 32px;
84 | margin: 0;
85 | word-wrap: break-word;
86 | }
87 |
88 | .dj_rtl .esriLayerList .esriLabel {
89 | padding: 10px 32px 10px 20px;
90 | }
91 |
92 | .esriLayerList .esriScaleInvisible .esriLabel {
93 | color: #999;
94 | }
95 |
96 | .esriLayerList .esriCheckbox {
97 | float: left;
98 | height: 16px;
99 | width: 16px;
100 | padding: 0;
101 | margin: 12px 5px 12px 10px;
102 | }
103 |
104 | .dj_rtl .esriLayerList .esriCheckbox {
105 | float: right;
106 | margin: 12px 10px 12px 5px;
107 | }
108 |
109 | .esriLayerList .esriButton {
110 | float: right;
111 | font-size: 16px;
112 | line-height: 20px;
113 | cursor: pointer;
114 | color: #999;
115 | }
116 |
117 | .esriLayerList .esriButton:hover,
118 | .esriLayerList .esriButton:active {
119 | color: #333;
120 | }
121 |
122 | .esriLayerList .esriContent {
123 | margin: 0 20px 5px 32px;
124 | }
125 |
126 | .dj_rtl .esriLayerList .esriContent {
127 | margin: 0 32px 5px 20px;
128 | }
129 |
130 | .dj_rtl .esriLayerList .esriButton {
131 | float: left;
132 | }
133 |
134 | .esriLayerList .esriClear {
135 | clear: both;
136 | }
--------------------------------------------------------------------------------
/js/LayerList/nls/LayerList.js:
--------------------------------------------------------------------------------
1 | define({
2 | root: ({
3 | widgets: {
4 | layerList: {
5 | noLayers: "No layers to display."
6 | }
7 | }
8 | })
9 | });
--------------------------------------------------------------------------------
/js/LayerList/templates/LayerList.html:
--------------------------------------------------------------------------------
1 |