class PopulateForm { constructor() { this.templates = window.jvbTemplates; this.formHelper = window.jvbForm; this.defineTemplates(); this.data = null; this.form = null; } /** * * @param {HTMLElement} form * @param {object} data * @param {object} data.fields * @param {object} data.images * @param {object} data.taxonomies */ populate (form, data = {}) { this.data = data; this.mergeRootData(); this.form = form; if (!this.formHelper) { this.formHelper = window.jvbForm; } // If still not available, queue for retry if (!this.formHelper) { requestAnimationFrame(() => { this.populate(form, data); }); return; } if (!Object.hasOwn(this.data, 'fields') || Object.keys(this.data.fields).length === 0) return; for (let [name, value] of Object.entries(this.data.fields)) { let field = form.querySelector(`[data-field="${name}"]`); if (field) { this.populateField(field, name, value); } } } mergeRootData(){ let check = ['status','date','modified']; check.forEach(ch =>{ this.data.fields[`post_${ch}`] = this.data[ch]; }); } /** * * @param {HTMLElement} field * @param {string} name * @param {mixed} value */ populateField(field, name, value) { let type = this.formHelper.getFieldType(field); if (!type || this.isEmptyValue(name) || this.isEmptyValue(value)) return; const handlers = { 'repeater': this.populateRepeater.bind(this), 'tag-list': this.populateTagList.bind(this), 'location': this.populateLocation.bind(this), 'selector': this.populateTaxonomy.bind(this), 'user': this.populateUser.bind(this), 'upload': this.populateUpload.bind(this), 'set': this.populateMultiValue.bind(this), 'checkbox': this.populateMultiValue.bind(this), 'select': this.populateSingleValue.bind(this), 'radio': this.populateSingleValue.bind(this), 'true-false': this.populateBoolean.bind(this), 'date': this.populateDate.bind(this), 'time': this.populateDate.bind(this), 'datetime': this.populateDate.bind(this), 'number': this.populateNumber.bind(this), 'textarea': this.populateTextarea.bind(this) }; if (Object.hasOwn(handlers, type)) { handlers[type](field, name, value); } else { this.populateText(field, name, value); } } populateRepeater(field, name, value) { if (!value || !Array.isArray(value)) return; const container = field.querySelector('.repeater-items'); let template = field.querySelector('template')?.className ?? false; if (!container || !template) return; window.removeChildren(container); value.forEach((data, index) => { data.index = index; const row = this.templates.create(template, data); if (!row) return; for (let [fieldName, fieldValue] of Object.entries(data)) { if (fieldName === 'index') continue; let subField = row.querySelector(`[data-field="${fieldName}"]`); if (subField) { this.populateField(subField, fieldName, fieldValue); } } container.append(row); }); } populateTagList(field, name, value) { if (!value || !Array.isArray(value)) return; const container = field.querySelector('.tag-items'); let template = field.querySelector('template')?.className ?? false; if (!container || !template) return; window.removeChildren(container); value.forEach((data, index) => { const row = this.templates.create(template, { label: this.getTagLabel(data, field.dataset.tagFormat ?? 'first_field'), fieldName: name, ...data }); if (!row) return; // Set hidden input values directly row.querySelectorAll('input[type="hidden"]').forEach(input => { const key = input.dataset.field; if (key && data[key] !== undefined) { input.value = data[key]; } }); container.append(row); }); } /** * Build tag label from data - mirrors addTagListItem logic */ getTagLabel(data, format) { const values = Object.values(data).filter(v => !this.isEmptyValue(v)); switch (format) { case 'first_field': return values[0] ?? 'New Item'; case 'all_fields': return values.join(', ') || 'New Item'; default: if (format.includes('{')) { let label = format; for (const [key, value] of Object.entries(data)) { label = label.replace(`{${key}}`, value); } return label; } return data[format] ?? values[0] ?? 'New Item'; } } populateLocation(field, name, value) { const subFields = ['address', 'lat', 'lng', 'street', 'city', 'province', 'postal_code', 'country']; subFields.forEach(subField => { if (Object.hasOwn(value, subField)) { let input = field.querySelector(`[data-location-field="${subField}"]`); if (input) input.value = String(value[subField]||''); } }); } populateTaxonomy(field, name, value) { let termIds = this.splitIDs(value); if (termIds.length === 0) return; const hiddenInput = field.querySelector(`input[type="hidden"][name="${name}"]`); if (hiddenInput) { hiddenInput.value = termIds.join(','); if (window.jvbSelector) { requestAnimationFrame(() => { window.jvbSelector.updateFieldFromInput(hiddenInput); }); } } } populateUser(field, name, value) { this.populateTaxonomy(field, name, value); } populateUpload(field, name, value) { if (name === 'timeline' || field.dataset.subtype && field.dataset.subtype === 'timeline') { this.populateTimelineGallery(field,name,value); return; } if (this.isEmptyValue(value)) return; const ids = this.splitIDs(value); if (ids.length === 0) return; const hiddenInput = field.querySelector(`input[type="hidden"]`); if (hiddenInput) { hiddenInput.value = ids.join(','); } const grid = field.querySelector('.item-grid'); let uploadContainer = field.querySelector('.file-upload-wrapper'); uploadContainer.hidden = ids.length > 0; field.querySelector('.progress')?.remove(); if (grid) { window.removeChildren(grid); ids.forEach(id => { let data = this.data.images[id]??{}; data.field = { config: { showMeta: true } }; data.id = id; grid.append(this.templates.create('uploadItem', data)); }); } this.populateUploadMeta(field, name, value); } populateUploadMeta(element, name, id) { // Find the image_data field group const imageDataField = element.querySelector('[data-field="image_data"]'); if (!imageDataField) return; let data = this.data.images[id]??false; if (!data) return; // Set upload ID or attachment ID imageDataField.dataset.attachmentId = data.id; imageDataField.setAttribute('data-ignore', ''); // Populate the metadata fields const meta = [ 'image-title', 'image-alt-text', 'image-caption' ]; for (const m of meta) { const input = imageDataField.querySelector(`[data-field="${m}"] input, [data-field="${m}"] textarea`); if (input && data[m]!=='') { input.value = data[m]; } } } populateTimelineGallery(field,name, value) { if (!value || !Array.isArray(value) || value.length === 0) return; let grid = field.querySelector('.item-grid'); let uploadContainer = field.querySelector('.file-upload-wrapper'); uploadContainer.hidden = value.length > 0; if (grid) { window.removeChildren(grid); field.querySelector('.progress')?.remove(); for (let data of value) { let point = this.templates.create('timelineItem', data); if (point) { grid.append(point); } } } } populateMultiValue(field, name, value) { if (typeof value === 'string') { try { value = JSON.parse(value); } catch (e) { value = value.split(',').map(v => v.trim()); } } if (!Array.isArray(value)) { value = [String(value)]; } let select = field.querySelector(`select[name="${name}"]`); if (select && select.multiple) { for (let option of select.options) { option.selected = value.includes(option.value); } return; } field.querySelectorAll(`[type="checkbox"][name=${name}]`).forEach(checkbox => { checkbox.checked = value.includes(checkbox.value); }); } populateSingleValue(field, name, value) { value = String(value || ''); // Try select first let select = field.querySelector(`select[name="${name}"]`); if (select) { select.value = value; return; } let input = field.querySelector(`[name="${name}"][value="${value}"]`); if (input) { input.checked = true; } } populateBoolean(field, name, value) { const input = field.querySelector(`[name="${name}"], input[type="checkbox"]`); if (input) { input.checked = Boolean(value); } } populateDate(field, name, value) { const input = field.querySelector(`[name="${name}"], input`); if (input) { if (typeof value === 'object' && Object.hasOwn(value, 'date')) { value = value.date; } try { const date = new Date(value); if (!isNaN(date.getTime())) { switch (input.type) { case 'date': input.value = date.toISOString().split('T')[0]; break; case 'time': input.value = date.toTimeString().slice(0, 5); break; case 'datetime-local': input.value = date.toISOString().slice(0, 16); break; default: input.value = value; } } } catch (e) { input.value = value; } } } populateNumber(field, name, value) { const input = field.querySelector(`[name="${name}"], input[type="number"]`); if (input) { input.value = Number(value) || 0; } } populateTextarea(field, name, value) { let textarea = field.querySelector('textarea'); if (!textarea.dataset.editor) { this.populateText(field, name, value) return; } textarea.value = String(value || ''); textarea.dispatchEvent(new Event('change', {bubbles: true})); } populateText(field, name, value) { let input = field.querySelector(`[name="${name}"], input, textarea`); if (input && input.type !== 'file') { input.value = String(value || ''); } } /******************************************************************** UTILITY ********************************************************************/ getFormHelper() { window.requestAnimationFrame(()=> { this.formHelper = window.jvbForm; }); } splitIDs(value) { return String(value).split(',') .map(v=>parseInt(v.trim())) .filter(v=>!isNaN(v) && v>0); } isEmptyValue(value) { if (value === null || value === undefined || value === '') return true; if (Array.isArray(value) && value.length === 0) return true; return typeof value === 'object' && Object.keys(value).length === 0; } defineTemplates() { const T = this.templates; const p = this; T.define('timelineItem', { refs: { select: '[name="select-item"]', video: 'video', file: '.select-item span', img: 'img', details: 'details[data-field]', imgAlt: '[name="image-alt-text"]', imgTitle: '[name="image-title"]', imgDesc: '[name="image-caption"]', }, manyRefs: { fields: '.field', }, setup({el, refs, manyRefs, data}) { el.dataset.itemId = data.id; if (refs.select) { let wrapper = refs.select.closest('.preview'); window.prefixInput(refs.select, `${data.id}-`, wrapper); } if (refs.video) refs.video.remove(); if (refs.file) refs.file.remove(); let imgData = p.data.images[data['post_thumbnail']]??false; if (refs.img && imgData) { refs.img.src = imgData.medium || imgData.small || imgData.large || ''; refs.img.title = imgData['image-title']??''; refs.img.alt = imgData['image-alt-text']??''; } if (refs.details) { let imgData = p.data.images[data.post_thumbnail]; refs.details.setAttribute('data-ignore', ''); refs.details.dataset.attachmentId = data.post_thumbnail; if (Object.hasOwn(imgData, 'image-alt-text') && refs.alt) { refs.alt.value = imgData['image-alt-text']; } if ((Object.hasOwn(imgData, 'image-title') || Object.hasOwn(data, 'file')) && refs.title) { refs.title.value = imgData['image-title']||data.file.name; } if (Object.hasOwn(imgData, 'image-caption') && refs.description) { refs.description.value = imgData['image-caption']; } } if (manyRefs.fields) { for (let field of manyRefs.fields) { if (field.dataset.fieldType === 'group') continue; if (field.dataset.field === 'post_thumbnail') { field.remove(); continue; } let name = field.dataset.field; let value = data[name]??''; if (!p.isEmptyValue(value)) { p.populateField(field, name, value); } const input = field.querySelector('input:not([type="file"])'); if (!input) continue; window.prefixInput(input, `[${data.id}]`, field); } } } }); } } document.addEventListener('DOMContentLoaded', function() { window.auth.subscribe(event => { if (event === 'auth-loaded') { window.jvbPopulate = new PopulateForm(); } }); });