/**********************************************
|
PopulateForm extracts saved data and populates the form field accordingly
|
**********************************************/
|
class PopulateForm {
|
constructor(form, itemDataOrFields = {}, legacyImages = {}, options = {}) {
|
// Support both old signature (fields, images) and new signature (item object)
|
this.item = this.normalizeItemData(itemDataOrFields, legacyImages);
|
this.form = form;
|
this.options = options;
|
|
// Populate all fields
|
for (let [fieldName, fieldValue] of Object.entries(this.item.fields)) {
|
let wrapper = form.querySelector(`[data-field="${fieldName}"]`);
|
if (wrapper) {
|
this.populateField(wrapper, fieldName, fieldValue);
|
}
|
}
|
}
|
|
/**
|
* Normalize data to consistent structure
|
* Supports both new format (item object) and legacy format (fields, images)
|
*/
|
normalizeItemData(itemDataOrFields, legacyImages) {
|
// Check if this is the new format (has a fields property) or legacy format
|
if (itemDataOrFields && typeof itemDataOrFields === 'object' && 'fields' in itemDataOrFields) {
|
// New format - already structured
|
return {
|
fields: itemDataOrFields.fields || {},
|
images: itemDataOrFields.images || {},
|
taxonomies: itemDataOrFields.taxonomies || {}
|
};
|
} else {
|
// Legacy format - fields and images passed separately
|
return {
|
fields: itemDataOrFields || {},
|
images: legacyImages || {},
|
taxonomies: {}
|
};
|
}
|
}
|
|
/**
|
* Check if a field is a taxonomy field
|
*/
|
isTaxonomyField(fieldName) {
|
return Object.hasOwn(this.item.taxonomies, fieldName) &&
|
Object.keys(this.item.taxonomies[fieldName]).length > 0;
|
}
|
|
/**
|
* Check if a value references image data
|
*/
|
isImageField(value) {
|
if (!this.item.images || Object.keys(this.item.images).length === 0) {
|
return false;
|
}
|
|
const ids = this.splitIDs(value);
|
return ids.some(id => Object.keys(this.item.images).includes(String(id)));
|
}
|
|
/**
|
* Split comma-separated IDs into array of integers
|
*/
|
splitIDs(value) {
|
return String(value).split(',')
|
.map(v => parseInt(v.trim()))
|
.filter(v => !isNaN(v) && v > 0);
|
}
|
|
/**
|
* Populate a single field with its value
|
*/
|
populateField(fieldWrapper, fieldName, fieldValue, options = {}) {
|
if (!fieldWrapper || fieldValue === undefined || fieldValue === null) {
|
return;
|
}
|
|
// Determine field type from classes or data attributes
|
const fieldType = this.getFieldType(fieldWrapper);
|
|
switch (fieldType) {
|
case 'upload':
|
case 'gallery':
|
case 'image':
|
this.populateUploadField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'repeater':
|
this.populateRepeaterField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'taxonomy':
|
this.populateTaxonomyField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'user':
|
this.populateUserField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'location':
|
this.populateLocationField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'set':
|
case 'checkbox':
|
this.populateSetField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'select':
|
case 'radio':
|
this.populateSelectField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'true_false':
|
this.populateBooleanField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'date':
|
case 'time':
|
case 'datetime':
|
this.populateDateField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'number':
|
this.populateNumberField(fieldWrapper, fieldName, fieldValue);
|
break;
|
|
case 'textarea':
|
if (fieldWrapper.querySelector('.editor-container')) {
|
this.populateEditorField(fieldWrapper, fieldName, fieldValue);
|
} else {
|
this.populateTextareaField(fieldWrapper, fieldName, fieldValue);
|
}
|
break;
|
|
case 'text':
|
case 'email':
|
case 'url':
|
case 'tel':
|
case 'phone':
|
default:
|
this.populateTextField(fieldWrapper, fieldName, fieldValue);
|
break;
|
}
|
}
|
|
/**
|
* Populate taxonomy fields with visual display
|
*/
|
populateTaxonomyField(fieldWrapper, fieldName, fieldValue) {
|
// Handle different value formats
|
let termIds = [];
|
|
if (Array.isArray(fieldValue)) {
|
termIds = fieldValue.map(v => String(v));
|
} else if (typeof fieldValue === 'string') {
|
try {
|
const parsed = JSON.parse(fieldValue);
|
termIds = Array.isArray(parsed) ? parsed.map(v => String(v)) : [String(parsed)];
|
} catch (e) {
|
termIds = fieldValue.split(',').map(v => v.trim()).filter(v => v);
|
}
|
} else if (fieldValue) {
|
termIds = [String(fieldValue)];
|
}
|
|
if (termIds.length === 0) {
|
return;
|
}
|
|
// Update hidden input
|
const hiddenInput = fieldWrapper.querySelector(`input[type="hidden"][name="${fieldName}"]`);
|
if (hiddenInput) {
|
hiddenInput.value = termIds.join(',');
|
|
// Trigger TaxonomySelector to update visual display
|
const toggle = fieldWrapper.querySelector('.taxonomy-toggle');
|
if (toggle && toggle.dataset.fieldId && window.jvbTaxonomy) {
|
// Use requestAnimationFrame to ensure DOM is ready
|
requestAnimationFrame(() => {
|
window.jvbTaxonomy.updateFieldFromInput(toggle.dataset.fieldId);
|
});
|
}
|
}
|
}
|
|
/**
|
* Populate upload fields (images, videos, files)
|
*/
|
populateUploadField(fieldWrapper, fieldName, fieldValue) {
|
// Check if this is a timeline gallery
|
const isTimeline = fieldWrapper.dataset.subtype === 'timeline' || fieldName === 'timeline';
|
|
if (isTimeline) {
|
this.populateTimelineGallery(fieldWrapper, fieldName, fieldValue);
|
return;
|
}
|
|
if (!fieldValue) {
|
return;
|
}
|
|
// Handle comma-separated IDs or single ID
|
const itemIds = this.splitIDs(fieldValue);
|
if (itemIds.length === 0) {
|
return;
|
}
|
|
// Update hidden input
|
const hiddenInput = fieldWrapper.querySelector(`input[type="hidden"][name="${fieldName}"]`);
|
if (hiddenInput) {
|
hiddenInput.value = itemIds.join(',');
|
}
|
|
// Update display grid
|
const grid = fieldWrapper.querySelector('.item-grid');
|
const uploadContainer = fieldWrapper.querySelector('.file-upload-container');
|
|
// Clear existing items first
|
if (grid) {
|
window.removeChildren(grid);
|
}
|
|
fieldWrapper.querySelector('.progress')?.remove();
|
|
if (grid) {
|
itemIds.forEach(itemId => {
|
const template = window.getTemplate('uploadItem');
|
if (!template) {
|
console.warn('uploadItem template not found');
|
return;
|
}
|
|
this.populateUploadItem(template, itemId);
|
grid.append(template);
|
});
|
|
// Hide upload container if items exist
|
if (itemIds.length > 0 && uploadContainer) {
|
uploadContainer.hidden = true;
|
}
|
}
|
}
|
|
/**
|
* Populate a single upload item
|
*/
|
populateUploadItem(template, itemId) {
|
let input = template.querySelector('input[name="select-item"]');
|
let label = template.querySelector('label[for="select-item"]');
|
|
template.dataset.id = itemId;
|
input.name = `select-item-${itemId}`;
|
input.id = input.name;
|
label.htmlFor = input.name;
|
|
const img = template.querySelector('img');
|
template.querySelector('video')?.remove();
|
|
// Populate with data from item.images
|
if (this.item.images[itemId]) {
|
const data = this.item.images[itemId];
|
if (img) {
|
img.src = data.medium || data.small || data.large || '';
|
img.alt = data['image-alt-text'] || data.alt || '';
|
}
|
|
// Populate metadata fields
|
const titleInput = template.querySelector('[name="image-title"]');
|
const altInput = template.querySelector('[name="image-alt-text"]');
|
const captionInput = template.querySelector('[name="image-caption"]');
|
|
if (titleInput) titleInput.value = data['image-title'] || data.title || '';
|
if (altInput) altInput.value = data['image-alt-text'] || data.alt || '';
|
if (captionInput) captionInput.value = data['image-caption'] || data.caption || '';
|
} else {
|
console.warn(`No image data found for ID: ${itemId}`);
|
}
|
|
// Remove hint if present
|
template.querySelector('details .upload-meta > .hint')?.remove();
|
}
|
|
/**
|
* Populate timeline gallery - FIXED iteration
|
*/
|
populateTimelineGallery(fieldWrapper, fieldName, fieldValue) {
|
console.log('Populating Timeline Gallery', fieldValue);
|
|
if (!fieldValue || !Array.isArray(fieldValue)) {
|
console.warn('Timeline field value must be an array');
|
return;
|
}
|
|
if (fieldValue.length === 0) {
|
return;
|
}
|
|
const grid = fieldWrapper.querySelector('.item-grid');
|
const uploadContainer = fieldWrapper.querySelector('.file-upload-container');
|
|
// Clear existing items
|
if (grid) {
|
window.removeChildren(grid);
|
}
|
|
fieldWrapper.querySelector('.progress')?.remove();
|
|
if (!grid) return;
|
|
// FIX: Iterate directly over array, not Object.entries
|
for (let itemData of fieldValue) {
|
const template = window.getTemplate('timelineItem');
|
if (!template) {
|
console.warn('timelineItem template not found');
|
continue;
|
}
|
|
const imageId = itemData.post_thumbnail;
|
const postId = itemData.id;
|
|
// Set template data attributes
|
template.dataset.id = imageId;
|
template.dataset.postId = postId;
|
|
// Update selection controls
|
let input = template.querySelector('input[name="select-item"]');
|
let label = template.querySelector('label[for="select-item"]');
|
if (input && label) {
|
input.name = `select-item-${imageId}`;
|
input.id = input.name;
|
label.htmlFor = input.name;
|
}
|
|
// Remove unnecessary elements
|
template.querySelector('video')?.remove();
|
template.querySelector('.select-item span')?.remove();
|
|
// Populate main image
|
const img = template.querySelector('img');
|
const imgData = this.item.images[imageId];
|
if (img && imgData) {
|
img.src = imgData.medium || imgData.small || imgData.large || '';
|
img.title = imgData['image-title'] || '';
|
img.alt = imgData['image-alt-text'] || '';
|
}
|
|
// Populate all fields within the template
|
const fields = template.querySelectorAll('.field');
|
fields.forEach(field => {
|
if (field.classList.contains('group')) {
|
return;
|
}
|
|
const input = field.querySelector('input:not([type="file"]), textarea');
|
if (!input) return;
|
|
const label = field.querySelector('label');
|
const fieldName = input.name.replace('upload_data::', '').replace(/^\[.*?\]/, '');
|
|
// Get value from itemData or imgData
|
let value = itemData[fieldName];
|
if (value === undefined && imgData) {
|
value = imgData[fieldName];
|
}
|
|
// Populate the field using our standard method
|
if (value !== undefined && value !== null) {
|
this.populateField(field, fieldName, value);
|
}
|
|
// Update field identifiers to include post ID
|
const newName = `[${postId}]${fieldName}`;
|
const newId = newName;
|
input.name = newName;
|
input.id = newId;
|
if (label) label.htmlFor = newId;
|
});
|
|
grid.append(template);
|
}
|
|
// Hide upload container if items exist
|
if (fieldValue.length > 0 && uploadContainer) {
|
uploadContainer.hidden = true;
|
}
|
}
|
|
// ... rest of the methods stay the same (populateTextField, populateTextareaField, etc.)
|
// I'll include the key ones that might need updating
|
|
populateTextField(fieldWrapper, fieldName, fieldValue) {
|
const input = fieldWrapper.querySelector(`[name="${fieldName}"], input, textarea`);
|
if (input && input.type !== 'file') {
|
input.value = String(fieldValue || '');
|
|
if (input.dataset.limit) {
|
const counter = fieldWrapper.querySelector('.char-count .current');
|
if (counter) {
|
counter.textContent = input.value.length;
|
}
|
}
|
}
|
}
|
|
populateTextareaField(fieldWrapper, fieldName, fieldValue) {
|
const textarea = fieldWrapper.querySelector(`textarea[name="${fieldName}"]`) ||
|
fieldWrapper.querySelector('textarea:not([data-editor="true"])');
|
|
if (textarea) {
|
textarea.value = String(fieldValue || '');
|
textarea.dispatchEvent(new Event('change', { bubbles: true }));
|
|
if (textarea.dataset.limit) {
|
const counter = fieldWrapper.querySelector('.char-count .current');
|
if (counter) {
|
counter.textContent = textarea.value.length;
|
const limit = parseInt(textarea.dataset.limit, 10);
|
fieldWrapper.classList.toggle('reached', textarea.value.length >= limit);
|
}
|
}
|
}
|
}
|
|
populateEditorField(fieldWrapper, fieldName, fieldValue) {
|
const textarea = fieldWrapper.querySelector(`textarea[name="${fieldName}"][data-editor="true"]`);
|
if (!textarea) return;
|
|
textarea.value = String(fieldValue || '');
|
const editorContainer = fieldWrapper.querySelector('.editor');
|
const content = fieldValue || '<p><br></p>';
|
|
if (editorContainer) {
|
let quillInstance = editorContainer.__quill;
|
|
if (!quillInstance && window.Quill) {
|
for (let instance of (window.Quill.instances || [])) {
|
if (instance.container === editorContainer) {
|
quillInstance = instance;
|
break;
|
}
|
}
|
}
|
|
if (quillInstance) {
|
quillInstance.root.innerHTML = content;
|
editorContainer.__quill = quillInstance;
|
} else {
|
editorContainer.innerHTML = content;
|
}
|
}
|
|
textarea.dispatchEvent(new Event('change', { bubbles: true }));
|
}
|
|
getFieldType(fieldWrapper) {
|
if (fieldWrapper.dataset.fieldType) return fieldWrapper.dataset.fieldType;
|
if (fieldWrapper.dataset.type) return fieldWrapper.dataset.type;
|
|
const typeClasses = [
|
'upload', 'repeater', 'taxonomy', 'user', 'location',
|
'set', 'checkbox', 'select', 'radio', 'true_false', 'date',
|
'time', 'datetime', 'editor', 'number', 'text', 'textarea',
|
'email', 'url', 'tel', 'phone'
|
];
|
|
for (const type of typeClasses) {
|
if (fieldWrapper.classList.contains(type)) {
|
return type;
|
}
|
}
|
|
const input = fieldWrapper.querySelector('input, select, textarea');
|
if (input) {
|
if (input.tagName === 'TEXTAREA') {
|
return input.dataset.editor === 'true' ? 'editor' : 'textarea';
|
}
|
if (input.type) {
|
return input.type === 'checkbox' && !fieldWrapper.classList.contains('true_false') ? 'set' : input.type;
|
}
|
}
|
|
return 'text';
|
}
|
|
// Include all other existing methods...
|
}
|
|
// Make available globally
|
window.jvbPopulate = PopulateForm;
|