pfy.ch

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);
});

© 2024 Pfych