/**
|
* TaxonomyCreator - Handles term creation for TaxonomySelector
|
*/
|
class TaxonomyCreator {
|
|
constructor(selector) {
|
this.selector = selector;
|
this.queue = window.jvbQueue;
|
|
this.initElements();
|
this.initListeners();
|
}
|
|
initElements() {
|
this.selectors = {
|
details: 'details.create-term',
|
parent: '#select_parent',
|
summary: '.create-term summary',
|
suggestion: '.term-suggestions',
|
name: '#term_name',
|
button: '.submit-term',
|
form: 'form.create-term',
|
label: {
|
name: '[for="term_name"]',
|
parent: '[for="select_parent"]'
|
},
|
loading: '.loading-message.create-term'
|
};
|
this.ui = window.uiFromSelectors(this.selectors, this.selector.container);
|
}
|
|
handleOpen(field) {
|
this.field = field;
|
if (this.ui.details) {
|
this.ui.details.hidden = !field.canCreate;
|
|
if (this.ui.summary) {
|
this.ui.summary.textContent = `Add new ${field.singular}`;
|
}
|
if (this.ui.label.name) {
|
this.ui.label.name.textContent = `Name this ${field.singular}`;
|
}
|
if (this.ui.label.parent) {
|
this.ui.label.parent.textContent = `Nest it under`;
|
}
|
}
|
}
|
|
/**
|
* Initialize event listeners
|
*/
|
initListeners() {
|
this.clickHandler = this.handleClick.bind(this);
|
document.addEventListener('click', this.clickHandler);
|
|
if (this.ui.form) {
|
this.ui.form.addEventListener('change', (e) => {
|
e.preventDefault();
|
e.stopPropagation();
|
});
|
}
|
}
|
|
/**
|
* Handle click events
|
*/
|
handleClick(e) {
|
// Handle opening create term form
|
if (window.targetCheck(e, this.selectors.summary)) {
|
if (this.ui.details.open) {
|
this.ui.name?.focus();
|
}
|
this.resetParentOptions();
|
return;
|
}
|
}
|
|
/**
|
* Handle term creation from modal form
|
*/
|
async handleTermCreation(data) {
|
if (!data.name || data.name.length < 2) return false;
|
try {
|
const response = await this.createTerm(data);
|
|
if (!response.success) {
|
// Term already exists - still add it
|
if (response.term && response.term.id) {
|
this.selector.setMessage(true, `Using existing "${response.term.name}"`);
|
return response.term;
|
}
|
|
// Other failure
|
this.selector.setMessage(true, response.message || 'Creation failed', false);
|
return false;
|
}
|
if (response.term?.pending) {
|
// Term requires approval
|
this.selector.setMessage(
|
true,
|
`"${data.name}" submitted for approval`,
|
false
|
);
|
// Don't add to selection since it's pending
|
return false;
|
}
|
if (response.success && response.term) {
|
await this.handleSuccessfulCreation(response.term, data);
|
this.clearForm();
|
}
|
return response.term;
|
} catch (error) {
|
console.error('Error creating term:', error);
|
return false;
|
}
|
}
|
|
/**
|
* Handle successful term creation
|
*/
|
async handleSuccessfulCreation(term, data) {
|
// Add term to store immediately - don't wait for fetch
|
const fullTerm = {
|
id: term.id,
|
name: term.name,
|
path: term.path || term.name,
|
slug: term.slug || term.name.toLowerCase().replace(/\s+/g, '-'),
|
parent: data.parent || 0,
|
taxonomy: data.taxonomy,
|
count: 0,
|
hasChildren: false
|
};
|
|
// Add to store data immediately so addSelected can find it
|
this.selector.store.data.set(term.id, fullTerm);
|
|
// Close create form
|
if (this.ui.details) {
|
this.ui.details.open = false;
|
}
|
|
// Clear cache and refetch in background for accuracy
|
this.selector.store.clearCache();
|
// Don't await - let it happen async
|
this.selector.store.fetch().catch(err => {
|
console.warn('Background fetch after term creation failed:', err);
|
});
|
}
|
|
/**
|
* Reset parent options in create form
|
*/
|
resetParentOptions() {
|
const field = this.selector.currentField();
|
if (!field) return;
|
const taxonomy = field.taxonomy;
|
if (!taxonomy) return;
|
|
if (!this.ui.parent) return;
|
|
let defaultOption = this.ui.parent.querySelector('option');
|
if (!defaultOption) return;
|
|
// Clear existing options
|
window.removeChildren(this.ui.parent);
|
this.ui.parent.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.get(currentParent);
|
if (parentTerm) {
|
let parentOption = defaultOption.cloneNode(true);
|
parentOption.value = parentTerm.id;
|
parentOption.textContent = parentTerm.name;
|
this.ui.parent.append(parentOption);
|
}
|
}
|
|
// Add all terms currently visible in the taxonomy
|
const visibleTerms = [];
|
this.selector.store.getFiltered().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;
|
this.ui.parent.append(option);
|
});
|
}
|
|
/**
|
* Create a new term
|
*/
|
async createTerm(data) {
|
if (!data.name || data.parent === undefined || !data.taxonomy) return;
|
try {
|
|
const response = await fetch(`${jvbSettings.api}terms`, {
|
method: 'POST',
|
headers: {
|
'Content-Type': 'application/json',
|
'X-WP-Nonce': window.auth.getNonce()
|
},
|
body: JSON.stringify(data)
|
});
|
|
if (!response.ok) {
|
throw new Error(`Server error: ${response.status}`);
|
}
|
|
return await response.json();
|
|
} catch (error) {
|
console.error('Error creating term:', error);
|
throw error;
|
}
|
}
|
|
/**
|
* Clear the creation form
|
*/
|
clearForm() {
|
if (this.ui.name) {
|
this.ui.name.value = '';
|
}
|
if (this.selector.ui.search.input){
|
this.selector.ui.search.input.value = '';
|
}
|
}
|
|
/**
|
* Clean up when destroyed
|
*/
|
destroy() {
|
// Remove event listeners
|
if (this.clickHandler) {
|
document.removeEventListener('click', this.clickHandler);
|
}
|
|
// Clear any pending operations
|
if (this.ui.loading) {
|
this.ui.loading.hidden = true;
|
}
|
}
|
}
|
|
window.jvbTaxCreator = TaxonomyCreator;
|