Skip to content

Commit

Permalink
Support setting group visibility
Browse files Browse the repository at this point in the history
  • Loading branch information
walkermatt committed Jul 1, 2019
1 parent a820672 commit baec8cc
Show file tree
Hide file tree
Showing 10 changed files with 673 additions and 49 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ See [the examples](./examples) for usage.

- `opt_options` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Control options, extends ol/control/Control~Control#options adding:
- `opt_options.tipLabel` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the button tooltip.
- `opt_options.groupSelectStyle` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** either `'none'` - groups don't get a checkbox,
`'children'` (default) groups have a checkbox and affect child visibility or
`'group'` groups have a checkbox but do not alter child visibility (like QGIS).

#### setMap

Expand Down
192 changes: 162 additions & 30 deletions dist/ol-layerswitcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,12 @@ var CSS_PREFIX = 'layer-switcher-';
* OpenLayers 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.
* @extends {ol/control/Control~Control}
* @param {Object} opt_options Control options, extends ol/control/Control~Control#options adding:
* @param {String} opt_options.tipLabel the button tooltip.
* @param {String} opt_options.groupSelectStyle either `'none'` - groups don't get a checkbox,
* `'children'` (default) groups have a checkbox and affect child visibility or
* `'group'` groups have a checkbox but do not alter child visibility (like QGIS).
*/

