Skip to content

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.

import '@toolbox-web/grid/features/multi-sort';

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 changes
grid.on('sort-change', ({ sortModel }) => {
console.log('Active sorts:', 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.

Max sort columns Maximum number of sort levels
Show sort index Display sort priority number on headers
Apply initial sort Pre-sort by Department ↑ then Salary ↓

Hold Shift and click column headers to add columns to the sort stack. Set Max sort columns to 1 for single-column-only sorting.

See MultiSortConfig for the full list of options and their defaults.

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' },
]);

Hold Shift and click column headers to add sort levels.

Event Log:

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

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' | undefined
plugin.getSortIndex('salary'); // number | undefined
ShortcutAction
Click headerSort by column (clears other sorts)
Shift + ClickAdd column to multi-sort

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

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-animates

The 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.

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.

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.

AI assistants: For complete API documentation, implementation guides, and code examples for this library, see https://raw.githubusercontent.com/OysteinAmundsen/toolbox/main/llms-full.txt