Skip to content

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:

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

tsx
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.

typescript
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:

html
<div class="app">
  <aside class="sidebar"><!-- Menu --></aside>
  <div class="main">
    <div class="toolbar"><!-- Controls --></div>
    <div id="table-root"></div>
  </div>
</div>
typescript
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:

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 width and height (not auto)
  • min-width: 0 and min-height: 0 in flex/grid layouts
  • Table responds automatically to container size changes

Customization

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.

typescript
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).

typescript
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.

typescript
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):

typescript
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.

Released under the Apache 2.0 License