var LayerSwitcher = function (_Control) {
Expand All @@ -121,6 +124,8 @@ var LayerSwitcher = function (_Control) {

var _this = possibleConstructorReturn(this, (LayerSwitcher.__proto__ || Object.getPrototypeOf(LayerSwitcher)).call(this, { element: element, target: options.target }));

_this.groupSelectStyle = ['none', 'children', 'group'].indexOf(options.groupSelectStyle) >= 0 ? options.groupSelectStyle : 'children';

_this.mapListeners = [];

_this.hiddenClassName = 'ol-unselectable ol-control layer-switcher';
Expand Down Expand Up @@ -164,7 +169,7 @@ var LayerSwitcher = function (_Control) {

/**
* Set the map instance the control is associated with.
* @param {ol.Map} map The map instance.
* @param {ol/Map~Map} map The map instance.
*/


Expand Down Expand Up @@ -219,34 +224,114 @@ var LayerSwitcher = function (_Control) {
}, {
key: 'renderPanel',
value: function renderPanel() {
LayerSwitcher.renderPanel(this.getMap(), this.panel);
LayerSwitcher.renderPanel(this.getMap(), this.panel, {
groupSelectStyle: this.groupSelectStyle
});
}

/**
* **Static** Re-draw the layer panel to represent the current state of the layers.
* @param {ol.Map} map The OpenLayers Map instance to render layers for
* @param {ol/Map~Map} map The OpenLayers Map instance to render layers for
* @param {Element} panel The DOM Element into which the layer tree will be rendered
*/

}], [{
key: 'renderPanel',
value: function renderPanel(map, panel) {
value: function renderPanel(map, panel, options) {

options = options || {};

LayerSwitcher.ensureTopVisibleBaseLayerShown_(map);

while (panel.firstChild) {
panel.removeChild(panel.firstChild);
}

// Reset indeterminate state for all layers and groups before
// applying based on groupSelectStyle
LayerSwitcher.forEachRecursive(map, function (l, idx, a) {
l.set('indeterminate', false);
});

if (options.groupSelectStyle === 'children' || options.groupSelectStyle === 'none') {
// Set visibile and indeterminate state of groups based on
// their children's visibility
LayerSwitcher.setGroupVisibility(map);
} else if (options.groupSelectStyle === 'group') {
// Set child indetermiate state based on their parent's visibility
LayerSwitcher.setChildVisibility(map);
}

var ul = document.createElement('ul');
panel.appendChild(ul);
// passing two map arguments instead of lyr as we're passing the map as the root of the layers tree
LayerSwitcher.renderLayers_(map, map, ul);
LayerSwitcher.renderLayers_(map, map, ul, options, function render(changedLyr) {
// console.log('render');
LayerSwitcher.renderPanel(map, panel, options);
});
}
}, {
key: 'isBaseGroup',
value: function isBaseGroup(lyr) {
var lyrs = lyr.getLayers ? lyr.getLayers().getArray() : [];
return lyrs.length && lyrs[0].get('type') === 'base';
}
}, {
key: 'setGroupVisibility',
value: function setGroupVisibility(map) {
// Get a list of groups, with the deepest first
var groups = LayerSwitcher.getGroupsAndLayers(map, function (l) {
return l.getLayers && !l.get('combine') && !LayerSwitcher.isBaseGroup(l);
}).reverse();
// console.log(groups.map(g => g.get('title')));
groups.forEach(function (group) {
// TODO Can we use getLayersArray, is it public in the esm build?
var descendantVisibility = group.getLayersArray().map(function (l) {
var state = l.getVisible();
// console.log('>', l.get('title'), state);
return state;
});
// console.log(descendantVisibility);
if (descendantVisibility.every(function (v) {
return v === true;
})) {
group.setVisible(true);
group.set('indeterminate', false);
} else if (descendantVisibility.every(function (v) {
return v === false;
})) {
group.setVisible(false);
group.set('indeterminate', false);
} else {
group.setVisible(true);
group.set('indeterminate', true);
}
});
}
}, {
key: 'setChildVisibility',
value: function setChildVisibility(map) {
// console.log('setChildVisibility');
var groups = LayerSwitcher.getGroupsAndLayers(map, function (l) {
return l.getLayers && !l.get('combine') && !LayerSwitcher.isBaseGroup(l);
});
groups.forEach(function (group) {
// console.log(group.get('title'));
var groupVisible = group.getVisible();
var groupIndeterminate = group.get('indeterminate');
group.getLayers().getArray().forEach(function (l) {
// console.log('>', l.get('title'));
l.set('indeterminate', false);
if ((!groupVisible || groupIndeterminate) && l.getVisible()) {
l.set('indeterminate', true);
}
});
});
}

/**
* **Static** Ensure only the top-most base layer is visible if more than one is visible.
* @param {ol.Map} map The map instance.
* @param {ol/Map~Map} map The map instance.
* @private
*/

Expand All @@ -261,19 +346,36 @@ var LayerSwitcher = function (_Control) {
});
if (lastVisibleBaseLyr) LayerSwitcher.setVisible_(map, lastVisibleBaseLyr, true);
}
}, {
key: 'getGroupsAndLayers',
value: function getGroupsAndLayers(lyr, filterFn) {
var layers = [];
filterFn = filterFn || function (l, idx, a) {
return true;
};
LayerSwitcher.forEachRecursive(lyr, function (l, idx, a) {
if (l.get('title')) {
if (filterFn(l, idx, a)) {
layers.push(l);
}
}
});
return layers;
}

/**
* **Static** 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.Map} map The map instance.
* @param {ol.layer.Base} The layer whos visibility will be toggled.
* @param {ol/Map~Map} map The map instance.
* @param {ol/layer/Base~BaseLayer} The layer whose visibility will be toggled.
*/

}, {
key: 'setVisible_',
value: function setVisible_(map, lyr, visible) {
value: function setVisible_(map, lyr, visible, groupSelectStyle) {
// console.log(lyr.get('title'), visible, groupSelectStyle);
lyr.setVisible(visible);
if (visible && lyr.get('type') === 'base') {
// Hide all other base layers regardless of grouping
Expand All @@ -283,46 +385,73 @@ var LayerSwitcher = function (_Control) {
}
});
}
if (lyr.getLayers && !lyr.get('combine') && groupSelectStyle === 'children') {
lyr.getLayers().forEach(function (l) {
LayerSwitcher.setVisible_(map, l, lyr.getVisible(), groupSelectStyle);
});
}
}

/**
* **Static** Render all layers that are children of a group.
* @private
* @param {ol.Map} map The map instance.
* @param {ol.layer.Base} lyr Layer to be rendered (should have a title property).
* @param {ol/Map~Map} map The map instance.
* @param {ol/layer/Base~BaseLayer} lyr Layer to be rendered (should have a title property).
* @param {Number} idx Position in parent group list.
*/

}, {
key: 'renderLayer_',
value: function renderLayer_(map, lyr, idx) {
value: function renderLayer_(map, lyr, idx, options, render) {

var li = document.createElement('li');

var lyrTitle = lyr.get('title');
var lyrId = LayerSwitcher.uuid();

var checkboxId = LayerSwitcher.uuid();

var label = document.createElement('label');

if (lyr.getLayers && !lyr.get('combine')) {

li.className = 'group';
var isBaseGroup = LayerSwitcher.isBaseGroup(lyr);

li.classList.add('group');
if (isBaseGroup) {
li.classList.add(CSS_PREFIX + 'base-group');
}

// Group folding
if (lyr.get('fold')) {
li.classList.add(CSS_PREFIX + 'fold');
li.classList.add(CSS_PREFIX + lyr.get('fold'));
label.onclick = function (e) {
var btn = document.createElement('button');
btn.onclick = function (e) {
LayerSwitcher.toggleFold_(lyr, li);
};
li.appendChild(btn);
}

if (!isBaseGroup && options.groupSelectStyle != 'none') {
var _input = document.createElement('input');
_input.type = 'checkbox';
_input.id = checkboxId;
_input.checked = lyr.getVisible();
_input.indeterminate = lyr.get('indeterminate');
_input.onchange = function (e) {
LayerSwitcher.setVisible_(map, lyr, e.target.checked, options.groupSelectStyle);
render(lyr);
};
li.appendChild(_input);
label.htmlFor = checkboxId;
}

label.innerHTML = lyrTitle;
li.appendChild(label);
var ul = document.createElement('ul');
li.appendChild(ul);

LayerSwitcher.renderLayers_(map, lyr, ul);
LayerSwitcher.renderLayers_(map, lyr, ul, options, render);
} else {

li.className = 'layer';
Expand All @@ -333,14 +462,16 @@ var LayerSwitcher = function (_Control) {
} else {
input.type = 'checkbox';
}
input.id = lyrId;
input.id = checkboxId;
input.checked = lyr.get('visible');
input.indeterminate = lyr.get('indeterminate');
input.onchange = function (e) {
LayerSwitcher.setVisible_(map, lyr, e.target.checked);
LayerSwitcher.setVisible_(map, lyr, e.target.checked, options.groupSelectStyle);
render(lyr);
};
li.appendChild(input);

label.htmlFor = lyrId;
label.htmlFor = checkboxId;
label.innerHTML = lyrTitle;

var rsl = map.getView().getResolution();
Expand All @@ -357,29 +488,29 @@ var LayerSwitcher = function (_Control) {
/**
* **Static** Render all layers that are children of a group.
* @private
* @param {ol.Map} map The map instance.
* @param {ol.layer.Group} lyr Group layer whos children will be rendered.
* @param {ol/Map~Map} map The map instance.
* @param {ol/layer/Group~LayerGroup} lyr Group layer whose children will be rendered.
* @param {Element} elm DOM element that children will be appended to.
*/

}, {
key: 'renderLayers_',
value: function renderLayers_(map, lyr, elm) {
value: function renderLayers_(map, lyr, elm, options, render) {
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(LayerSwitcher.renderLayer_(map, l, i));
elm.appendChild(LayerSwitcher.renderLayer_(map, l, i, options, render));
}
}
}

/**
* **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`
* @param {ol/layer/Group~LayerGroup} lyr The layer group to start iterating from.
* @param {Function} fn Callback which will be called for each `ol/layer/Base~BaseLayer`
* found under `lyr`. The signature for `fn` is the same as `ol/Collection~Collection#forEach`
*/

}, {
Expand All @@ -394,7 +525,7 @@ var LayerSwitcher = function (_Control) {
}

/**
* **Static** Generate a UUID
* **Static** Generate a UUID
* Adapted from http://stackoverflow.com/a/2117523/526860
* @returns {String} UUID
*/
Expand Down Expand Up @@ -448,6 +579,7 @@ var LayerSwitcher = function (_Control) {

/**
* Fold/unfold layer group
* @private
*/

}, {
Expand Down
19 changes: 19 additions & 0 deletions examples/select-groups.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
html, body {
height: 100%;
padding: 0;
margin: 0;
font-family: sans-serif;
font-size: small;
}

#map {
width: 100%;
height: 100%;
}

#group-select-style {
position: absolute;
top: 0.5em;
right: 0.5em;
z-index: 100;
}
19 changes: 19 additions & 0 deletions examples/select-groups.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>OpenLayers 3 - Selectable Groups</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<link rel="stylesheet" href="https://openlayers.org/en/v5.3.0/css/ol.css" />
<link rel="stylesheet" href="../src/ol-layerswitcher.css" />
<link rel="stylesheet" href="select-groups.css" />
</head>
<body>
<div id="map"></div>
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<script src="https://openlayers.org/en/v5.3.0/build/ol.js"></script>
<script src="../dist/ol-layerswitcher.js"></script>
<script src="select-groups.js"></script>
</body>
</html>
Loading

0 comments on commit baec8cc

Please sign in to comment.