ZDDC/tables/js/util.js
2026-06-11 13:32:31 -05:00

151 lines
4.7 KiB
JavaScript

(function (app) {
'use strict';
const util = {};
util.h = function (tag, attrs) {
const el = document.createElement(tag);
if (attrs) {
for (const k of Object.keys(attrs)) {
const v = attrs[k];
if (v == null || v === false) {
continue;
}
if (k === 'className') {
el.className = v;
} else if (k.length > 2 && k.slice(0, 2) === 'on' && typeof v === 'function') {
el.addEventListener(k.slice(2).toLowerCase(), v);
} else if (v === true) {
el.setAttribute(k, '');
} else {
el.setAttribute(k, v);
}
}
}
for (let i = 2; i < arguments.length; i++) {
const c = arguments[i];
if (c == null || c === false) {
continue;
}
if (typeof c === 'string' || typeof c === 'number') {
el.appendChild(document.createTextNode(String(c)));
} else {
el.appendChild(c);
}
}
return el;
};
// Resolve a column's `field` against a row data object.
// - "" or "/" → the whole object
// - "/foo/bar" → JSON Pointer (RFC 6901) lookup
// - "foo" → top-level key
util.resolveField = function (data, field) {
if (data == null) {
return undefined;
}
if (!field || field === '/') {
return data;
}
if (field.charAt(0) !== '/') {
return data[field];
}
const segments = field.split('/').slice(1).map(function (s) {
return s.replace(/~1/g, '/').replace(/~0/g, '~');
});
let cur = data;
for (let i = 0; i < segments.length; i++) {
if (cur == null) {
return undefined;
}
if (Array.isArray(cur)) {
const idx = parseInt(segments[i], 10);
if (Number.isNaN(idx)) {
return undefined;
}
cur = cur[idx];
} else if (typeof cur === 'object') {
cur = cur[segments[i]];
} else {
return undefined;
}
}
return cur;
};
// Format a raw cell value per column's `format` hint.
util.formatCell = function (value, format) {
if (value == null || value === '') {
return '';
}
if (format === 'date') {
const d = new Date(value);
if (!isNaN(d.getTime())) {
return d.toISOString().slice(0, 10);
}
return String(value);
}
if (format === 'datetime') {
const d = new Date(value);
if (!isNaN(d.getTime())) {
return d.toLocaleString();
}
return String(value);
}
if (format === 'number') {
const n = Number(value);
if (Number.isFinite(n)) {
return n.toLocaleString();
}
return String(value);
}
if (format === 'bool' || typeof value === 'boolean') {
return value ? '✓' : '';
}
if (typeof value === 'object') {
try {
return JSON.stringify(value);
} catch (e) {
return String(value);
}
}
return String(value);
};
// Compare two cell values for sorting. null/undefined sort last.
// Numbers compared numerically, dates compared as Date, otherwise string compare.
util.compareCells = function (a, b, format) {
const aMissing = a == null || a === '';
const bMissing = b == null || b === '';
if (aMissing && bMissing) {
return 0;
}
if (aMissing) {
return 1;
}
if (bMissing) {
return -1;
}
if (format === 'date' || format === 'datetime') {
const da = new Date(a).getTime();
const db = new Date(b).getTime();
if (!isNaN(da) && !isNaN(db)) {
return da - db;
}
}
if (format === 'number' || (typeof a === 'number' && typeof b === 'number')) {
const na = Number(a);
const nb = Number(b);
if (Number.isFinite(na) && Number.isFinite(nb)) {
return na - nb;
}
}
const sa = String(a).toLowerCase();
const sb = String(b).toLowerCase();
if (sa < sb) return -1;
if (sa > sb) return 1;
return 0;
};
app.modules.util = util;
})(window.tablesApp);