Integration Guide
This guide covers the essentials for integrating Extable into your application.
Integration
Getting Started
Start with the Basic Usage Demo to understand initialization and basic setup.
See also:
- Data Format Guide - Schema definition and supported types
- Data Access from API - Async data fetching patterns
- Init Options Reference - Table initialization options
Server-side Rendering (SSR)
@extable/core/ssr generates static HTML for initial render. The HTML is not hydrated by the React wrapper; client-side rendering will re-create the table. Treat SSR as a fast, SEO-friendly snapshot, and let the client mount replace it.
Next.js (pages router) example
import { renderTableHTML } from "@extable/core/ssr";
export async function getServerSideProps() {
const result = renderTableHTML({
data,
schema,
cssMode: "both",
wrapWithRoot: true,
defaultClass: "extable",
includeStyles: true,
});
return {
props: {
ssrHtml: result.html,
ssrCss: result.css ?? "",
},
};
}
export default function Page({ ssrHtml, ssrCss }) {
return (
<>
{ssrCss && <style dangerouslySetInnerHTML={{ __html: ssrCss }} />}
<div dangerouslySetInnerHTML={{ __html: ssrHtml }} />
{/* Client render: use Extable (React wrapper) or ExtableCore in a separate container */}
</>
);
}Client re-render note
When you mount the client table, it will build its own DOM. To avoid showing two tables, either:
- Replace the SSR container on mount (clear its innerHTML and mount the client table there), or
- Keep SSR HTML in a separate container and hide/remove it after the client table mounts.
If you need true DOM hydration, a dedicated hydration API would be required (out of scope for the current SSR MVP).
Shortcut Key Registration
Register keyboard shortcuts for Undo/Redo operations.
import { ExtableCore } from "@extable/core";
const table = new ExtableCore({
root: container,
schema,
defaultData,
defaultView: {},
});
// Register keyboard handler for Ctrl+Z / Ctrl+Shift+Z (undo/redo)
const onKey = (e: KeyboardEvent) => {
const key = e.key.toLowerCase();
const isMod = e.metaKey || e.ctrlKey;
if (!isMod) return;
// Undo: Ctrl/Cmd+Z
if (key === "z") {
if (!table) return;
e.preventDefault();
e.stopPropagation();
if (e.shiftKey) {
table.redo(); // Ctrl/Cmd+Shift+Z
} else {
table.undo(); // Ctrl/Cmd+Z
}
}
};
document.addEventListener("keydown", onKey, { capture: true });
window.addEventListener("beforeunload", () => {
document.removeEventListener("keydown", onKey, { capture: true });
});Keyboard Shortcuts:
- Ctrl/Cmd+Z - Undo last change
- Ctrl/Cmd+Shift+Z - Redo last undone change
See Data Access API for undo/redo details.
Layout & Responsive Design
The table container must have explicit dimensions. Below are patterns for each framework with HTML, class/style configurations, and corresponding CSS or Tailwind utilities.
The table displays to fill its container (#table-root). Set explicit dimensions on the container and parent using flexbox:
<div class="app">
<aside class="sidebar"><!-- Menu --></aside>
<div class="main">
<div class="toolbar"><!-- Controls --></div>
<div id="table-root"></div>
</div>
</div>const core = new ExtableCore({
root: document.getElementById("table-root")!,
defaultData: data,
schema: schema,
options: {
// Optional: apply default classes/styles
defaultClass: "table-container",
defaultStyle: { border: "1px solid #e0e0e0" },
},
});CSS:
.app {
display: flex;
height: 100vh;
}
.sidebar {
width: 250px;
border-right: 1px solid #e0e0e0;
overflow-y: auto;
}
.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.toolbar {
padding: 8px 12px;
border-bottom: 1px solid #e0e0e0;
flex-shrink: 0;
}
#table-root {
flex: 1;
min-height: 0;
}
.table-container {
/* Applied via options.defaultClass */
}Container Requirements
- Explicit
widthandheight(notauto) min-width: 0andmin-height: 0in flex/grid layouts- Table responds automatically to container size changes
Horizontal Scroll Troubleshooting
If horizontal scrolling does not appear even when total column width is larger than the viewport, check the layout chain first.
Common failure signatures:
- The page or outer panel scrolls horizontally, but
.extable-viewportdoes not. - The table area expands wider than expected inside a
flexorgridpane. - Only specific routes/panels fail while others work with the same table config.
Checklist:
- Ensure the Extable mount element has explicit size (
width/height, usually100%). - In every flex/grid item from the app shell down to the table pane, set
min-width: 0andmin-height: 0. - Keep
.extable-viewportas the scroll host; avoid forcing ancestor containers to absorb overflow unexpectedly.
Typical fix:
.main-pane {
min-width: 0;
min-height: 0;
}
.table-pane {
min-width: 0;
min-height: 0;
}Customization
- Validation - Schema-based constraints
- Edit Modes - Protect specific cells/columns
- Formulas - Add computed columns
- Conditional Style - Style cells dynamically
- Styling - Theme and appearance
Interaction Design
Choose the interaction pattern based on your application's requirements.
See Data Access API for complete API reference.
Read-only Mode
Users can view and search data, but cannot make edits.
const table = new ExtableCore({
root: container,
schema,
defaultData,
defaultView: {},
options: {
editMode: "readonly", // Disable all editing
},
});Use cases: Reports, dashboards, audit logs.
Direct Mode
Edits are applied immediately and require no user confirmation. Changes are sent to server immediately (if configured).
const table = new ExtableCore({
root: container,
schema,
defaultData,
defaultView: {},
options: {
editMode: "direct", // Default: changes apply immediately
},
});
// Listen for individual row changes
table.subscribeRowState((rowId, event) => {
if (event === "edit") {
console.log(`Row ${rowId} was edited - send to server`);
} else if (event === "new") {
console.log(`Row ${rowId} was inserted`);
} else if (event === "delete") {
console.log(`Row ${rowId} was deleted`);
}
});Use cases: Quick-edit forms, live dashboards, immediate feedback systems.
Commit Mode
Edits are queued as pending changes. User must explicitly commit to confirm all changes at once.
const table = new ExtableCore({
root: container,
schema,
defaultData,
defaultView: {},
options: {
editMode: "commit", // Require explicit commit
},
});
// Monitor pending changes to update UI
table.subscribeTableState((state) => {
const saveButton = document.getElementById("save-btn");
// Enable save button only when there are pending changes
saveButton!.disabled = !state.canCommit;
console.log(`${state.pendingCellCount} cells pending`);
});When user clicks "Save" / "Submit" / "Confirm" button (implemented by your app):
async function handleSave() {
try {
const changes = await table.commit();
// Send delta to server
const response = await fetch("/api/table/sync", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ changes }),
});
if (response.ok) {
console.log("Changes saved successfully");
}
} catch (error) {
console.error("Save failed:", error);
}
}Use cases: Form workflows, bulk imports, transactional updates, audit trails.
Testing
For unit tests and E2E testing strategies, see the Unit Testing Guide.