Sortable Grid
Grid with sorting enabled, multi-column sort, and custom comparators.
This example demonstrates how to enable sorting on columns, handle single and multi-column sorting, use custom comparators, and respond to sort events.
Enable Column Sorting
Add sortable: true to column definitions to enable sorting:
import { createGrid } from '@zengrid/core';import type { Column, GridOptions } from '@zengrid/core';
const columns: Column[] = [ { id: 'id', header: 'ID', width: 60, sortable: true, // Enable sorting }, { id: 'name', header: 'Name', width: 200, sortable: true, }, { id: 'email', header: 'Email', width: 250, sortable: true, }, { id: 'role', header: 'Role', width: 150, sortable: true, }, { id: 'status', header: 'Status', width: 120, sortable: true, },];
const grid = createGrid(container, { columns, rowCount: 100, rowHeight: 40, data: (rowIndex, colId) => { // Data callback implementation return `Cell ${rowIndex}-${colId}`; },});Clicking a sortable column header cycles through ascending, descending, and no sort states.
Single-Column Sort
Sort by a single column programmatically:
// Sort by name ascendinggrid.sort('name', 'asc');
// Sort by name descendinggrid.sort('name', 'desc');
// Clear sortgrid.sort('name', null);
// Get current sort stateconst sortState = grid.getSortState();console.log('Current sort:', sortState);// Output: [{ colId: 'name', direction: 'asc' }]Multi-Column Sort
Hold Shift and click column headers for multi-column sorting, or use the API:
import type { SortState } from '@zengrid/core';
// Sort by role (ascending), then by name (descending)const sortState: SortState = [ { colId: 'role', direction: 'asc' }, { colId: 'name', direction: 'desc' },];
grid.setSortState(sortState);
// Clear all sortinggrid.setSortState([]);Multi-column sorting applies sorts in order. In the example above, rows are first sorted by role, then within each role group, sorted by name descending.
Custom Comparators
Define custom sorting logic for specific columns:
import type { Column } from '@zengrid/core';
const columns: Column[] = [ { id: 'name', header: 'Name', width: 200, sortable: true, comparator: (a: string, b: string) => { // Case-insensitive sort return a.toLowerCase().localeCompare(b.toLowerCase()); }, }, { id: 'priority', header: 'Priority', width: 120, sortable: true, comparator: (a: string, b: string) => { // Custom priority order const priorities = { High: 3, Medium: 2, Low: 1 }; return priorities[a] - priorities[b]; }, }, { id: 'date', header: 'Date', width: 150, sortable: true, comparator: (a: string, b: string) => { // Date comparison return new Date(a).getTime() - new Date(b).getTime(); }, },];The comparator function receives two cell values and should return a negative number if a < b, positive if a > b, or 0 if equal.
Sort Icons Customization
Customize sort indicator icons using CSS:
/* Default sort icon (unsorted) */.zengrid-header-sort-icon::before { content: '⇅'; opacity: 0.3;}
/* Ascending sort icon */.zengrid-header-sort-icon.asc::before { content: '↑'; opacity: 1; color: #0066cc;}
/* Descending sort icon */.zengrid-header-sort-icon.desc::before { content: '↓'; opacity: 1; color: #0066cc;}
/* Multi-sort index indicator */.zengrid-header-sort-index { font-size: 10px; margin-left: 2px; color: #666;}Sort Events
Listen to sort events to update external state or analytics:
// Listen for sort state changesgrid.on('sort:changed', (event) => { console.log('Sort changed:', event.sortState);
// Update URL or external state const params = new URLSearchParams(); event.sortState.forEach((sort, index) => { params.append(`sort${index}`, `${sort.colId}:${sort.direction}`); });
// Update browser URL without reload window.history.replaceState( null, '', `?${params.toString()}` );});
// Listen for column header clicksgrid.on('header:click', (event) => { console.log('Header clicked:', event.colId);});Complete Sortable Example
import { createGrid } from '@zengrid/core';import type { Column, GridOptions } from '@zengrid/core';
// Sample dataconst data = [ { id: 1, name: 'Alice', role: 'Admin', status: 'Active', priority: 'High' }, { id: 2, name: 'Bob', role: 'Editor', status: 'Inactive', priority: 'Medium' }, { id: 3, name: 'Charlie', role: 'Viewer', status: 'Active', priority: 'Low' }, // ... more rows];
const columns: Column[] = [ { id: 'id', header: 'ID', width: 60, sortable: true }, { id: 'name', header: 'Name', width: 200, sortable: true, comparator: (a, b) => a.toLowerCase().localeCompare(b.toLowerCase()), }, { id: 'role', header: 'Role', width: 150, sortable: true }, { id: 'status', header: 'Status', width: 120, sortable: true }, { id: 'priority', header: 'Priority', width: 120, sortable: true, comparator: (a, b) => { const priorities = { High: 3, Medium: 2, Low: 1 }; return priorities[a] - priorities[b]; }, },];
const grid = createGrid(container, { columns, rowCount: data.length, rowHeight: 40, data: (rowIndex, colId) => { return data[rowIndex]?.[colId] ?? ''; },});
// Enable multi-sort by defaultgrid.setSortState([ { colId: 'role', direction: 'asc' }, { colId: 'name', direction: 'asc' },]);
// Log sort changesgrid.on('sort:changed', (event) => { console.log('Sort state:', event.sortState);});When using custom comparators, ensure they return consistent results. Inconsistent comparators can lead to unpredictable sort order.
Performance Considerations
- Sorting is performed on the full dataset, not just visible rows
- For large datasets (>100k rows), consider server-side sorting
- Custom comparators should be optimized for performance
- ZenGrid uses Timsort algorithm for stable, efficient sorting
Use grid.getStats() to monitor sorting performance. The stats object includes timing information for sort operations.
Next Steps
- Add filtering to combine with sorting
- Enable editing on sorted grids
- Learn about multi-column sorting strategies