@unicef-polymer/etools-unicef
Version:
eTools UNICEF library of reusable components
474 lines (469 loc) • 16.7 kB
JavaScript
import { __decorate } from "tslib";
import { LitElement, html, css } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { etoolsFiltersStyles } from './etools-filters-styles';
import '../etools-icons/etools-icon';
import '@shoelace-style/shoelace/dist/components/switch/switch.js';
import '../etools-button/etools-button';
import '@shoelace-style/shoelace/dist/components/dropdown/dropdown.js';
import '../etools-dropdown/etools-dropdown-multi';
import '../etools-dropdown/etools-dropdown';
import '../etools-input/etools-input';
import '../etools-date-time/datepicker-lite.js';
import '../etools-loading/etools-loading';
import debounce from 'lodash-es/debounce';
import { getTranslation } from './utils/translate';
export var EtoolsFilterTypes;
(function (EtoolsFilterTypes) {
EtoolsFilterTypes[EtoolsFilterTypes["Search"] = 0] = "Search";
EtoolsFilterTypes[EtoolsFilterTypes["Dropdown"] = 1] = "Dropdown";
EtoolsFilterTypes[EtoolsFilterTypes["DropdownMulti"] = 2] = "DropdownMulti";
EtoolsFilterTypes[EtoolsFilterTypes["Toggle"] = 3] = "Toggle";
EtoolsFilterTypes[EtoolsFilterTypes["Date"] = 4] = "Date";
})(EtoolsFilterTypes || (EtoolsFilterTypes = {}));
let EtoolsFilters = class EtoolsFilters extends LitElement {
constructor() {
super();
this.filters = [];
/** Set this to true if the Loading... overlay should be displayed over the page,
* not just over the etools-filter component */
this.filterLoadingAbsolute = false;
this.lastSelectedValues = null;
if (!this.language) {
// @ts-ignore
this.language = window.EtoolsLanguage || 'en';
}
this.handleLanguageChange = this.handleLanguageChange.bind(this);
}
handleLanguageChange(e) {
this.language = e.detail.language;
}
static get styles() {
return [
etoolsFiltersStyles,
css `
/* Set datepicker prefix icon color using mixin (cannot be used in etools-filter-styles) */
datepicker-lite {
--etools-input-prefix: {
color: var(--secondary-text-color, rgba(0, 0, 0, 0.54));
};
}
*[hidden] {
display: none !important;
}
.date {
margin-inline-end: 16px;
}
etools-button[variant='text'] {
--sl-color-primary-600: var(--primary-color, rgb(2, 132, 199));
}
`
];
}
getSearchTmpl(f) {
// language=HTML
return html `
<etools-input
class="filter search"
part="filter-search"
?hidden="${!f.selected}"
type="search"
clearable
autocomplete="off"
always-float-label
.value="${f.selectedValue}"
placeholder="${f.filterName}"
data-filter-key="${f.filterKey}"
-changed="${this.textInputChange}"
>
<etools-icon name="search" slot="prefix"></etools-icon>
</etools-input>
`;
}
getDropdownTmpl(f) {
// language=HTML
return html `
<etools-dropdown
id="${f.filterKey}"
?hidden="${!f.selected}"
class="filter"
part="filter-dropdown"
label="${f.filterName}"
placeholder="—"
?disabled="${f.disabled}"
.options="${f.selectionOptions}"
.optionValue="${f.optionValue ? f.optionValue : 'value'}"
.optionLabel="${f.optionLabel ? f.optionLabel : 'label'}"
.selected="${f.selectedValue}"
trigger-value-change-event
-selected-item-changed="${this.filterSelectionChange}"
data-filter-key="${f.filterKey}"
?hide-search="${f.hideSearch}"
.minWidth="${f.minWidth}"
.loadDataMethod="${f.loadDataDropdownOptions}"
horizontal-align="left"
no-dynamic-align
enable-none-option
>
</etools-dropdown>
`;
}
getDropdownMultiTmpl(f) {
// language=HTML
return html `
<etools-dropdown-multi
id="${f.filterKey}"
?hidden="${!f.selected}"
class="filter"
part="filter-dropdownmulti"
label="${f.filterName}"
placeholder="${getTranslation(this.language, 'SELECT')}"
?disabled="${f.disabled}"
.options="${f.selectionOptions}"
.optionValue="${f.optionValue ? f.optionValue : 'value'}"
.optionLabel="${f.optionLabel ? f.optionLabel : 'label'}"
.selectedValues="${[...f.selectedValue]}"
trigger-value-change-event
-selected-items-changed="${this.filterMultiSelectionChange}"
data-filter-key="${f.filterKey}"
?hide-search="${f.hideSearch}"
.minWidth="${f.minWidth}"
.loadDataMethod="${f.loadDataDropdownOptions}"
horizontal-align="left"
no-dynamic-align
>
</etools-dropdown-multi>
`;
}
getDateTmpl(f) {
// language=HTML
return html `
<datepicker-lite
class="filter date"
part="filter-datepicker"
?hidden="${!f.selected}"
.label="${f.filterName}"
.value="${f.selectedValue}"
fire-date-has-changed
-has-changed="${this.filterDateChange}"
data-filter-key="${f.filterKey}"
selected-date-display-format="D MMM YYYY"
>
</datepicker-lite>
`;
}
getToggleTmpl(f) {
// language=HTML
return html `
<div class="filter toggle" ?hidden="${!f.selected}" style="padding: 8px 0; box-sizing: border-box;">
${f.filterName}
<sl-switch
title="${f.filterName}"
id="toggleFilter"
part="filter-toggle"
?checked="${f.selectedValue}"
data-filter-key="${f.filterKey}"
-change="${this.filterToggleChange}"
>
</sl-switch>
</div>
`;
}
selectedFiltersTmpl(filters) {
if (!filters) {
return html `<etools-loading
source="filters-loading"
?absolute=${this.filterLoadingAbsolute}
loading-text="${getTranslation(this.language, 'LOADING')}"
active
></etools-loading>`;
}
const tmpl = [];
filters.forEach((f) => {
let filterHtml;
switch (f.type) {
case EtoolsFilterTypes.Search:
filterHtml = this.getSearchTmpl(f);
break;
case EtoolsFilterTypes.Dropdown:
filterHtml = this.getDropdownTmpl(f);
break;
case EtoolsFilterTypes.DropdownMulti:
filterHtml = this.getDropdownMultiTmpl(f);
break;
case EtoolsFilterTypes.Date:
filterHtml = this.getDateTmpl(f);
break;
case EtoolsFilterTypes.Toggle:
filterHtml = this.getToggleTmpl(f);
break;
}
if (filterHtml) {
tmpl.push(filterHtml);
}
});
return tmpl;
}
filterMenuOptions(filters) {
if (!this.filters) {
return [];
}
const menuOptions = [];
filters.forEach((f) => {
// language=HTML
menuOptions.push(html `
<sl-menu-item
type="checkbox"
?disabled="${f.disabled}"
?checked="${f.selected}"
data-filter-key="${f.filterKey}"
value="${f.filterKey}"
>
${f.filterName}
</sl-menu-item>
`);
});
return menuOptions;
}
connectedCallback() {
this.afterFilterChange = debounce(this.afterFilterChange.bind(this), 1000);
super.connectedCallback();
document.addEventListener('language-changed', this.handleLanguageChange);
}
disconnectedCallback() {
super.disconnectedCallback();
document.removeEventListener('language-changed', this.handleLanguageChange);
}
render() {
this.setDefaultLastSelectedValues();
// language=HTML
return html `
<style>
etools-dropdown[disabled] {
opacity: 60%;
}
etools-dropdown-multi[disabled] {
opacity: 60%;
}
</style>
<div id="filters" part="filters">${this.selectedFiltersTmpl(this.filters)}</div>
<div id="filters-selector" part="filters-selector">
<sl-dropdown id="filterMenu" stay-open-on-select>
<etools-button variant="text" class="trigger-button" slot="trigger">
<etools-icon name="filter-list" slot="prefix"></etools-icon>
${this.textFilters || getTranslation(this.language, 'FILTERS')}
</etools-button>
<sl-menu -select="${this.selectFilter}">
<sl-menu-item class="clear-all-filters" ="${this.clearAllFilters}">
<etools-button variant="text" tabindex="-1"
>${this.textClearAll || getTranslation(this.language, 'CLEAR_ALL')}</etools-button
>
</sl-menu-item>
${this.filterMenuOptions(this.filters)}
</sl-menu>
</sl-dropdown>
</div>
`;
}
setDefaultLastSelectedValues() {
if (!this.lastSelectedValues && this.filters) {
this.lastSelectedValues = this.getAllFiltersAndTheirValues();
}
}
clearAllFilters() {
if (this.filters.length === 0) {
return;
}
// Clear selected value in filters
this.filters.forEach((f) => {
if (f.disabled) {
return;
}
f.selectedValue = this.getFilterEmptyValue(f.type);
});
// clear selecter filters
this.filters.forEach((f) => {
if (f.disabled) {
return;
}
if (f.filterKey === 'search') {
// TODO - using FilterKeys.search here breaks the app
return;
}
f.selected = false;
});
// repaint
this.requestUpdate();
this.updateComplete.then(() => this.fireFiltersChangeEvent());
}
selectFilter(e) {
const menuOption = e.detail.item;
if (!menuOption.value) {
return;
}
const filterOption = this.getFilterOption(menuOption);
const wasSelected = menuOption.hasAttribute('checked');
// toggle selected state
filterOption.selected = !wasSelected;
// reset selected value if filter was unselected and had a value
if (wasSelected) {
filterOption.selectedValue = this.getFilterEmptyValue(filterOption.type);
}
// repaint&fire change event
this.requestUpdate();
this.updateComplete.then(() => this.fireFiltersChangeEvent());
}
// get filter empty value by type
getFilterEmptyValue(filterType) {
switch (filterType) {
case EtoolsFilterTypes.Search:
return '';
case EtoolsFilterTypes.Toggle:
return false;
case EtoolsFilterTypes.Date:
case EtoolsFilterTypes.Dropdown:
return null;
case EtoolsFilterTypes.DropdownMulti:
return [];
}
}
getFilterOption(filterElement) {
const filterKey = filterElement.getAttribute('data-filter-key');
if (!filterKey) {
throw new Error('[EtoolsFilters.getFilterOption] No data-filter-key attr found on clicked option');
}
const filterOption = this.filters.find((f) => f.filterKey === filterKey);
if (!filterOption) {
// something went wrong... filter option not found
throw new Error(`[EtoolsFilters.getFilterOption] Filter option not found by filterKey: "${filterKey}"`);
}
return filterOption;
}
textInputChange(e) {
if (!e.detail) {
return;
}
const filterEl = e.currentTarget;
const filterOption = this.getFilterOption(filterEl);
if (filterOption.selectedValue === e.detail.value) {
return;
}
filterOption.selectedValue = e.detail.value;
this.requestUpdate();
this.updateComplete.then(() => this.fireFiltersChangeEvent());
}
filterSelectionChange(e) {
const filterEl = e.currentTarget;
const filterOption = this.getFilterOption(filterEl);
filterOption.selectedValue = e.detail.selectedItem ? e.detail.selectedItem[filterEl.optionValue] : null;
this.requestUpdate();
this.updateComplete.then(() => this.fireFiltersChangeEvent());
}
filterMultiSelectionChange(e) {
const filterEl = e.currentTarget;
const filterOption = this.getFilterOption(filterEl);
const currentSelectedVal = e.detail.selectedItems.length > 0
? e.detail.selectedItems.map((optionObj) => optionObj[filterEl.optionValue])
: [];
if (JSON.stringify(currentSelectedVal) === JSON.stringify(filterOption.selectedValue)) {
return;
}
filterOption.selectedValue = currentSelectedVal;
this.afterFilterChange();
}
afterFilterChange() {
this.requestUpdate();
this.updateComplete.then(() => this.fireFiltersChangeEvent());
}
filterDateChange(e) {
const filterEl = e.currentTarget;
const filterOption = this.getFilterOption(filterEl);
filterOption.selectedValue = filterEl.value; // get datepicker value
this.requestUpdate();
this.updateComplete.then(() => this.fireFiltersChangeEvent());
}
filterToggleChange(e) {
if (!e.detail) {
return;
}
const filterEl = e.currentTarget;
const filterOption = this.getFilterOption(filterEl);
if (filterOption.selectedValue === filterEl.checked) {
return;
}
filterOption.selectedValue = filterEl.checked; // get toggle btn value
this.requestUpdate();
this.updateComplete.then(() => this.fireFiltersChangeEvent());
}
// update filter values from parent element (! one way data flow)
updateFilters(filterValues) {
if (!filterValues || Object.keys(filterValues).length === 0) {
return;
}
const keys = Object.keys(filterValues);
this.filters.forEach((f) => {
if (keys.indexOf(f.filterKey) > -1) {
// filter found by key
if (!f.selected) {
// select filter is not already selected
f.selected = true;
}
// update value
f.selectedValue = filterValues[f.filterKey];
}
});
this.requestUpdate();
this.lastSelectedValues = { ...this.getAllFiltersAndTheirValues(), ...filterValues };
}
// fire change custom event to notify parent that filters were updated
fireFiltersChangeEvent() {
const selectedValues = this.getAllFiltersAndTheirValues();
if (JSON.stringify(this.lastSelectedValues) === JSON.stringify(selectedValues)) {
return;
}
this.lastSelectedValues = { ...selectedValues };
this.dispatchEvent(new CustomEvent('filter-change', {
detail: this.getSelectedFiltersAndTheirValues(),
bubbles: true,
composed: true
}));
}
// build and return and object based on filterKey and selectedValue
getAllFiltersAndTheirValues() {
const allFilters = {};
if (this.filters) {
this.filters.forEach((f) => {
allFilters[f.filterKey] = f.selectedValue;
});
}
return allFilters;
}
getSelectedFiltersAndTheirValues() {
const selectedFilters = {};
this.filters.forEach((f) => {
if (f.selected) {
selectedFilters[f.filterKey] = f.selectedValue;
}
});
return selectedFilters;
}
};
__decorate([
property({ type: Array })
], EtoolsFilters.prototype, "filters", void 0);
__decorate([
property({ type: Boolean })
], EtoolsFilters.prototype, "filterLoadingAbsolute", void 0);
__decorate([
property({ type: String })
], EtoolsFilters.prototype, "textClearAll", void 0);
__decorate([
property({ type: String })
], EtoolsFilters.prototype, "textFilters", void 0);
__decorate([
property({ type: String })
], EtoolsFilters.prototype, "language", void 0);
EtoolsFilters = __decorate([
customElement('etools-filters')
], EtoolsFilters);
export { EtoolsFilters };