class TaxonomySelectorPrev { constructor (container, options = {}) { this.a11y = window.jvbA11y; this.cache = window.jvbCache; this.error = window.jvbError; this.taxonomy = container.dataset.taxonomy; this.container = container; this.initConfig(options); this.initElements(); this.initListeners(); this.page = 1; this.hasMore = true; this.isLoading = false; this.searchQuery = ''; this.terms = new Map(); this.navigationPath = []; this.currentParent = 0; this.currentParentName = ''; this.currentPath = ''; this.fetchSpecificTerms = false; this.creator = false; if ('jvbTaxCreator' in window && this.container.querySelector('.create-new-term-section')) { this.creator = new window.jvbTaxCreator(this); } //Render the selected items this.updateSelected(); this.modal.backButton.hidden = this.currentParent === 0; } initConfig(options) { //Set up currently selected items this.selectedTerms = {}; if (options.selected) { if (typeof options.selected === 'object' && !window.isEmptyObject(options.selected)) { this.options.selected.forEach(item => { this.selectedTerms[item.id] = item.name; }); } else { this.selectedTerms = options.selected; } } let embeddedOptions = JSON.parse(this.container.dataset.config??'{}'); this.maxSelections = options.maxSelections || embeddedOptions.maxSelections || 0; this.isHierarchal = options.hierarchical || embeddedOptions.maxSelections || 0; this.base = options.base || embeddedOptions.base || ''; this.onSuccess = options.onSuccess || embeddedOptions.onSuccess || null; this.onClose = options.onClose || embeddedOptions.onClose || null; this.isFeed = options.feed || embeddedOptions.feed || false; if (this.isFeed) { this.feedSelected = document.querySelector('.selected-items-section .selected-items') } } initElements() { //Separate items by container this.elements = { modal: this.container.querySelector('dialog'), selectedTerms: this.container.querySelector('.selected-items'), }; this.modal = { searchInput: this.elements.modal.querySelector('input[type=search]'), termsList: this.elements.modal.querySelector('.items-container'), termsWrap: this.elements.modal.querySelector('.items-wrap'), breadcrumbs: this.elements.modal.querySelector('nav.term-navigation'), loading: this.elements.modal.querySelector('.loading'), loadingText: this.elements.modal.querySelector('.loading span'), clearSearch: this.elements.modal.querySelector('.clear-search'), selectedTerms: this.elements.modal.querySelector('.selected-items'), backButton: this.elements.modal.querySelector('.back-to-parent'), sentinel: this.elements.modal.querySelector('.scroll-sentinel') }; } initListeners() { this.container.addEventListener('click', this.handleClick.bind(this)); this.container.addEventListener('change', this.handleChange.bind(this)); let o = this.container.closest('.field')?.querySelector('.add-item-btn'); if (!o) { o = '.filter-toggle'; // o = this.container.closest('.jvb-selector')?.querySelector('.filter-toggle'); } else { o = '.add-item-btn'; } this.observer = new IntersectionObserver( entries => { entries.forEach(entry => { if (entry.isIntersecting && !this.isLoading && this.hasMore) { this.fetchTerms(); } }); }, { root: this.modal.termsWrap, threshold: 0.5 } ); this.m = new window.jvbModal( this.elements.modal, { save: null, open: o, openMessage: `Opened ${this.taxonomy} selection. Choose from checkboxes or search to filter results.`, onOpen: () => this.openModal(), onClose:() => this.closeModal() } ); } handleClick(e) { if (window.targetCheck(e, '.remove-item')) { let item = window.targetCheck(e, '.selected-item'); if (item) { this.removeSelectedTerm(item.dataset.id); } } else if (window.targetCheck(e, '.back-to-parent')) { this.toParent(); } else if (window.targetCheck(e, '.toggle-children')) { let item = e.target.closest('li'); this.toChild(parseInt(item.dataset.id), item.querySelector('.term-name').textContent); } else if (window.targetCheck(e, '.path-level')) { let btn = window.targetCheck(e, '.path-level'); if (btn.textContent !== this.currentParentName) { window.removeChildren(this.modal.termsList); let level = parseInt(btn.dataset.level); this.navigatioPath = this.navigationPath.slice(0, level + 1); this.currentParent = parseInt(btn.dataset.id); this.currentParentName = btn.textContent; this.page = 1; this.hasMore = true; this.fetchTerms(); } } } handleChange(e) { e.preventDefault(); e.stopPropagation(); let input = e.target; if (e.target.checked) { let label = e.target.closest('li').querySelector('label'); this.addSelectedTerm(input.id, label.title, label.dataset.path); } else { this.removeSelectedTerm(input.id); } } handleSearch(e) { let query = this.modal.searchInput.value.trim(); if (query === this.searchQuery) return; this.searchQuery = query; this.page = 1; this.hasMore = true; window.removeChildren(this.modal.termsList); if (query.length >= 2 || query.length === 0) { this.fetchTerms(false, true); } else { this.hideLoading(); this.showEmptyState('No Results. \nEnter at least 2 characters to search.') } } openModal() { this.observer.observe(this.modal.sentinel); this.fetchTerms(); this.modal.searchInput.focus(); this.modal.searchInput.addEventListener('input', window.debounce(() => this.handleSearch())); } closeModal() { this.observer.unobserve(this.modal.sentinel); window.removeChildren(this.modal.termsList); this.updateSelected(); this.resetState(); if(this.isFeed && typeof this.onClose === 'function') { this.onClose(this.taxonomy, this.selectedTerms); } this.modal.searchInput.removeEventListener('input', window.debounce(() => this.handleSearch())); } nextPage() { if (this.hasMore) { this.page++; } } resetPage() { this.page = 1; this.hasMore = true; } resetState() { this.resetPage(); this.searchQuery = ''; this.page = 1; this.currentParent = 0; this.currentParentName = ''; this.currentPath = ''; this.navigationPath = []; this.hasMore = true; this.isLoading = false; this.retries = { count: 0, max: 3, delay: 1000 }; } toParent() { this.navigationPath.pop(); let prv = this.navigationPath[this.navigationPath.length - 1]; this.currentParent = prv ? prv.id : 0; this.currentParentName = prv ? prv.name : ''; window.removeChildren(this.modal.termsList); this.page = 1; this.hasMore = true; this.fetchTerms(); } toChild(termId, termName) { this.navigationPath.push({ id: termId, name: termName }); this.currentParent = termId; this.currentParentName = termName; window.removeChildren(this.modal.termsList); this.page = 1; this.hasMore = true; this.fetchTerms(); } buildRequest() { let params = new URLSearchParams({ taxonomy: this.taxonomy, parent: this.currentParent, search: this.searchQuery, page: this.page }); if (this.isFeed && this.fetchSpecificTerms) { params.append('termIDs', this.fetchSpecificTerms); } if (this.isFeed && window.feedBlock?.config?.context) { params.append('main_context', JSON.stringify({ context: window.feedBlock.config.context, id: window.feedBlock.config.source })); params.append('content', window.feedBlock.filters.content); } return params.toString(); } showLoading() { this.isLoading = true; this.modal.loading.hidden = false; this.elements.modal.classList.add('loading'); let text = (this.searchQuery !== '') ? 'searching for "'+this.searchQuery+'" items' : ((this.currentParentName === '') ? 'loading items' : 'loading '+this.currentParentName+' items'); this.stopTyping = window.typeLoop(this.modal.loadingText, text); } hideLoading() { this.isLoading = false; this.modal.loading.hidden = true; this.elements.modal.classList.remove('loading'); this.stopTyping(); } async fetchTerms(forceRefresh = false, showPath = false) { if (this.isLoading) { return; } try { this.showLoading(); const data = await this.cache.fetchWithCache( `${jvbSettings.api}terms?`+this.buildRequest(), { method: 'GET', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': jvbSettings.nonce, } }, { content: this.taxonomy, // forceRefresh: true forceRefresh: forceRefresh } ); if (this.fetchSpecificTerms) { this.fetchSpecificTerms = false; return data.terms; } if (!data || !data.terms || data.terms.length === 0) { if (this.page === 1) { this.showEmptyState(); } this.hasMore = false; } else { this.hasMore = data.pagination['has_more']; this.renderTerms(data.terms, this.page > 1, showPath); if (this.hasMore) { this.nextPage(); } } } catch (e) { this.handleError(e); } finally { this.hideLoading(); } } handleError(error){ return this.error.log( error, { component: 'Taxonomy Selector', action: 'fetchTerms' }, () => this.fetchTerms() ); } addPlaceholders() { } renderTerms(terms, append = false, path = false) { if (!append) { window.removeChildren(this.modal.termsList); this.addPlaceholders(); } if (terms.length === 0) { this.a11y.announceUpdate(0, append); return; } this.updateBreadcrumbs(); this.disabled = this.isLimitReached(); for (let [id, term] of Object.entries(terms)) { if (!term) return; this.terms.set(term.id, term.name); this.createTermElement({ id: parseInt(term.id), name: term.name, hasChildren: term.hasChildren, path: term.path || null, show: path }); } } createTermElement(term) { if (!term || !term.name) return; let item = window.getTemplate('termListItem'); item.dataset.id = term.id; const isSelected = (term.id in this.selectedTerms); let input = item.querySelector('input'); let label = item.querySelector('label'); let name = item.querySelector('span'); [ input.id, input.name, input.value, input.disabled, input.checked, label.htmlFor, label.title, label.dataset.path, name.textContent ] = [ `${this.base}${term.id}`, `${this.base}${this.taxonomy}-select`, term.id, isSelected ? false : this.disabled, isSelected, `${this.base}${term.id}`, term.path || term.name, term.path, term.show ? term.path : term.name, ]; if (term.hasChildren) { let button = window.getTemplate('termChildrenToggle'); button.ariaLabel = `View sub-terms of ${term.name}`; item.append(button); } this.modal.termsList.append(item); } updateBreadcrumbs() { window.removeChildren(this.modal.breadcrumbs); this.modal.breadcrumbs.append(this.modal.backButton); this.modal.backButton.hidden = this.currentParent === 0; this.navigationPath.forEach((level, index) => { let button = window.getTemplate('termBreadcrumb'); [ button.dataset.level, button.dataset.id, button.title, button.textContent ] = [ index, level.id, level.path || level.name, level.name ]; this.modal.breadcrumbs.append(button); }); } showEmptyState(message = ''){ let template = window.getTemplate('noResults'); if (message !== '') { template.querySelector('span').textContent = message; } this.modal.termsList.append(template); } updateSelected() { if (this.isFeed) { return; } let checks = this.getSpacesToUpdate(); checks.forEach(check => { window.removeChildren(check); }); for (const [id, term] of Object.entries(this.selectedTerms)) { this.addTermToBoxes(id, term.name, term.path); } this.disabled = this.isLimitReached(); this.setCheckboxes(this.disabled); } async addTermsFromURL(ids) { this.fetchSpecificTerms = ids; let terms = await this.fetchTerms(); terms.forEach(term => { this.addSelectedTerm(term.id, term.name, term.path); }); } addSelectedTerm(id, name, path) { this.selectedTerms[id] = { name: name, path: path }; this.addTermToBoxes(id, name, path); } getSpacesToUpdate() { let checks = [ this.modal.selectedTerms, this.elements.selectedTerms ]; if (this.isFeed) { checks.push(this.feedSelected); } return checks; } addTermToBoxes(id, name, path) { let checks = this.getSpacesToUpdate(); checks.forEach(check => { if (!check.querySelector(`[data-id="${id}"]`)) { let item = window.getTemplate('selectedTerm'); [ item.dataset.id, item.dataset.path, item.dataset.name, item.dataset.taxonomy, item.querySelector('span').textContent, item.querySelector('button').title ] = [ id, path, name, this.taxonomy, path, `Remove ${name}` ]; if (check === this.feedSelected) { item.prepend(window.getIcon(this.taxonomy)); } check.append(item); } }); let input = this.modal.termsList.querySelector(`input[value="${id}"]`); if (input) { input.checked = true; } } removeSelectedTerm(id) { delete this.selectedTerms[id]; let checks = [ this.modal.selectedTerms, this.elements.selectedTerms ]; if (this.isFeed) { checks.push(this.feedSelected); } checks.forEach(check => { check.querySelector(`[data-id="${id}"]`)?.remove(); }); let input = this.modal.termsList.querySelector(`input[value="${id}"]`); if (input) { input.checked = false; } } setCheckboxes(disabled) { this.modal.termsList.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { if (!checkbox.checked) { checkbox.disabled = disabled; } }); } isLimitReached() { return this.maxSelections > 0 && this.getSelectedTerms().length > this.maxSelections; } getSelectedTerms() { return this.modal.termsList.querySelectorAll('input:checked'); } } window.jvbSelector = TaxonomySelectorPrev;