107 lines
4.4 KiB
JavaScript
107 lines
4.4 KiB
JavaScript
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 })); }
|
|
} |