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!`); } }