Multi-Sort Plugin
This plugin is not needed if you only want single-column sorting—just set sortable: true on the columns. This is built in to the core. Multi-Sort extends that functionality by enabling sorting by multiple columns at once—hold Shift and click additional column headers to build up a sort stack. Priority badges show the sort order, so users always know which column takes precedence.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/multi-sort';Basic Usage
Section titled “Basic Usage”Mark columns as sortable: true and enable the feature. Users Shift+click to add columns to the sort stack, and regular click to reset to single-column sort.
import { queryGrid } from '@toolbox-web/grid';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: [ { field: 'name', header: 'Name', sortable: true }, { field: 'department', header: 'Department', sortable: true }, { field: 'salary', header: 'Salary', type: 'number', sortable: true }, { field: 'startDate', header: 'Start Date', type: 'date', sortable: true }, ], features: { multiSort: { maxSortColumns: 3, showSortIndex: true, }, },};
// Listen for sort changesgrid.on('sort-change', ({ sortModel }) => { console.log('Active sorts:', sortModel);});import '@toolbox-web/grid-react/features/multi-sort';import { DataGrid } from '@toolbox-web/grid-react';
function SortableGrid({ data }) { return ( <DataGrid rows={data} columns={[ { field: 'name', header: 'Name', sortable: true }, { field: 'department', header: 'Department', sortable: true }, { field: 'salary', header: 'Salary', type: 'number', sortable: true }, ]} multiSort={{ maxSortColumns: 3 }} onSortChange={(detail) => console.log('Sort changed:', detail.sortModel)} /> );}<script setup>import '@toolbox-web/grid-vue/features/multi-sort';import { TbwGrid, TbwGridColumn } from '@toolbox-web/grid-vue';
const data = [ { name: 'Alice', department: 'Engineering', salary: 95000 }, { name: 'Bob', department: 'Marketing', salary: 75000 },];
const onSortChange = (e) => { console.log('Sort changed:', e.detail.sortModel);};</script>
<template> <TbwGrid :rows="data" :multi-sort="{ maxSortColumns: 3 }" @sort-change="onSortChange" > <TbwGridColumn field="name" header="Name" sortable /> <TbwGridColumn field="department" header="Department" sortable /> <TbwGridColumn field="salary" header="Salary" type="number" sortable /> </TbwGrid></template>// Feature import - enables the [multiSort] inputimport { GridMultiSortDirective } from '@toolbox-web/grid-angular/features/multi-sort';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig } from '@toolbox-web/grid';
@Component({ selector: 'app-sortable-grid', imports: [Grid, GridMultiSortDirective], template: ` <tbw-grid [rows]="rows" [columns]="columns" [multiSort]="{ maxSortColumns: 3 }" (sort-change)="onSortChange($event)" style="height: 400px; display: block;"> </tbw-grid> `,})export class SortableGridComponent { rows = [ { name: 'Alice', department: 'Engineering', salary: 95000 }, { name: 'Bob', department: 'Marketing', salary: 75000 }, ];
columns: ColumnConfig[] = [ { field: 'name', header: 'Name', sortable: true }, { field: 'department', header: 'Department', sortable: true }, { field: 'salary', header: 'Salary', type: 'number', sortable: true }, ];
onSortChange(e: CustomEvent) { console.log('Sort changed:', e.detail.sortModel); }}Use the controls to explore different multi-sort configurations: adjust the maximum number of sort levels, toggle priority badges, or apply an initial sort.
<tbw-grid style="height: 350px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/multi-sort';
const sampleData = [ { id: 1, name: 'Alice', department: 'Engineering', salary: 95000, joined: '2023-01-15' }, { id: 2, name: 'Bob', department: 'Marketing', salary: 75000, joined: '2022-06-20' }, { id: 3, name: 'Carol', department: 'Engineering', salary: 105000, joined: '2021-03-10' }, { id: 4, name: 'Dan', department: 'Engineering', salary: 85000, joined: '2023-08-05' }, { id: 5, name: 'Eve', department: 'Marketing', salary: 72000, joined: '2024-01-12' }, { id: 6, name: 'Frank', department: 'Sales', salary: 82000, joined: '2022-11-30' },];const columns = [ { field: 'id', header: 'ID', type: 'number', sortable: true }, { field: 'name', header: 'Name', sortable: true }, { field: 'department', header: 'Department', sortable: true }, { field: 'salary', header: 'Salary', type: 'number', sortable: true }, { field: 'joined', header: 'Joined', type: 'date', sortable: true },];
const grid = queryGrid('tbw-grid');
function rebuild(maxSortColumns = 3, showSortIndex = true, initialSort = false) { grid.gridConfig = { columns, features: { multiSort: { maxSortColumns, showSortIndex } }, }; grid.rows = sampleData;
if (initialSort) { grid.ready().then(() => { grid.getPluginByName('multiSort')?.setSortModel([ { field: 'department', direction: 'asc' }, { field: 'salary', direction: 'desc' }, ]); }); }}
rebuild();Hold Shift and click column headers to add columns to the sort stack. Set Max sort columns to 1 for single-column-only sorting.
Configuration Options
Section titled “Configuration Options”See MultiSortConfig for the full list of options and their defaults.
Initial Sort
Section titled “Initial Sort”Use setSortModel() to set a pre-configured sort order after the grid initializes:
const plugin = grid.getPluginByName('multiSort');plugin.setSortModel([ { field: 'department', direction: 'asc' }, { field: 'salary', direction: 'desc' },]);Events
Section titled “Events”Hold Shift and click column headers to add sort levels.
The grid fires a sort-change event whenever the sort model changes:
grid.on('sort-change', ({ sortModel }) => { console.log('Sort model:', sortModel); // [{ field: 'name', direction: 'asc' }, { field: 'salary', direction: 'desc' }]});Programmatic API
Section titled “Programmatic API”See MultiSortPlugin for the full list of methods.
const plugin = grid.getPluginByName('multiSort');
plugin.setSortModel([{ field: 'department', direction: 'asc' }]);plugin.getSortModel(); // SortModel[]plugin.clearSort();plugin.getSortDirection('salary'); // 'asc' | 'desc' | undefinedplugin.getSortIndex('salary'); // number | undefinedKeyboard Shortcuts
Section titled “Keyboard Shortcuts”| Shortcut | Action |
|---|---|
Click header | Sort by column (clears other sorts) |
Shift + Click | Add column to multi-sort |
Styling
Section titled “Styling”The grid uses Light DOM, so standard CSS selectors work for styling sort indicators:
/* Sort indicator icon */tbw-grid .sort-indicator { margin-left: 4px;}
/* Sort priority badge */tbw-grid .sort-index { background: var(--tbw-multi-sort-badge-bg, var(--tbw-color-panel-bg)); color: var(--tbw-multi-sort-badge-color, var(--tbw-color-fg)); width: var(--tbw-multi-sort-badge-size, 1em); height: var(--tbw-multi-sort-badge-size, 1em);}Row Insertion with Active Sort
Section titled “Row Insertion with Active Sort”When multi-sort is active, assigning rows automatically re-sorts the data — so a
newly inserted row will jump to its sorted position. If you want the row to stay
exactly where you placed it (e.g., after a user clicks “Add Row”), use insertRow():
grid.insertRow(3, newRow); // stays at visible index 3, auto-animatesThe row is also added to source data, so the next full grid.rows = freshData
assignment re-sorts normally. See the
API Reference → Insert & Remove Rows
for full details.
Custom Sort Logic
Section titled “Custom Sort Logic”MultiSortPlugin owns the multi-column sort path and does not consult
gridConfig.sortHandler — that property is only honored by the single-column
sort path used when MultiSortPlugin is absent. To customize sort behavior
when multi-sort is enabled, use column.sortComparator on the columns
that need it. sortComparator is honored by every sort code path in the grid
(core, multi-sort, tree, server-side sortMode: 'local').
grid.gridConfig = { columns: [ { field: 'name', header: 'Name', sortable: true }, { field: 'salary', header: 'Salary', type: 'number', sortable: true, // Custom comparator: pin negative values (debts) to the end regardless of direction sortComparator: (a, b) => { if (a < 0 && b >= 0) return 1; if (b < 0 && a >= 0) return -1; return a - b; }, }, ], features: { multiSort: true },};Comparator signature: (a, b, rowA, rowB) => number — return < 0 to put
a first, > 0 for b, 0 for equal. Direction (asc/desc) is applied
automatically by the sort engine.
Server-Side Sorting
Section titled “Server-Side Sorting”For backends that own sort ordering, use
ServerSidePlugin — it ships the current sortModel (all
columns, in priority order) to your getRows handler so the backend can
return rows in the requested order. This is the right pattern for paginated /
infinite-scroll datasets where client-side sort isn’t viable.
import { ServerSidePlugin } from '@toolbox-web/grid/plugins/server-side';import { MultiSortPlugin } from '@toolbox-web/grid/plugins/multi-sort';
grid.gridConfig = { columns: [ { field: 'name', header: 'Name', sortable: true }, { field: 'salary', header: 'Salary', sortable: true }, ], plugins: [ new MultiSortPlugin(), new ServerSidePlugin({ dataSource: { getRows: async ({ startNode, endNode, sortModel }) => { // sortModel: [{ field: 'name', direction: 'asc' }, { field: 'salary', direction: 'desc' }] const sortQuery = sortModel .map((s) => `${s.field}:${s.direction}`) .join(','); const response = await fetch( `/api/data?from=${startNode}&to=${endNode}&sort=${sortQuery}`, ); const { rows, totalNodeCount } = await response.json(); return { rows, totalNodeCount }; }, }, }), ],};sortComparator is synchronous, so use it only for custom client-side
comparisons. If sorting needs to be owned by your backend, use
ServerSidePlugin; if you only need custom single-column sort behavior, use
sortHandler instead.
See Also
Section titled “See Also”- Filtering — Filter rows by column values
- Server-Side Data — Block-based virtual scrolling for large datasets
- Plugins Overview — Plugin compatibility and combinations