1. Plugins
  2. The Plugin System

Plugins

The Plugin System

Svelte Headless Table is designed with extensibility in mind. Its complex features are powered by an extensive suite of plugins.

  • addSortBy
  • addColumnFilters
  • addTableFilter
  • addColumnOrder
  • addHiddenColumns
  • addPagination
  • addSubRows
  • addGroupBy
  • addExpandedRows
  • addSelectedRows
  • addResizedColumns
  • addGridLayout

Defining plugins

Svelte Headless Table treats each plugin as an extension to its core. Every plugin optionally defines transformations on the rows and columns of the table, and extends the functionality of column definitions, rows, and cells.

For this example, we extend a basic table with addSortBy and addColumnOrder.

const table = createTable(data, {
  sort: addSortBy({ disableMultiSort: true }),
  colOrder: addColumnOrder(),
});

Plugins are configurable via function arguments.

INFO

sort and colOrder are just names to identify the plugins – they can be any name you prefer as long as they remain consistent. This lets you add multiple plugins to a table without any naming conflicts.

The order in which you define plugins matters – the order of object keys is predictable and plugins are evaluated from first to last.

Configuring columns

Plugins optionally define additional column options to configure column behavior. Column options are specified under the optional plugins property in the column definition.

const columns = table.createColumns([
  table.column({
    header: 'Name',
    accessor: 'name',
    plugins: {
      sort: { invert: true },
    },
  }),
  table.column({
    header: 'Age',
    accessor: 'age',
  }),
]);
INFO

The column options of a plugin are specified under its given name. Because we named our sorting plugin sort, its column options are defined under plugins.sort.

Connecting plugins to markup

Plugins extend the view model with prop sets that provide additional props to table components via .props(). Props can include state and event handlers.

.props() returns a Readable store, so pass it into Subscribe to access its value.

<table {...$tableAttrs}>
  <thead>
    {#each $headerRows as headerRow (headerRow.id)}
      <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs>
        <tr {...rowAttrs}>
          {#each headerRow.cells as cell (cell.id)}
            <Subscribe
              attrs={cell.attrs()} let:attrs
              props={cell.props()} let:props
            >
              <th {...attrs} on:click={props.sort.toggle}>
                <Render of={cell.render()} />
                {#if props.sort.order === 'asc'}
                  ⬇️
                {:else if props.sort.order === 'desc'}
                  ⬆️
                {/if}
              </th>
            </Subscribe>
          {/each}
        </tr>
      </Subscribe>
    {/each}
  </thead>
  <tbody {...$tableBodyAttrs}>
    {#each $rows as row (row.id)}
      <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
        <tr {...rowAttrs}>
          {#each row.cells as cell (cell.id)}
            <Subscribe attrs={cell.attrs()} let:attrs>
              <td {...attrs}>
                <Render of={cell.render()} />
              </td>
            </Subscribe>
          {/each}
        </tr>
      </Subscribe>
    {/each}
  </tbody>
</table>
INFO

The view model extensions of a plugin are specified under its given name.

Each plugin may define different extensions for different table components, including HeaderRows, HeaderCells, BodyRows, BodyCells, and more.

Controlling plugin state

All plugins are driven by Svelte stores. Therefore, Svelte Headless Table plugins are programmatically controllable by default.

Plugins expose state via the pluginStates property on the view model. Thanks to Svelte stores, state can be easily subscribed to and modified!

const {
  headerRows,
  rows,
  tableAttrs,
  tableBodyAttrs,
  pluginStates,
} = table.createViewModel(columns);
const { columnIdOrder } = pluginStates.colOrder;
$columnIdOrder = ['age', 'name'];
INFO

The state of a plugin is specified under its given name.

Final result

Putting it all together, we have a simple table with extended functionality!

Explore this example in the REPL.

Age Name
21 Ada Lovelace
52 Barbara Liskov
38 Richard Hamming
<script>
  const data = readable([
    { name: 'Ada Lovelace', age: 21 },
    { name: 'Barbara Liskov', age: 52 },
    { name: 'Richard Hamming', age: 38 },
  ]);

  const table = createTable(data, {
    sort: addSortBy({ disableMultiSort: true }),
    colOrder: addColumnOrder(),
  });

  const columns = table.createColumns([
    table.column({
      header: 'Name',
      accessor: 'name',
      plugins: {
        sort: { invert: true },
      },
    }),
    table.column({
      header: 'Age',
      accessor: 'age',
    }),
  ]);

  const {
    headerRows,
    rows,
    tableAttrs,
    tableBodyAttrs,
    pluginStates,
  } = table.createViewModel(columns);
  const { columnIdOrder } = pluginStates.colOrder;
  $columnIdOrder = ['age', 'name'];
</script>

<table {...$tableAttrs}>
  <thead>
    {#each $headerRows as headerRow (headerRow.id)}
      <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs>
        <tr {...rowAttrs}>
          {#each headerRow.cells as cell (cell.id)}
            <Subscribe
              attrs={cell.attrs()} let:attrs
              props={cell.props()} let:props
            >
              <th {...attrs} on:click={props.sort.toggle}>
                <Render of={cell.render()} />
                {#if props.sort.order === 'asc'}
                  ⬇️
                {:else if props.sort.order === 'desc'}
                  ⬆️
                {/if}
              </th>
            </Subscribe>
          {/each}
        </tr>
      </Subscribe>
    {/each}
  </thead>
  <tbody {...$tableBodyAttrs}>
    {#each $rows as row (row.id)}
      <Subscribe rowAttrs={row.attrs()} let:rowAttrs>
        <tr>
          {#each row.cells as cell (cell.id)}
            <Subscribe attrs={cell.attrs()} let:attrs>
              <td {...attrs}>
                <Render of={cell.render()} />
              </td>
            </Subscribe>
          {/each}
        </tr>
      </Subscribe>
    {/each}
  </tbody>
</table>

<style>
  table {
    font-family: sans-serif;
    border-spacing: 0;
    border-top: 1px solid black;
    border-left: 1px solid black;
  }

  th,
  td {
    margin: 0;
    padding: 0.5rem;
    border-bottom: 1px solid black;
    border-right: 1px solid black;
  }
</style>