|
class TaxonomySelectorOld
|
{
|
constructor(container, options = {}) {
|
this.initialized = false;
|
this.container = container;
|
this.config = this.initializeConfig(options);
|
this.a11y = window.jvbA11y;
|
this.cache = window.jvbCache;
|
this.loading = window.jvbLoading;
|
|
this.taxonomy = container.dataset.taxonomy;
|
this.selectedItems = options.selected ?? {};
|
|
this.commonTerms = new Map();
|
this.pendingTerms = new Map();
|
this.page = 1;
|
this.loading = false;
|
this.hasMore = true;
|
this.searchQuery = '';
|
this.createNew = false;
|
this.currentTerms = new Map();
|
this.initialState = new Map();
|
|
this.init();
|
|
this.navigationPath = [];
|
this.currentParent = 0;
|
this.currentParentName = '';
|
this.currentPath = '';
|
this.limitWasReached = false;
|
|
if (this.config.selected) {
|
if (typeof this.config.selected === 'object' && !window.isEmptyObject(this.config.selected)) {
|
this.config.selected.forEach(item => {
|
this.selectedItems[item.id] = item.name;
|
});
|
} else {
|
this.selectedItems = this.config.selected;
|
}
|
|
this.updateSelected();
|
}
|
|
if (this.selectedItems) {
|
for (const [id, name] of Object.entries(this.selectedItems)) {
|
this.initialState.set(id, name);
|
}
|
}
|
|
this.initialized = true;
|
this.boundTermListener = this.initializeToggleButtons.bind(this);
|
}
|
|
initializeConfig(options) {
|
const defaultConfig = this.container ?
|
JSON.parse(this.container.dataset.config || '{}') : {};
|
|
return {
|
...defaultConfig,
|
...options,
|
selectedItems: options.selected ?? {},
|
maxSelections: options.maxSelections || defaultConfig.maxSelections || 0,
|
hierarchical: options.hierarchical || defaultConfig.hierarchical || false,
|
base: options.base || defaultConfig.base || '',
|
onSuccess: options.onSuccess || defaultConfig.onSuccess || null,
|
onClose: options.onClose || defaultConfig.onClose || null,
|
onCreate: options.onCreate || defaultConfig.onCreate || null,
|
values: options.values || defaultConfig.values || null,
|
};
|
}
|
|
init() {
|
this.modal = this.container.querySelector('dialog');
|
this.modalSelected = this.container.querySelector('.selected-items');
|
this.searchInput = this.modal.querySelector('input[type=search]');
|
this.itemsContainer = this.modal.querySelector('.items-container');
|
this.itemsWrap = this.modal.querySelector('.items-wrap');
|
this.selectedContainer = this.container.querySelector('.selected-items');
|
this.breadcrumbNav = this.modal.querySelector('nav.term-navigation');
|
this.loadingText = this.modal.querySelector('p.loading');
|
this.noResultsText = this.modal.querySelector('p.no-results') || this.createNoResultsElement();
|
this.clearSearchButton = this.modal.querySelector('.clear-search');
|
this.modalSelectedItems = this.modal.querySelector('.selected-items .selected');
|
|
// Initialize common terms from config
|
if (this.config.common) {
|
Object.entries(this.config.common).forEach(([id, term]) => {
|
this.commonTerms.set(id, term);
|
});
|
}
|
|
this.initializeEventListeners();
|
this.initializeInfiniteScroll();
|
this.initializeTermCreation();
|
this.updateModalSelected();
|
}
|
|
updateModalSelected() {
|
if (!this.modalSelectedItems) return;
|
|
// Clear existing selected items
|
removeChildren(this.modalSelectedItems);
|
|
for (const [id, term] of Object.entries(this.selectedItems)) {
|
const itemDiv = window.getTemplate('selectedTerm');
|
itemDiv.dataset.id = id;
|
let name = itemDiv.querySelector('span');
|
let button = itemDiv.querySelector('button');
|
[name.textContent, button.ariaLabel] =
|
[escapeHtml(term), `Remove ${escapeHtml(term)}`];
|
|
this.modalSelectedItems.appendChild(itemDiv);
|
}
|
|
// Add event listeners for removal
|
this.modalSelectedItems.addEventListener('click', e => {
|
const removeBtn = e.target.closest('.remove-item');
|
if (!removeBtn) return;
|
|
const item = removeBtn.closest('.selected-item');
|
if (item) {
|
this.removeItem(item.dataset.id);
|
}
|
});
|
}
|
|
createNoResultsElement() {
|
const noResults = window.getTemplate('noResults');
|
noResults.className = 'no-results';
|
noResults.hidden = true;
|
this.itemsWrap.appendChild(noResults);
|
return noResults;
|
}
|
|
buildParams() {
|
let params = new URLSearchParams({
|
taxonomy: this.taxonomy,
|
parent: this.currentParent || 0,
|
search: this.searchQuery || '',
|
per_page: 20,
|
page: this.page,
|
});
|
if (this.config.feed) {
|
if (window.feedBlock && window.feedBlock.config && 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;
|
}
|
|
showLoading() {
|
if (this.loading) return;
|
this.loading = true;
|
this.loadingText.hidden = false;
|
this.noResultsText.hidden = true;
|
this.modal.classList.add('loading');
|
}
|
hideLoading()
|
{
|
this.loading = false;
|
this.loadingText.hidden = true;
|
this.modal.classList.remove('loading');
|
}
|
async fetchTerms(forceRefresh = false) {
|
try {
|
const params = this.buildParams();
|
|
const data = await this.cache.fetchWithCache(
|
`${jvbSettings.api}terms?` + params.toString(),
|
{
|
method: 'GET',
|
headers: {
|
'Content-Type': 'application/json',
|
'X-WP-Nonce': jvbSettings.nonce
|
}
|
},
|
{
|
content: this.taxonomy,
|
forceRefresh: forceRefresh
|
}
|
);
|
return data;
|
} catch (e) {
|
console.error('Error fetching terms:', error);
|
this.noResultsText.hidden = false;
|
this.noResultsText.textContent = 'Error loading terms. Please try again.';
|
throw error;
|
}
|
|
}
|
async fetchAndRenderTerms(forceRefresh = false) {
|
if (this.loading || (!this.hasMore && !forceRefresh)) return;
|
|
try {
|
this.showLoading();
|
return;
|
const response = await this.fetchTerms(forceRefresh);
|
|
// Check if we have results
|
const hasResults = response.terms && Object.keys(response.terms).length > 0;
|
|
await this.renderTerms(response.terms, this.page === 1);
|
|
// Show no results message if needed
|
this.noResultsText.hidden = hasResults || !this.searchQuery;
|
|
// Update pagination info if available
|
const paginationInfo = this.modal.querySelector('.pagination-info');
|
if (paginationInfo && response.pagination) {
|
paginationInfo.textContent = `Showing ${Object.keys(response.terms).length} of ${response.pagination.total_terms} items`;
|
}
|
|
this.cache.setItem(this.taxonomy + 'List', response.terms, this.taxonomy);
|
|
this.page++;
|
this.hasMore = response.pagination.has_more;
|
|
return true;
|
} catch (error) {
|
console.error('Error fetching terms:', error);
|
this.noResultsText.hidden = false;
|
this.noResultsText.textContent = 'Error loading terms. Please try again.';
|
throw error;
|
} finally {
|
this.hideLoading();
|
}
|
}
|
|
async createTerm(name, parent = 0)
|
{
|
let loadingMessage = this.modal.querySelector('.loading-message.create-term');
|
|
let text = loadingMessage.querySelector('span');
|
const form = this.createNew.querySelector('form');
|
const submitButton = form.querySelector('button[type="submit"]');
|
try {
|
loadingMessage.hidden = false;
|
window.typeText(text, 'Checking term...');
|
|
this.searchQuery = name;
|
const check = await this.fetchTerms();
|
|
console.log(check);
|
if (check.terms.length > 0) {
|
this.showTermSuggestions(check.terms);
|
return false;
|
}
|
|
//Term doesn't already exist, let's continue
|
const response = await fetch(`${jvbSettings.api}terms`, {
|
method: 'POST',
|
headers: {
|
'Content-Type': 'application/json',
|
'X-WP-Nonce': jvbSettings.nonce
|
},
|
body: JSON.stringify({
|
taxonomy: this.taxonomy,
|
name: name,
|
parent: parent
|
})
|
})
|
|
if (!response.ok) {
|
throw new Error(`Server error: ${response.status}`);
|
}
|
|
const result = await response.json();
|
|
if (result.success) {
|
form.reset();
|
this.m.hasChanges = false;
|
this.m.handleClose();
|
if (this.config.onCreate) {
|
this.config.onCreate(result);
|
}
|
window.addNotification('SUCCESS\n\nWe\'ve put your suggestion to the masses (well, verified artists). Upon approval, '+ name +' will be available for use.')
|
}
|
|
return result;
|
} catch (error) {
|
console.error('Error creating term:', error);
|
throw error;
|
} finally {
|
submitButton.disabled = false;
|
loadingMessage.hidden = true;
|
}
|
}
|
|
// Helper method to show term suggestions when similar terms exist
|
showTermSuggestions(suggestions) {
|
const suggestionContainer = this.createNew.querySelector('.term-suggestions') ||
|
this.createSuggestionContainer();
|
|
// Clear existing suggestions
|
removeChildren(suggestionContainer);
|
|
// Add heading
|
const heading = document.createElement('h4');
|
heading.textContent = 'Similar terms already exist:';
|
suggestionContainer.appendChild(heading);
|
|
// Create list of suggestions
|
const list = document.createElement('ul');
|
list.className = 'term-suggestion-list';
|
|
suggestions.forEach(term => {
|
const item = document.createElement('li');
|
|
// Create term path display if available
|
let termDisplay = term.name;
|
if (term.path) {
|
termDisplay = term.path;
|
}
|
|
const button = document.createElement('button');
|
button.type = 'button';
|
button.className = 'use-existing-term';
|
button.setAttribute('data-id', term.id);
|
button.textContent = termDisplay;
|
|
button.addEventListener('click', () => {
|
// Add this term to selected items
|
this.selectedItems[term.id] = term.name;
|
this.updateSelected();
|
|
// Close the create new section
|
this.createNew.hidden = true;
|
|
// Optionally, close the modal
|
this.m.handleClose();
|
});
|
|
item.appendChild(button);
|
list.appendChild(item);
|
});
|
|
suggestionContainer.appendChild(list);
|
suggestionContainer.hidden = false;
|
}
|
|
// Create container for term suggestions if it doesn't exist
|
createSuggestionContainer() {
|
const container = document.createElement('div');
|
container.className = 'term-suggestions';
|
container.hidden = true;
|
|
// Insert after the form
|
this.createNew.querySelector('form').after(container);
|
return container;
|
}
|
|
// Show message when term suggestion is pending approval
|
showApprovalPendingMessage(termName) {
|
const pendingContainer = this.createNew.querySelector('.term-pending-approval') ||
|
this.createPendingContainer();
|
|
// Clear and set content
|
removeChildren(pendingContainer);
|
|
const message = document.createElement('div');
|
message.className = 'approval-message';
|
message.innerHTML = `
|
<h4>Thank you for your suggestion!</h4>
|
<p>Your suggestion "<strong>${escapeHtml(termName)}</strong>" has been submitted for review.</p>
|
<p>Other verified artists will review and approve your suggestion.</p>
|
<p>You'll receive a notification when it's approved.</p>
|
<button type="button" class="close-pending-message">Got it</button>
|
`;
|
|
pendingContainer.appendChild(message);
|
pendingContainer.hidden = false;
|
|
// Add event listener to close button
|
pendingContainer.querySelector('.close-pending-message').addEventListener('click', () => {
|
pendingContainer.hidden = true;
|
this.createNew.hidden = true;
|
});
|
}
|
|
createTermElement(term) {
|
if (!term || !term.name) return null;
|
|
const li = window.getTemplate('termListItem');
|
li.dataset.id = term.id;
|
|
const isSelected = (term.id in this.selectedItems);
|
let input = li.querySelector('input');
|
let label = li.querySelector('label');
|
let name = label.querySelector('span');
|
|
[input.id, input.name, input.value, input.disabled, label.htmlFor, label.title, name.textContent] =
|
[`${this.config.base}${term.id}`, `${this.config.base}${this.taxonomy}`, term.id, isSelected ? false : this.disabled, `${this.config.base}${term.id}`, escapeHtml(term.path || term.name), (term.show) ? term.path : term.name];
|
|
input.checked = isSelected;
|
|
if (term.hasChildren) {
|
let button = window.getTemplate('termChildrenToggle');
|
button.ariaLabel = `View sub-categories of ${escapeHtml(term.name)}`;
|
li.append(button);
|
}
|
|
return li;
|
}
|
|
updateSelected() {
|
if (this.config.feed) {
|
return;
|
}
|
|
// Clear existing selected items
|
removeChildren(this.selectedContainer);
|
|
for (const [id, term] of Object.entries(this.selectedItems)) {
|
const itemDiv = window.getTemplate('selectedTerm');
|
itemDiv.dataset.id = id;
|
let name = itemDiv.querySelector('span');
|
let button = itemDiv.querySelector('button');
|
[name.textContent, button.ariaLabel] =
|
[escapeHtml(term), `Remove ${escapeHtml(term)}`];
|
|
this.selectedContainer.appendChild(itemDiv);
|
}
|
|
if (this.isSelectionLimitReached()) {
|
this.disabled = true;
|
this.disableCheckboxes();
|
} else {
|
this.disabled = false;
|
this.enableCheckboxes();
|
}
|
|
// Also update the modal selected terms list if it exists
|
this.updateModalSelected();
|
|
// Only trigger change if there are actual changes
|
if (this.hasSelectionChanged()) {
|
if (this.initialized && this.config.onSuccess) {
|
this.config.onSuccess();
|
}
|
// Update initial state
|
this.initialState = new Map(Object.entries(this.selectedItems));
|
}
|
}
|
|
hasSelectionChanged() {
|
// First check if sizes are different
|
if (Object.keys(this.selectedItems).length !== this.initialState.size) {
|
return true;
|
}
|
|
// Then check if any items differ
|
for (const [id, name] of Object.entries(this.selectedItems)) {
|
if (!this.initialState.has(id) || this.initialState.get(id) !== name) {
|
return true;
|
}
|
}
|
|
// Also check if any items were removed
|
for (const id of this.initialState.keys()) {
|
if (!(id in this.selectedItems)) {
|
return true;
|
}
|
}
|
|
return false;
|
}
|
initializeEventListeners() {
|
let o = this.container.closest('.field')?.querySelector('.add-item-btn');
|
if (!o) {
|
o = this.container.closest('.jvb-selector')?.querySelector('.filter-toggle');
|
}
|
|
this.modal.addEventListener('click', (e)=> {
|
if (e.target.classList.contains('new-term-toggle') ||
|
e.target.closest('.new-term-toggle')) {
|
const isHidden = this.createNew.hidden;
|
this.createNew.hidden = !isHidden;
|
|
if (!this.createNew.hidden) {
|
this.createNew.querySelector('input[name="term_name"]').focus();
|
}
|
this.resetParentOptions();
|
}
|
});
|
|
this.m = new window.jvbModal(
|
this.modal,
|
{
|
save: null,
|
open: o,
|
openMessage: 'Opened ' + this.taxonomy + ' selection. Choose from checkboxes to filter results.',
|
onOpen: () => this.openModal(),
|
onClose: () => this.closeModal()
|
}
|
);
|
|
// Selected items handling
|
if (!this.config.feed) {
|
this.selectedContainer.addEventListener('click', e => {
|
const removeBtn = e.target.closest('.remove-item');
|
if (!removeBtn) return;
|
|
const item = removeBtn.closest('.selected-item');
|
if (item) {
|
this.removeItem(item.dataset.id);
|
}
|
});
|
} else {
|
this.container.querySelector('.filter-toggle').addEventListener('click', e => {
|
this.m.handleOpen();
|
});
|
}
|
|
this.itemsContainer.addEventListener('change', e => {
|
const input = e.target;
|
e.preventDefault();
|
if (input.type === 'checkbox' || input.type === 'radio') {
|
const item = input.closest('li');
|
if (!item) return;
|
|
const termId = item.dataset.id;
|
const termName = item.querySelector('.term-name').textContent;
|
|
if (input.checked) {
|
// Add to selected items
|
this.selectedItems[termId] = termName;
|
} else {
|
delete this.selectedItems[termId];
|
// Remove from selected items
|
}
|
|
if (this.isSelectionLimitReached()) {
|
this.disabled = true;
|
this.disableCheckboxes();
|
} else if (!this.disabled && this.limitWasReached === true) {
|
this.enableCheckboxes();
|
}
|
|
// Update the selection display
|
this.updateSelected();
|
}
|
});
|
|
// Clear search button
|
if (this.clearSearchButton) {
|
this.clearSearchButton.addEventListener('click', () => {
|
this.searchInput.value = '';
|
this.searchQuery = '';
|
this.page = 1;
|
this.hasMore = true;
|
this.fetchAndRenderTerms();
|
});
|
}
|
|
// Back to parent button
|
const backButton = this.breadcrumbNav.querySelector('.back-to-parent');
|
if (backButton) {
|
backButton.addEventListener('click', () => this.navigateToParent());
|
}
|
}
|
|
openModal() {
|
this.searchInput.value = '';
|
|
// Wait for the modal to be fully open before fetching the first page of terms
|
setTimeout(() => {
|
this.fetchAndRenderTerms();
|
}, 50);
|
|
this.searchInput.focus();
|
|
// Search handling with debounce
|
this.searchInput.addEventListener('input',
|
window.debounce(() => this.handleSearch(), 300));
|
}
|
closeModal() {
|
this.updateSelected();
|
if (this.searchQuery) {
|
this.searchQuery = '';
|
this.page = 1;
|
this.hasMore = true;
|
}
|
if (this.config.feed) {
|
if (this.config.onClose) {
|
this.config.onClose();
|
}
|
}
|
}
|
|
disableCheckboxes() {
|
this.limitWasReached = true;
|
this.itemsContainer.querySelectorAll('input').forEach(input => {
|
input.disabled = (!input.checked);
|
});
|
}
|
|
enableCheckboxes() {
|
this.limitWasReached = false;
|
this.itemsContainer.querySelectorAll('input:disabled').forEach(input => {
|
input.disabled = false;
|
});
|
}
|
|
isSelectionLimitReached() {
|
return this.config.maxSelections > 0 &&
|
Object.keys(this.selectedItems).length >= this.config.maxSelections;
|
}
|
|
removeItem(id) {
|
delete this.selectedItems[id];
|
|
// Update checkboxes in the modal
|
const input = this.modal.querySelector(`input[value="${id}"]`);
|
if (input) {
|
input.checked = false;
|
}
|
|
this.updateSelected();
|
}
|
|
async handleSearch() {
|
const query = this.searchInput.value.trim();
|
|
if (query === this.searchQuery) return;
|
|
this.searchQuery = query;
|
|
// Reset pagination
|
this.page = 1;
|
this.hasMore = true;
|
|
// Don't show loading if already showing (prevents flickers)
|
if (!this.loading) {
|
this.showLoading();
|
}
|
|
try {
|
// Store checked state before clearing
|
const checkedState = new Map();
|
this.itemsContainer.querySelectorAll('input:checked').forEach(input => {
|
checkedState.set(input.value, true);
|
});
|
|
// Clear existing content
|
removeChildren(this.itemsContainer);
|
|
// Only search if query is at least 2 chars or empty
|
if (query.length >= 2 || query.length === 0) {
|
this.loading = false;
|
await this.fetchAndRenderTerms();
|
} else {
|
// For very short queries, just clear and show message
|
this.hideLoading();
|
this.noResultsText.hidden = false;
|
this.noResultsText.textContent = 'Enter at least 2 characters to search';
|
}
|
|
// Restore checked state
|
checkedState.forEach((_, value) => {
|
const input = this.itemsContainer.querySelector(`input[value="${value}"]`);
|
if (input) input.checked = true;
|
});
|
|
} catch (error) {
|
console.error('Search error:', error);
|
this.showError('Search failed');
|
}
|
}
|
|
// Term creation initialization
|
initializeTermCreation() {
|
if (!this.config.createNew) return;
|
|
this.createNew = this.modal.querySelector('.create-new-term-section');
|
if (!this.createNew) return;
|
|
const toggle = this.modal.querySelector('.new-term-toggle');
|
const form = this.createNew.querySelector('form');
|
if (!form) return;
|
|
// let submitButton = form.querySelector('button[type="submit"]');
|
// submitButton.addEventListener('click', (e)=> {
|
// e.preventDefault();
|
// });
|
|
// Handle form submission
|
form.addEventListener('submit', (e) => {
|
e.preventDefault();
|
|
const termName = e.target.querySelector('input[name="term_name"]').value.trim();
|
const parentId = e.target.querySelector('input#select_parent')?.value;
|
|
try {
|
form.querySelector('button').disabled = true;
|
const response = this.createTerm(termName, parentId);
|
|
if (response.success) {
|
this.showNotification(
|
'Thank you! Your suggestion has been submitted for review.',
|
'success'
|
);
|
e.target.reset();
|
this.createNew.hidden = true;
|
}
|
} catch (error) {
|
console.error('Error creating term:', error);
|
this.showError('Failed to submit suggestion');
|
} finally {
|
form.querySelector('button').disabled = false;
|
}
|
});
|
}
|
|
resetParentOptions() {
|
let ids = Array.from(this.currentTerms);
|
let select = this.modal.querySelector('#select_parent');
|
if (!select) return;
|
|
let option = select.querySelector('option');
|
if (!option) return;
|
|
removeChildren(select);
|
select.append(option);
|
|
if (this.currentParentName !== '') {
|
let o = option.cloneNode(true);
|
[o.value, o.textContent] =
|
[this.currentParent, this.currentParentName];
|
select.append(o);
|
}
|
|
if (ids.length > 0) {
|
ids.forEach(id => {
|
let o = option.cloneNode(true);
|
[o.id, o.value, o.textContent] =
|
[`select-parent-${id[0]}`, id[0], ' — ' + id[1]];
|
select.append(o);
|
});
|
}
|
}
|
|
renderTerms(terms, clearExisting = true, path = false) {
|
if (!terms) return;
|
|
const targetContainer = this.itemsContainer;
|
|
console.log(this.page, 'Current Page');
|
console.log(clearExisting, 'Clear Existing');
|
|
if (clearExisting) {
|
this.currentTerms.clear();
|
removeChildren(targetContainer);
|
|
if (this.itemsWrap.querySelector('details')) {
|
this.itemsWrap.querySelector('details').removeAttribute('open');
|
}
|
|
// Term Navigation
|
let backButton = this.breadcrumbNav.querySelector('.back-to-parent');
|
removeChildren(this.breadcrumbNav);
|
this.breadcrumbNav.append(backButton);
|
|
// Show navigation path if we're not at root
|
if (this.navigationPath.length > 0) {
|
backButton.hidden = false;
|
|
// Create the navigation path
|
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, escapeHtml(level.name)];
|
this.breadcrumbNav.append(button);
|
});
|
} else {
|
backButton.hidden = true;
|
}
|
}
|
|
// Check if we have any terms
|
const hasTerms = terms && typeof terms === 'object' && Object.keys(terms).length > 0;
|
|
// Show appropriate message if no terms
|
if (!hasTerms) {
|
this.noResultsText.hidden = false;
|
if (this.searchQuery) {
|
this.noResultsText.textContent = `No results found for "${this.searchQuery}"`;
|
} else if (this.currentParent !== 0) {
|
this.noResultsText.textContent = 'No subcategories found';
|
} else {
|
this.noResultsText.textContent = 'No categories available';
|
}
|
return;
|
} else {
|
this.noResultsText.hidden = true;
|
}
|
|
// Render terms
|
this.disabled = this.isSelectionLimitReached();
|
|
Object.entries(terms).forEach(([id, term]) => {
|
if (!term) return;
|
this.currentTerms.set(id, typeof term === 'string' ? term : term.name);
|
|
const termElement = this.createTermElement({
|
id: parseInt(id),
|
name: typeof term === 'string' ? term : term.name,
|
hasChildren: typeof term === 'object' ? term.hasChildren : false,
|
path: term.path || null,
|
show: path
|
});
|
|
if (termElement) targetContainer.appendChild(termElement);
|
});
|
|
this.resetParentOptions();
|
|
this.container.removeEventListener('click', this.boundTermListener);
|
this.container.addEventListener('click', this.boundTermListener);
|
}
|
|
async navigateToParent() {
|
// Pop current level from navigation path
|
this.navigationPath.pop();
|
|
// Get previous parent from path or default to root
|
const previousLevel = this.navigationPath[this.navigationPath.length - 1];
|
this.currentParent = previousLevel ? previousLevel.id : 0;
|
this.currentParentName = previousLevel ? previousLevel.name : '';
|
|
this.page = 1;
|
this.hasMore = true;
|
|
await this.fetchAndRenderTerms();
|
}
|
|
|
async navigateToChildren(termId, termName) {
|
// Add current level to navigation path
|
this.navigationPath.push({
|
id: termId,
|
name: termName
|
});
|
|
this.currentParent = termId;
|
this.currentParentName = termName;
|
|
this.page = 1;
|
this.hasMore = true;
|
|
await this.fetchAndRenderTerms();
|
}
|
|
initializeToggleButtons(e) {
|
if (e.target.classList.contains('toggle-children') || e.target.closest('.toggle-children')) {
|
const item = e.target.closest('li');
|
if (!item) return;
|
|
const termId = parseInt(item.dataset.id);
|
const termName = item.querySelector('.term-name').textContent;
|
|
this.navigateToChildren(termId, termName);
|
}
|
|
if (e.target.classList.contains('path-level') || e.target.closest('.path-level')) {
|
const btn = e.target.classList.contains('path-level') ? e.target : e.target.closest('.path-level');
|
|
// Skip if already on this level
|
if (btn.textContent === this.currentParentName) return;
|
|
const level = parseInt(btn.dataset.level);
|
|
// Navigate to the specific level
|
this.navigationPath = this.navigationPath.slice(0, level + 1);
|
this.currentParent = parseInt(btn.dataset.id);
|
this.currentParentName = btn.textContent;
|
this.page = 1;
|
this.hasMore = true;
|
|
this.fetchAndRenderTerms();
|
}
|
|
if (e.target.classList.contains('back-to-parent') || e.target.closest('.back-to-parent')) {
|
this.navigateToParent();
|
}
|
}
|
|
showNotification(message, type = 'info') {
|
window.addNotification(message, type);
|
}
|
|
showError(message) {
|
this.showNotification(message, 'error');
|
}
|
|
initializeInfiniteScroll() {
|
const observer = new IntersectionObserver(entries => {
|
entries.forEach(entry => {
|
if (entry.isIntersecting && !this.loading && this.hasMore) {
|
this.fetchAndRenderTerms();
|
}
|
});
|
}, {
|
root: this.itemsWrap,
|
threshold: 0.5
|
});
|
|
// Observe the sentinel element
|
const sentinel = this.container.querySelector('.scroll-sentinel');
|
if (sentinel) {
|
observer.observe(sentinel);
|
}
|
}
|
|
cleanup() {
|
// Remove event listeners
|
this.modal?.remove();
|
this.searchInput = null;
|
this.itemsContainer = null;
|
this.selectedContainer = null;
|
}
|
}
|
|
window.jvbSelector = TaxonomySelectorOld;
|