Installation
Use the package from npm, or load the source files directly in a browser module setup.
npm
npm install dog-table
Package entry points
-
dog-table- exportsdist/data-table.jsby default, withdog-table/minfor the minified bundle -
dog-table/css- exportsdist/data-table.css, withdog-table/css/minfor minified CSS -
dog-table/locale/*- maps todist/locale/*.js -
dog-table/plugin/*- maps todist/plugin/*.js -
dog-table/utils- exportsdist/utils/index.js -
src/core/contains internal support modules and is not exported as a package subpath
Quick Start
The basic flow is: load CSS, create a DogTable, pass
data and columns, then call
init().
Browser modules
<link rel="stylesheet" href="./src/data-table.css" />
<div id="dogs"></div>
<script type="module">
import { DogTable } from "./src/data-table.js";
const table = new DogTable("#dogs", {
data: [
{ id: 1, name: "Mochi", age: 3, status: "ready" },
{ id: 2, name: "Pepper", age: 5, status: "pending" },
],
columns: [
{ key: "name", label: "Name" },
{ key: "age", label: "Age" },
{ key: "status", label: "Status" },
],
});
table.init();
</script>
Bundlers
import { DogTable } from "dog-table";
import "dog-table/css";
const table = new DogTable("#dogs", {
data,
columns: [
{ key: "name", label: "Name" },
{ key: "breed", label: "Breed" },
{ key: "age", label: "Age" },
],
});
table.init();
Examples
Ten focused examples covering all major DogTable features.
1. Basic Local Table
const table = new DogTable("#app", {
data: [
{ id: 1, name: "Mochi", breed: "Shiba Inu", age: 3 },
{ id: 2, name: "Pepper", breed: "Border Collie", age: 5 },
],
columns: [
{ key: "name", label: "Name", sortable: true },
{ key: "breed", label: "Breed", sortable: true },
{ key: "age", label: "Age", type: "number" },
],
pageSize: 5,
searchable: true,
pagination: true,
});
table.init();
2. Remote Data with API
const table = new DogTable("#app", {
remote: {
url: "https://api.example.com/dogs",
dataKey: "results",
totalKey: "count",
headers: { Authorization: "Bearer TOKEN" },
queryParams: {
page: "page",
pageSize: "limit",
sort: "sort_by",
order: "order",
search: "q",
},
},
columns: [
{ key: "name", label: "Name", sortable: true },
{ key: "breed", label: "Breed", sortable: true },
{ key: "age", label: "Age", type: "number" },
],
pageSize: 10,
});
table.init();
3. Inline Editing
const table = new DogTable("#app", {
data: [
{ id: 1, name: "Mochi", age: 3, status: "Ready" },
],
columns: [
{ key: "name", label: "Name", editable: true },
{ key: "age", label: "Age", type: "number", editable: true },
{ key: "status", label: "Status", editable: true },
],
remote: {
url: "https://api.example.com/dogs",
update: {
url: "https://api.example.com/dogs/{id}",
method: "PUT",
headers: { Authorization: "Bearer TOKEN" },
buildBody: (ctx) => JSON.stringify({ [ctx.field]: ctx.value }),
mapResponse: (payload) => payload.data,
},
},
hooks: {
onUpdateSuccess: ({ rowId, field, value, row }) => {
console.log(`Row ${rowId}: ${field} = ${value}`, row);
},
},
});
table.init();
4. Create New Records
const table = new DogTable("#app", {
data: [],
columns: [
{ key: "name", label: "Name", required: true },
{ key: "breed", label: "Breed", required: true },
{ key: "age", label: "Age", type: "number", required: true },
{
key: "status",
label: "Status",
inputType: "select",
options: ["Ready", "Pending", "Adopted"],
defaultValue: "Ready",
},
],
create: {
triggerLabel: "Add Dog",
title: "Register New Dog",
submitLabel: "Save Dog",
initialValues: { status: "Ready" },
remote: {
url: "https://api.example.com/dogs",
method: "POST",
headers: { Authorization: "Bearer TOKEN" },
buildBody: (ctx) => JSON.stringify(ctx.data),
mapResponse: (payload) => payload.data,
},
refetchAfterSubmit: true,
},
});
table.init();
5. Row Selection & CSV Export
const table = new DogTable("#app", {
data: [
{ id: 1, name: "Mochi", breed: "Shiba Inu", age: 3 },
{ id: 2, name: "Pepper", breed: "Border Collie", age: 5 },
],
columns: [
{ key: "name", label: "Name" },
{ key: "breed", label: "Breed" },
{ key: "age", label: "Age", type: "number" },
],
selectable: true,
rowKey: "id",
hooks: {
onSelectionChange: (selectedData) => {
console.log("Selected:", selectedData);
},
},
});
table.init();
// API usage
table.getSelectedData();
table.exportCSV("dogs-export.csv");
6. Data Formatting
const table = new DogTable("#app", {
data: [
{ id: 1, name: "Mochi", price: 1500.50, birthDate: "2021-03-15" },
],
columns: [
{ key: "name", label: "Name" },
{
key: "price",
label: "Price",
type: "currency",
currency: "USD",
formatOptions: { minimumFractionDigits: 2 },
},
{
key: "birthDate",
label: "Birth Date",
type: "date",
formatOptions: { year: "numeric", month: "long", day: "numeric" },
},
],
});
table.init();
7. Grouped Rows
const table = new DogTable("#app", {
data: [
{ id: 1, name: "Mochi", category: "Small", age: 3 },
{ id: 2, name: "Pepper", category: "Medium", age: 5 },
{ id: 3, name: "Buddy", category: "Large", age: 2 },
],
columns: [
{ key: "name", label: "Name" },
{ key: "category", label: "Category" },
{ key: "age", label: "Age", type: "number" },
],
groupBy: "category",
groupLabel: (value, rows) => `${value} Dogs (${rows.length})`,
initialSort: { key: "category", direction: "asc" },
});
table.init();
8. Expandable Row Details
const table = new DogTable("#app", {
data: [
{
id: 1,
name: "Mochi",
breed: "Shiba Inu",
description: "A loyal Shiba Inu.",
},
],
columns: [
{ key: "name", label: "Name" },
{ key: "breed", label: "Breed" },
],
rowDetail: {
render: (row) => `
<div class="dog-details">
<p><strong>Description:</strong> ${row.description}</p>
</div>
`,
toggleLabel: (row, isExpanded) =>
isExpanded ? "Hide Details" : "Show Details",
},
});
table.init();
// Programmatic control
table.expandRowDetail(1);
table.collapseRowDetail(1);
9. State Persistence & Live Sync
const table = new DogTable("#app", {
remote: {
url: "https://api.example.com/dogs",
dataKey: "results",
totalKey: "count",
},
columns: [
{ key: "name", label: "Name", sortable: true },
{ key: "breed", label: "Breed", sortable: true },
{ key: "age", label: "Age", type: "number" },
],
persistence: "local",
persistenceKey: "my-dog-table",
autoRefresh: 5000,
hooks: {
onBeforeRefresh: () => console.log("Refreshing..."),
},
});
table.init();
// Live sync controls
table.live.start();
table.live.stop();
table.live.toggle();
10. Custom Rendering, Sorting & Hooks
const table = new DogTable("#app", {
data: [
{ id: 1, name: "Mochi", age: 3, score: 85 },
{ id: 2, name: "Pepper", age: 5, score: 92 },
],
columns: [
{
key: "name",
label: "Name",
sortable: true,
render: (value, row) => `<a href="/dogs/${row.id}">${value}</a>`,
},
{
key: "age",
label: "Age",
sortable: true,
sortValue: (value) => value * 7,
render: (value) => `${value} years`,
},
{
key: "score",
label: "Score",
sortable: true,
render: (value) => `<div class="progress"><span>${value}%</span></div>`,
},
],
initialSort: { key: "score", direction: "desc" },
searchDebounce: 300,
theme: "default",
paginationGuard: { maxPage: 50, minPageSize: 5, maxPageSize: 100 },
hooks: {
onInit: (state) => console.log("Initialized", state),
onUpdate: (processed) => console.log("Updated", processed),
onPageChange: (page) => console.log("Page:", page),
onSortChange: ({ sortKey, sortDirection }) =>
console.log(`Sort: ${sortKey} (${sortDirection})`),
onSearchChange: (query) => console.log("Search:", query),
onFetchStart: () => console.log("Fetching..."),
onFetchSuccess: (payload) => console.log("Loaded:", payload),
onFetchError: (error) => console.error("Fetch failed:", error),
onLoadingChange: (isLoading) => console.log("Loading:", isLoading),
onDataUpdated: (rawData) => console.log("Data:", rawData.length, "rows"),
onDestroy: () => console.log("Destroyed"),
},
});
table.init();
// Programmatic API
table.setPage(2);
table.setSearch("Mochi");
table.setSort("age", "asc");
table.setPageSize(10);
table.clearSearch();
table.clearSort();
table.reset();
table.setData(newData);
table.setColumns(newColumns);
table.setTheme("bootstrap");
table.destroy();
Core Concepts
Local data
Client-side search, sort, and pagination happen automatically
when you pass data and columns.
Set pagination: false if you want to render all
loaded rows without page controls.
Remote data
Add a remote config to fetch server data. Page,
sort, and search state are converted into query params, while
repeated query contexts keep their own pagination state.
Columns
Columns define labels, sort rules, formatting, search behavior, custom rendering, visibility, and editability.
Hooks
Lifecycle hooks expose init, fetch, update, sort, search, paging, row detail, selection, and loading changes.
Grouping and detail
Use groupBy, groupLabel,
rowKey, and rowDetail.render() to
build grouped tables with expandable rows.
Progressive enhancement
Persistence, CSV export, inline editing, locale switching, and live polling are already wired into the table instance.
Architecture
keeps the public DogTable API stable while
separating state management, data processing, rendering, remote
loading, event binding, and plugin bootstrapping.
v1.5.1-beta adds performance optimizations across all layers.
DogTable
The main controller coordinates update flow, public methods,
hooks, and module lifecycles. Uses requestAnimationFrame
batching to prevent multiple renders per frame, and deduplicates
in-flight fetch requests.
TableState
Owns page, page size, search query, sort state, error/loading state, and pagination guard constraints.
DataEngine
Handles filtering, sorting, pagination slicing, grouping, and
cache reuse without touching the DOM. Precomputes sort keys
before comparison (O(n) instead of O(n log n) property access),
and memoizes buildDisplayRows() to avoid rebuilds
when data hasn't changed.
RemoteAdapter
Wraps remote fetching, aliases identical datasets in memory, and keeps pagination metadata isolated per query context. Fetch requests are deduplicated so concurrent calls share the same promise.
TableRenderer
Uses DOM diffing to track row nodes via Map and
only rebuilds changed rows instead of full innerHTML
replacement. Preserves scroll position and reduces DOM churn.
Query cache guide
Use the dedicated guide for the full separation rules between shared remote data and per-query pagination state.
Renderers
TableRenderer, PaginationRenderer,
and MetaRenderer keep UI output separate from
business logic.
EventBinder
Centralizes DOM bind/unbind behavior for header sorting, pagination, row actions, search, and create/live controls.
VirtualScroller
Optional virtual scrolling module that renders only visible rows plus a buffer. Enable for large datasets with 100+ rows per page.
await table.update();
// -> fetch remote rows when needed (deduplicated + debounced)
// -> process state through DataEngine (memoized + precomputed sort keys)
// -> render header/body/meta/pagination (rAF-batched + DOM diffing)
Remote Example
const table = new DogTable("#dogs", {
columns: [
{ key: "name", label: "Name" },
{ key: "age", label: "Age" },
],
remote: {
url: "/api/dogs",
queryParams: {
page: "page",
pageSize: "limit",
sort: "sort",
order: "order",
search: "q",
},
mapResponse(payload) {
return {
rows: payload.items,
totalItems: payload.total,
};
},
},
});
table.init();
const allRowsTable = new DogTable("#all-dogs", {
data,
pagination: false,
columns: [
{ key: "name", label: "Name" },
{ key: "breed", label: "Breed" },
{ key: "age", label: "Age" },
],
});
allRowsTable.init();
Table Options
| Option | Type | Default | Purpose |
|---|---|---|---|
data |
Array<object> |
[] |
Raw local rows. |
columns |
Array<object> |
[] |
Column definitions used for rendering and behavior. |
pagination |
boolean |
true |
Enables page slicing and pagination controls. Set
false to render all loaded rows.
|
pageSize |
number |
5 |
Rows shown per page. |
paginationGuard |
boolean | { maxPage, minPageSize, maxPageSize }
|
false |
Optional guardrails for page/page-size bounds. Use
true for defaults (25 / 1 / 100). Ignored
when pagination is false.
|
searchable |
boolean |
true |
Shows or hides the built-in search control. |
searchDebounce |
number |
250 |
Debounce delay for the search input. |
fetchDebounce |
number |
0 |
Debounce delay for remote fetch requests. Set to a positive value to prevent request spam during rapid state changes. Use table.fetchNow() to bypass. |
language |
object |
built-in labels | Overrides visible UI copy such as search, paging, empty, and loading text. |
initialSort |
{ key, direction } | null |
null |
Initial sort state. |
theme |
string | object |
"default" |
Preset name or custom theme map. |
classNames |
object |
{} |
Additional class name overrides merged with the active theme. |
remote |
object | null |
null |
Enables server-driven data loading through
fetch().
|
groupBy |
string | function | null |
null |
Groups rows before rendering. |
groupLabel |
function | null |
null |
Builds custom labels for group header rows. |
rowKey |
string | function | null |
null |
Stable id source for row detail and selection features. |
rowDetail |
object | null |
null |
Enables expandable detail rows. |
persistence |
"url" | "local" | "session" | null |
null |
Persists search, paging, sort, and page size state. |
persistenceKey |
string | null |
null |
Custom storage or URL key prefix. |
selectable |
boolean |
false |
Adds row selection checkboxes. |
create |
object | null |
null |
Enables built-in creation workflows with modal forms and validation. |
hooks |
object |
{} |
Lifecycle callbacks. |
Column Definition
| Field | Type | Purpose |
|---|---|---|
key |
string |
Primary property used for value lookup, sort state, and default filtering. |
label |
string |
Visible header label. |
accessor |
string |
Alternate row property used for rendering and editing. |
sortable |
boolean |
Disable built-in sorting for a column. |
searchable |
boolean |
Exclude a column from local search matching. |
visible |
boolean |
Hide a column while keeping it in state. |
editable |
boolean |
Makes body cells editable through the editor plugin. |
render(value, row) |
function |
Custom cell rendering. Can return a Node,
string, or primitive.
|
sortValue(value, row) |
function |
Transforms values before local sorting. |
filter({ value, row, query }) |
function |
Custom local-search matching for the column. |
format or type |
string |
Used by the formatter plugin for built-in display formatting. |
formatOptions |
object |
Options passed to the formatter logic. |
locale |
string |
Locale override for formatted output. |
API Methods
| Method | What it does |
|---|---|
init() |
Builds the table UI, binds events, initializes live mode, and renders the first state. |
update({ skipFetch }) |
Re-renders the table. Scheduled via requestAnimationFrame to batch multiple calls. Use updateSync() for immediate render.
|
updateSync({ skipFetch }) |
Synchronous version of update() that renders immediately without rAF batching. Use when you need instant visual feedback.
|
fetchData() |
Triggers a remote fetch. Respects fetchDebounce if configured. In-flight requests are deduplicated.
|
fetchNow() |
Bypasses fetch debounce and executes the fetch immediately. Cancels any pending debounced fetch. |
loadState() / saveState() |
Manually interact with the persistence plugin. |
setPage(pageNumber) |
Changes the active page and updates the table. |
setSearch(query) |
Updates the search term. New queries start at page 1, while previously seen remote queries restore their cached pagination state. |
clearSearch() |
Clears the search input and filter state. |
setPageSize(size) |
Updates page size and resets to page 1. |
setSort(key, direction) |
Applies a specific sort state and preserves pagination independently for each remote query context. |
clearSort() |
Removes active sorting. |
setData(data) |
Replaces local row data and clears error/detail state. |
setColumns(columns) |
Replaces the current columns and rerenders. |
setTheme(theme, classNames) |
Swaps theme mapping and rebuilds the DOM shell. |
setLanguage(language) |
Merges language overrides and rebuilds visible UI text. |
toggleRowDetail(rowId) |
Expands or collapses a detail row. |
expandRowDetail(rowId) /
collapseRowDetail(rowId)
|
Explicit detail row control. |
toggleRowSelection(rowId, isSelected) |
Selects or deselects a row when selection is enabled. |
selectAll(isSelected) |
Selects or clears all rows in the current processed set. |
toggleColumnVisibility(columnKey, isVisible)
|
Shows or hides a column. |
getSelectedData() |
Returns selected row objects. |
getState() |
Returns a copy of the current table state. |
setLoading(isLoading) |
Manually updates loading state and triggers
onLoadingChange.
|
exportCSV(filename) |
Exports visible columns from the current raw data to CSV. |
openCreateModal() |
Opens the record creation modal if the feature is enabled. |
reset() |
Clears search, sort, errors, and expanded detail rows. |
destroy() |
Removes event listeners, aborts fetches, stops live mode, and clears the container. |
Hooks
| Hook | When it fires |
|---|---|
onInit(state) |
After the first init() render completes. |
onFetchStart(state) |
Before a remote request begins. |
onFetchSuccess(payload) |
After remote data is mapped successfully. |
onFetchError(error) |
When a remote request fails. |
onDataUpdated(rows) |
After fresh remote rows are stored. |
onLoadingChange(isLoading) |
Whenever loading state changes. |
onPageChange(page) |
When the visible page changes. |
onSortChange({ sortKey, sortDirection }) |
When sort state changes. |
onSearchChange(query) |
When the normalized search query changes. |
onRowToggle({ rowId, expanded }) |
When a detail row is opened or closed. |
onSelectionChange(selectedRows) |
When row selection changes. |
onUpdate(snapshot) |
After a successful render/update cycle. |
onCreateSuccess(row) |
After a new record is successfully created and added. |
onCreateError(error) |
When a create request fails. |
Plugins and Modules
PersistencePlugin
Stores search, page, sort, and page size in url,
localStorage, or sessionStorage.
SelectionPlugin
Tracks selected row ids and exposes
getSelectedData(), toggleRow(), and
selectAll().
ExportPlugin
Exports visible columns to CSV with
toCSV(filename).
FormatterPlugin
Formats output values before render using column formatting hints.
EditorPlugin
Starts inline editing for cells marked
editable in column definitions.
LivePlugin
Handles auto-refresh timers, visibility pausing, and adaptive backoff for remote tables.
CreatePlugin
Manages the built-in record creation workflow, field validation, and modal state.
DataFetcher
Builds request URLs from table state, supports query param remapping, and aborts stale requests.
PluginManager
Initializes persistence, selection, export, formatter, editor, live, and create plugins for the controller.
ThemeManager
Resolves built-in theme slots and class-name overrides used throughout the rendered UI.
utils
Exports escapeHtml(value) and
debounce(callback, wait).
VirtualScroller
Optional virtual scrolling for large datasets. Enable with
table.virtualScroller.enable({ rowHeight, bufferSize })
and disable with table.virtualScroller.disable().
Good Defaults to Know
pagination defaults to true
pageSize defaults to 5
paginationGuard is false by
default
paginationGuard is ignored when
pagination: false
paginationGuard: true uses
maxPage 25 and maxPageSize 100
Search is enabled by default
Search input debounce is 250ms
Fetch debounce is 0 (disabled) by default
Sort direction starts at asc
Remote responses expect data and
total unless mapped
Grouping labels fall back to
Ungrouped (count)
Demo Map
Use the demos as working reference implementations for each feature area.
Basic
Local data, search, sort, pagination, and the simplest
pagination: false example.
Custom Cells
Render functions, DOM nodes, and custom search behavior.
Themes
Default, Bootstrap, and Tailwind-oriented class maps.
Remote
Server-style fetch flow with sorting and paging.
Grouping + Detail
Grouped rows with expandable detail panels.
Localization
Language overrides and built-in locale usage.
Advanced Features
Selection, export, persistence, and visibility control.
Formatting
Formatter helpers for dates, numbers, and currencies.
Inline Editing
Editable cells and update flow.
Live Sync
Auto-refresh and status UI behavior.
Pagination Guard
Optional caps for max page and page-size validation.
Create Record
Built-in modal workflows for adding new rows with validation.