import {EVNT_VALCHANGE} from "./filter-constants.js";
export class ModalFilterBase {
static _$getFilterColumnHeaders (btnMeta) {
return btnMeta.map((it, i) => $(``));
}
/**
* @param opts Options object.
* @param opts.modalTitle
* @param opts.fnSort
* @param opts.pageFilter
* @param [opts.namespace]
* @param [opts.allData]
*/
constructor (opts) {
this._modalTitle = opts.modalTitle;
this._fnSort = opts.fnSort;
this._pageFilter = opts.pageFilter;
this._namespace = opts.namespace;
this._allData = opts.allData || null;
this._isRadio = !!opts.isRadio;
this._list = null;
this._filterCache = null;
}
get pageFilter () { return this._pageFilter; }
get allData () { return this._allData; }
_$getWrpList () { return $(`
`); }
_$getColumnHeaderPreviewAll (opts) {
return $(``);
}
/**
* @param $wrp
* @param opts
* @param opts.$iptSearch
* @param opts.$btnReset
* @param opts.$btnOpen
* @param opts.$btnToggleSummaryHidden
* @param opts.$wrpMiniPills
* @param opts.isBuildUi If an alternate UI should be used, which has "send to right" buttons.
*/
async pPopulateWrapper ($wrp, opts) {
opts = opts || {};
await this._pInit();
const $ovlLoading = $(`Loading...
`).appendTo($wrp);
const $iptSearch = (opts.$iptSearch || $(``)).disableSpellcheck();
const $btnReset = opts.$btnReset || $(``);
const $dispNumVisible = $(``);
const $wrpIptSearch = $$`
${$iptSearch}
${$dispNumVisible}
`;
const $wrpFormTop = $$`${$wrpIptSearch}${$btnReset}
`;
const $wrpFormBottom = opts.$wrpMiniPills || $(``);
const $wrpFormHeaders = $(``);
const $cbSelAll = opts.isBuildUi || this._isRadio ? null : $(``);
const $btnSendAllToRight = opts.isBuildUi ? $(``) : null;
if (!opts.isBuildUi) {
if (this._isRadio) $wrpFormHeaders.append(``);
else $$``.appendTo($wrpFormHeaders);
}
const $btnTogglePreviewAll = this._$getColumnHeaderPreviewAll(opts)
.appendTo($wrpFormHeaders);
this._$getColumnHeaders().forEach($ele => $wrpFormHeaders.append($ele));
if (opts.isBuildUi) $btnSendAllToRight.appendTo($wrpFormHeaders);
const $wrpForm = $$`${$wrpFormTop}${$wrpFormBottom}${$wrpFormHeaders}
`;
const $wrpList = this._$getWrpList();
const $btnConfirm = opts.isBuildUi ? null : $(``);
this._list = new List({
$iptSearch,
$wrpList,
fnSort: this._fnSort,
});
const listSelectClickHandler = new ListSelectClickHandler({list: this._list});
if (!opts.isBuildUi && !this._isRadio) listSelectClickHandler.bindSelectAllCheckbox($cbSelAll);
ListUiUtil.bindPreviewAllButton($btnTogglePreviewAll, this._list);
SortUtil.initBtnSortHandlers($wrpFormHeaders, this._list);
this._list.on("updated", () => $dispNumVisible.html(`${this._list.visibleItems.length}/${this._list.items.length}`));
this._allData = this._allData || await this._pLoadAllData();
await this._pageFilter.pInitFilterBox({
$wrpFormTop,
$btnReset,
$wrpMiniPills: $wrpFormBottom,
namespace: this._namespace,
$btnOpen: opts.$btnOpen,
$btnToggleSummaryHidden: opts.$btnToggleSummaryHidden,
});
this._allData.forEach((it, i) => {
this._pageFilter.mutateAndAddToFilters(it);
const filterListItem = this._getListItem(this._pageFilter, it, i);
this._list.addItem(filterListItem);
if (!opts.isBuildUi) {
if (this._isRadio) filterListItem.ele.addEventListener("click", evt => listSelectClickHandler.handleSelectClickRadio(filterListItem, evt));
else filterListItem.ele.addEventListener("click", evt => listSelectClickHandler.handleSelectClick(filterListItem, evt));
}
});
this._list.init();
this._list.update();
const handleFilterChange = () => {
const f = this._pageFilter.filterBox.getValues();
this._list.filter(li => this._isListItemMatchingFilter(f, li));
};
this._pageFilter.trimState();
this._pageFilter.filterBox.on(EVNT_VALCHANGE, handleFilterChange);
this._pageFilter.filterBox.render();
handleFilterChange();
$ovlLoading.remove();
const $wrpInner = $$`
${$wrpForm}
${$wrpList}
${opts.isBuildUi ? null : $$`
${$btnConfirm}
`}
`.appendTo($wrp.empty());
return {
$wrpIptSearch,
$iptSearch,
$wrpInner,
$btnConfirm,
pageFilter: this._pageFilter,
list: this._list,
$cbSelAll,
$btnSendAllToRight,
};
}
_isListItemMatchingFilter (f, li) { return this._isEntityItemMatchingFilter(f, this._allData[li.ix]); }
_isEntityItemMatchingFilter (f, it) { return this._pageFilter.toDisplay(f, it); }
async pPopulateHiddenWrapper () {
await this._pInit();
this._allData = this._allData || await this._pLoadAllData();
await this._pageFilter.pInitFilterBox({namespace: this._namespace});
this._allData.forEach(it => {
this._pageFilter.mutateAndAddToFilters(it);
});
this._pageFilter.trimState();
}
handleHiddenOpenButtonClick () {
this._pageFilter.filterBox.show();
}
handleHiddenResetButtonClick (evt) {
this._pageFilter.filterBox.reset(evt.shiftKey);
}
_getStateFromFilterExpression (filterExpression) {
const filterSubhashMeta = Renderer.getFilterSubhashes(Renderer.splitTagByPipe(filterExpression), this._namespace);
const subhashes = filterSubhashMeta.subhashes.map(it => `${it.key}${HASH_SUB_KV_SEP}${it.value}`);
const unpackedSubhashes = this.pageFilter.filterBox.unpackSubHashes(subhashes, {force: true});
return this.pageFilter.filterBox.getNextStateFromSubHashes({unpackedSubhashes});
}
/**
* N.b.: assumes any preloading has already been done
* @param filterExpression
*/
getItemsMatchingFilterExpression ({filterExpression}) {
const nxtStateOuter = this._getStateFromFilterExpression(filterExpression);
const f = this._pageFilter.filterBox.getValues({nxtStateOuter});
const filteredItems = this._filterCache.list.getFilteredItems({
items: this._filterCache.list.items,
fnFilter: li => this._isListItemMatchingFilter(f, li),
});
return this._filterCache.list.getSortedItems({items: filteredItems});
}
getEntitiesMatchingFilterExpression ({filterExpression}) {
const nxtStateOuter = this._getStateFromFilterExpression(filterExpression);
const f = this._pageFilter.filterBox.getValues({nxtStateOuter});
return this._allData.filter(this._isEntityItemMatchingFilter.bind(this, f));
}
getRenderedFilterExpression ({filterExpression}) {
const nxtStateOuter = this._getStateFromFilterExpression(filterExpression);
return this.pageFilter.filterBox.getDisplayState({nxtStateOuter});
}
/**
* @param [opts]
* @param [opts.filterExpression] A filter expression, as usually found in @filter tags, which will be applied.
*/
async pGetUserSelection ({filterExpression = null} = {}) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async resolve => {
const {$modalInner, doClose} = await this._pGetShowModal(resolve);
await this.pPreloadHidden($modalInner);
this._doApplyFilterExpression(filterExpression);
this._filterCache.$btnConfirm.off("click").click(async () => {
const checked = this._filterCache.list.visibleItems.filter(it => it.data.cbSel.checked);
resolve(checked);
doClose(true);
// region reset selection state
if (this._filterCache.$cbSelAll) this._filterCache.$cbSelAll.prop("checked", false);
this._filterCache.list.items.forEach(it => {
if (it.data.cbSel) it.data.cbSel.checked = false;
it.ele.classList.remove("list-multi-selected");
});
// endregion
});
await UiUtil.pDoForceFocus(this._filterCache.$iptSearch[0]);
});
}
async _pGetShowModal (resolve) {
const {$modalInner, doClose} = await UiUtil.pGetShowModal({
isHeight100: true,
isWidth100: true,
title: `Filter/Search for ${this._modalTitle}`,
cbClose: (isDataEntered) => {
this._filterCache.$wrpModalInner.detach();
if (!isDataEntered) resolve([]);
},
isUncappedHeight: true,
});
return {$modalInner, doClose};
}
_doApplyFilterExpression (filterExpression) {
if (!filterExpression) return;
const filterSubhashMeta = Renderer.getFilterSubhashes(Renderer.splitTagByPipe(filterExpression), this._namespace);
const subhashes = filterSubhashMeta.subhashes.map(it => `${it.key}${HASH_SUB_KV_SEP}${it.value}`);
this.pageFilter.filterBox.setFromSubHashes(subhashes, {force: true, $iptSearch: this._filterCache.$iptSearch});
}
_getNameStyle () { return `bold`; }
/**
* Pre-heat the modal, thus allowing access to the filter box underneath.
*
* @param [$modalInner]
*/
async pPreloadHidden ($modalInner) {
// If we're rendering in "hidden" mode, create a dummy element to attach the UI to.
$modalInner = $modalInner || $(``);
if (this._filterCache) {
this._filterCache.$wrpModalInner.appendTo($modalInner);
} else {
const meta = await this.pPopulateWrapper($modalInner);
const {$iptSearch, $btnConfirm, pageFilter, list, $cbSelAll} = meta;
const $wrpModalInner = meta.$wrpInner;
this._filterCache = {$iptSearch, $wrpModalInner, $btnConfirm, pageFilter, list, $cbSelAll};
}
}
/** Widths should total to 11/12ths, as 1/12th is set aside for the checkbox column. */
_$getColumnHeaders () { throw new Error(`Unimplemented!`); }
async _pInit () { /* Implement as required */ }
async _pLoadAllData () { throw new Error(`Unimplemented!`); }
async _getListItem () { throw new Error(`Unimplemented!`); }
}