/********************************************** 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(','); if (window.jvbTaxonomy) { requestAnimationFrame(() => { window.jvbTaxonomy.updateFieldFromInput(hiddenInput); }); } } } /** * 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; } } 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 || '