diff --git a/.gitignore b/.gitignore index e301f41afb..18d99c43e5 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,7 @@ tests/end2end/cypress/screenshots/ tests/js-units/node_modules/ tests/.env tests/end2end/playwright-report/ +tests/end2end/playwright/.auth tests/end2end/test-results /.composer diff --git a/assets/src/components/BaseLayers.js b/assets/src/components/BaseLayers.js new file mode 100644 index 0000000000..5dce4e2162 --- /dev/null +++ b/assets/src/components/BaseLayers.js @@ -0,0 +1,46 @@ +import { mainLizmap, mainEventDispatcher } from '../modules/Globals.js'; +import {html, render} from 'lit-html'; + + +export default class BaseLayers extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + + if (mainLizmap.state.baseLayers.baseLayerNames.length === 0) { + document.getElementById('switcher-baselayer').classList.add('hide'); + return; + } + + this._template = () => html` + ${mainLizmap.state.baseLayers.baseLayerNames.length > 1 + ? html` + ` + : + html`${mainLizmap.state.baseLayers.baseLayerNames[0].title}` + } + `; + + render(this._template(), this); + + mainEventDispatcher.addListener( + () => { + render(this._template(), this); + }, ['baselayers.selection.changed'] + ); + } + + disconnectedCallback() { + mainEventDispatcher.removeListener( + () => { + render(this._template(), this); + }, ['baselayers.selection.changed'] + ); + } +} \ No newline at end of file diff --git a/assets/src/components/Print.js b/assets/src/components/Print.js index ea6a00953e..b027f895d6 100644 --- a/assets/src/components/Print.js +++ b/assets/src/components/Print.js @@ -230,34 +230,17 @@ export default class Print extends HTMLElement { } // Add visible layers - for (const layer of mainLizmap._lizmap3.map.layers) { - if (((layer instanceof OpenLayers.Layer.WMS) || (layer instanceof OpenLayers.Layer.WMTS)) - && layer.getVisibility() && layer?.params?.LAYERS) { - // Get config - let configLayer; - let layerCleanName = mainLizmap._lizmap3.cleanName(layer.name); - - if (layerCleanName) { - let qgisName = mainLizmap._lizmap3.getLayerNameByCleanName(layerCleanName); - configLayer = mainLizmap.config.layers[qgisName]; - } - if (!configLayer) { - configLayer = mainLizmap.config.layers[layer.params['LAYERS']] || mainLizmap.config.layers[layer.name]; - } - // If the layer has no config or no `id` it is not a QGIS layer or group - if (!configLayer || !configLayer?.id) { - return; - } - + for (const layer of mainLizmap.state.rootMapGroup.findMapLayers().slice().reverse()) { + if (layer.visibility) { // Add layer to the list of printed layers - printLayers.push(layer.params['LAYERS']); + printLayers.push(layer.wmsName); // Optionally add layer style if needed (same order as layers ) - styleLayers.push(layer.params?.['STYLES'] || ''); + styleLayers.push(layer.wmsSelectedStyleName); // Handle qgis layer opacity otherwise client value override it - if (configLayer?.opacity) { - opacityLayers.push(parseInt(255 * layer.opacity * configLayer.opacity)); + if (layer.layerConfig?.opacity) { + opacityLayers.push(parseInt(255 * layer.opacity * layer.layerConfig.opacity)); } else { opacityLayers.push(parseInt(255 * layer.opacity)); } diff --git a/assets/src/components/Treeview.js b/assets/src/components/Treeview.js new file mode 100644 index 0000000000..4b17a18080 --- /dev/null +++ b/assets/src/components/Treeview.js @@ -0,0 +1,112 @@ +import { mainLizmap } from '../modules/Globals.js'; + +import { html, render } from 'lit-html'; +import { when } from 'lit-html/directives/when.js'; + +export default class Treeview extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + + this._onChange = () => { + render(this._layerTemplate(mainLizmap.state.layerTree), this); + }; + + this._layerTemplate = layerTreeGroupState => + html` + `; + + render(this._layerTemplate(mainLizmap.state.layerTree), this); + + mainLizmap.state.layerTree.addListener( + this._onChange, + ['layer.loading.changed', 'layer.visibility.changed', 'group.visibility.changed', 'layer.style.changed', 'layer.symbology.changed', 'layer.filter.changed', 'layer.expanded.changed', 'group.expanded.changed'] + ); + } + + disconnectedCallback() { + mainLizmap.state.layerTree.removeListener( + this._onChange, + ['layer.loading.changed', 'layer.visibility.changed', 'group.visibility.changed', 'layer.style.changed', 'layer.symbology.changed', 'layer.filter.changed', 'layer.expanded.changed', 'group.expanded.changed'] + ); + } + + _createDocLink(layerName) { + let url = lizMap.config.layers?.[layerName]?.link; + + // Test if the url is internal + const mediaRegex = /^(\/)?media\//; + if (mediaRegex.test(url)) { + const mediaLink = lizUrls.media + '?' + new URLSearchParams(lizUrls.params); + url = mediaLink + '&path=/' + url; + } + return url; + } + + _createRemoveCacheLink(layerName) { + if(!lizUrls.removeCache){ + return; + } + const removeCacheServerUrl = lizUrls.removeCache + '?' + new URLSearchParams(lizUrls.params); + return removeCacheServerUrl + '&layer=' + layerName; + } + + _removeCache(event) { + if (! confirm(lizDict['tree.button.removeCache.confirmation'])){ + event.preventDefault(); + } + } + + _toggleMetadata (layerName, isGroup){ + lizMap.events.triggerEvent("lizmapswitcheritemselected", + { 'name': layerName, 'type': isGroup ? "group" : "layer", 'selected': true} + ) + } +} \ No newline at end of file diff --git a/assets/src/index.js b/assets/src/index.js index c4b2102c9b..582f351397 100644 --- a/assets/src/index.js +++ b/assets/src/index.js @@ -13,6 +13,8 @@ import PasteGeom from './components/edition/PasteGeom.js'; import ActionSelector from './components/ActionSelector.js'; import Print from './components/Print.js'; import FullScreen from './components/FullScreen.js'; +import BaseLayers from './components/BaseLayers.js'; +import Treeview from './components/Treeview.js'; import { mainLizmap, mainEventDispatcher } from './modules/Globals.js'; @@ -35,6 +37,8 @@ lizMap.events.on({ window.customElements.define('lizmap-action-selector', ActionSelector); window.customElements.define('lizmap-print', Print); window.customElements.define('lizmap-fullscreen', FullScreen); + window.customElements.define('lizmap-base-layers', BaseLayers); + window.customElements.define('lizmap-treeview', Treeview); lizMap.mainLizmap = mainLizmap; lizMap.mainEventDispatcher = mainEventDispatcher; diff --git a/assets/src/legacy/attributeTable.js b/assets/src/legacy/attributeTable.js index 7a5a6f74bc..33a5a96755 100644 --- a/assets/src/legacy/attributeTable.js +++ b/assets/src/legacy/attributeTable.js @@ -83,14 +83,12 @@ var lizAttributeTable = function() { }; // Get existing filter if exists (via permalink) - var layer = lizMap.map.getLayersByName(cleanName)[0]; + const layer = lizMap.mainLizmap.baseLayersMap.getLayerByTypeName(cleanName); - if( layer - && 'FILTER' in layer.params - && layer.params['FILTER'] - ){ + const wmsParams = layer?.getSource?.().getParams?.(); - config.layers[configLayerName]['request_params']['filter'] = layer.params['FILTER']; + if (wmsParams?.['FILTER']) { + config.layers[configLayerName]['request_params']['filter'] = wmsParams['FILTER']; // Send signal so that getFeatureInfo takes it into account lizMap.events.triggerEvent("layerFilterParamChanged", @@ -167,13 +165,9 @@ var lizAttributeTable = function() { // Disable attribute table if limitDataToBbox and layer not visible in map if(limitDataToBbox){ - var layer = lizMap.map.getLayersByName( cleanName )[0]; - var ms = lizMap.map.getScale(); + let layer = lizMap.mainLizmap.baseLayersMap.getLayerByTypeName(cleanName); if( layer ) { - var lvisibility = layer.maxScale < ms && ms < layer.minScale; - if( !lvisibility ){ - var msg = lizDict['attributeLayers.msg.layer.not.visible']; - lizMap.addMessage( msg, 'info', true).attr('id','lizmap-attribute-message'); + if(warnResolution(layer)){ return false; } } @@ -264,6 +258,17 @@ var lizAttributeTable = function() { } $('body').css('cursor', 'auto'); + function warnResolution(layer) { + const mapResolution = lizMap.mainLizmap.baseLayersMap.getView().getResolution(); + const visibility = layer.getMaxResolution() > mapResolution && mapResolution > layer.getMinResolution(); + if( !visibility ){ + const msg = lizDict['attributeLayers.msg.layer.not.visible']; + lizMap.addMessage( msg, 'info', true).attr('id','lizmap-attribute-message'); + return true; + } + return false; + } + function getDataAndFillAttributeTable(layerName, filter, tableSelector, callBack){ let layerConfig = lizMap.config.layers[layerName]; @@ -623,13 +628,9 @@ var lizAttributeTable = function() { .removeClass('btn-warning'); // Disable if the layer is not visible - var layer = lizMap.map.getLayersByName( cleanName )[0]; - var ms = lizMap.map.getScale(); + let layer = lizMap.mainLizmap.baseLayersMap.getLayerByTypeName(cleanName); if( layer ) { - var lvisibility = layer.maxScale < ms && ms < layer.minScale; - if( !lvisibility ){ - var msg = lizDict['attributeLayers.msg.layer.not.visible']; - lizMap.addMessage( msg, 'info', true).attr('id','lizmap-attribute-message'); + if(warnResolution(layer)){ return false; } }else{ @@ -1922,11 +1923,6 @@ var lizAttributeTable = function() { lizMap.lizmapLayerFilterActive = null; // Empty layer filter - var layer = lizMap.map.getLayersByName( lizMap.cleanName(featureType) )[0]; - if( layer ) { - delete layer.params['FILTER']; - delete layer.params['FILTERTOKEN']; - } config.layers[featureType]['request_params']['filter'] = null; config.layers[featureType]['request_params']['exp_filter'] = null; config.layers[featureType]['request_params']['filtertoken'] = null; @@ -1976,7 +1972,6 @@ var lizAttributeTable = function() { // Get first elements of the pile and withdraw it from the pile var typeName = typeNamePile.shift(); - var cleanName = lizMap.cleanName(typeName); // Get corresponding filter var aFilter = typeNameFilter[typeName]; @@ -1988,16 +1983,7 @@ var lizAttributeTable = function() { applyEmptyLayerFilter( typeName, typeNamePile, typeNameFilter, typeNameDone, cascade ); } - // Change background in switcher - var trFilteredBgcolor = 'inherit'; - var displayUnFilterSwitcherTool = false; - if( aFilter ){ - trFilteredBgcolor = 'rgba(255, 171, 0, 0.4)'; - displayUnFilterSwitcherTool = true; - } - $('#switcher .treeTable tr#group-' + cleanName).css('background-color', trFilteredBgcolor ); - $('#switcher .treeTable tr#layer-' + cleanName).css('background-color', trFilteredBgcolor ); - $('#layerActionUnfilter' ).toggle( ( lizMap.lizmapLayerFilterActive !== null ) ).css( 'background-color', 'rgba(255, 171, 0, 0.4)'); + $('#layerActionUnfilter').toggle((lizMap.lizmapLayerFilterActive !== null)); } function buildChildParam( relation, typeNameDone ) { @@ -2153,7 +2139,6 @@ var lizAttributeTable = function() { var pivotParam = getPivotParam( typeNameId, attributeLayerConfig, typeNameDone ); // **3** Apply filter to the typeName and redraw if necessary - var layer = lizMap.map.getLayersByName( lizMap.cleanName(typeName) )[0]; layerConfig['request_params']['filter'] = null; layerConfig['request_params']['exp_filter'] = null; layerConfig['request_params']['filtertoken'] = null; @@ -2161,19 +2146,6 @@ var lizAttributeTable = function() { // Update layer state lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layerConfig.name).expressionFilter = null; - if( layer ) { - delete layer.params['FILTER']; - delete layer.params['FILTERTOKEN']; - } - - // Redraw openlayers layer - if( layer - && layerConfig['geometryType'] != 'none' - && layerConfig['geometryType'] != 'unknown' - ){ - layer.redraw(true); - } - // Refresh attributeTable var opTable = '#attribute-layer-table-'+lizMap.cleanName( typeName ); if( $( opTable ).length ){ @@ -2326,10 +2298,6 @@ var lizAttributeTable = function() { var layerN = attributeLayersDic[lizMap.cleanName(typeName)]; var lFilter = null; - var layer = lizMap.map.getLayersByName( lizMap.cleanName(typeName) )[0]; - if( layer && layer.params) { - layerN = layer.params['LAYERS']; - } // Add false value to hide all features if we need to hide layer if( typeNamePkeyValues.length == 0 ) @@ -2357,57 +2325,32 @@ var lizAttributeTable = function() { layerConfig['request_params']['exp_filter'] = aFilter; // Add filter to openlayers layer - if( layer - && layer.params - ){ - if( aFilter ){ - // Get filter token - var sdata = { + if( aFilter ){ + // Get filter token + fetch(lizUrls.service, { + method: "POST", + body: new URLSearchParams({ service: 'WMS', request: 'GETFILTERTOKEN', typename: typeName, filter: lFilter - }; - $.post(lizUrls.service, sdata, function(result){ - layer.params['FILTERTOKEN'] = result.token; - delete layer.params['FILTER']; - layerConfig['request_params']['filtertoken'] = result.token; - - // Update layer state - lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layerConfig.name).filterToken = { - expressionFilter: layerConfig['request_params']['exp_filter'], - token: result.token - }; - - // Redraw openlayers layer - if( layerConfig['geometryType'] - && layerConfig.geometryType != 'none' - && layerConfig.geometryType != 'unknown' - ){ - layer.redraw(true); - } - }); - - } - else{ - delete layer.params['FILTER']; - delete layer.params['FILTERTOKEN']; - layerConfig['request_params']['filtertoken'] = null; + }) + }).then(response => { + return response.json(); + }).then(result => { + layerConfig['request_params']['filtertoken'] = result.token; // Update layer state - lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layerConfig.name).expressionFilter = null; - } + lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layerConfig.name).filterToken = { + expressionFilter: layerConfig['request_params']['exp_filter'], + token: result.token + }; + }); } else { - // Update layer state - lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layerConfig.name).expressionFilter = layerConfig['request_params']['exp_filter']; - } + layerConfig['request_params']['filtertoken'] = null; - // Redraw openlayers layer - if( layer - && layerConfig['geometryType'] != 'none' - && layerConfig['geometryType'] != 'unknown' - ){ - layer.redraw(true); + // Update layer state + lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(layerConfig.name).expressionFilter = null; } // Refresh attributeTable @@ -2432,12 +2375,9 @@ var lizAttributeTable = function() { var cData = typeNameChildren[x]; var cFilter = null; var cExpFilter = null; - var wmsCname = cName; // Get WMS layer name (can be different depending on QGIS Server version) - var wlayer = lizMap.map.getLayersByName( lizMap.cleanName(cName) )[0]; - if( wlayer && wlayer.params) { - wmsCname = wlayer.params['LAYERS']; - } + let layer = lizMap.mainLizmap.baseLayersMap.getLayerByName(lizMap.cleanName(cName)); + var wmsCname = layer?.getSource?.().getParams?.()?.['LAYERS'] || cName; // Build filter for children // and add child to the typeNameFilter and typeNamePile objects @@ -2473,12 +2413,10 @@ var lizAttributeTable = function() { // the cFilter will be based on this value but with the layer name as prefix var cExpFilter = null; var orObj = null; - var pwmsName = pivotParam['otherParentTypeName']; // Get WMS layer name - var pwlayer = lizMap.map.getLayersByName( lizMap.cleanName(pwmsName) )[0]; - if( pwlayer && pwlayer.params) { - pwmsName = pwlayer.params['LAYERS']; - } + let pwlayer = lizMap.mainLizmap.baseLayersMap.getLayerByTypeName(lizMap.cleanName(pwmsName)); + let pwmsName = pwlayer.getSource?.().getParams?.()?.['LAYERS'] || pivotParam['otherParentTypeName']; + if( aFilter ){ if( pivotParam['otherParentValues'].length > 0 ){ cExpFilter = '"' + pivotParam['otherParentRelation'].referencedField + '"'; @@ -2556,69 +2494,56 @@ var lizAttributeTable = function() { var parentFeatureType = lizMap.lizmapLayerFilterActive; updateMapLayerDrawing( parentFeatureType, cascadeToChildren ); } - }); } - function updateMapLayerDrawing( featureType, cascade ){ cascade = typeof cascade !== 'undefined' ? cascade : true; // Get layer config var lConfig = config.layers[featureType]; - if( !lConfig ) + if( !lConfig ){ return; + } // Get OL layer to update params if it exists var cleanName = lizMap.cleanName(featureType); - var layer = lizMap.map.getLayersByName( cleanName )[0]; + let layer = lizMap.mainLizmap.baseLayersMap.getLayerByTypeName(cleanName); // Build filter from filteredFeatures var cFilter = null; - if ( lConfig['filteredFeatures'] - && lConfig['filteredFeatures'].length > 0 - ){ + if (lConfig?.['filteredFeatures']?.length) { // The values must be separated by comma AND spaces // since QGIS controls the syntax for the FILTER parameter cFilter = '$id IN ( ' + lConfig['filteredFeatures'].join( ' , ' ) + ' ) '; } - var wmsName = featureType; - if ( lConfig['shortname'] ) - wmsName = lConfig['shortname']; + const wmsName = lConfig?.['shortname'] || featureType; // Build selection parameter from selectedFeatures - if( lConfig['selectedFeatures'] - && lConfig['selectedFeatures'].length - ) { + if( lConfig?.['selectedFeatures']?.length) { lConfig['request_params']['selection'] = wmsName + ':' + lConfig['selectedFeatures'].join(); // Get selection token - var sdata = { - service: 'WMS', - request: 'GETSELECTIONTOKEN', - typename: wmsName, - ids: lConfig['selectedFeatures'].join() - }; - $.post(lizUrls.service, sdata, function(result){ - lConfig['request_params']['selectiontoken'] = result.token; - + fetch(lizUrls.service, { + method: "POST", + body: new URLSearchParams({ + service: 'WMS', + request: 'GETSELECTIONTOKEN', + typename: wmsName, + ids: lConfig.selectedFeatures.join() + }) + }).then(response => { + return response.json(); + }).then(result => { + lConfig.request_params['selectiontoken'] = result.token; // Update layer state lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(lConfig.name).selectionToken = { selectedFeatures: lConfig.selectedFeatures, token: result.token }; - - if ( layer ) { - //layer.params['SELECTION'] = wmsName + ':' + lConfig['selectedFeatures'].join(); - layer.params['SELECTIONTOKEN'] = result.token; - } }); } else { - if ( layer ){ - //delete layer.params['SELECTION']; - delete layer.params['SELECTIONTOKEN']; - } lConfig['request_params']['selection'] = null; lConfig['request_params']['selectiontoken'] = null; @@ -2631,79 +2556,61 @@ var lizAttributeTable = function() { typeNameFilter[featureType] = cFilter; var typeNameDone = []; updateLayer(typeNamePile, typeNameFilter, typeNameDone, cascade ); - } - function updateMapLayerSelection( featureType ) { + function updateMapLayerSelection(featureType) { // Get layer config var lConfig = config.layers[featureType]; - if( !lConfig ) + if (!lConfig){ return; + } // Get OL layer to be redrawn var cleanName = lizMap.cleanName(featureType); - var layer = lizMap.map.getLayersByName( cleanName )[0]; - if( !layer ) + let layer = lizMap.mainLizmap.baseLayersMap.getLayerByTypeName(cleanName); + + if (!layer) { return; + } - var wmsName = featureType; - if ( lConfig['shortname'] ) - wmsName = lConfig['shortname']; + const wmsName = lConfig?.['shortname'] || featureType; // Build selection parameter from selectedFeatures - if( lConfig.selectedFeatures - && lConfig.selectedFeatures.length - ) { - if ( !( 'request_params' in lConfig ) ) + if (lConfig?.selectedFeatures?.length) { + if (!('request_params' in lConfig)) { lConfig['request_params'] = {}; + } lConfig.request_params['selection'] = wmsName + ':' + lConfig.selectedFeatures.join(); // Get selection token - var sdata = { - service: 'WMS', - request: 'GETSELECTIONTOKEN', - typename: wmsName, - ids: lConfig.selectedFeatures.join() - }; - $.post(lizUrls.service, sdata, function(result){ + fetch(lizUrls.service, { + method: "POST", + body: new URLSearchParams({ + service: 'WMS', + request: 'GETSELECTIONTOKEN', + typename: wmsName, + ids: lConfig.selectedFeatures.join() + }) + }).then(response => { + return response.json(); + }).then(result => { lConfig.request_params['selectiontoken'] = result.token; - if ( layer ) - layer.params['SELECTIONTOKEN'] = result.token; - // Update layer state lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(lConfig.name).selectionToken = { selectedFeatures: lConfig.selectedFeatures, token: result.token }; - - // Redraw openlayers layer - if( lConfig['geometryType'] - && lConfig.geometryType != 'none' - && lConfig.geometryType != 'unknown' - ){ - layer.redraw(true); - } }); - } - else { - //delete layer.params['SELECTION']; - if ( layer ) - delete layer.params['SELECTIONTOKEN']; - if ( !( 'request_params' in lConfig ) ) + } else { + + if (!('request_params' in lConfig)) { lConfig['request_params'] = {}; + } lConfig.request_params['selection'] = null; lConfig.request_params['selectiontoken'] = null; // Update layer state lizMap.mainLizmap.state.layersAndGroupsCollection.getLayerByName(lConfig.name).selectedFeatures = null; - - // Redraw openlayers layer - if( lConfig['geometryType'] - && lConfig.geometryType != 'none' - && lConfig.geometryType != 'unknown' - ){ - layer.redraw(true); - } } } diff --git a/assets/src/legacy/edition.js b/assets/src/legacy/edition.js index 5f07f794c9..4c71df4d93 100644 --- a/assets/src/legacy/edition.js +++ b/assets/src/legacy/edition.js @@ -666,16 +666,12 @@ OpenLayers.Geometry.pointOnSegment = function(point, segment) { eventListeners: { activate: function( evt ) { lizMap.deactivateToolControls( evt ); - if ( lizMap.controls.featureInfo !== null ) - lizMap.controls.featureInfo.deactivate(); }, deactivate: function( evt ) { for ( var c in editCtrls ) { if ( c != 'panel' && editCtrls[c].active ) editCtrls[c].deactivate(); } - if ( lizMap.controls.featureInfo !== null ) - lizMap.controls.featureInfo.activate(); } } }), diff --git a/assets/src/legacy/map.js b/assets/src/legacy/map.js index 3a65246bbb..81f982f7a8 100644 --- a/assets/src/legacy/map.js +++ b/assets/src/legacy/map.js @@ -1790,305 +1790,7 @@ window.lizMap = function() { /** * create the layer switcher */ - function getSwitcherLi(aNode, aLevel) { - var nodeConfig = aNode.config; - var html = '
  • '; - - // add checkbox to display children or legend image - html += ''; - // add button to manage visibility - html += ''; - // add layer title - html += ''+nodeConfig.title+''; - - // Read the plugin metadata to get the legend options - // depending on the configuration version - let lizmap_plugin_metadata = getLizmapDesktopPluginMetadata(); - if (lizmap_plugin_metadata.lizmap_web_client_target_version >= 30600) { - var legendOption = nodeConfig.legend_image_option; - } else { - var legendOption = 'hide_at_startup'; - if (nodeConfig.noLegendImage && nodeConfig.noLegendImage == 'True') { - legendOption = 'disabled'; - } - } - - if (('children' in aNode) && aNode['children'].length!=0) { - html += getSwitcherUl(aNode, aLevel+1); - } else if (nodeConfig.type == 'layer' && legendOption != 'disabled') { - var url = getLayerLegendGraphicUrl(aNode.name, false); - if ( url != null && url != '' ) { - html += ''; - } - } - html += '
  • '; - return html; - } - - function getSwitcherUl(aNode, aLevel) { - var html = '