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;