Files
TWA-App/TWASys-App/wwwroot/js/libs/js-AAutoFill.js
2025-10-22 09:41:40 +07:00

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 })); }
}