Column Virtualization Plugin
The Column Virtualization plugin improves performance for grids with many columns.
Installation
Section titled “Installation”import '@toolbox-web/grid/features/column-virtualization';Basic Usage
Section titled “Basic Usage”import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/column-virtualization';
const grid = queryGrid('tbw-grid');grid.gridConfig = { columns: generateManyColumns(100), // Your column generator fitMode: 'fixed', // Required for column virtualization features: { columnVirtualization: true },};grid.rows = data;import '@toolbox-web/grid-react/features/column-virtualization';import { DataGrid } from '@toolbox-web/grid-react';import type { GridConfig } from '@toolbox-web/grid-react';
const gridConfig: GridConfig = { columns: generateManyColumns(100), fitMode: 'fixed', features: { columnVirtualization: true },};
function WideGrid({ data }) { return <DataGrid rows={data} gridConfig={gridConfig} style={{ height: '400px' }} />;}<script setup>import '@toolbox-web/grid-vue/features/column-virtualization';import { TbwGrid } from '@toolbox-web/grid-vue';import type { GridConfig } from '@toolbox-web/grid-vue';
const data = [{ col0: 'Row 1', col1: 'Value 1' }, { col0: 'Row 2', col1: 'Value 2' }];
const gridConfig: GridConfig = { columns: Array.from({ length: 100 }, (_, i) => ({ field: `col${i}`, header: `Column ${i + 1}`, width: 120, })), fitMode: 'fixed', features: { columnVirtualization: true },};</script>
<template> <TbwGrid :rows="data" :grid-config="gridConfig" style="height: 400px" /></template>Define everything inside gridConfig.features, exactly like the other frameworks. The side-effect import registers the feature so the adapter can bridge it.
import '@toolbox-web/grid-angular/features/column-virtualization';import { Component } from '@angular/core';import { Grid } from '@toolbox-web/grid-angular';import type { ColumnConfig, GridConfig } from '@toolbox-web/grid-angular';
@Component({ selector: 'app-wide-grid', imports: [Grid], template: ` <tbw-grid [rows]="rows" [gridConfig]="gridConfig" style="height: 400px; display: block;"> </tbw-grid> `,})export class WideGridComponent { rows = [...];
gridConfig: GridConfig = { columns: this.generateManyColumns(100), fitMode: 'fixed', features: { columnVirtualization: true }, };
generateManyColumns(count: number): ColumnConfig[] { return Array.from({ length: count }, (_, i) => ({ field: `col${i}`, header: `Column ${i + 1}`, width: 120, })); }}Default Column Virtualization
Section titled “Default Column Virtualization”<tbw-grid style="height: 400px;"></tbw-grid>import '@toolbox-web/grid';import { queryGrid } from '@toolbox-web/grid';import '@toolbox-web/grid/features/column-virtualization';
const grid = queryGrid('tbw-grid');
function generateColumns(count: number) { const columns = [{ field: 'id', header: 'ID', type: 'number', width: 60 }]; for (let i = 1; i < count; i++) { columns.push({ field: `col${i}`, header: `Column ${i}`, type: 'number', width: 100 }); } return columns;}
function generateRows(rowCount: number, colCount: number) { const rows = []; for (let r = 0; r < rowCount; r++) { const row: Record<string, number> = { id: r + 1 }; for (let c = 1; c < colCount; c++) { row[`col${c}`] = Math.floor(Math.random() * 1000); } rows.push(row); } return rows;}
function rebuild(columnCount = 50, threshold = 30, overscan = 3, autoEnable = true) { const columns = generateColumns(columnCount); const rows = generateRows(100, columnCount); grid.gridConfig = { columns, fitMode: 'fixed', features: { columnVirtualization: { autoEnable, threshold, overscan } }, }; grid.rows = rows;}
rebuild();Use the Column Count slider to add up to 1000 columns, and toggle Auto Enable to compare performance with and without virtualization.
Configuration Options
Section titled “Configuration Options”See ColumnVirtualizationConfig for the full list
of options and defaults. By default the plugin activates itself once a grid exceeds 30 columns;
set autoEnable: false to control activation yourself.
Requirements
Section titled “Requirements”fitMode: 'fixed'— the plugin is designed for fixed-width column layouts- Columns should have explicit widths (columns without widths default to 100px)
Pinned columns and utility columns (selection checkbox, master-detail expander, row-drag handle) are never virtualized away: they are always rendered, so pinned ones stay visible while you scroll horizontally. Unpinned utility columns scroll out of view like any unpinned column; pin them to keep them on screen.
Export, Clipboard and Column APIs
Section titled “Export, Clipboard and Column APIs”Virtualization only limits which columns are rendered. grid.columns and every column API
(setColumnVisible(), pinning, reordering, …) keep working on the full column list, including the
columns scrolled out of view. The Export and
Clipboard plugins therefore include every column when you export or copy
without a cell selection, or pass columns explicitly.
Selection and Focus
Section titled “Selection and Focus”Selection ranges, the selection anchor, and the col of every
CellRange index the full list of visible columns, not the rendered window. A selection stays on its
columns while the grid scrolls horizontally, selectAll() (Ctrl+A) covers every column, and
copying or pasting a selection uses the selected columns even when they are scrolled out of view.
Column-mode selection (selectColumn(), selectAllColumns(), getSelectedColumns()) works on
columns outside the window too.
The focused cell also stays on its column. When you scroll it out of view, no cell shows the focus
ring and grid.focusedCell.colIndex is -1, while grid.focusedCell.field still names the column.
The next key press on the grid or one of its cells scrolls the column back into view before it is
handled, as in a spreadsheet. Keys typed into a control (an input or button rendered in a header or
cell) leave it scrolled out. Home / End go to the first / last column of the whole grid, not of
the rendered window.
Arrow keys and Tab step through every column: they scroll the next column into view instead of
stopping at the rendered window’s edge or jumping into a pinned column.
A paste without a multi-cell selection writes into consecutive columns of the full list, so a wide paste continues past the rendered window instead of stopping at its edge. A selected range pastes into the columns it selects.
Editing
Section titled “Editing”Rows with open editors follow horizontal scrolling like other rows. An editor whose column stays in
view keeps its element, focus and draft, and columns that scroll into view get editors (in
mode: 'grid', or on the row being edited in row mode). A focused editor whose column scrolls out of
view is blurred first, which commits its value. Tab in an editor continues to the next editable
column even when it is outside the rendered window, and
beginCellEdit() scrolls an off-window column into view before opening
its editor.
The rendered window is computed after every other column plugin (pinning, grouping, pivot),
whatever their order in plugins, from their final output. Pinned order, group header rows, and
pivot columns are therefore preserved.
A custom plugin that maps columns onto rendered DOM cells by index should read
this.visibleColumns (the rendered columns) rather than this.columns (the full list).
Programmatic API
Section titled “Programmatic API”const plugin = grid.getPluginByName('columnVirtualization');
// Check if virtualization is currently activeconst active = plugin.getIsVirtualized();
// Get the visible column index rangeconst { start, end } = plugin.getVisibleColumnRange();
// Scroll a specific column into viewplugin.scrollToColumn(42);See Also
Section titled “See Also”- Performance — Performance optimization tips
- Pinned Columns — Sticky columns (excluded from virtualization)