High-Performance Virtual Scrolling Data Table - dgtable
| File Size: | 403 KB |
|---|---|
| Views Total: | 3093 |
| Last Update: | |
| Publish Date: | |
| Official Website: | Go to website |
| License: | MIT |
DGTable.js is a Vanilla JavaScript library (formerly a jQuery plugin) for creating high-performance virtual data tables that renders only the rows currently visible in the viewport.
Features:
- Renders large datasets with virtual scrolling.
- Sorts one column or multiple columns.
- Resizes, reorders, hides, and shows columns.
- Pins columns to the table start or end.
- Supports absolute, percentage, and relative column widths.
- Shows hover previews for truncated cell text.
- Filters rows with built-in and custom logic.
- Supports right-to-left table layouts.
- Handles variable row heights.
- Loads data asynchronously through Web Workers.
Use Cases:
- Admin dashboards that display large server log streams or audit trails get smooth scrolling from the virtual row engine.
- Financial platforms that sort transaction records across multiple fields simultaneously use the multi-column sort configuration.
- CRM tools that let users reorganize their workspace use column reordering and visibility controls.
- Reporting UI with many columns uses sticky column pinning to keep key identifiers on screen during horizontal scroll.
Table Of Contents:
How to use it:
1. Install DGTable with NPM and import it into your JS project.
$ npm install @danielgindi/dgtable
import DGTable from '@danielgindi/dgtable';
2. Or load the UMD build directly in the browser:
<script src="/path/to/dist/dgtable.jslib.umd.min.js"></script>
3. Create a new DGTable instance and define the column data as follows.
// Define the table with a column configuration and a fixed height
const table = new DGTable({
columns: [
{ name: 'userId', label: 'User ID', width: 90 },
{ name: 'username', label: 'Username', width: '25%' },
{ name: 'role', label: 'Role', width: '20%' },
{ name: 'email', label: 'Email' }, // auto width
],
height: 450,
virtualTable: true, // Activate virtual scrolling for large datasets
maxColumnsSortCount: 2, // Allow sorting by up to 2 columns at once
});
// Attach the table's wrapper element to the page
document.getElementById('table-wrapper').appendChild(table.el);
// Load the initial dataset
table.setRows([
{ userId: 101, username: 'alice_w', role: 'Admin', email: '[email protected]' },
{ userId: 102, username: 'bob_m', role: 'Editor', email: '[email protected]' },
{ userId: 103, username: 'carol_t', role: 'Viewer', email: '[email protected]' },
]);
// Trigger the initial render
table.render();
4. Custom cell formatting:
const table = new DGTable({
columns: [
{ name: 'status', label: 'Status', width: 110 },
{ name: 'product', label: 'Product' },
{ name: 'qty', label: 'Qty', width: 80 },
],
height: 400,
virtualTable: true,
// Return an HTML string for each cell; receives value, column name, and the full row object
cellFormatter: function(value, columnName, rowData) {
if (columnName === 'status') {
// Render a colored badge based on the status value
const color = value === 'Active' ? '#2a9d5c' : '#999';
return `<span style="color:${color}; font-weight:600;">${value}</span>`;
}
if (columnName === 'qty' && Number(value) < 5) {
// Highlight low stock quantities in red
return `<span style="color:#c0392b;">${value}</span>`;
}
return String(value);
},
});
5. Web Worker integration.
// Check browser support before creating a worker
if (table.isWorkerSupported()) {
// Bind a Web Worker to stream data into the table asynchronously
const worker = table.createWebWorker('/workers/data-feed.js', true, false);
worker.addEventListener('message', function(e) {
if (e.data.done) {
// Unbind the worker once the data transfer is complete
table.unbindWebWorker(worker);
}
});
}
6. All configuration options.
Table Options
el(Element): An existing DOM element to use as the table's container.className(string): CSS class applied to the wrapper element. Defaults to'dgtable-wrapper'.height(number): Table height in pixels.width(DGTable.Width): Width handling mode. AcceptsNONE,AUTO, orSCROLL. Defaults toNONE.virtualTable(boolean): Activates virtual scrolling. Recommended for large datasets. Defaults totrue.estimatedRowHeight(number): Estimated row height used for virtual scroll calculations. Defaults to40.rowsBufferSize(number): Number of rows rendered outside the visible area as a scroll buffer. Defaults to3.
Column Layout Options
columns(ColumnOptions[]): Array of column definition objects. Defaults to[].minColumnWidth(number): Global minimum column width in pixels. Defaults to35.maxStickyColumnRelativeWidth(number): Maximum relative width for a sticky column.resizableColumns(boolean): Activates drag-to-resize on all columns globally. Defaults totrue.movableColumns(boolean): Activates drag-to-reorder on all columns globally. Defaults totrue.maxColumnsSortCount(number): Maximum number of columns active in a multi-column sort. Defaults to1.allowCancelSort(boolean): Cycles sort direction through ascending, descending, and none. Defaults totrue.adjustColumnWidthForSortArrow(boolean): Auto-expands a column's width to accommodate the sort indicator. Defaults totrue.relativeWidthGrowsToFillWidth(boolean): Expands relative-width columns to fill available table space. Defaults totrue.relativeWidthShrinksToFillWidth(boolean): Shrinks relative-width columns to fit available table space. Defaults tofalse.convertColumnWidthsToRelative(boolean): Converts auto-calculated widths to relative widths internally. Defaults tofalse.autoFillTableWidth(boolean): Stretches all columns to fill the full table width. Defaults tofalse.resizeAreaWidth(number): Width of the drag handle for column resizing, in pixels. Defaults to8.
Column Definition Object
name(string): Required. Unique identifier for the column.label(string): Text displayed in the column header. Defaults toname.width(number | string): Column width as pixels (90), percentage ('25%'), or relative decimal (0.25).dataPath(string | string[]): Path to the data property in each row object. Defaults to[name].comparePath(string | string[]): Path used for sort comparisons. Defaults todataPath.resizable(boolean): Allows the user to resize this specific column. Defaults totrue.sortable(boolean): Allows the user to sort by this specific column. Defaults totrue.movable(boolean): Allows the user to reorder this specific column. Defaults totrue.visible(boolean): Sets the initial visibility of the column. Defaults totrue.sticky('start'|'end'|false|null): Pins the column to the left ('start') or right ('end') edge of the table.cellClasses(string): Additional CSS classes applied to every body cell in this column.ignoreMin(boolean): Skips the globalminColumnWidthconstraint for this column.order(number): Sets the initial display order of the column.
Formatting and Filtering Options
cellFormatter(function): A function(value, columnName, rowData) => stringthat returns an HTML string for each body cell.headerCellFormatter(function): A function(label, columnName) => stringthat returns an HTML string for each header cell.filter(function): A default filter function(row, args) => booleancalled whentable.filter(args)executes.sortColumn(string | string[] | ColumnSortOptions | ColumnSortOptions[]): Sets the initial sort column or columns at construction time.onComparatorRequired(function): A callback(columnName, descending, defaultComparator) => ComparatorFunctionthat returns a custom comparator for a specific column.customSortingProvider(function): A function(data, sort) => RowData[]that replaces the built-in sort algorithm entirely.
Styling Options
tableClassName(string): Base CSS class applied to the table element. Defaults to'dgtable'.cellClasses(string): Additional CSS classes applied to all body cells globally. Defaults to''.resizerClassName(string): CSS class for the column resize handle element. Defaults to'dgtable-resize'.cellPreviewClassName(string): CSS class for the cell preview tooltip element. Defaults to'dgtable-cell-preview'.allowCellPreview(boolean): Shows a tooltip on hover for truncated body cell content. Defaults totrue.allowHeaderCellPreview(boolean): Shows a tooltip on hover for truncated header cell content. Defaults totrue.cellPreviewAutoBackground(boolean): Copies the originating cell's background color to the preview element. Defaults totrue.
7. API methods.
// ── Rendering ──
// Perform the initial render or re-render after a configuration change
table.render();
// Force a complete teardown and DOM rebuild of the entire table
table.clearAndRender();
// ── Column Management ──
// Replace the entire column set with a new array of column definitions
table.setColumns(columns, render);
// Insert a new column; second arg is the column name or index to insert before (-1 appends)
table.addColumn(columnData, before, render);
// Remove a column by its name property
table.removeColumn('columnName', render);
// Change the displayed header label for an existing column
table.setColumnLabel('columnName', 'New Label');
// Move a column from one position to another by index
table.moveColumn(srcIndex, destIndex, visibleOnly);
// Show or hide a column (the data remains in the model)
table.setColumnVisible('columnName', true);
// Check whether a column is currently visible
table.isColumnVisible('columnName');
// Set a column's width programmatically
table.setColumnWidth('columnName', 150);
// Get a column's current width
table.getColumnWidth('columnName');
// Retrieve the full configuration object for one column
table.getColumnConfig('columnName');
// Retrieve the full configuration array for all columns
table.getColumnsConfig();
// ── Sorting ──
// Sort by a column; second arg = descending, third arg = append to existing sort
table.sort('columnName', false, false);
// Re-apply the current sort state to the existing data
table.resort();
// Restore a previously saved sort state
table.setSortedColumns([{ column: 'username', descending: false }]);
// Retrieve the current sort state as a serializable array
table.getSortedColumns();
// Set the maximum number of columns active in a multi-column sort
table.setMaxColumnSortCount(3);
// Get the current maximum column sort count
table.getMaxColumnSortCount();
// ── Data Management ──
// Replace the entire dataset; pass true to re-sort after loading
table.setRows(data, resort);
// Insert new rows at a specific index; use -1 to append
table.addRows(data, at, resort, render);
// Remove a single row by its index in the full dataset
table.removeRow(rowIndex, render);
// Remove multiple consecutive rows starting at an index
table.removeRows(rowIndex, count, render);
// Force a single row to re-render its cells after an external data mutation
table.refreshRow(rowIndex, render);
// Re-render all currently visible virtual rows
table.refreshAllVirtualRows();
// Get the total number of rows in the dataset
table.getRowCount();
// Get the number of rows that pass the active filter
table.getFilteredRowCount();
// Retrieve the data object for a row by its index in the full dataset
table.getDataForRow(rowIndex);
// Retrieve the data object for a row by its index in the filtered dataset
table.getDataForFilteredRow(filteredIndex);
// Find a row's index in the full dataset by its data object reference
table.getIndexForRow(rowData);
// Find a row's index in the filtered dataset by its data object reference
table.getIndexForFilteredRow(rowData);
// Get the DOM element for a rendered row by its dataset index
table.getRowElement(rowIndex);
// Get the vertical pixel position of a row within the table's scroll area
table.getRowYPos(rowIndex);
// ── Filtering ──
// Register a custom filter function; receives each row and the args object
table.setFilter(function(row, args) {
return row.status === args.status;
});
// Execute the active filter with a set of arguments
table.filter({ status: 'Active' });
// Use the built-in column keyword filter
table.filter({ column: 'email', keyword: '@company.com', caseSensitive: false });
// Remove the active filter and display all rows
table.clearFilter();
// ── Formatters ──
// Set a global cell formatter for all body cells
table.setCellFormatter(function(value, columnName, rowData) {
return `<span class="cell-${columnName}">${value}</span>`;
});
// Set a global formatter for all header cells
table.setHeaderCellFormatter(function(label, columnName) {
return `<span class="hdr-${columnName}">${label}</span>`;
});
// Get the rendered HTML string for a cell by row index and column name
table.getHtmlForRowCell(rowIndex, 'columnName');
// Get the rendered HTML string for a cell from a row data object directly
table.getHtmlForRowDataCell(rowData, 'columnName');
// ── Layout ──
// Notify the table that its container width has changed so it recalculates column widths
table.tableWidthChanged(forceUpdate, renderColumns);
// Notify the table that its container height has changed
table.tableHeightChanged();
// Set the global minimum column width in pixels
table.setMinColumnWidth(40);
// Get the current global minimum column width
table.getMinColumnWidth();
// Set the maximum relative width for sticky columns
table.setMaxStickyColumnRelativeWidth(0.4);
// Get the maximum relative width for sticky columns
table.getMaxStickyColumnRelativeWidth();
// ── Cell Preview ──
// Hide the currently visible cell preview tooltip
table.hideCellPreview();
// Alias for hideCellPreview()
table.abortCellPreview();
// ── Column Feature Toggles ──
// Enable or disable interactive column moving globally
table.setMovableColumns(true);
// Check whether column moving is currently active
table.getMovableColumns();
// Enable or disable interactive column resizing globally
table.setResizableColumns(true);
// Check whether column resizing is currently active
table.getResizableColumns();
// ── Sorting Customization ──
// Register a callback that returns a custom comparator for a specific column
table.setOnComparatorRequired(function(columnName, descending, defaultComparator) {
if (columnName === 'priority') {
const rank = { High: 0, Medium: 1, Low: 2 };
return function(a, b) {
return rank[a.priority] - rank[b.priority];
};
}
return defaultComparator;
});
// Replace the entire sort algorithm with a custom implementation
table.setCustomSortingProvider(function(data, sort) {
// Call sort(data) to use the built-in sort, or return your own sorted array
return sort(data);
});
// ── Web Workers ──
// Check whether the browser supports Web Workers
table.isWorkerSupported();
// Create and bind a Web Worker for async data loading
table.createWebWorker('/workers/table-feed.js', true, false);
// Detach a previously bound Web Worker
table.unbindWebWorker(worker);
// Generate a Blob URL from an inline script element for use as a worker source
table.getUrlForElementContent('worker-script-element-id');
// ── DOM Access ──
// Direct reference to the table's wrapper DOM element
table.el;
// Get the header row DOM element
table.getHeaderRowElement();
// ── Lifecycle ──
// Destroy the table, remove all DOM nodes, and release all event listeners
table.destroy();
// Alias for destroy()
table.close();
// Alias for destroy()
table.remove();
8. Event handlers.
// Fires after the table completes a render cycle
table.on('render', function() {
console.log('Table rendered');
});
// Fires when the table rebuilds its underlying DOM structure
table.on('renderskeleton', function() {
console.log('Table skeleton rebuilt');
});
// Fires when a new row DOM element is created during virtual scrolling
// data: { filteredRowIndex, rowIndex, rowEl, rowData }
table.on('rowcreate', function(data) {
data.rowEl.setAttribute('data-uid', data.rowData.userId);
});
// Fires when the user clicks a row
// data: { event, filteredRowIndex, rowIndex, rowEl, rowData }
table.on('rowclick', function(data) {
console.log('Clicked row index:', data.rowIndex, 'Data:', data.rowData);
});
// Fires just before a virtual row's DOM element is removed from the DOM
table.on('rowdestroy', function(rowEl) {
console.log('Row DOM element removed:', rowEl);
});
// Fires when a cell preview tooltip becomes visible
// data: { el, name, rowIndex, rowData, cell, cellEl }
table.on('cellpreview', function(data) {
if (data.name === 'email' && data.el) {
// Append a custom badge to the preview content
data.el.innerHTML += '<span class="copy-hint">Click to copy</span>';
}
});
// Fires just before a cell preview tooltip closes
// data: { el, name, rowIndex, rowData, cell, cellEl }
table.on('cellpreviewdestroy', function(data) {
console.log('Preview closed for column:', data.name);
});
// Fires when the table creates its header row element
table.on('headerrowcreate', function(headerRowEl) {
console.log('Header row element:', headerRowEl);
});
// Fires when the user right-clicks a header cell
// data: { columnName, pageX, pageY, bounds }
table.on('headercontextmenu', function(data) {
console.log('Context menu at column:', data.columnName, 'X:', data.pageX, 'Y:', data.pageY);
// data.bounds contains { left, top, width, height } of the header cell
});
// Fires after a column is added to the table
table.on('addcolumn', function(columnName) {
console.log('Column added:', columnName);
});
// Fires after a column is removed from the table
table.on('removecolumn', function(columnName) {
console.log('Column removed:', columnName);
});
// Fires after the user drags a column to a new position
// data: { name, src, dest }
table.on('movecolumn', function(data) {
console.log(`Column "${data.name}" moved from position ${data.src} to ${data.dest}`);
});
// Fires when a hidden column becomes visible
table.on('showcolumn', function(columnName) {
console.log('Column shown:', columnName);
});
// Fires when a visible column is hidden
table.on('hidecolumn', function(columnName) {
console.log('Column hidden:', columnName);
});
// Fires after the user resizes a column by dragging
// data: { name, width, oldWidth }
table.on('columnwidth', function(data) {
console.log(`Column "${data.name}" resized from ${data.oldWidth}px to ${data.width}px`);
});
// Fires after rows are added to the table
// data: { count, clear }
table.on('addrows', function(data) {
console.log(`${data.count} row(s) added. Full dataset cleared first: ${data.clear}`);
});
// Fires after a sort operation completes
// data: { sorts, resort }
table.on('sort', function(data) {
console.log('Current sort state:', data.sorts);
});
// Fires each time a filter is applied; receives the filter arguments
table.on('filter', function(args) {
console.log('Active filter arguments:', args);
});
// Fires when the active filter is cleared
table.on('filterclear', function() {
console.log('Filter cleared, all rows visible');
});
Alternatives:
- Tabulator: A full-featured JavaScript table library with spreadsheet-style inline editing, tree data, and REST data source integration.
- Handsontable: A spreadsheet-style data grid for JavaScript and major frameworks, optimized for cell-level editing workflows.
Changelog:
2025-06-21
- v2.0.28
2025-01-27
- v0.6.19: bugfix
2024-10-14
- v0.6.17: bugfix
2023-11-30
- v0.6.11: bugfix
2023-01-12
- v0.6.10: [feature] allowCancelSort
2022-12-26
- v0.6.9: bugfix
2022-12-04
- v0.6.8: bugfix
2022-11-22
- v0.6.7: bugfix
2022-11-20
- v0.6.6: [fix] restore default behavior of refreshRow to immediately render changes
2022-11-17
- v0.6.5: [fix] allowCellPreview setting was no treated correctly since virtual table support
2022-11-08
- v0.6.3: bugfix
2022-11-07
- v0.6.1: auto estimate estimatedRowHeight by default
2022-10-13
- v0.5.59: corrected single sortColumn option support
2022-05-18
- v0.5.58: [fix] do not trigger events after destroy
2022-04-03
- v0.5.57: [fix] preview cell should inherit cursor style
2022-03-28
- v0.5.56: [fix] emit rowclick events from cell preview
2022-03-16
- v0.5.55: [fix] avoid index corruption when trigerring rowclick
This awesome jQuery plugin is developed by danielgindi. For more Advanced Usages, please check the demo page or visit the official website.











