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/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/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..9f38e16 --- /dev/null +++ b/example/table/index.html @@ -0,0 +1,42 @@ + + + + + + + Nanoradio + + + + + + + + + Home + + + + + + + + + + + + + + + + + + + diff --git a/example/table/index.js b/example/table/index.js new file mode 100644 index 0000000..49639a2 --- /dev/null +++ b/example/table/index.js @@ -0,0 +1,9 @@ +// 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', () => { + // eslint-disable-next-line no-restricted-globals + new EventSource('/esbuild').addEventListener('change', () => location.reload()); +}); 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/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..131b542 --- /dev/null +++ b/src/element/TableColumnElement.js @@ -0,0 +1,43 @@ +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 + */ +export class TableColumnElement extends LitElement { + static get localName() { + return 'js-tablecol'; + } + + 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/TableBodyElement.js b/src/element/TableElement.js similarity index 56% rename from src/element/TableBodyElement.js rename to src/element/TableElement.js index 4269e88..3d00632 100644 --- a/src/element/TableBodyElement.js +++ b/src/element/TableElement.js @@ -1,22 +1,32 @@ 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 source node #data = null; + // Table header node #head = null; + // Table column renderers + #renderer = {}; + + // Default renderer + #default; + static get localName() { - return 'js-tablebody'; + return 'js-table'; } static get properties() { @@ -48,8 +58,7 @@ export class TableBodyElement extends LitElement { th { text-transform: capitalize; } - .wrap { - max-height: 40px; + .cell { overflow: hidden; } code, pre { @@ -92,6 +101,27 @@ 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) { + // 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')); + } + // Set column renderer + this.#renderer[name] = elements[i]; + } else { + // Set the default renderer + this.#default = elements[i]; + } + } + } } render() { @@ -111,29 +141,46 @@ export class TableBodyElement extends LitElement { return rows; } - #renderColumns(row) { - const columns = []; + #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)}
`; } - columns.push(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 instanceof Object) { - return html`${JSON.stringify(cell)}`; - } - return html`${cell}`; + #renderCell(value, key) { + return this.#rendererFor(key).render(value, key); } } 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 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;