/**
|
* This separates out all create logic from the base TaxonomySelector.js
|
* Updated to work with centralized DataStore architecture
|
*/
|
|
class TaxonomyCreator {
|
|
constructor(selector) {
|
this.selector = selector;
|
|
// Only initialize modal elements if modal exists
|
if (selector.modal) {
|
this.createNew = selector.modal.querySelector('.create-new-term');
|
this.toggle = selector.modal.querySelector('.new-term-toggle');
|
this.form = this.createNew?.querySelector('.create-new-term-section');
|
}
|
|
this.initListeners();
|
|
// Only init term creation UI if we have modal elements
|
if (this.form) {
|
this.initTermCreation();
|
}
|
}
|
|
initListeners() {
|
this.clickHandler = this.handleClick.bind(this);
|
document.addEventListener('click', this.clickHandler);
|
}
|
|
handleClick(e) {
|
if (window.targetCheck(e, '.create-new-term summary')) {
|
if (this.createNew.open) {
|
this.createNew.querySelector('input[name="term_name"]').focus();
|
}
|
this.resetParentOptions();
|
}
|
|
if (window.targetCheck(e, '.submit-term')) {
|
this.handleTermCreation(e);
|
}
|
|
// Handle autocomplete create button
|
if (window.targetCheck(e, '.create-term')) {
|
this.handleAutocompleteCreate(e);
|
}
|
}
|
|
async handleTermCreation(e) {
|
const taxonomy = this.selector.currentConfig?.taxonomy;
|
if (!taxonomy) return;
|
|
const termName = this.form.querySelector('input[name="term_name"]').value.trim();
|
const parentId = parseInt(this.form.querySelector('input#select_parent')?.value) || 0;
|
|
if (!termName) return;
|
|
try {
|
this.form.querySelector('button').disabled = true;
|
const response = await this.createTerm(termName, parentId, taxonomy);
|
|
if (response.success && response.term) {
|
let term = response.term;
|
|
this.createNew.open = false;
|
await this.selector.store.clearCache();
|
|
// Add to store's data BEFORE display update
|
this.selector.store.data.set(term.id, {
|
id: term.id,
|
name: term.name,
|
path: termPath,
|
taxonomy: field.taxonomy,
|
parent: 0,
|
count: 0,
|
hasChildren: false,
|
slug: term.slug || termName.toLowerCase().replace(/\s+/g, '-')
|
});
|
|
this.selector.addSelectedTermToModal(term.id, term.name, term.path || term.name);
|
|
const currentParent = this.selector.store.filters.parent || 0;
|
if (currentParent === parentId) {
|
await this.selector.store.setFilters({
|
taxonomy,
|
parent: parentId,
|
page: 1,
|
search: ''
|
});
|
}
|
|
this.form.querySelector('input[name="term_name"]').value = '';
|
const suggestionContainer = this.createNew.querySelector('.term-suggestions');
|
if (suggestionContainer) {
|
suggestionContainer.hidden = true;
|
}
|
this.selector.store.cache.clear();
|
}
|
} catch (error) {
|
console.error('Error creating term:', error);
|
this.selector.error?.log(error, {
|
component: 'TaxonomyCreator',
|
action: 'handleTermCreation'
|
});
|
} finally {
|
this.form.querySelector('button').disabled = false;
|
}
|
}
|
|
async handleAutocompleteCreate(e) {
|
const button = e.target.closest('.create-term');
|
const fieldId = this.selector.getFieldId(button);
|
const field = this.selector.fields.get(fieldId);
|
|
if (!field) return;
|
|
const input = field.container.querySelector('input[data-autocomplete]');
|
const termName = input?.value.trim() || button.dataset.query;
|
|
if (!termName) return;
|
|
const originalHTML = button.innerHTML;
|
|
try {
|
button.disabled = true;
|
button.textContent = 'Creating...';
|
|
const response = await this.createTerm(termName, 0, field.taxonomy);
|
|
if (response.success && response.term) {
|
const term = response.term;
|
const termPath = term.path || term.name;
|
|
field.selectedTerms.add(parseInt(term.id));
|
// Add to store's data BEFORE display update
|
this.selector.store.data.set(term.id, {
|
id: term.id,
|
name: term.name,
|
path: termPath,
|
taxonomy: field.taxonomy,
|
parent: 0,
|
count: 0,
|
hasChildren: false,
|
slug: term.slug || termName.toLowerCase().replace(/\s+/g, '-')
|
});
|
this.selector.addTermToDisplay(field.id, term.id, term.name, termPath);
|
|
field.input.value = Array.from(field.selectedTerms).join(',');
|
field.input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
field.autocompleteDropdown.hidden = true;
|
if (input) input.value = '';
|
|
this.selector.store.clearCache();
|
await this.selector.store.setFilters({
|
taxonomy: field.taxonomy,
|
page: 1,
|
search: '',
|
parent: 0
|
});
|
} else if (response.reason === 'exists' && response.term) {
|
// Term already exists - just add it
|
const term = response.term;
|
field.selectedTerms.add(parseInt(term.id));
|
this.selector.addTermToDisplay(field.id, term.id, term.name, term.path || term.name);
|
|
field.input.value = Array.from(field.selectedTerms).join(',');
|
field.input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
field.autocompleteDropdown.hidden = true;
|
if (input) input.value = '';
|
}
|
} catch (error) {
|
console.error('Error creating term:', error);
|
button.innerHTML = originalHTML;
|
button.disabled = false;
|
this.selector.error?.log(error, {
|
component: 'TaxonomyCreator',
|
action: 'handleAutocompleteCreate'
|
});
|
}
|
}
|
|
initTermCreation() {
|
if (!this.form) {
|
return;
|
}
|
|
this.form.addEventListener('change', (e) => {
|
e.preventDefault();
|
e.stopPropagation();
|
});
|
}
|
|
resetParentOptions() {
|
const taxonomy = this.selector.currentConfig?.taxonomy;
|
if (!taxonomy) return;
|
|
let select = this.createNew.querySelector('#select_parent');
|
if (!select) return;
|
|
let defaultOption = select.querySelector('option');
|
if (!defaultOption) return;
|
|
// Clear existing options
|
window.removeChildren(select);
|
select.append(defaultOption.cloneNode(true));
|
|
// Get current parent from store filters
|
const currentParent = this.selector.store.filters.parent || 0;
|
|
// If we're in a sub-category, add the current parent as an option
|
if (currentParent !== 0) {
|
const parentTerm = this.selector.store.data.get(currentParent);
|
if (parentTerm) {
|
let parentOption = defaultOption.cloneNode(true);
|
parentOption.value = parentTerm.id;
|
parentOption.textContent = parentTerm.name;
|
select.append(parentOption);
|
}
|
}
|
|
// Add all terms currently visible in the taxonomy (from store cache)
|
const visibleTerms = [];
|
this.selector.store.data.forEach(term => {
|
if (term.taxonomy === taxonomy && term.parent === currentParent) {
|
visibleTerms.push(term);
|
}
|
});
|
|
// Sort by name
|
visibleTerms.sort((a, b) => a.name.localeCompare(b.name));
|
|
// Add to select
|
visibleTerms.forEach(term => {
|
let option = defaultOption.cloneNode(true);
|
option.id = `select-parent-${term.id}`;
|
option.value = term.id;
|
option.textContent = ' — ' + term.name;
|
select.append(option);
|
});
|
}
|
|
async createTerm(name, parent = 0, taxonomy) {
|
try {
|
// Search for the exact term first
|
await this.selector.store.setFilters({
|
taxonomy: taxonomy,
|
search: name,
|
page: 1,
|
parent: 0
|
});
|
|
// Check if exact match exists in results
|
const exactMatch = Array.from(this.selector.store.data.values())
|
.find(term =>
|
term.taxonomy === taxonomy &&
|
term.name.toLowerCase() === name.toLowerCase()
|
);
|
|
if (exactMatch) {
|
// For modal context, show suggestions
|
if (this.createNew) {
|
this.showTermSuggestions([exactMatch], true);
|
}
|
return { success: false, reason: 'exists', term: exactMatch };
|
}
|
|
// Term doesn't exist, create it
|
const response = await fetch(`${jvbSettings.api}terms`, {
|
method: 'POST',
|
headers: {
|
'Content-Type': 'application/json',
|
'X-WP-Nonce': window.auth.getNonce()
|
},
|
body: JSON.stringify({
|
taxonomy: taxonomy,
|
name: name,
|
parent: parent
|
})
|
});
|
|
if (!response.ok) {
|
throw new Error(`Server error: ${response.status}`);
|
}
|
|
return await response.json();
|
|
} catch (error) {
|
console.error('Error creating term:', error);
|
throw error;
|
}
|
}
|
|
/**
|
* Search for existing terms using the store
|
*/
|
async searchExistingTerms(searchQuery, taxonomy) {
|
return new Promise((resolve) => {
|
// Set up a one-time listener for the search results
|
const handleSearchResults = (event, data) => {
|
if (event === 'data-loaded') {
|
this.selector.store.unsubscribe(handleSearchResults);
|
resolve(data.data?.items || []);
|
}
|
};
|
|
this.selector.store.subscribe(handleSearchResults);
|
|
// Trigger search
|
this.selector.store.setFilters({
|
taxonomy: taxonomy,
|
search: searchQuery,
|
page: 1,
|
parent: 0
|
});
|
});
|
}
|
|
/**
|
* Show term suggestions when similar terms exist
|
*/
|
showTermSuggestions(suggestions, isExact = false) {
|
const suggestionContainer = this.createNew.querySelector('.term-suggestions') ||
|
this.createSuggestionContainer();
|
|
// Clear existing suggestions
|
window.removeChildren(suggestionContainer);
|
|
// Add heading
|
const heading = document.createElement('h4');
|
heading.textContent = isExact ?
|
'This term already exists:' :
|
'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');
|
|
const button = document.createElement('button');
|
button.type = 'button';
|
button.className = 'use-existing-term';
|
button.setAttribute('data-id', term.id);
|
button.textContent = term.path || term.name;
|
|
button.addEventListener('click', () => {
|
// Add this term to modal selection
|
this.selector.addSelectedTermToModal(term.id, term.name, term.path || term.name);
|
|
// Close the create new section
|
this.createNew.open = false;
|
|
// Clear suggestions
|
suggestionContainer.hidden = true;
|
|
// Clear the form
|
this.form.querySelector('input[name="term_name"]').value = '';
|
});
|
|
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;
|
}
|
|
|
/**
|
* Clean up when modal closes
|
*/
|
destroy() {
|
// Remove event listeners
|
if (this.clickHandler) {
|
document.removeEventListener('click', this.clickHandler);
|
}
|
|
// Clear any pending operations
|
const loadingMessage = this.createNew?.querySelector('.loading-message.create-term');
|
if (loadingMessage) {
|
loadingMessage.hidden = true;
|
}
|
|
// Clear suggestions
|
const suggestionContainer = this.createNew?.querySelector('.term-suggestions');
|
if (suggestionContainer) {
|
suggestionContainer.hidden = true;
|
}
|
}
|
}
|
|
window.jvbTaxCreator = TaxonomyCreator;
|