ui_layers_legends_categorized.mjs
/**
### /ui/layers/legends/categorized
The categorized theme legend module exports the categorizedTheme to the `ui.layers.legend{}` library object.
@requires /ui/elements/legendIcon
@module /ui/layers/legends/categorized
*/
/**
@function categorizedTheme
@description
The categorizedTheme method creates and returns a categorized theme legend for the current layer.style.theme.
@param {layer} layer The decorated mapp layer.
@returns {HTMLElement} The categorized theme legend element.
*/
export default function categorizedTheme(layer) {
const theme = layer.style.theme
theme.filterOnly ??= layer.style.filterOnly
theme.legend ??= {}
theme.legend.grid = []
// Make 'left' default alignment.
theme.legend.alignContents ??= 'left'
theme.legend.alignContents += ' contents'
// Switch all control
theme.legend.switch = theme.field && layer.filter && mapp.utils.html`<div
class="switch-all"
style="grid-column: 1/3;">
${mapp.dictionary.layer_style_switch_caption}
<button
class="primary-colour 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>.`
theme.categories.forEach(cat => {
cat.field ??= theme.field
// Check whether cat is in current filter.
cat.disabled ??= layer.filter?.current[cat.field]?.ni?.indexOf(cat.value) >= 0
if (layer.featureFields && theme.distribution === 'count') {
cat.count = layer.featureFields[cat.field]?.[cat.value]
if (!cat.disabled && !cat.count) return;
}
const catLegendIcon = mapp.ui.elements.legendIcon({
width: 24,
height: 24,
...(cat._style || cat.style)
})
const icon = mapp.utils.html`<div
style="height: 24px; width: 24px; grid-column: 1;">
${catLegendIcon}`;
const classList = `label ${layer.filter && 'switch' ||''} ${cat.disabled && 'disabled' ||''}`;
const cat_label = cat.label + (cat.count? ` [${cat.count}]`:'')
// Cat label with filter function.
const label = mapp.utils.html`<div
class=${classList}
style="grid-column: 2;"
onclick=${e => catToggle(e, layer, cat)}>${cat_label}`
cat.node = mapp.utils.html.node`<div
data-id=${cat.value}
class="${theme.legend.alignContents}">
${icon}${label}`
// Push icon and label into legend grid.
theme.legend.grid.push(cat.node)
})
// Attach row for cluster locations.
if (layer.style.cluster) {
// Create cluster icon.
const icon = mapp.utils.html`
<div
style="height: 40px; width: 40px;">
${mapp.ui.elements.legendIcon({
width: 40,
height: 40,
icon: layer.style.cluster.icon
})}`
// Create cluster label.
const label = mapp.utils.html`
<div
class="label">
${mapp.dictionary.layer_style_cluster}`
// Push icon and label into legend grid.
theme.legend.grid.push(mapp.utils.html`<div
data-id="cluster"
class=${theme.legend.alignContents}>
${icon}${label}`)
}
theme.legend.layout ??= 'grid'
theme.legend.node = mapp.utils.html.node`
<div class="legend">
${theme.legend.switch || ''}
<div class=${`contents-wrapper ${theme.legend.layout}`}>
${theme.legend.grid}`
layer.style.legend ??= theme.legend.node
if (layer.style.legend) {
layer.style.legend.replaceChildren(...theme.legend.node.children)
}
return theme.legend.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.
*/
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)
cat.style = cat._style
delete cat._style
}
layer.style.theme.filterOnly
? layer.L.changed()
: layer.reload()
}
/**
@function filterAdd
@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.
*/
function filterAdd(layer, cat) {
if (layer.style.theme.filterOnly) return;
// 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) {
filter.card
.querySelector('.filter')
.replaceWith(mapp.ui.layers.filters.ni(layer, filter))
}
}
/**
@function filterRemove
@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.
*/
function filterRemove(layer, cat) {
if (layer.style.theme.filterOnly) return;
if (Array.isArray(cat.keys)) {
cat.keys.forEach(key => {
// 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) {
filter.card
.querySelector('.filter')
.replaceWith(mapp.ui.layers.filters.ni(layer, filter))
}
}