Skip to content

Updating table support #20

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jspm_packages/
.next
.nuxt
dist
build
.cache/
.vuepress/dist
.serverless/
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"eslint.experimental.useFlatConfig": true,
"eslint.useFlatConfig": true,
"eslint.options": { "overrideConfigFile": "./config/eslint.config.mjs" },
}
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@ npm install
Development mode,

```bash
npm run dev
npm run dev
```

Build,

```bash
npm run build
```
40 changes: 40 additions & 0 deletions config/esbuild.table.mjs
Original file line number Diff line number Diff line change
@@ -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();
}
5 changes: 0 additions & 5 deletions example/esbuild.js

This file was deleted.

42 changes: 42 additions & 0 deletions example/table/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nanoradio</title>
<script type="module" src="index.js" defer></script>
<link href="index.css" rel="stylesheet">
<style type="text/css">
.content-scroll {
overflow-y: scroll;
}
body {
overflow: hidden;
}
</style>
</head>

<body>
<js-canvas>
<js-nav>
<js-navitem>Home</js-navitem>
</js-nav>
<js-content class="content-scroll">
<js-table id="items-table" data="#items-data">
<!-- Table columns -->
<js-itemcol name="title"></js-itemcol>

<!-- Hide other columns by default -->
<js-tablecol hidden></js-tablecol>
</js-table>
</js-content>
<js-nav>
<!-- data source and model for items -->
<js-provider id="items-provider" path="http://cm5.lan:8000/feed/v1/document?limit=50"></js-provider>
<js-array id="items-data" provider="#items-provider" select="body"></js-array>
</js-nav>
</js-canvas>
</body>

</html>
9 changes: 9 additions & 0 deletions example/table/index.js
Original file line number Diff line number Diff line change
@@ -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());
});
43 changes: 43 additions & 0 deletions example/table/item.js
Original file line number Diff line number Diff line change
@@ -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`
<div style="padding: 5px;">
<js-tag size="small">${this.#text(value, 'author')}</js-tag>
<h4>${this.#text(value, 'title')}</h4>
<small><strong>${this.#text(value, 'pubdate')}</strong></small>
<p>${this.#text(value, 'desc')}</p>
${value.media ? this.#renderAudio(value.media[0]) : ''}
</div>
`;
default:
}
return html`${cell}`;
}

// eslint-disable-next-line class-methods-use-this
#renderAudio(media) {
return html`
<audio controls>
<source src="${media.url}" type="${media.type}">
Your browser does not support the audio element.
</audio>
`;
}
}

customElements.define(ItemColumn.localName, ItemColumn); // js-itemcol
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
43 changes: 43 additions & 0 deletions src/element/TableColumnElement.js
Original file line number Diff line number Diff line change
@@ -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
* <js-tablecol name="id">ID</js-tablecol>
*/
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`<code>${JSON.stringify(cell)}</code>`;
}
return html`${cell}`;
}
}
87 changes: 67 additions & 20 deletions src/element/TableBodyElement.js → src/element/TableElement.js
Original file line number Diff line number Diff line change
@@ -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
* <js-tablebody data="#data-source-id"></js-tablebody>
* <js-table data="#data-source-id"><!-- .... --></js-table>
*/
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() {
Expand Down Expand Up @@ -48,8 +58,7 @@ export class TableBodyElement extends LitElement {
th {
text-transform: capitalize;
}
.wrap {
max-height: 40px;
.cell {
overflow: hidden;
}
code, pre {
Expand Down Expand Up @@ -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() {
Expand All @@ -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`<td><div class="cell">${this.#renderCell(row, key)}</div></td>`;
}
columns.push(html`<td><div class="wrap">${this.#renderCell(row[key])}</div></td>`);
});
} else {
this.columns.push('value');
columns.push(html`<td>${this.#renderCell(row)}</td>`);
cells.push(html`<td>${this.#renderCell(row)}</td>`);
}

return columns;
// Any missing columns we fill
for (let i = 0; i < this.columns.length; i += 1) {
if (!cells[i]) {
cells[i] = html`<td></td>`;
}
}

// 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`<code>${JSON.stringify(cell)}</code>`;
}
return html`${cell}`;
#renderCell(value, key) {
return this.#rendererFor(key).render(value, key);
}
}
6 changes: 3 additions & 3 deletions src/element/TableHeadElement.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export class TableHeadElement extends LitElement {

#renderColumns(row) {
const columns = [];
for (const cell in row) {
columns.push(html`<th>${this.#renderCell(row[cell])}</th>`);
}
Object.keys(row).forEach((key) => {
columns.push(html`<th>${this.#renderCell(row[key])}</th>`);
});
return columns;
}

Expand Down
Loading