ui_layers_legends_utils.mjs
/**
## /ui/layers/legends/utils
The categorized theme legend module exports the categorizedTheme to the `ui.layers.legend{}` library object.
Dictionary entries:
- layer_style_switch_caption
- layer_style_switch_all
- layer_style_cluster
@requires /dictionary
@module /ui/layers/legends/utils
*/
/**
@function catElement
@description
Create legend entry for category within a theme.
@param {Object} cat The category object.
@param {Object} theme The theme configuration object.
@param {Object} layer The layer object.
@returns {HTMLElement} The cat element for the legend grid.
**/
export function catElement(cat, theme, layer) {
cat.field ??= theme.field;
// Check whether cat is in current filter.
cat.disabled = layer.filter?.current[cat.field]?.ni?.includes(cat.value);
if (!cat.disabled) {
// The style must be restored for the theme layer render.
cat.style ??= cat._style;
}
if (layer.featureFields && theme.distribution === 'count') {
// Build a params object to pass to the numericFormatter.
const params = {
value: layer.featureFields[cat.field]?.[cat.value],
};
cat.count = mapp.utils.formatNumericValue(params);
if (!theme.legend?.showEmptyCat && !cat.count) return;
}
const catLegendIcon = mapp.ui.elements.legendIcon({
height: 24,
width: 24,
...(cat._style || cat.style),
});
const icon = mapp.utils.html`<div
style="height: 24px; width: 24px; grid-column: 1;">
${catLegendIcon}`;
const classList = ['label'];
if (layer.filter) {
classList.push('switch');
}
if (cat.disabled) {
classList.push('disabled');
}
const cat_label = cat.label + (cat.count ? ` [${cat.count}]` : '');
const label = mapp.utils.html.node`<div
class=${classList.join(' ')}>
${cat_label}`;
label.onclick = (e) => catToggle(e, layer, cat);
cat.node = mapp.utils.html.node`<div
data-id=${cat.value}
class="contents">
${icon}${label}`;
return cat.node;
}
/**
@function catToggle
@description
The method toggles the disabled class on the event target element.
If toggled [on] the filterAdd method will be called and the style will be set to null.
If toggled [off] the filterRemove method will be called and the style will be restored from the cat._style.
@param {Event} e The cat label click event.
@param {layer} layer The decorated mapp layer.
@param {object} cat The cat object from the theme.
*/
export function catToggle(e, layer, cat) {
const toggle = e.target.classList.toggle('disabled');
if (toggle) {
cat.disabled = true;
filterAdd(layer, cat);
// Store style to toggle on.
cat._style = cat.style;
// The feature should not be rendered.
cat.style = null;
} else {
delete cat.disabled;
filterRemove(layer, cat);
// If the filter is defined in the config for initial display of just a subset of catergories, _style may not yet exist.
cat._style ??= cat.style;
// Set the style to _style that is a stored style.
cat.style = cat._style;
delete cat._style;
}
layer.style.theme.filterOnly ? layer.L.changed() : layer.reload();
}
/**
@function filterAdd
@async
@description
Add the cat value to the current filter.
@param {layer} layer The decorated mapp layer.
@param {object} cat The cat object from the theme.
*/
async function filterAdd(layer, cat) {
if (layer.style.theme.filterOnly) return;
const inFilter = layer.filter.list?.find(
(f) => f.type === 'in' && f.field === cat.field,
);
if (inFilter?.card) {
// Remove filter set in filterpanel.
mapp.ui.layers.filters.removeFilter(layer, inFilter);
}
// Create empty field filter object if non exists.
if (!layer.filter.current[cat.field]) {
layer.filter.current[cat.field] = {};
}
// Create empty NI filter array for field if non exists.
if (!layer.filter.current[cat.field].ni) {
layer.filter.current[cat.field].ni = [];
}
// Push cat value into the NI filter array.
layer.filter.current[cat.field].ni.push(cat.keys || cat.value);
// Flatten the filter in case of arrays filter.
layer.filter.current[cat.field].ni =
layer.filter.current[cat.field].ni.flat();
const filter = layer.filter.list?.find(
(f) => f.type === 'ni' && f.field === cat.field,
);
if (filter?.card) {
const filterElement = await mapp.ui.layers.filters.ni(layer, filter);
filter.card.querySelector('.filter').replaceWith(filterElement);
}
}
/**
@function filterRemove
@async
@description
Remove the cat value to the current filter.
@param {layer} layer The decorated mapp layer.
@param {object} cat The cat object from the theme.
*/
async function filterRemove(layer, cat) {
if (layer.style.theme.filterOnly) return;
if (Array.isArray(cat.keys)) {
for (const cat of cat.keys) {
// Splice key out of the NI array.
layer.filter.current[cat.field].ni.splice(
layer.filter.current[cat.field].ni.indexOf(key),
1,
);
}
} else {
// Splice value out of the NI array.
layer.filter.current[cat.field].ni.splice(
layer.filter.current[cat.field].ni.indexOf(cat.value),
1,
);
}
// Delete current field filter if NI array is empty.
if (!layer.filter.current[cat.field].ni.length) {
delete layer.filter.current[cat.field].ni;
if (!Object.keys(layer.filter.current[cat.field]).length) {
delete layer.filter.current[cat.field];
}
}
const filter = layer.filter.list?.find(
(f) => f.type === 'ni' && f.field === cat.field,
);
if (filter?.card) {
const filterElement = await mapp.ui.layers.filters.ni(layer, filter);
filter.card.querySelector('.filter').replaceWith(filterElement);
}
}
/**
@function clusterLegend
@description
Adds a cluster icon for the theme.
@param layer The layer the theme exists on.
@returns {HTMLElement} The cat element for the legend grid.
**/
export function clusterLegend(layer) {
// Create cluster icon.
const icon = mapp.utils.html`<div
style="height: 40px; width: 40px;">
${mapp.ui.elements.legendIcon({
height: 40,
icon: layer.style.cluster.icon,
width: 40,
})}`;
// Create cluster label.
const label = mapp.utils.html`<div
class="label">
${mapp.dictionary.layer_style_cluster}`;
// Push icon and label into legend grid.
const el = mapp.utils.html`<div
data-id="cluster"
class="contents">
${icon}${label}`;
return el;
}
/**
@function themeLegend
@description
The method assigns theme.legend properties to control the layout through css classes.
@param {object} theme
@property {object} theme.legend The configuration for the theme legend.
**/
export function themeLegend(theme) {
theme.legend ??= {};
theme.legend.layout ??= 'grid';
const classList = ['contents-wrapper', theme.legend.layout];
if (theme.legend.nowrap) {
classList.push('nowrap');
}
theme.legend.classList = classList.join(' ');
}
/**
@function themeLegendSwitch
@description
The method returns a HTMLElement with a button which toggles all label elements in the legend.
@returns {HTMLElement} HTMLElement with nested button.
*/
export function themeLegendSwitch() {
return mapp.utils.html`<div
class="switch-all"
style="grid-column: 1/3;">
${mapp.dictionary.layer_style_switch_caption}
<button
class="bold"
onclick=${(e) => {
const allSwitches = [
...e.target.closest('.legend').querySelectorAll('.switch'),
];
const disabledSwitches = allSwitches.filter((switch_) =>
switch_.classList.contains('disabled'),
);
if (
disabledSwitches.length == 0 ||
disabledSwitches.length == allSwitches.length
) {
// if all switches are either enabled or disabled, click on all
allSwitches.forEach((switch_) => switch_.click());
} else {
// if only some of them are enabled, click only on disabled ones
disabledSwitches.forEach((switch_) => switch_.click());
}
}}>${mapp.dictionary.layer_style_switch_all}
</button>.`;
}