From ca5fbfb5c952da1b97ef46acfe12ecd3097c078f Mon Sep 17 00:00:00 2001 From: David Thorpe Date: Fri, 17 Jan 2025 10:39:19 +0100 Subject: [PATCH 1/2] Updated tables --- .gitignore | 1 + .vscode/settings.json | 2 +- config/esbuild.table.mjs | 40 +++++++++++++++ example/esbuild.js | 5 -- example/table/index.html | 41 +++++++++++++++ example/table/index.js | 8 +++ package.json | 4 +- src/element/TableColumnElement.js | 22 ++++++++ .../{TableBodyElement.js => TableElement.js} | 50 ++++++++++++++----- src/element/TableHeadElement.js | 6 +-- src/element/element.js | 12 +++-- 11 files changed, 164 insertions(+), 27 deletions(-) create mode 100644 config/esbuild.table.mjs delete mode 100644 example/esbuild.js create mode 100644 example/table/index.html create mode 100644 example/table/index.js create mode 100644 src/element/TableColumnElement.js rename src/element/{TableBodyElement.js => TableElement.js} (68%) diff --git a/.gitignore b/.gitignore index 9ef30d7..08565d8 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ jspm_packages/ .next .nuxt dist +build .cache/ .vuepress/dist .serverless/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 51d1042..cd0a312 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { - "eslint.experimental.useFlatConfig": true, + "eslint.useFlatConfig": true, "eslint.options": { "overrideConfigFile": "./config/eslint.config.mjs" }, } diff --git a/config/esbuild.table.mjs b/config/esbuild.table.mjs new file mode 100644 index 0000000..80a8e71 --- /dev/null +++ b/config/esbuild.table.mjs @@ -0,0 +1,40 @@ +import esbuild from 'esbuild'; + +const commonOptions = { + outdir: 'dist', + format: 'esm', + bundle: true, + loader: { + '.svg': 'file', + '.woff': 'file', + '.woff2': 'file', + '.ttf': 'file', + '.otf': 'file', + '.html': 'copy', + '.json': 'copy', + }, + logLevel: 'info', + entryPoints: [], +}; + +// Add the table +commonOptions.entryPoints.push('example/table/index.js', 'example/table/index.html'); + +// Build the table, and optionally serve it in development +if (process.env.NODE_ENV === 'production') { + await esbuild.build({ + ...commonOptions, + minify: true, + sourcemap: false, + }).catch(() => process.exit(1)); +} else if (process.env.NODE_ENV === 'development') { + let ctx = await esbuild.context({ + ...commonOptions, + minify: false, + sourcemap: true, + }) + let { host, port } = await ctx.serve({ + servedir: commonOptions.outdir, + }); + await ctx.watch(); +} diff --git a/example/esbuild.js b/example/esbuild.js deleted file mode 100644 index cabc717..0000000 --- a/example/esbuild.js +++ /dev/null @@ -1,5 +0,0 @@ -/* Code to reload in the esbuild serve development environment */ -window.addEventListener('load', () => { - // eslint-disable-next-line no-restricted-globals - //new EventSource('/esbuild').addEventListener('change', () => location.reload()); -}); diff --git a/example/table/index.html b/example/table/index.html new file mode 100644 index 0000000..a84b072 --- /dev/null +++ b/example/table/index.html @@ -0,0 +1,41 @@ + + + + + + + Nanoradio + + + + + + + + + Home + + + + + Author + Date + Title + Description + Media + + + + + + + + + diff --git a/example/table/index.js b/example/table/index.js new file mode 100644 index 0000000..71b87da --- /dev/null +++ b/example/table/index.js @@ -0,0 +1,8 @@ +// This file defines all the styles and elements used for the web components +import '../../src/index'; + +/* Code to reload in the esbuild serve development environment */ +window.addEventListener('load', () => { + // eslint-disable-next-line no-restricted-globals + new EventSource('/esbuild').addEventListener('change', () => location.reload()); +}); diff --git a/package.json b/package.json index df97261..2f9b7cf 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "Javascript Framework", "main": "dist/index.js", "scripts": { - "geojson-dev": "rm -fr dist && install -d dist && NODE_ENV=development node config/esbuild.geojson.mjs", - "build": "rm -fr dist && install -d dist && NODE_ENV=production node config/esbuild.geojson.mjs", + "dev": "NODE_ENV=development node config/esbuild.table.mjs", + "build": "rm -fr dist && install -d dist && NODE_ENV=production node esbuild.table.mjs", "lint": "ESLINT_USE_FLAT_CONFIG=true eslint -c config/eslint.config.mjs --cache --fix ./src/**/*.js", "docs": "jsdoc -c config/jsdoc.config.json" }, diff --git a/src/element/TableColumnElement.js b/src/element/TableColumnElement.js new file mode 100644 index 0000000..ba2cc0e --- /dev/null +++ b/src/element/TableColumnElement.js @@ -0,0 +1,22 @@ +import { LitElement } from "lit"; + +/** + * @class TableColumnElement + * + * This class provides a table column element, for rendering + * a table cell. It also provides properties for the column. + * + * @example + * ID + */ +export class TableColumnElement extends LitElement { + static get localName() { + return 'js-tablecol'; + } + + static get properties() { + return { + name: { type: String, reflect: true }, + }; + } +} diff --git a/src/element/TableBodyElement.js b/src/element/TableElement.js similarity index 68% rename from src/element/TableBodyElement.js rename to src/element/TableElement.js index 4269e88..7bbc780 100644 --- a/src/element/TableBodyElement.js +++ b/src/element/TableElement.js @@ -1,22 +1,24 @@ import { LitElement, html, css } from 'lit'; import { EventType } from '../core/EventType'; import { TableHeadElement } from './TableHeadElement'; +import { TableColumnElement } from './TableColumnElement'; /** - * @class TableBodyElement + * @class TableElement * - * This class provides a table body element. + * This class provides a table element, in which the header, footer + * and columns are rendered. * * @example - * + * */ -export class TableBodyElement extends LitElement { +export class TableElement extends LitElement { #data = null; #head = null; static get localName() { - return 'js-tablebody'; + return 'js-table'; } static get properties() { @@ -48,8 +50,7 @@ export class TableBodyElement extends LitElement { th { text-transform: capitalize; } - .wrap { - max-height: 40px; + .cell { overflow: hidden; } code, pre { @@ -92,6 +93,21 @@ export class TableBodyElement extends LitElement { firstUpdated() { // Set the table header this.#head = this.querySelector(TableHeadElement.localName); + + // Get the table columns + const elements = this.childNodes; + for (let i = 0; i < elements.length; i += 1) { + if (elements[i] instanceof TableColumnElement) { + const name = elements[i].getAttribute('name'); + if (name && name !== '') { + // Append the column to the list + if (this.columns.indexOf(name) === -1) { + this.columns.push(elements[i].getAttribute('name')); + } + // TODO: Set this column as the renderer + } + } + } } render() { @@ -112,25 +128,35 @@ export class TableBodyElement extends LitElement { } #renderColumns(row) { - const columns = []; - + const cells = []; if (row instanceof Object) { Object.keys(row).forEach((key) => { if (this.columns.indexOf(key) === -1) { this.columns.push(key); } - columns.push(html`
${this.#renderCell(row[key])}
`); + cells[this.columns.indexOf(key)] = html`
${this.#renderCell(row[key])}
`; }); } else { this.columns.push('value'); - columns.push(html`${this.#renderCell(row)}`); + cells.push(html`${this.#renderCell(row)}`); } - return columns; + // Any missing columns we fill + for (let i = 0; i < this.columns.length; i += 1) { + if (!cells[i]) { + cells[i] = html``; + } + } + + // Return cells for rendering in a row + return cells; } // eslint-disable-next-line class-methods-use-this #renderCell(cell) { + if (cell === null || cell === undefined || cell === '') { + return html`nil`; + } if (cell instanceof Object) { return html`${JSON.stringify(cell)}`; } diff --git a/src/element/TableHeadElement.js b/src/element/TableHeadElement.js index 49bc939..e899a9e 100644 --- a/src/element/TableHeadElement.js +++ b/src/element/TableHeadElement.js @@ -56,9 +56,9 @@ export class TableHeadElement extends LitElement { #renderColumns(row) { const columns = []; - for (const cell in row) { - columns.push(html`${this.#renderCell(row[cell])}`); - } + Object.keys(row).forEach((key) => { + columns.push(html`${this.#renderCell(row[key])}`); + }); return columns; } diff --git a/src/element/element.js b/src/element/element.js index a94ea2d..57201c2 100644 --- a/src/element/element.js +++ b/src/element/element.js @@ -1,6 +1,4 @@ // Elements -import { TableBodyElement } from './TableBodyElement'; -import { TableHeadElement } from './TableHeadElement'; import { ButtonElement } from './ButtonElement'; import { CloseButtonElement } from './CloseButtonElement'; import { TagElement } from './TagElement'; @@ -12,9 +10,11 @@ import { NavElement } from './NavElement'; import { NavItemElement } from './NavItemElement'; import { NavSpacerElement } from './NavSpacerElement'; +import { TableElement } from './TableElement'; +import { TableHeadElement } from './TableHeadElement'; +import { TableColumnElement } from './TableColumnElement'; + // Define Web Components -customElements.define(TableBodyElement.localName, TableBodyElement); // js-tablebody -customElements.define(TableHeadElement.localName, TableHeadElement); // js-tablehead customElements.define(ButtonElement.localName, ButtonElement); // js-button customElements.define(CloseButtonElement.localName, CloseButtonElement); // js-close customElements.define(TagElement.localName, TagElement); // js-tag @@ -25,3 +25,7 @@ customElements.define(ToastElement.localName, ToastElement); // js-toast customElements.define(NavElement.localName, NavElement); // js-nav customElements.define(NavItemElement.localName, NavItemElement); // js-navitem customElements.define(NavSpacerElement.localName, NavSpacerElement); // js-navspacer + +customElements.define(TableElement.localName, TableElement); // js-table +customElements.define(TableHeadElement.localName, TableHeadElement); // js-tablehead +customElements.define(TableColumnElement.localName, TableColumnElement); // js-tablecol From 971192ea4cf8096a1890c35e2d5e32298d3f5412 Mon Sep 17 00:00:00 2001 From: David Thorpe Date: Fri, 17 Jan 2025 14:11:03 +0100 Subject: [PATCH 2/2] Updated --- README.md | 9 +++++- example/table/index.html | 21 +++++++------- example/table/index.js | 1 + example/table/item.js | 43 ++++++++++++++++++++++++++++ src/element/TableColumnElement.js | 23 ++++++++++++++- src/element/TableElement.js | 47 ++++++++++++++++++++++--------- src/tokens.css | 2 +- 7 files changed, 120 insertions(+), 26 deletions(-) create mode 100644 example/table/item.js diff --git a/README.md b/README.md index ff7b70c..c0b64c4 100644 --- a/README.md +++ b/README.md @@ -9,4 +9,11 @@ npm install Development mode, ```bash -npm run dev \ No newline at end of file +npm run dev +``` + +Build, + +```bash +npm run build +``` diff --git a/example/table/index.html b/example/table/index.html index a84b072..9f38e16 100644 --- a/example/table/index.html +++ b/example/table/index.html @@ -19,22 +19,23 @@ - + Home - - Author - Date - Title - Description - Media + + + + + - - - + + + + + diff --git a/example/table/index.js b/example/table/index.js index 71b87da..49639a2 100644 --- a/example/table/index.js +++ b/example/table/index.js @@ -1,5 +1,6 @@ // This file defines all the styles and elements used for the web components import '../../src/index'; +import './item.js' /* Code to reload in the esbuild serve development environment */ window.addEventListener('load', () => { diff --git a/example/table/item.js b/example/table/item.js new file mode 100644 index 0000000..7f8746f --- /dev/null +++ b/example/table/item.js @@ -0,0 +1,43 @@ +import { html } from 'lit'; +import { TableColumnElement } from '../../src/element/TableColumnElement'; + +export class ItemColumn extends TableColumnElement { + static get localName() { + return 'js-itemcol'; + } + + // eslint-disable-next-line class-methods-use-this + #text(value, key) { + return value instanceof Object ? value[key] : value; + } + + render(value, key) { + const cell = value instanceof Object ? value[key] : value; + switch (key) { + case 'title': + return html` +
+ ${this.#text(value, 'author')} +

${this.#text(value, 'title')}

+ ${this.#text(value, 'pubdate')} +

${this.#text(value, 'desc')}

+ ${value.media ? this.#renderAudio(value.media[0]) : ''} +
+ `; + default: + } + return html`${cell}`; + } + + // eslint-disable-next-line class-methods-use-this + #renderAudio(media) { + return html` + + `; + } +} + +customElements.define(ItemColumn.localName, ItemColumn); // js-itemcol diff --git a/src/element/TableColumnElement.js b/src/element/TableColumnElement.js index ba2cc0e..131b542 100644 --- a/src/element/TableColumnElement.js +++ b/src/element/TableColumnElement.js @@ -1,10 +1,12 @@ -import { LitElement } from "lit"; +import { LitElement, html } from 'lit'; /** * @class TableColumnElement * * This class provides a table column element, for rendering * a table cell. It also provides properties for the column. + * The name property is used to identify the column in the + * table, and the hidden property is used to hide the column. * * @example * ID @@ -17,6 +19,25 @@ export class TableColumnElement extends LitElement { static get properties() { return { name: { type: String, reflect: true }, + hidden: { type: Boolean, reflect: true }, }; } + + /** + * Get the column title. + * + * @returns {string} + */ + get title() { + return this.textContent; + } + + // eslint-disable-next-line class-methods-use-this + render(value, key) { + const cell = value instanceof Object ? value[key] : value; + if (cell instanceof Object) { + return html`${JSON.stringify(cell)}`; + } + return html`${cell}`; + } } diff --git a/src/element/TableElement.js b/src/element/TableElement.js index 7bbc780..3d00632 100644 --- a/src/element/TableElement.js +++ b/src/element/TableElement.js @@ -13,10 +13,18 @@ import { TableColumnElement } from './TableColumnElement'; * */ export class TableElement extends LitElement { + // Data source node #data = null; + // Table header node #head = null; + // Table column renderers + #renderer = {}; + + // Default renderer + #default; + static get localName() { return 'js-table'; } @@ -98,13 +106,19 @@ export class TableElement extends LitElement { const elements = this.childNodes; for (let i = 0; i < elements.length; i += 1) { if (elements[i] instanceof TableColumnElement) { + // Column name and title const name = elements[i].getAttribute('name'); + // If the name is not empty, add it to the column list if (name && name !== '') { // Append the column to the list if (this.columns.indexOf(name) === -1) { this.columns.push(elements[i].getAttribute('name')); } - // TODO: Set this column as the renderer + // Set column renderer + this.#renderer[name] = elements[i]; + } else { + // Set the default renderer + this.#default = elements[i]; } } } @@ -127,14 +141,28 @@ export class TableElement extends LitElement { return rows; } + #rendererFor(key) { + const renderer = this.#renderer[key]; + if (renderer) { + return renderer; + } + return this.#default; + } + + #hidden(key) { + return this.#rendererFor(key).hidden; + } + #renderColumns(row) { const cells = []; if (row instanceof Object) { Object.keys(row).forEach((key) => { - if (this.columns.indexOf(key) === -1) { - this.columns.push(key); + if (!this.#hidden(key)) { + if (this.columns.indexOf(key) === -1) { + this.columns.push(key); + } + cells[this.columns.indexOf(key)] = html`
${this.#renderCell(row, key)}
`; } - cells[this.columns.indexOf(key)] = html`
${this.#renderCell(row[key])}
`; }); } else { this.columns.push('value'); @@ -152,14 +180,7 @@ export class TableElement extends LitElement { return cells; } - // eslint-disable-next-line class-methods-use-this - #renderCell(cell) { - if (cell === null || cell === undefined || cell === '') { - return html`nil`; - } - if (cell instanceof Object) { - return html`${JSON.stringify(cell)}`; - } - return html`${cell}`; + #renderCell(value, key) { + return this.#rendererFor(key).render(value, key); } } diff --git a/src/tokens.css b/src/tokens.css index b189dbc..b01044a 100644 --- a/src/tokens.css +++ b/src/tokens.css @@ -28,7 +28,7 @@ --success-color: #285; --warning-color: #f72; --error-color: #f55; - --light-color: #eee; + --light-color: #f9f9f9; --white-color: #fff; --dark-color: #333; --black-color: #000;