Rendering a table from data
While building my Todo page, I wanted to see if It'd be possible to parse the todo.txt spec and then render a table from it.
However, rendering anything in the DOM from plain JS can get super bulk and messy really quickly, so I thought I'd write a cleaner, reusable utility to render out tables.
I write all my scripts in Typescript, so first lets layout some basic data & types.
We've got a Column generic Type that contains information about our columns.
export interface Column<T> {
header: string;
accessor: keyof T;
renderer?: (value: T[keyof T], row?: T) => string;
}
And then we have a basic footprint of our render function.
export const renderTable = <T>(args: {
columns: Column<T>[];
data: T[];
}): Element => {}
We're going to pass in an array of Objects, with the object type
here being T
.
This function will be used accordingly:
import {renderTable} from "./render-table";
interface ExampleDataInterface {
name: string;
age: number;
}
const exampleData: ExampleDataInterface = [
{name: "John Doe", age: 25, approved: false},
{name: "Jane Doe", age: 26, approved: true}
]
const tableElement = renderTable<ExampleDataInterface>({
columns: [
{header: 'Legal Name', accessor: 'name'},
{header: 'Users Age', accessor: 'age'},
{
header: 'Approved',
accessor: 'approved',
renderer: (value) => value ? 'Yes' : 'No'
}
],
data: exampleData
})
Inside our render function we first want to destructure our arguments and create our basic elements.
// Destructure our args into their own variables
const { columns, data, sortable } = args;
// Create the base table elements
const wrapper = document.createElement('div');
const table = document.createElement('table');
const header = document.createElement('thead');
const body = document.createElement('tbody');
// Connect the base table elements together
table.append(header);
table.append(body);
wrapper.append(table);
We now want to generate our header row where our Column titles will live.
// Create the header row elements and apply a className
const headerRow = document.createElement('tr');
headerRow.className = 'header';
// For our columns, add a th containing the header value
columns.forEach((cell) => {
const columnHeader = document.createElement('th');
columnHeader.innerText = cell.header;
headerRow.append(columnHeader);
});
// Append the header tr to the tables thead
header.append(headerRow);
Next we want to generate our data rows. We can do this by iterating
over each item in our data
array and then iterating over
each column
data.forEach((cell) => {
// Create a new row for this data
const row = document.createElement('tr');
// For each column in the table
columns.forEach((parentColumn) => {
// Create a new cell
const newCell = document.createElement('td');
// Set the cells inner text to either the output of the provided renderer, or the raw text
newCell.innerText = `${
parentColumn.renderer
? parentColumn.renderer(cell[parentColumn.accessor], cell)
: cell[parentColumn.accessor]
}`;
// Append the cell to the newly created row
row.append(newCell);
});
// Append the row to the tables tbody
body.append(row);
});