Repository: walkermatt/ol3-layerswitcher Branch: master Commit: c06477ef6bc0 Files: 22 Total size: 54.7 KB Directory structure: gitextract_zbkqn7gv/ ├── .gitignore ├── README.md ├── bower.json ├── examples/ │ ├── addlayer.html │ ├── addlayer.js │ ├── browserify/ │ │ ├── README.md │ │ ├── index.html │ │ ├── index.js │ │ └── package.json │ ├── layerswitcher.css │ ├── layerswitcher.html │ ├── layerswitcher.js │ ├── scroll.css │ ├── scroll.html │ └── scroll.js ├── package.json ├── src/ │ ├── ol3-layerswitcher.css │ └── ol3-layerswitcher.js ├── test/ │ ├── index.html │ └── spec/ │ ├── ol3-layerswitcher.js │ └── twomaps.js └── util/ └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules .idea/ ================================================ FILE: README.md ================================================ # DEPRECATED: ol3-layerswitcher is now [ol-layerswitcher](https://github.com/walkermatt/ol-layerswitcher) If you require support for the ["old" `openlayers` NPM package](https://www.npmjs.com/package/openlayers) then you can use the code in the master branch of this repository via: npm install git+https://github.com/walkermatt/ol3-layerswitcher.git#master But you probably want to use the [`ol` package](https://www.npmjs.com/package/ol) for new projects in which case you want [ol-layerswitcher](https://github.com/walkermatt/ol-layerswitcher). # OpenLayers LayerSwitcher Grouped layer list control for an OpenLayer v3/v4 map. All layers should have a `title` property and base layers should have a `type` property set to `base`. Group layers (`ol.layer.Group`) can be used to visually group layers together. See [examples/layerswitcher.js](examples/layerswitcher.js) for usage. ## Examples The examples demonstrate usage and can be viewed online thanks to [RawGit](http://rawgit.com/): * [Basic usage](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/layerswitcher.html) * Create a layer switcher control. Each layer to be displayed in the layer switcher has a `title` property as does each Group; each base map layer has a `type: 'base'` property. * [Add layer](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/addlayer.html) * Add a layer to an existing layer group after the layer switcher has been added to the map. * [Scrolling](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/scroll.html) * Demonstrate the panel scrolling vertically, control the height of the layer switcher by setting the `max-height` (see [examples/scroll.css](examples/scroll.css)) and it's position relative to the bottom of the map (see the `.layer-switcher.shown` selector in [src/ol3-layerswitcher.css](src/ol3-layerswitcher.css)). * [Browserify](examples/browserify/) * Example of using ol3-layerswitcher with Browserify (see [examples/browserify/README.md](examples/browserify/README.md) for details of building. The source for all examples can be found in [examples](examples). ## Tests To run the tests you'll need to install the dependencies via `npm`. In the root of the repository run: npm install Then run the tests by opening [test/index.html](test/index.html) in a browser. ## API ### `new ol.control.LayerSwitcher(opt_options)` OpenLayers v3/v4 Layer Switcher Control. See [the examples](./examples) for usage. #### Parameters: |Name|Type|Description| |:---|:---|:----------| |`opt_options`|`Object`| Control options, extends olx.control.ControlOptions adding: **`tipLabel`** `String` - the button tooltip. | #### Extends `ol.control.Control` #### Methods ##### `showPanel()` Show the layer panel. ##### `hidePanel()` Hide the layer panel. ##### `renderPanel()` Re-draw the layer panel to represent the current state of the layers. ##### `setMap(map)` Set the map instance the control is associated with. ###### Parameters: |Name|Type|Description| |:---|:---|:----------| |`map`|`ol.Map`| The map instance. | ##### `(static) ol.control.LayerSwitcher.forEachRecursive(lyr,fn)` **Static** Call the supplied function for each layer in the passed layer group recursing nested groups. ###### Parameters: |Name|Type|Description| |:---|:---|:----------| |`lyr`|`ol.layer.Group`| The layer group to start iterating from. | |`fn`|`function`| Callback which will be called for each `ol.layer.Base` found under `lyr`. The signature for `fn` is the same as `ol.Collection#forEach` | ##### `(static) ol.control.LayerSwitcher.uuid()` Generate a UUID ## License MIT (c) Matt Walker. ## Also see If you find the layer switcher useful you might also like the [ol3-popup](https://github.com/walkermatt/ol3-popup). ================================================ FILE: bower.json ================================================ { "name": "ol3-layerswitcher", "version": "1.1.0", "homepage": "https://github.com/walkermatt/ol3-layerswitcher", "authors": [ "Matt Walker (http://longwayaround.org.uk)" ], "description": "Layer switcher control for OpenLayers v3/v4", "main": [ "src/ol3-layerswitcher.js", "src/ol3-layerswitcher.css" ], "keywords": [ "openlayers", "ol", "layerswitcher" ], "license": "MIT", "ignore": [ "**/.*", "node_modules", "bower_components", "test" ] } ================================================ FILE: examples/addlayer.html ================================================ OpenLayers 3 - LayerSwitcher Add Layer
================================================ FILE: examples/addlayer.js ================================================ (function() { // Create a group for overlays. Add the group to the map when it's created // but add the overlay layers later var overlayGroup = new ol.layer.Group({ title: 'Overlays', layers: [ ] }); // Create a map containing two group layers var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Group({ 'title': 'Base maps', layers: [ new ol.layer.Tile({ title: 'OSM', type: 'base', source: new ol.source.OSM() }) ] }), overlayGroup ], view: new ol.View({ center: ol.proj.transform([-0.92, 52.96], 'EPSG:4326', 'EPSG:3857'), zoom: 6 }) }); // Create a LayerSwitcher instance and add it to the map var layerSwitcher = new ol.control.LayerSwitcher(); map.addControl(layerSwitcher); // Add a layer to a pre-exiting ol.layer.Group after the LayerSwitcher has // been added to the map. The layer will appear in the list the next time // the LayerSwitcher is shown or LayerSwitcher#renderPanel is called. overlayGroup.getLayers().push( new ol.layer.Image({ title: 'Countries', minResolution: 500, maxResolution: 5000, source: new ol.source.ImageArcGISRest({ ratio: 1, params: {'LAYERS': 'show:0'}, url: "https://ons-inspire.esriuk.com/arcgis/rest/services/Administrative_Boundaries/Countries_December_2016_Boundaries/MapServer" }) }) ); })(); ================================================ FILE: examples/browserify/README.md ================================================ # Using ol3-layerswitcher with Browserify Based on [OpenLayers 3 Browserify Tutorial](https://openlayers.org/en/latest/doc/tutorials/browserify.html). ## Building For a one-time build run: npm run build If you want to make changes and have the project auto build run: npm run start ## Requiring ol3-layerswitcher // Pass the path to `ol3-layerswitcher.js` minus the extension to `require` // which will return the constructor var LayerSwitcher = require('../../src/ol3-layerswitcher'); // Create an instance var layerSwitcher = new LayerSwitcher(); // Add to a `ol.Map` instance as normal map.addControl(layerSwitcher); ================================================ FILE: examples/browserify/index.html ================================================ Using Browserify with OpenLayers
================================================ FILE: examples/browserify/index.js ================================================ var ol = require('openlayers'); var LayerSwitcher = require('../../src/ol3-layerswitcher'); var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Group({ 'title': 'Base maps', layers: [ new ol.layer.Tile({ title: 'Water color', type: 'base', visible: false, source: new ol.source.Stamen({ layer: 'watercolor' }) }), new ol.layer.Tile({ title: 'OSM', type: 'base', source: new ol.source.OSM() }) ] }) ], view: new ol.View({ center: [0, 0], zoom: 0 }) }); var layerSwitcher = new LayerSwitcher(); map.addControl(layerSwitcher); ================================================ FILE: examples/browserify/package.json ================================================ { "name": "ol3-layerswitcher-browserify", "version": "1.1.2", "description": "Example of using ol3-layerswitcher with Browserify", "main": "index.js", "scripts": { "start": "watchify index.js --outfile bundle.js", "build": "browserify index.js | uglifyjs --compress --output bundle.js" }, "author": "", "license": "ISC", "devDependencies": { "browserify": "^13.3.0", "openlayers": "^4.0.1", "uglify-js": "^2.7.5", "watchify": "^3.8.0" } } ================================================ FILE: examples/layerswitcher.css ================================================ html, body { height: 100%; padding: 0; margin: 0; font-family: sans-serif; font-size: small; } #map { width: 100%; height: 100%; } ================================================ FILE: examples/layerswitcher.html ================================================ OpenLayers 3 - LayerSwitcher
================================================ FILE: examples/layerswitcher.js ================================================ (function() { var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Group({ 'title': 'Base maps', layers: [ new ol.layer.Group({ title: 'Water color with labels', type: 'base', combine: true, visible: false, layers: [ new ol.layer.Tile({ source: new ol.source.Stamen({ layer: 'watercolor' }) }), new ol.layer.Tile({ source: new ol.source.Stamen({ layer: 'terrain-labels' }) }) ] }), new ol.layer.Tile({ title: 'Water color', type: 'base', visible: false, source: new ol.source.Stamen({ layer: 'watercolor' }) }), new ol.layer.Tile({ title: 'OSM', type: 'base', visible: true, source: new ol.source.OSM() }) ] }), new ol.layer.Group({ title: 'Overlays', layers: [ new ol.layer.Image({ title: 'Countries', source: new ol.source.ImageArcGISRest({ ratio: 1, params: {'LAYERS': 'show:0'}, url: "https://ons-inspire.esriuk.com/arcgis/rest/services/Administrative_Boundaries/Countries_December_2016_Boundaries/MapServer" }) }) ] }) ], view: new ol.View({ center: ol.proj.transform([-0.92, 52.96], 'EPSG:4326', 'EPSG:3857'), zoom: 6 }) }); var layerSwitcher = new ol.control.LayerSwitcher({ tipLabel: 'Légende' // Optional label for button }); map.addControl(layerSwitcher); })(); ================================================ FILE: examples/scroll.css ================================================ /* Set the maxmimum height of the layerswitcher when it's shown */ .layer-switcher.shown { max-height: 170px; } ================================================ FILE: examples/scroll.html ================================================ OpenLayers 3 - LayerSwitcher Scrolling
================================================ FILE: examples/scroll.js ================================================ (function() { var thunderforestAttributions = [ new ol.Attribution({ html: 'Tiles © Thunderforest' }), ol.source.OSM.ATTRIBUTION ]; var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Group({ 'title': 'Base maps', layers: [ new ol.layer.Tile({ title: 'Stamen - Water color', type: 'base', visible: false, source: new ol.source.Stamen({ layer: 'watercolor' }) }), new ol.layer.Tile({ title: 'Stamen - Toner', type: 'base', visible: false, source: new ol.source.Stamen({ layer: 'toner' }) }), new ol.layer.Tile({ title: 'Thunderforest - OpenCycleMap', type: 'base', visible: false, source: new ol.source.OSM({ url: 'http://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', attributions: thunderforestAttributions }) }), new ol.layer.Tile({ title: 'Thunderforest - Outdoors', type: 'base', visible: false, source: new ol.source.OSM({ url: 'http://{a-c}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png', attributions: thunderforestAttributions }) }), new ol.layer.Tile({ title: 'Thunderforest - Landscape', type: 'base', visible: false, source: new ol.source.OSM({ url: 'http://{a-c}.tile.thunderforest.com/landscape/{z}/{x}/{y}.png', attributions: thunderforestAttributions }) }), new ol.layer.Tile({ title: 'Thunderforest - Transport', type: 'base', visible: false, source: new ol.source.OSM({ url: 'http://{a-c}.tile.thunderforest.com/transport/{z}/{x}/{y}.png', attributions: thunderforestAttributions }) }), new ol.layer.Tile({ title: 'Thunderforest - Transport Dark', type: 'base', visible: false, source: new ol.source.OSM({ url: 'http://{a-c}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}.png', attributions: thunderforestAttributions }) }), new ol.layer.Tile({ title: 'OSM', type: 'base', visible: true, source: new ol.source.OSM() }) ] }), new ol.layer.Group({ title: 'Overlays', layers: [ new ol.layer.Image({ title: 'Countries', source: new ol.source.ImageArcGISRest({ ratio: 1, params: {'LAYERS': 'show:0'}, url: "https://ons-inspire.esriuk.com/arcgis/rest/services/Administrative_Boundaries/Countries_December_2016_Boundaries/MapServer" }) }) ] }) ], view: new ol.View({ center: ol.proj.transform([-0.92, 52.96], 'EPSG:4326', 'EPSG:3857'), zoom: 6 }) }); map.addControl(new ol.control.LayerSwitcher()); })(); ================================================ FILE: package.json ================================================ { "name": "ol3-layerswitcher", "version": "1.1.2", "description": "Layer switcher control for OpenLayers v3/v4", "repository": { "type": "git", "url": "https://github.com/walkermatt/ol3-layerswitcher.git" }, "author": "Matt Walker (http://longwayaround.org.uk)", "contributors": [ "Thomas Gratier ", "Poul Kjeldager Sørensen ", "Micho García ", "olivierdalang " ], "keywords": [ "openlayers", "layerswitcher", "ol" ], "license": "MIT", "bugs": { "url": "https://github.com/walkermatt/ol3-layerswitcher/issues" }, "scripts": { "doc": "node_modules/.bin/jsdoc --explain src/ol3-layerswitcher.js | node_modules/.bin/dirtydocs util/README.md > README.md" }, "dependencies": { "openlayers": "~4.0.1" }, "devDependencies": { "expect.js": "~0.3.1", "jquery": "~1.11.1", "lodash": "~2.4.1", "mocha": "~1.20.1", "jsdoc": "~3.3.0-beta1", "dirtydocs": "0.0.1" } } ================================================ FILE: src/ol3-layerswitcher.css ================================================ .layer-switcher.shown.ol-control { background-color: transparent; } .layer-switcher.shown.ol-control:hover { background-color: transparent; } .layer-switcher { position: absolute; top: 3.5em; right: 0.5em; text-align: left; } .layer-switcher.shown { bottom: 3em; } .layer-switcher .panel { padding: 0 1em 0 0; margin: 0; border: 4px solid #eee; border-radius: 4px; background-color: white; display: none; max-height: 100%; overflow-y: auto; } .layer-switcher.shown .panel { display: block; } .layer-switcher button { float: right; width: 38px; height: 38px; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACE1BMVEX///8A//8AgICA//8AVVVAQID///8rVVVJtttgv98nTmJ2xNgkW1ttyNsmWWZmzNZYxM4gWGgeU2JmzNNr0N1Rwc0eU2VXxdEhV2JqytQeVmMhVmNoydUfVGUgVGQfVGQfVmVqy9hqy9dWw9AfVWRpydVry9YhVmMgVGNUw9BrytchVWRexdGw294gVWQgVmUhVWPd4N6HoaZsy9cfVmQgVGRrytZsy9cgVWQgVWMgVWRsy9YfVWNsy9YgVWVty9YgVWVry9UgVWRsy9Zsy9UfVWRsy9YgVWVty9YgVWRty9Vsy9aM09sgVWRTws/AzM0gVWRtzNYgVWRuy9Zsy9cgVWRGcHxty9bb5ORbxdEgVWRty9bn6OZTws9mydRfxtLX3Nva5eRix9NFcXxOd4JPeINQeIMiVmVUws9Vws9Vw9BXw9BYxNBaxNBbxNBcxdJexdElWWgmWmhjyNRlx9IqXGtoipNpytVqytVryNNrytZsjZUuX210k5t1y9R2zNR3y9V4lp57zth9zdaAnKOGoaeK0NiNpquV09mesrag1tuitbmj1tuj19uktrqr2d2svcCu2d2xwMO63N+7x8nA3uDC3uDFz9DK4eHL4eLN4eIyYnDX5OM5Z3Tb397e4uDf4uHf5uXi5ePi5+Xj5+Xk5+Xm5+Xm6OY6aHXQ19fT4+NfhI1Ww89gx9Nhx9Nsy9ZWw9Dpj2abAAAAWnRSTlMAAQICAwQEBgcIDQ0ODhQZGiAiIyYpKywvNTs+QklPUlNUWWJjaGt0dnd+hIWFh4mNjZCSm6CpsbW2t7nDzNDT1dje5efr7PHy9PT29/j4+Pn5+vr8/f39/f6DPtKwAAABTklEQVR4Xr3QVWPbMBSAUTVFZmZmhhSXMjNvkhwqMzMzMzPDeD+xASvObKePPa+ffHVl8PlsnE0+qPpBuQjVJjno6pZpSKXYl7/bZyFaQxhf98hHDKEppwdWIW1frFnrxSOWHFfWesSEWC6R/P4zOFrix3TzDFLlXRTR8c0fEEJ1/itpo7SVO9Jdr1DVxZ0USyjZsEY5vZfiiAC0UoTGOrm9PZLuRl8X+Dq1HQtoFbJZbv61i+Poblh/97TC7n0neCcK0ETNUrz1/xPHf+DNAW9Ac6t8O8WH3Vp98f5lCaYKAOFZMLyHL4Y0fe319idMNgMMp+zWVSybUed/+/h7I4wRAG1W6XDy4XmjR9HnzvDRZXUAYDFOhC1S/Hh+fIXxen+eO+AKqbs+wAo30zDTDvDxKoJN88sjUzDFAvBzEUGFsnADoIvAJzoh2BZ8sner+Ke/vwECuQAAAABJRU5ErkJggg==') /*logo.png*/; background-repeat: no-repeat; background-position: 2px; background-color: white; border: none; } .layer-switcher.shown button { display: none; } .layer-switcher button:focus, .layer-switcher button:hover { background-color: white; } .layer-switcher ul { padding-left: 1em; list-style: none; } .layer-switcher li.group { padding-top: 5px; } .layer-switcher li.group > label { font-weight: bold; } .layer-switcher li.layer { display: table; } .layer-switcher li.layer label, .layer-switcher li.layer input { display: table-cell; vertical-align: sub; } .layer-switcher label.disabled { opacity:0.4; } .layer-switcher input { margin: 4px; } .layer-switcher.touch ::-webkit-scrollbar { width: 4px; } .layer-switcher.touch ::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); border-radius: 10px; } .layer-switcher.touch ::-webkit-scrollbar-thumb { border-radius: 10px; -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); } ================================================ FILE: src/ol3-layerswitcher.js ================================================ (function (root, factory) { if(typeof define === "function" && define.amd) { define(["openlayers"], factory); } else if(typeof module === "object" && module.exports) { module.exports = factory(require("openlayers")); } else { root.LayerSwitcher = factory(root.ol); } }(this, function(ol) { /** * OpenLayers v3/v4 Layer Switcher Control. * See [the examples](./examples) for usage. * @constructor * @extends {ol.control.Control} * @param {Object} opt_options Control options, extends olx.control.ControlOptions adding: * **`tipLabel`** `String` - the button tooltip. */ ol.control.LayerSwitcher = function(opt_options) { var options = opt_options || {}; var tipLabel = options.tipLabel ? options.tipLabel : 'Legend'; this.mapListeners = []; this.hiddenClassName = 'ol-unselectable ol-control layer-switcher'; if (ol.control.LayerSwitcher.isTouchDevice_()) { this.hiddenClassName += ' touch'; } this.shownClassName = 'shown'; var element = document.createElement('div'); element.className = this.hiddenClassName; var button = document.createElement('button'); button.setAttribute('title', tipLabel); element.appendChild(button); this.panel = document.createElement('div'); this.panel.className = 'panel'; element.appendChild(this.panel); ol.control.LayerSwitcher.enableTouchScroll_(this.panel); var this_ = this; button.onmouseover = function(e) { this_.showPanel(); }; button.onclick = function(e) { e = e || window.event; this_.showPanel(); e.preventDefault(); }; this_.panel.onmouseout = function(e) { e = e || window.event; if (!this_.panel.contains(e.toElement || e.relatedTarget)) { this_.hidePanel(); } }; ol.control.Control.call(this, { element: element, target: options.target }); }; ol.inherits(ol.control.LayerSwitcher, ol.control.Control); /** * Show the layer panel. */ ol.control.LayerSwitcher.prototype.showPanel = function() { if (!this.element.classList.contains(this.shownClassName)) { this.element.classList.add(this.shownClassName); this.renderPanel(); } }; /** * Hide the layer panel. */ ol.control.LayerSwitcher.prototype.hidePanel = function() { if (this.element.classList.contains(this.shownClassName)) { this.element.classList.remove(this.shownClassName); } }; /** * Re-draw the layer panel to represent the current state of the layers. */ ol.control.LayerSwitcher.prototype.renderPanel = function() { this.ensureTopVisibleBaseLayerShown_(); while(this.panel.firstChild) { this.panel.removeChild(this.panel.firstChild); } var ul = document.createElement('ul'); this.panel.appendChild(ul); this.renderLayers_(this.getMap(), ul); }; /** * Set the map instance the control is associated with. * @param {ol.Map} map The map instance. */ ol.control.LayerSwitcher.prototype.setMap = function(map) { // Clean up listeners associated with the previous map for (var i = 0, key; i < this.mapListeners.length; i++) { ol.Observable.unByKey(this.mapListeners[i]); } this.mapListeners.length = 0; // Wire up listeners etc. and store reference to new map ol.control.Control.prototype.setMap.call(this, map); if (map) { var this_ = this; this.mapListeners.push(map.on('pointerdown', function() { this_.hidePanel(); })); this.renderPanel(); } }; /** * Ensure only the top-most base layer is visible if more than one is visible. * @private */ ol.control.LayerSwitcher.prototype.ensureTopVisibleBaseLayerShown_ = function() { var lastVisibleBaseLyr; ol.control.LayerSwitcher.forEachRecursive(this.getMap(), function(l, idx, a) { if (l.get('type') === 'base' && l.getVisible()) { lastVisibleBaseLyr = l; } }); if (lastVisibleBaseLyr) this.setVisible_(lastVisibleBaseLyr, true); }; /** * Toggle the visible state of a layer. * Takes care of hiding other layers in the same exclusive group if the layer * is toggle to visible. * @private * @param {ol.layer.Base} The layer whos visibility will be toggled. */ ol.control.LayerSwitcher.prototype.setVisible_ = function(lyr, visible) { var map = this.getMap(); lyr.setVisible(visible); if (visible && lyr.get('type') === 'base') { // Hide all other base layers regardless of grouping ol.control.LayerSwitcher.forEachRecursive(map, function(l, idx, a) { if (l != lyr && l.get('type') === 'base') { l.setVisible(false); } }); } }; /** * Render all layers that are children of a group. * @private * @param {ol.layer.Base} lyr Layer to be rendered (should have a title property). * @param {Number} idx Position in parent group list. */ ol.control.LayerSwitcher.prototype.renderLayer_ = function(lyr, idx) { var this_ = this; var li = document.createElement('li'); var lyrTitle = lyr.get('title'); var lyrId = ol.control.LayerSwitcher.uuid(); var label = document.createElement('label'); if (lyr.getLayers && !lyr.get('combine')) { li.className = 'group'; label.innerHTML = lyrTitle; li.appendChild(label); var ul = document.createElement('ul'); li.appendChild(ul); this.renderLayers_(lyr, ul); } else { li.className = 'layer'; var input = document.createElement('input'); if (lyr.get('type') === 'base') { input.type = 'radio'; input.name = 'base'; } else { input.type = 'checkbox'; } input.id = lyrId; input.checked = lyr.get('visible'); input.onchange = function(e) { this_.setVisible_(lyr, e.target.checked); }; li.appendChild(input); label.htmlFor = lyrId; label.innerHTML = lyrTitle; var rsl = this.getMap().getView().getResolution(); if (rsl > lyr.getMaxResolution() || rsl < lyr.getMinResolution()){ label.className += ' disabled'; } li.appendChild(label); } return li; }; /** * Render all layers that are children of a group. * @private * @param {ol.layer.Group} lyr Group layer whos children will be rendered. * @param {Element} elm DOM element that children will be appended to. */ ol.control.LayerSwitcher.prototype.renderLayers_ = function(lyr, elm) { var lyrs = lyr.getLayers().getArray().slice().reverse(); for (var i = 0, l; i < lyrs.length; i++) { l = lyrs[i]; if (l.get('title')) { elm.appendChild(this.renderLayer_(l, i)); } } }; /** * **Static** Call the supplied function for each layer in the passed layer group * recursing nested groups. * @param {ol.layer.Group} lyr The layer group to start iterating from. * @param {Function} fn Callback which will be called for each `ol.layer.Base` * found under `lyr`. The signature for `fn` is the same as `ol.Collection#forEach` */ ol.control.LayerSwitcher.forEachRecursive = function(lyr, fn) { lyr.getLayers().forEach(function(lyr, idx, a) { fn(lyr, idx, a); if (lyr.getLayers) { ol.control.LayerSwitcher.forEachRecursive(lyr, fn); } }); }; /** * Generate a UUID * @returns {String} UUID * * Adapted from http://stackoverflow.com/a/2117523/526860 */ ol.control.LayerSwitcher.uuid = function() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.toString(16); }); } /** * @private * @desc Apply workaround to enable scrolling of overflowing content within an * element. Adapted from https://gist.github.com/chrismbarr/4107472 */ ol.control.LayerSwitcher.enableTouchScroll_ = function(elm) { if(ol.control.LayerSwitcher.isTouchDevice_()){ var scrollStartPos = 0; elm.addEventListener("touchstart", function(event) { scrollStartPos = this.scrollTop + event.touches[0].pageY; }, false); elm.addEventListener("touchmove", function(event) { this.scrollTop = scrollStartPos - event.touches[0].pageY; }, false); } }; /** * @private * @desc Determine if the current browser supports touch events. Adapted from * https://gist.github.com/chrismbarr/4107472 */ ol.control.LayerSwitcher.isTouchDevice_ = function() { try { document.createEvent("TouchEvent"); return true; } catch(e) { return false; } }; var LayerSwitcher = ol.control.LayerSwitcher; return LayerSwitcher; })); ================================================ FILE: test/index.html ================================================ ol3-layerswitcher spec runner
================================================ FILE: test/spec/ol3-layerswitcher.js ================================================ describe('ol.control.LayerSwitcher', function() { var map, target, switcher; beforeEach(function() { target = document.createElement('div'); document.body.appendChild(target); switcher = new ol.control.LayerSwitcher(); map = new ol.Map({ target: target, layers: [ new ol.layer.Group({ title: 'Base', layers: [ new ol.layer.Tile({ title: 'Foo', type: 'base', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), new ol.layer.Tile({ title: 'Too', type: 'base', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }) ] }), // Combined base group new ol.layer.Group({ title: 'Combined-Base-Group', type: 'base', combine: true, layers: [ new ol.layer.Tile({ source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), new ol.layer.Tile({ source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }) ] }), // Combined overlay group new ol.layer.Group({ title: 'Combined-Overlay-Group', type: 'overlay', combine: true, layers: [ new ol.layer.Tile({ source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), new ol.layer.Tile({ source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }) ] }), // Group with no title (group and it's children should be ignored) new ol.layer.Group({ layers: [ new ol.layer.Tile({ title: 'Never shown', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), new ol.layer.Tile({ source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }) ] }), new ol.layer.Tile({ title: 'Bar', minResolution: 1000, maxResolution: 5000, source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), // Layer with no title (should be ignored) new ol.layer.Tile({ source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }) ], controls: [switcher] }); }); afterEach(function() { document.body.removeChild(target); switcher = null; map = null; target = null; }); describe('DOM creation', function() { it('creates the expected DOM elements', function() { expect(jQuery('.layer-switcher').length).to.be(1); }); }); describe('Show and hide', function() { it('is initially hidden', function() { expect(jQuery('.layer-switcher').hasClass('.shown')).to.be(false); expect(jQuery('.layer-switcher .panel:visible').length).to.be(0); }); it('is shown on button click', function() { jQuery('.layer-switcher button').click(); expect(jQuery('.layer-switcher.shown').length).to.be(1); expect(jQuery('.layer-switcher .panel:visible').length).to.be(1); }); it('is hidden on map click', function() { jQuery('#map').click(); expect(jQuery('.layer-switcher').hasClass('.shown')).to.be(false); expect(jQuery('.layer-switcher .panel:visible').length).to.be(0); }); }); describe('Layer list', function() { it('displays all layers with a title in reverse order', function() { switcher.showPanel(); var titles = jQuery('.layer-switcher label').map(function() { return jQuery(this).text(); }).get(); expect(titles).to.eql(['Bar', 'Combined-Overlay-Group', 'Combined-Base-Group', 'Base', 'Too', 'Foo']); }); it('only displays layers with a title', function() { switcher.showPanel(); var elmTitles = jQuery('.layer-switcher label').map(function() { return jQuery(this).text(); }).get(); var lyrsWithTitle = shownLyrs(map.getLayerGroup()); expect(lyrsWithTitle.length).to.eql(elmTitles.length); }); it('don\'t display layers without a title', function() { switcher.showPanel(); // This is basically to ensure that our test layers include layers without a title var lyrsWithoutTitle = _.filter(allLyrs(map.getLayerGroup()), function(lyr) {return !lyr.get('title')}); expect(lyrsWithoutTitle.length).not.to.equal(0); }); it('displays normal layers as checkbox', function() { switcher.showPanel(); var titles = jQuery('.layer-switcher input[type=checkbox]').siblings('label').map(function() { return jQuery(this).text(); }).get(); expect(titles).to.eql(['Bar', 'Combined-Overlay-Group']); }); it('greys out normal layer title labels when outside of layer resolution', function() { map.getView().setResolution(6000); switcher.showPanel(); var layerResTooHigh = jQuery('.layer-switcher label.disabled').map(function() { return jQuery(this).text(); }).get(); map.getView().setResolution(500); var layerResTooLow = jQuery('.layer-switcher label.disabled').map(function() { return jQuery(this).text(); }).get(); expect([layerResTooHigh, layerResTooLow]).to.eql([['Bar'], ['Bar']]); }); it('displays base layers as radio buttons', function() { switcher.showPanel(); var titles = jQuery('.layer-switcher input[type=radio]').siblings('label').map(function() { return jQuery(this).text(); }).get(); expect(titles).to.eql(['Combined-Base-Group', 'Too', 'Foo']); }); it('should display uncombined groups without an input', function() { switcher.showPanel(); var groups = jQuery('.layer-switcher label:not([for])') var titles = groups.map(function() { return jQuery(this).text(); }).get(); expect(titles).to.eql(['Base']); expect(groups.siblings('input').length).to.be(0); }); it('should display combined groups with an input', function () { switcher.showPanel(); var titles = jQuery('.layer-switcher label[for]').map(function() { return jQuery(this).text(); }).get(); expect(titles).to.contain('Combined-Base-Group'); expect(titles).to.contain('Combined-Overlay-Group'); }); it('should display combined groups without sub layers', function () { switcher.showPanel(); var groups = jQuery('.layer-switcher label[for]') expect(groups.siblings('ul').length).to.be(0); }); }); describe('Overlay layer visibility', function() { it('Toggles overlay layer visibility on click', function() { switcher.showPanel(); var bar = getLayerByTitle('Bar'); bar.setVisible(true); jQuery('.layer-switcher label:contains("Bar")').siblings('input').click(); expect(bar.getVisible()).to.be(false); expect(jQuery('.layer-switcher label:contains("Bar")').siblings('input').get(0).checked).to.be(bar.getVisible()); bar.setVisible(false) jQuery('.layer-switcher label:contains("Bar")').siblings('input').click(); expect(bar.getVisible()).to.be(true); expect(jQuery('.layer-switcher label:contains("Bar")').siblings('input').get(0).checked).to.be(bar.getVisible()); }); }); describe('Base layer visibility', function() { it('Only one base layer is visible after renderPanel', function() { var foo = getLayerByTitle('Foo'); var too = getLayerByTitle('Too'); var cbg = getLayerByTitle('Combined-Base-Group'); var baseLayers = [foo, too, cbg]; // Enable all base layers _.forEach(baseLayers, function (l) { l.setVisible(true); }); switcher.renderPanel(); var visibleBaseLayerCount = _.countBy(baseLayers, function (l){ return l.getVisible(); }); expect(visibleBaseLayerCount.true).to.be(1); }); it('Only top most base layer is visible after renderPanel if more than one is visible', function() { var foo = getLayerByTitle('Foo'); var too = getLayerByTitle('Too'); var cbg = getLayerByTitle('Combined-Base-Group'); var baseLayers = [foo, too, cbg]; // Enable all base layers _.forEach(baseLayers, function (l) { l.setVisible(true); }); switcher.renderPanel(); expect(cbg.getVisible()).to.be(true); }); it('Clicking on unchecked base layer shows it', function() { var too = getLayerByTitle('Too'); too.setVisible(false); switcher.renderPanel(); jQuery('.layer-switcher label:contains("Too")').siblings('input').click(); expect(too.getVisible()).to.be(true); expect(jQuery('.layer-switcher label:contains("Too")').siblings('input').get(0).checked).to.be(true); }); it('Clicking on checked base layer does not change base layer', function() { var foo = getLayerByTitle('Foo'); foo.setVisible(true); switcher.renderPanel(); jQuery('.layer-switcher label:contains("Foo")').siblings('input').click(); expect(foo.getVisible()).to.be(true); expect(jQuery('.layer-switcher label:contains("Foo")').siblings('input').get(0).checked).to.be(true); }); }); describe('Removes cleanly', function() { it('Removes cleanly when ol.Map#removeControl is called', function() { map.removeControl(switcher); }); }); /** * Returns the title of a given layer or null if lyr is falsey */ function lyrTitle(lyr) { return (lyr) ? lyr.get('title') : null; } /** * Returns the Layer instance that has the given title */ function getLayerByTitle(title) { var layer = null; ol.control.LayerSwitcher.forEachRecursive(map, function(lyr) { if (lyr.get('title') && lyr.get('title') === title) { layer = lyr; return; } }); return layer; } /** * Return a flattened Array of all layers regardless including those not * shown by the LayerSwitcher */ function allLyrs(lyrs) { return flatten(lyrs, function (lyr) { return (lyr.getLayers) ? lyr.getLayers().getArray() : lyr; }); } /** * Return a flattened Array of only those layers that the LayerSwitcher * should show */ function shownLyrs(lyrs) { // Pass in the Array from the root LayerGroup as it doesn't have a // title but we don't want to filter out all layers lyrs = lyrs.getLayers().getArray(); var flat = flatten(lyrs, function (lyr) { // Return a Groups layer array only if the group has a title // otherwise just return the group so that it's children will be // skipped return (lyr.getLayers && lyr.get('title')) ? lyr.getLayers().getArray() : lyr; }); // Only return layers with a title return _.filter(flat, lyrTitle); } /** * Flattens a given nested collection using the provided function getArray * to get an Array of the collections children. */ function flatten(srcCollection, getArray) { getArray = getArray || function (item) {return item}; var src = getArray(srcCollection), dest = []; for (var i = 0, item; i < src.length; i++) { item = src[i]; dest = dest.concat(item); if (_.isArray(getArray(item))) { dest = dest.concat(flatten(item, getArray)); } } return dest; } }); ================================================ FILE: test/spec/twomaps.js ================================================ describe('ol.control.LayerSwitcher - Two maps', function() { var map1, map2, target1, target2, switcher1, switcher2; beforeEach(function() { target1 = document.createElement('div'); target1.id = 'map1'; document.body.appendChild(target1); target2 = document.createElement('div'); target2.id = 'map2'; document.body.appendChild(target2); switcher1 = new ol.control.LayerSwitcher(); switcher2 = new ol.control.LayerSwitcher(); map1 = new ol.Map({ target: target1, layers: [ new ol.layer.Group({ title: 'Base', layers: [ new ol.layer.Tile({ title: 'Foo', type: 'base', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), new ol.layer.Tile({ title: 'Too', type: 'base', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }) ] }), new ol.layer.Tile({ title: 'Bar', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), ], controls: [switcher1] }); map2 = new ol.Map({ target: target2, layers: [ new ol.layer.Group({ title: 'Base', layers: [ new ol.layer.Tile({ title: 'Foo', type: 'base', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), new ol.layer.Tile({ title: 'Too', type: 'base', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }) ] }), new ol.layer.Tile({ title: 'Bar', source: new ol.source.TileDebug({ projection: 'EPSG:3857', tileGrid: ol.tilegrid.createXYZ({ maxZoom: 22 }) }) }), ], controls: [switcher2] }); }); afterEach(function() { document.body.removeChild(target1); document.body.removeChild(target2); target1 = null; target2 = null; switcher1 = null; switcher2 = null; map1 = null; map2 = null; }); describe('Layer IDs are unique', function() { it('Inputs for layers with the same title in different maps will have different IDs', function() { switcher1.showPanel(); switcher2.showPanel(); var bar1Id = jQuery('#map1 .layer-switcher label:contains("Bar")').siblings('input').attr('id'); var bar2Id = jQuery('#map2 .layer-switcher label:contains("Bar")').siblings('input').attr('id'); expect(bar1Id).to.not.equal(bar2Id); }); }); /** * Returns the Layer instance that has the given title */ function getLayerByTitle(map, title) { var layer = null; ol.control.LayerSwitcher.forEachRecursive(map, function(lyr) { if (lyr.get('title') && lyr.get('title') === title) { layer = lyr; return; } }); return layer; } }); ================================================ FILE: util/README.md ================================================ # OpenLayers LayerSwitcher Grouped layer list control for an OpenLayer v3/v4 map. All layers should have a `title` property and base layers should have a `type` property set to `base`. Group layers (`ol.layer.Group`) can be used to visually group layers together. See [examples/layerswitcher.js](examples/layerswitcher.js) for usage. ## Examples The examples demonstrate usage and can be viewed online thanks to [RawGit](http://rawgit.com/): * [Basic usage](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/layerswitcher.html) * Create a layer switcher control. Each layer to be displayed in the layer switcher has a `title` property as does each Group; each base map layer has a `type: 'base'` property. * [Add layer](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/addlayer.html) * Add a layer to an existing layer group after the layer switcher has been added to the map. * [Scrolling](http://rawgit.com/walkermatt/ol3-layerswitcher/master/examples/scroll.html) * Demonstrate the panel scrolling vertically, control the height of the layer switcher by setting the `max-height` (see [examples/scroll.css](examples/scroll.css)) and it's position relative to the bottom of the map (see the `.layer-switcher.shown` selector in [src/ol3-layerswitcher.css](src/ol3-layerswitcher.css)). * [Browserify](examples/browserify/) * Example of using ol3-layerswitcher with Browserify (see [examples/browserify/README.md](examples/browserify/README.md) for details of building. The source for all examples can be found in [examples](examples). ## Tests To run the tests you'll need to install the dependencies via `npm`. In the root of the repository run: npm install Then run the tests by opening [test/index.html](test/index.html) in a browser. ## API {% for class in classes -%} ### `new {{ class.longname }}({{ class.signature }})` {{ class.description }} #### Parameters: |Name|Type|Description| |:---|:---|:----------| {% for param in class.params %}|`{{ param.name }}`|`{{ param.type.names[0] }}`| {{ param.description }} |{% endfor %} #### Extends `{{ class.augments }}` #### Methods {% for method in class.methods -%} ##### `{% if method.scope == 'static' %}(static) {{ class.longname }}.{% endif %}{{ method.name }}({{ method.signature }})` {{ method.description }} {% if method.params -%} ###### Parameters: |Name|Type|Description| |:---|:---|:----------| {% for param in method.params -%} |`{{ param.name }}`|`{{ param.type.names[0] }}`| {{ param.description }} | {% endfor %} {% endif %} {%- endfor %} {%- endfor -%} ## License MIT (c) Matt Walker. ## Also see If you find the layer switcher useful you might also like the [ol3-popup](https://github.com/walkermatt/ol3-popup).