From 11636c2ba396c7e18b6d328a26a727974d7d2381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 24 Feb 2022 15:16:06 +0100 Subject: [PATCH] default format for arrays, objects, null and explicit undefined, in tables closes #166 #164 --- src/format.js | 32 +++++++++++++++++ src/style.css | 4 +++ src/table.js | 12 +++---- test/inputs/tables.js | 23 ++++++++++++ test/output/tableCircular.html | 17 +++++++++ test/output/tableVariousObjects.html | 53 ++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 test/output/tableCircular.html create mode 100644 test/output/tableVariousObjects.html diff --git a/src/format.js b/src/format.js index c67f614..30d9435 100644 --- a/src/format.js +++ b/src/format.js @@ -1,4 +1,5 @@ import {format as isoformat} from "isoformat"; +import {html} from "htl"; // Note: use formatAuto (or any other localized format) to present values to the // user; stringify is only intended for machine values. @@ -18,6 +19,33 @@ export const formatLocaleNumber = localize(locale => { return value => value === 0 ? "0" : value.toLocaleString(locale); // handle negative zero }); +export function formatObjects(format, circular = true) { + return value => value === null ? gray("null") + : value === undefined ? gray("undefined") + : Number.isNaN(value) ? gray(NaN) + : circular && Array.isArray(value) ? formatArray(value) + : circular && isTypedArray(value) ? formatArray(value) + : value instanceof Date ? formatDate(value) + : circular && typeof value === "object" ? formatObject(value) + : format(value); +} + +function isTypedArray(_) { + return _?.prototype?.__proto__?.constructor?.name == "TypedArray"; +} + +function formatObject(o) { + const subformat = formatObjects(formatAuto, false); + return `{${Object.entries(o).map(([key, value]) => `${key}: ${subformat(value)}`).join(", ")}}`; +} + +function formatArray(value) { + const subformat = formatObjects(formatAuto, false); + const l = value.length; + value = Array.from({length: Math.min(30, l)}, (_, i) => subformat(value[i])); + return `[${value.join(", ")}${l > 30 ? "…" : "" }]`; +} + export const formatAuto = formatLocaleAuto(); export const formatNumber = formatLocaleNumber(); @@ -45,3 +73,7 @@ export function localize(f) { let key = localize, value; return (locale = "en") => locale === key ? value : (value = f(key = locale)); } + +function gray(label) { + return html`${label}`; +} diff --git a/src/style.css b/src/style.css index 23632ca..228ac82 100644 --- a/src/style.css +++ b/src/style.css @@ -242,6 +242,10 @@ form.__ns__-table { padding: 3px 6.5px 3px 0; } +.__ns__-table td .gray { + color: #888; +} + .__ns__-table tr > :not(:first-of-type) { padding-left: var(--length2); } diff --git a/src/table.js b/src/table.js index 0b588bf..fffce63 100644 --- a/src/table.js +++ b/src/table.js @@ -1,10 +1,10 @@ import {html} from "htl"; import {arrayify, maybeColumns} from "./array.js"; import {length} from "./css.js"; -import {formatDate, formatLocaleAuto, formatLocaleNumber} from "./format.js"; +import {formatDate, formatLocaleAuto, formatLocaleNumber, formatObjects} from "./format.js"; import {newId} from "./id.js"; import {identity} from "./identity.js"; -import {defined, ascending, descending} from "./sort.js"; +import {ascending, descending} from "./sort.js"; const rowHeight = 22; @@ -130,8 +130,8 @@ function initialize( input.value = i; if (d != null) for (let j = 0; j < columns.length; ++j) { let column = columns[j]; + if (!(column in d)) continue; let value = d[column]; - if (!defined(value)) continue; value = format[column](value, i, data); if (!(value instanceof Node)) value = document.createTextNode(value); itr.childNodes[j + 1].appendChild(value); @@ -334,9 +334,9 @@ function formatof(base = {}, data, columns, locale) { continue; } switch (type(data, column)) { - case "number": format[column] = formatLocaleNumber(locale); break; - case "date": format[column] = formatDate; break; - default: format[column] = formatLocaleAuto(locale); break; + case "number": format[column] = formatObjects(formatLocaleNumber(locale)); break; + case "date": format[column] = formatObjects(formatDate); break; + default: format[column] = formatObjects(formatLocaleAuto(locale)); break; } } return format; diff --git a/test/inputs/tables.js b/test/inputs/tables.js index 5e8b27a..e7cb44d 100644 --- a/test/inputs/tables.js +++ b/test/inputs/tables.js @@ -28,3 +28,26 @@ export function tableCustomHeader() { export function tableCustomHeaderHtml() { return Inputs.table([{foo: "hello"}], {header: {foo: html`Foo`}}); } + +export function tableVariousObjects() { + return Inputs.table([ + {A: null}, + {A: undefined}, // explicit undefined + {}, + {A: ["a", "b"]}, + {A: Float32Array.from([1, 2, 3])}, + {A: Array.from({length: 30}, (_, i)=> i)}, + {A: Array.from({length: 31}, (_, i)=> i)}, + {A: [[1, 2], ["a"]]}, + {A: {key: "value"}}, + {A: {key: {key: "value"}}} + ]); +} + +export function tableCircular() { + const o = {}; + o.circular = o; + return Inputs.table([ + {A: o} + ]); +} diff --git a/test/output/tableCircular.html b/test/output/tableCircular.html new file mode 100644 index 0000000..740d0e3 --- /dev/null +++ b/test/output/tableCircular.html @@ -0,0 +1,17 @@ +
+ + + + + + + + + + + + + +
A
{circular: [object Object]}
+ +
\ No newline at end of file diff --git a/test/output/tableVariousObjects.html b/test/output/tableVariousObjects.html new file mode 100644 index 0000000..2b4b9d9 --- /dev/null +++ b/test/output/tableVariousObjects.html @@ -0,0 +1,53 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A
null
undefined
[a, b]
{0: 1, 1: 2, 2: 3}
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29…]
[1,2, a]
{key: value}
{key: [object Object]}
+ +
\ No newline at end of file