export default class AAutoFill extends window.AObject{ constructor(root = document) { super(); this.root = root; } fill(data) { const sel = '[data-id],[data-control],[data-controll]'; this.root.querySelectorAll(sel).forEach(el => { const path = el.dataset.id ?? el.dataset.control ?? el.dataset.controll; const val = AAutoFill.get(data, path); if (val === undefined) return; // không có dữ liệu -> bỏ qua AAutoFill.apply(el, val, this.root); }); } // --- helpers --- static get(obj, path) { if (!obj || !path) return undefined; // dot + bracket: user.items[0].name const parts = path.split('.').flatMap(p => { const m = [...p.matchAll(/([^\[\]]+)|\[(\d+)\]/g)]; return m.map(g => g[1] ?? Number(g[2])); }); return parts.reduce((acc, k) => (acc == null ? undefined : acc[k]), obj); } static apply(el, value, root) { const tag = el.tagName.toLowerCase(); const type = (el.type || '').toLowerCase(); if (tag === 'input') { if (type === 'checkbox') { if (Array.isArray(value)) { el.checked = value.map(String).includes(el.value); } else { el.checked = AAutoFill.truthy(value, el.value); } AAutoFill.emit(el, 'change'); return; } if (type === 'radio') { const key = el.name || el.dataset.id || el.dataset.control || el.dataset.controll; const q = `input[type=radio][name="${CSS.escape(key)}"],input[type=radio][data-id="${CSS.escape(key)}"],input[type=radio][data-control="${CSS.escape(key)}"],input[type=radio][data-controll="${CSS.escape(key)}"]`; root.querySelectorAll(q).forEach(r => r.checked = String(r.value) === String(value)); AAutoFill.emit(el, 'change'); return; } if (type === 'date' || type === 'time' || type === 'datetime-local') { el.value = AAutoFill.formatForInput(type, value); AAutoFill.emit(el); AAutoFill.emit(el, 'change'); return; } el.value = value ?? ''; AAutoFill.emit(el); AAutoFill.emit(el, 'change'); return; } if (tag === 'select') { if (el.multiple) { const arr = Array.isArray(value) ? value.map(String) : [String(value)]; [...el.options].forEach(o => o.selected = arr.includes(o.value) || arr.includes(o.text)); } else { el.value = String(value); if (el.value !== String(value)) { const opt = [...el.options].find(o => o.text === String(value)); if (opt) opt.selected = true; } } AAutoFill.emit(el, 'change'); return; } if (tag === 'textarea') { el.value = value ?? ''; AAutoFill.emit(el); return; } if (el.isContentEditable) { (el.dataset.html === 'true') ? el.innerHTML = String(value ?? '') : el.textContent = String(value ?? ''); return; } // fallback: textContent el.textContent = String(value ?? ''); } static truthy(v, compareVal) { if (typeof v === 'boolean') return v; const s = String(v).toLowerCase(); return ['1', 'true', 'yes', 'on', String(compareVal).toLowerCase()].includes(s); } static formatForInput(type, v) { const asDate = (x) => { if (x instanceof Date) return x; const n = Number(x); if (!Number.isNaN(n) && n > 0) { // epoch (ms or sec) const d = new Date(n > 1e12 ? n : n * 1000); if (!isNaN(d)) return d; } const d = new Date(x); return isNaN(d) ? null : d; }; const pad = (n) => String(n).padStart(2, '0'); const d = asDate(v); if (!d) return String(v ?? ''); if (type === 'date') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; if (type === 'time') return `${pad(d.getHours())}:${pad(d.getMinutes())}`; if (type === 'datetime-local') return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`; return String(v ?? ''); } static emit(el, name = 'input') { el.dispatchEvent(new Event(name, { bubbles: true })); } }