All writing

Mastering dynamic data tables with the IColumn class

A small utility class that turns table columns into reusable, dynamic, sortable building blocks.

Building scalable and reusable table components is a common challenge. Every table wants slightly different headers, cells, props, and sorting rules — and before long the component that was supposed to save you time has grown a new prop for each new screen.

The IColumn class is my attempt at a different approach: describe each column as an object, let the table component render it. The configuration moves out of the table and into plain data, which is much easier to generate, reuse, and test.

Key features

  • Customizable headers and rows. Any Vue component can render a header or a cell, with props and events wired in.
  • Flexible props and events. Pass a static object, or a function that receives the row and returns one.
  • Built-in sorting. Strings and numbers work out of the box; supply a sorter for anything else.
  • Special column types. default for normal data, actions for row buttons at the end, selection for checkboxes at the start.

Options

PropertyTypeDescription
titlestring | functionColumn header text
rowKeystring | functionData property key for the row
propsObject | functionComponent props (static or dynamic)
rowComponentObject | undefinedCell rendering component
headerComponentObject | undefinedHeader rendering component
type'default' | 'actions' | 'selection'Column behavior type
disabledboolean | functionDisables column functionality
sortablebooleanEnables column sorting
sorterfunctionCustom comparison function
sortValueObject | undefinedTracks sorting state

The constructor

The constructor pulls the options apart, fills in sensible defaults, and normalizes props and events so the table never has to care whether they were static or functions.

export class IColumn {
  constructor(options) {
    const {
      title,
      rowKey,
      type = 'default',
      sortable = false,
      sorter = undefined,
      headerComponent = undefined,
      rowComponent = undefined,
      disabled = false,
      ...extras
    } = options;

    this.title = title;
    this.rowKey = rowKey;
    this.type = type;
    this.sortable = sortable;
    this.sorter =
      sorter || ((row1, row2) => row1[this.rowKey] > row2[this.rowKey]);
    this.headerComponent = this.processComponent(headerComponent);
    this.rowComponent = this.processComponent(rowComponent);
    this.disabled = disabled;
    this.extras = extras;
  }

  processComponent(component) {
    if (!component) return undefined;
    return {
      is: component.is || '',
      props:
        typeof component.props === 'function'
          ? component.props
          : () => component.props,
      events:
        typeof component.events === 'function'
          ? component.events
          : () => component.events,
    };
  }
}

Why bother?

Because column definitions become data, you get three things for free:

  • Dynamic behavior per row without branching inside the table.
  • A single place to reason about rendering and interactivity.
  • Cross-cutting features — sorting, actions, selection — that slot in once and work everywhere.

That's the whole idea: treat each column as a structured, dynamic, reusable object, and let the table stay boring.

Originally published on LinkedIn.