BR.LayersTab = BR.ControlLayers.extend({ previewLayer: null, previewBounds: null, saveLayers: [], initialize: function(layersConfig, baseLayers, overlays, options) { L.Control.Layers.prototype.initialize.call(this, baseLayers, overlays, options); this.layersConfig = layersConfig; }, addTo: function(map) { this._map = map; this.onAdd(map); L.DomUtil.get('layers-control-wrapper').appendChild(this._section); this.initOpacitySlider(map); this.initButtons(); this.initJsTree(); return this; }, onAdd: function(map) { BR.ControlLayers.prototype.onAdd.call(this, map); map.on('baselayerchange overlayadd overlayremove', this.storeActiveLayers, this); }, onRemove: function(map) { BR.ControlLayers.prototype.onRemove.call(this, map); map.off('baselayerchange overlayadd overlayremove', this.storeActiveLayers, this); }, initOpacitySlider: function(map) { var self = this; var overlayOpacitySlider = new BR.OpacitySlider({ id: 'overlay', reversed: false, orientation: 'horizontal', defaultValue: 1, title: i18next.t('layers.opacity-slider'), callback: function(opacity) { for (var i = 0; i < self._layers.length; i++) { if (!self._layers[i].overlay || !map.hasLayer(self._layers[i].layer)) { continue; } if (self._layers[i].layer.setOpacity) { self._layers[i].layer.setOpacity(opacity); } else { self._layers[i].layer.setStyle({opacity:opacity}); } } } }); L.DomUtil.get('leaflet-control-layers-overlays-opacity-slider').appendChild(overlayOpacitySlider.getElement()); }, initButtons: function() { var expandTree = function(e) { this.jstree.open_all(); }; var collapseTree = function(e) { this.jstree.close_all(); }; var toggleOptionalLayers = function(e) { var button = L.DomUtil.get('optional_layers_button'); var treeButtons = L.DomUtil.get('tree-button-group'); var div = L.DomUtil.get('optional-layers-tree'); div.hidden = !div.hidden; treeButtons.hidden = !treeButtons.hidden; button.classList.toggle('active'); if (div.hidden) { this.deselectNode(); } }; L.DomUtil.get('expand_tree_button').onclick = L.bind(expandTree, this); L.DomUtil.get('collapse_tree_button').onclick = L.bind(collapseTree, this); L.DomUtil.get('optional_layers_button').onclick = L.bind(toggleOptionalLayers, this); }, initJsTree: function() { var layerIndex = BR.layerIndex; var treeData = this.toJsTree(BR.confLayers.tree); var oldSelected = null; var onSelectNode = function(e, data) { var layerData = layerIndex[data.node.id]; var selected = data.selected[0]; if (selected !== oldSelected) { this.showPreview(layerData); oldSelected = selected; } else { data.instance.deselect_node(data.node); } }; var onDeselectNode = function(e, data) { this.hidePreview(); oldSelected = null; }; var onCheckNode = function(e, data) { var layerData = layerIndex[data.node.id]; var layer = this.createLayer(layerData); var name = layerData.properties.name; var overlay = layerData.properties.overlay; if (overlay) { this.addOverlay(layer, name); } else { this.addBaseLayer(layer, name); } this.storeDefaultLayers(); }; var onUncheckNode = function(e, data) { var obj = this.getLayerById(data.node.id); if (!obj) return; this.removeLayer(obj.layer); if (this._map.hasLayer(obj.layer)) { this._map.removeLayer(obj.layer); if (!obj.overlay) { this.activateFirstLayer(); } } this.storeDefaultLayers(); }; $('#optional-layers-tree') .on('select_node.jstree', L.bind(onSelectNode, this)) .on('deselect_node.jstree', L.bind(onDeselectNode, this)) .on('check_node.jstree', L.bind(onCheckNode, this)) .on('uncheck_node.jstree', L.bind(onUncheckNode, this)) .on('ready.jstree', function(e, data) { data.instance.open_all(); }) .jstree({ plugins: ['checkbox'], checkbox: { whole_node: false, tie_selection: false }, core: { multiple: false, themes: { icons: false, dots: false }, data: treeData } }); this.jstree = $('#optional-layers-tree').jstree(true); }, toJsTree: function(layerTree) { var data = { children: [] }; var self = this; function createRootNode(name) { var rootNode = { text: i18next.t('sidebar.layers.category.' + name, name), state: { disabled: true }, children: [] }; return rootNode; } function getText(props, parent) { var text = ''; var code = props.country_code || props.language_code; if (code && parent.text !== code) { text += '' + code + ''; } text += props.name; return text; } function createNode(id, layerData, parent) { var props = layerData.properties; var url = props.url; var keyObj = self.layersConfig.getKeyName(url); var childNode = null; // when key required only add if configured if (!keyObj || (keyObj && BR.keys[keyObj.name])) { childNode = { id: id, text: getText(props, parent), state: { checked: self.layersConfig.isDefaultLayer(id, props.overlay) } }; } return childNode; } function walkTree(inTree, outTree) { function walkObject(obj) { for (name in obj) { var value = obj[name]; var rootNode = createRootNode(name); outTree.children.push(rootNode); walkTree(value, rootNode); } } if (Array.isArray(inTree)) { for (var i = 0; i < inTree.length; i++) { var entry = inTree[i]; if (typeof entry === 'object') { walkObject(entry); } else { var layer = BR.layerIndex[entry]; if (layer) { var childNode = createNode(entry, layer, outTree); if (childNode) { outTree.children.push(childNode); } } else { console.error('Layer "' + entry + '" not found'); } } } } else { walkObject(inTree); } } walkTree(layerTree, data); return data.children; }, storeDefaultLayers: function() { var baseLayers = []; var overlays = []; for (var i = 0; i < this._layers.length; i++) { var obj = this._layers[i]; // id set in LayersConfig.createLayer var id = obj.layer.id; if (id) { if (obj.overlay) { overlays.push(id); } else { baseLayers.push(id); } } } this.layersConfig.storeDefaultLayers(baseLayers, overlays); }, createLayer: function(layerData) { var layer = this.layersConfig.createLayer(layerData); var overlay = layerData.properties.overlay; // preview z-index, like in BR.ControlLayers._addLayer layer.options.zIndex = overlay ? this._lastZIndex + 1 : 0; return layer; }, getLayerById: function(id) { for (var i = 0; i < this._layers.length; i++) { var obj = this._layers[i]; if (obj.layer.id === id) { return obj; } } return null; }, getLayerByLegacyName: function(legacyName) { var obj = null; var id = this.layersConfig.legacyNameToIdMap[legacyName]; if (id) { obj = this.getLayerById(id); } return obj; }, activateDefaultBaseLayer: function() { var index = BR.conf.defaultBaseLayerIndex || 0; var activeBaseLayer = this.getActiveBaseLayer(); if (!activeBaseLayer) { this.activateBaseLayerIndex(index); } }, saveRemoveActiveLayers: function() { this.saveLayers = this.removeActiveLayers(); }, restoreActiveLayers: function(overlaysOnly) { for (var i = 0; i < this.saveLayers.length; i++) { var obj = this.saveLayers[i]; if (!overlaysOnly || (overlaysOnly && obj.overlay)) { var hasLayer = !!this._getLayer(L.Util.stamp(obj.layer)); if (hasLayer) { this.activateLayer(obj); } else if (!obj.overlay) { // saved base layer has been removed during preview, select first this.activateFirstLayer(); } } } this.saveLayers = []; }, removePreviewLayer: function() { if (this.previewLayer && this._map.hasLayer(this.previewLayer)) { this._map.removeLayer(this.previewLayer); this.previewLayer = null; return true; } return false; }, showPreviewBounds: function(layerData) { if (layerData.geometry) { this.previewBounds = L.geoJson(layerData.geometry, { // fill/mask outside of bounds polygon with Leaflet.snogylop invert: true, // reduce unmasked areas appearing due to clipping while panning and zooming out renderer: L.svg({ padding: 1 }), color: '#333', fillOpacity: 0.4, weight: 2 }).addTo(this._map); } }, removePreviewBounds: function() { if (this.previewBounds && this._map.hasLayer(this.previewBounds)) { this._map.removeLayer(this.previewBounds); this.previewBounds = null; } }, deselectNode: function() { var selected = this.jstree.get_selected(); if (selected.length > 0) { this.jstree.deselect_node(selected[0]); } }, onBaselayerchange: function() { // execute after current input click handler, // otherwise added overlay checkbox state doesn't update setTimeout( L.Util.bind(function() { this.removePreviewBounds(); this.removePreviewLayer(); this.restoreActiveLayers(true); this.deselectNode(); }, this), 0 ); }, showPreview: function(layerData) { var layer = this.createLayer(layerData); this._map.addLayer(layer); this.removePreviewBounds(); if (!this.removePreviewLayer()) { this.saveRemoveActiveLayers(); this._map.once('baselayerchange', this.onBaselayerchange, this); } this.previewLayer = layer; this.showPreviewBounds(layerData); }, hidePreview: function(layer) { this._map.off('baselayerchange', this.onBaselayerchange, this); this.removePreviewBounds(); this.removePreviewLayer(); this.restoreActiveLayers(); }, toLayerString: function(obj) { return obj.layer.id ? obj.layer.id : obj.name; }, getLayerFromString: function(layerString) { var obj = this.getLayerById(layerString); if (!obj) { // fallback to name for custom and config layers obj = this.getLayer(layerString); if (!obj) { // legacy layer name support obj = this.getLayerByLegacyName(layerString); } } return obj; }, storeActiveLayers: function() { if (BR.Util.localStorageAvailable()) { var objList = this.getActiveLayers(); var idList = objList.map( L.bind(function(obj) { return this.toLayerString(obj); }, this) ); var str = JSON.stringify(idList); localStorage.setItem('map/activeLayers', str); } }, loadActiveLayers: function() { if (BR.Util.localStorageAvailable()) { var item = localStorage.getItem('map/activeLayers'); if (item) { var idList = JSON.parse(item); for (var i = 0; i < idList.length; i++) { var id = idList[i]; var obj = this.getLayerFromString(id); if (obj) { this.activateLayer(obj); } } } } } }); BR.layersTab = function(baseLayers, overlays, options) { return new BR.LayersTab(baseLayers, overlays, options); };