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);
|
let fields = row.querySelectorAll('.field');
|
this.populate(fields, data);
|
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) => {
|
data.index = index;
|
const row = this.templates.create(template, data);
|
let fields = row.querySelectorAll('.field');
|
this.populate(fields, data);
|
container.append(row);
|
});
|
}
|
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-container');
|
uploadContainer.hidden = ids.length > 0;
|
field.querySelector('.progress')?.remove();
|
if (grid) {
|
window.removeChildren(grid);
|
ids.forEach(id => {
|
let data = this.data.images[id]??{};
|
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-container');
|
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();
|
}
|
});
|
});
|