@litable/core
v1.0.4
Published
LiTable core — framework-agnostic, dependency-free table plugin
Maintainers
Readme
@litable/core
Framework-agnostic, dependency-free table plugin — the engine behind all @litable/* framework wrappers.
Install
npm install @litable/coreCDN:
<link rel="stylesheet" href="litable.min.css">
<script src="litable.min.js"></script>
<!-- window.LiTable is now available -->Quick Start
Existing HTML table
import LiTable from '@litable/core';
new LiTable('#myTable', {
sorting: true,
filtering: true,
pagination: { pageSize: 10 },
});Inline data
new LiTable('#myTable', {
data: [
{ id: 1, name: 'Alice', city: 'New York' },
{ id: 2, name: 'Bob', city: 'London' },
],
pagination: { pageSize: 10 },
});AJAX
new LiTable('#myTable', {
ajax: 'https://api.example.com/users',
pagination: { pageSize: 20 },
});Data Sources
Use exactly one of the three sources. They are mutually exclusive.
| Option | Type | Description |
|----------|----------|-------------|
| ajax | string | URL to fetch JSON data. Supports array, { data: [...] }, or nested envelope response. |
| data | Row[] | Inline array of objects. Column headers are auto-generated from field names. |
| (none) | — | Reads existing <tbody> rows directly from the HTML. |
AJAX response formats (all auto-detected)
[{ ... }, { ... }]
{ "data": [{ ... }] }
{ "statusCode": 200, "data": { "total": 12363, "page": 1, "limit": 10, "data": [{ ... }] } }Server-Side Mode
Set serverSide: true to delegate pagination, sorting, and search to the API. LiTable appends query parameters automatically on every interaction.
new LiTable('#myTable', {
ajax: 'https://api.example.com/data?school=1811&isPaginated=true',
serverSide: true,
pagination: { pageSize: 20 },
});Query params sent to the server
| Param | Example | When included |
|------------|-------------------|---------------|
| page | page=1 | Always |
| pageSize | pageSize=20 | Always |
| sortBy | sortBy=name | After a column header is clicked |
| sortDir | sortDir=asc | After a column header is clicked (asc or desc) |
| search | search=alice | When search box is non-empty (debounced 400 ms) |
Example URL: https://api.example.com/data?school=1811&page=2&pageSize=20&sortBy=name&sortDir=asc&search=alice
Expected server response
{
"statusCode": 200,
"message": "successful",
"data": {
"total": 12363,
"page": 2,
"limit": 20,
"data": [...]
}
}Also accepted (flat envelope):
{ "total": 12363, "page": 2, "limit": 20, "data": [...] }columns[]
columns is an override map — every field in the data source is rendered as a column. A columns entry applies properties to a targeted column without hiding the rest.
Target a column by:
- Number — positional index (0 = first,
-1= last,-2= second-to-last, …) - String — field name in the data object (e.g.
'gradeName')
columns: [
{ key: 0, pinLeft: true },
{ key: 1, pinLeft: true, render: (val) => `<b style="color:red;">${val}</b>` },
{ key: 'status', header: 'Status', render: (val) => `<span class="badge">${val}</span>` },
{ key: -1, pinRight: true },
{ key: -2, pinRight: true },
]ColumnDef fields
| Field | Type | Description |
|-------------|---------------------------|-------------|
| key | string \| number | Column target — field name or positional index |
| header | string | Override the column header text |
| pinLeft | boolean | Sticky left (stacks in order of columns entries) |
| pinRight | boolean | Sticky right (stacks in order of columns entries) |
| hidden | boolean | Hide this column (th + all td) |
| width | number \| string | Column width (px number or CSS string) |
| minWidth | number | Minimum column width in px |
| maxWidth | number | Maximum column width in px |
| sortable | boolean | Set false to disable sorting for this column |
| render | (value, row) => string | Custom cell renderer — return an HTML string |
| attribute | object | Set an HTML attribute on th, td, or both |
attribute
attribute: {
name: 'data-type', // HTML attribute name
value: 'badge', // HTML attribute value
target: 'td' // 'th' | 'td' | 'both'
}Auto headers: If
headeris omitted, the field name is used with the first character uppercased (gradeName→GradeName).
Sticky Columns & Headers
new LiTable('#myTable', {
ajax: '...',
columns: [
{ key: 0, pinLeft: true }, // pin column 0 left
{ key: 1, pinLeft: true }, // pin column 1 left (stacks)
{ key: -1, pinRight: true }, // pin last column right
{ key: -2, pinRight: true }, // pin second-to-last right (stacks)
],
stickyHeader: [0, 1], // pin first two <thead> rows to the top
});Pagination
pagination: {
pageSize: 20, // initial rows per page
pageSizes: [5, 10, 20, 50, 100, 'All'], // dropdown options — 'All' shows all rows
sizeLabel: ['Show', 'entries'], // label around the size selector
}Virtual Scrolling
Renders only the rows visible in the viewport. Can be combined with pagination.
virtualScrolling: {
height: '400px', // scroll container height (required)
width: '100%', // optional max-width on the scroll container
rowHeight: 40, // fixed row height in px (auto-measured if omitted)
bufferSize: 5, // extra rows rendered above/below viewport
yBufferSize: 10, // overrides bufferSize for vertical axis only
}Sorting
Enabled by default. Click any <th> to sort ascending; click again to toggle to descending.
sorting: true // default — set false to disable globallyDisable sorting on a specific column with the no-sort attribute on <th>:
<th no-sort>Actions</th>Filtering / Search
filtering: true // default
// or with customization:
filtering: {
searchLabel: 'Search:',
searchPlaceholder: 'Filter rows...',
}In serverSide mode the search term is sent as ?search=term with a 400 ms debounce instead of doing client-side filtering.
Export
export: {
formats: ['csv', 'excel', 'pdf', 'image'],
filename: 'my-data',
}Supported formats: 'csv', 'excel', 'pdf', 'image'.
Editable (CRUD)
Row selection with modal-based add / edit / delete. Requires wrapper: true (the default).
editable: {
type: 'full_row', // only supported type — clicking a row selects it
url: '/api/crud', // POST endpoint for all three operations
key: [0], // column index(es) that form the primary key
hide: [3, 5], // column indexes hidden in Add/Edit form (e.g. auto-generated fields)
action: {
add: true, // show "New" button
edit: true, // show "Edit" button (enabled on row select)
delete: true, // show "Delete" button (enabled on row select)
},
}Request format
POSTs to editable.url with action_vals = base64-encoded JSON:
{
"opration": "add | update | delete",
"condition": [{ "id": "5" }],
"data": { "Name": "Alice", "Age": "30" },
"key": ["id"]
}Response format
| Operation | Success | Failure |
|-----------|---------|---------|
| add | [{ col: val, ... }] | null |
| update | { "keyVal": true } | { "keyVal": false } |
| delete | { "keyVal": true } | { "keyVal": false } |
Theme
theme: {
striped: true, // alternate row background
stripeColor: '#f9f9f9', // stripe color (default #f9f9f9)
border: {
borderWidth: 1, // border thickness in px — creates uniform cell borders via box-shadow
borderColor: '#cccccc', // border color (default #cccccc)
tableBorder: true, // outer table border
rowBorder: true, // horizontal row separators
columnBorder: true, // vertical column separators
},
}All Options at a Glance
new LiTable('#myTable', {
// ── Data ──
ajax: 'https://api.example.com/data?school=1811&isPaginated=true',
serverSide: true,
data: [], // alternative to ajax
columns: [
{ key: 0, pinLeft: true },
{ key: 1, pinLeft: true, render: (val) => `<b style="color:red;">${val}</b>` },
{ key: -1, pinRight: true },
{ key: -2, pinRight: true },
],
// ── Layout ──
wrapper: true, // render the controls wrapper (default true)
stickyHeader: [0, 1], // sticky thead row indices
// ── Features ──
sorting: true,
filtering: { searchLabel: 'Search:', searchPlaceholder: '' },
pagination: { pageSize: 20, pageSizes: [10, 20, 50, 100, 'All'] },
info: true, // "Showing X to Y of Z entries" text
virtualScrolling: { height: '400px' },
columnResizing: true,
columnReordering: true,
rowSelection: { mode: 'multiple' },
inlineEditing: true,
// ── Export ──
export: {
formats: ['csv', 'excel', 'pdf', 'image'],
filename: 'my-data',
},
// ── CRUD ──
editable: {
type: 'full_row',
url: '/api/crud',
key: [0],
hide: [3, 5],
action: { add: true, edit: true, delete: true },
},
// ── Theme ──
theme: {
striped: true,
border: {
tableBorder: true,
borderWidth: 1,
columnBorder: true,
rowBorder: true,
borderColor: 'gray',
},
},
// ── Callbacks ──
onSort: (column, direction) => console.log(column, direction),
onFilter: (term) => console.log(term),
onPageChange: (page, pageSize) => console.log(page, pageSize),
onRowSelect: (rows) => console.log(rows),
onChange: (data) => console.log(data),
beforeRender: () => {},
onRender: () => {},
});Instance API
const table = new LiTable('#myTable', { ... });
table.update({ data: newData }); // update options & re-render
table.refresh(); // re-render with current state
table.reload(); // full re-initialise from original HTML
table.destroy(); // remove all enhancements
table.getData(); // → Row[]
table.setData(newData); // replace all rows & re-render
table.getHead(); // → <thead> element
table.setHead(htmlOrElement); // replace <thead>
table.getBodyRow(); // → HTMLTableRowElement[]
table.setBodyRow(rows); // replace internal row array (call refresh() after)Global State
window.litable['myTable'].thlist // string[] — column header labels
window.litable['myTable'].thrlist // HTMLCollection — <thead> row elements
window.litable['myTable'].d // Row[] — raw data (ajax/data mode)
window.litable['myTable'].sel_list // number[] — selected row indicesFramework Wrappers
| Package | Framework |
|--------------------|--------------|
| @litable/react | React 18/19 |
| @litable/angular | Angular 17+ |
| @litable/vue3 | Vue 3 |
| @litable/vue2 | Vue 2.7 |
| @litable/svelte | Svelte 4/5 |
| @litable/solid | SolidJS |
| @litable/preact | Preact 10 |
| @litable/alpine | Alpine.js 3 |
| @litable/qwik | Qwik |
| @litable/astro | Astro 4 |
| @litable/nuxt | Nuxt 3 |
License
Copyright © 2026 Naveen Yadav. All rights reserved.
Proprietary and confidential — see LICENSE.
