CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tabulator-tables

Interactive table generation JavaScript library with sorting, filtering, editing, formatting, and extensive customization capabilities

Pending
Overview
Eval results
Files

history-undo.mddocs/

History and Undo/Redo

Comprehensive history tracking and undo/redo functionality for all table operations including cell edits, row additions, deletions, and movements.

Capabilities

History Management

Core functions for managing operation history and performing undo/redo operations.

/**
 * Undo the last operation
 * @returns True if operation was undone, false if no operations to undo
 */
undo(): boolean;

/**
 * Redo the last undone operation  
 * @returns True if operation was redone, false if no operations to redo
 */
redo(): boolean;

/**
 * Get the number of operations that can be undone
 * @returns Number of undo operations available
 */
getHistoryUndoSize(): number;

/**
 * Get the number of operations that can be redone
 * @returns Number of redo operations available
 */
getHistoryRedoSize(): number;

/**
 * Clear all history
 */
clearHistory(): void;

History Configuration:

interface HistoryOptions {
  history?: boolean;
}

Usage Examples:

import { Tabulator } from "tabulator-tables";

// Enable history tracking
const table = new Tabulator("#table", {
  data: [
    { id: 1, name: "Alice", age: 25, department: "Engineering" },
    { id: 2, name: "Bob", age: 30, department: "Sales" },
    { id: 3, name: "Charlie", age: 28, department: "Marketing" }
  ],
  columns: [
    { title: "ID", field: "id" },
    { title: "Name", field: "name", editor: "input" },
    { title: "Age", field: "age", editor: "number" },
    { title: "Department", field: "department", editor: "list", 
      editorParams: { values: ["Engineering", "Sales", "Marketing"] } }
  ],
  history: true // Enable history tracking
});

// Undo/Redo operations
document.getElementById("undo-btn").addEventListener("click", () => {
  const undone = table.undo();
  if (undone) {
    console.log("Operation undone");
  } else {
    console.log("Nothing to undo");
  }
});

document.getElementById("redo-btn").addEventListener("click", () => {
  const redone = table.redo();
  if (redone) {
    console.log("Operation redone");
  } else {
    console.log("Nothing to redo");
  }
});

// Check history state
function updateHistoryUI() {
  const undoCount = table.getHistoryUndoSize();
  const redoCount = table.getHistoryRedoSize();
  
  document.getElementById("undo-count").textContent = undoCount;
  document.getElementById("redo-count").textContent = redoCount;
  
  document.getElementById("undo-btn").disabled = undoCount === 0;
  document.getElementById("redo-btn").disabled = redoCount === 0;
}

// Update UI after operations
table.on("historyUndo", updateHistoryUI);
table.on("historyRedo", updateHistoryUI);
table.on("dataChanged", updateHistoryUI);

// Clear history
document.getElementById("clear-history").addEventListener("click", () => {
  table.clearHistory();
  updateHistoryUI();
});

Trackable Operations

History automatically tracks the following operations when enabled:

Cell Editing

All cell value changes are tracked with old and new values.

// Cell edits are automatically tracked
table.on("cellEdited", function(cell) {
  console.log(`Cell ${cell.getField()} changed from ${cell.getOldValue()} to ${cell.getValue()}`);
  // This change is now in the history stack
});

// Programmatic cell updates are also tracked
table.updateData([
  { id: 1, name: "Alice Updated" } // This update will be tracked
]);

Row Operations

Row additions, deletions, and movements are tracked.

// Add row - tracked in history
table.addRow({ id: 4, name: "Diana", age: 32, department: "HR" });

// Delete row - tracked in history  
table.deleteRow(1);

// Move rows - tracked in history (if movableRows is enabled)
const row = table.getRow(2);
row.move(0); // Move to top

Bulk Operations

Multiple operations can be undone as a single action when performed together.

// Multiple edits in quick succession
table.updateData([
  { id: 1, name: "Alice Smith" },
  { id: 2, name: "Bob Johnson" },
  { id: 3, name: "Charlie Brown" }
]);

// Single undo will revert all changes
table.undo(); // Reverts all three name changes

Keyboard Shortcuts

Standard keyboard shortcuts are automatically enabled when history is active:

  • Ctrl+Z / Cmd+Z: Undo last operation
  • Ctrl+Y / Cmd+Y: Redo last undone operation
  • Ctrl+Shift+Z / Cmd+Shift+Z: Alternative redo shortcut
// Keyboard shortcuts work automatically
const table = new Tabulator("#table", {
  history: true // Shortcuts are enabled automatically
});

// Custom keyboard shortcut handling
document.addEventListener("keydown", function(e) {
  if ((e.ctrlKey || e.metaKey) && e.key === "z" && !e.shiftKey) {
    if (table.getHistoryUndoSize() > 0) {
      e.preventDefault();
      table.undo();
    }
  }
  
  if ((e.ctrlKey || e.metaKey) && (e.key === "y" || (e.key === "z" && e.shiftKey))) {
    if (table.getHistoryRedoSize() > 0) {
      e.preventDefault();
      table.redo();
    }
  }
});

History Events

React to history operations with dedicated events.

// History operation events
table.on("historyUndo", function(action, component, data) {
  console.log("Undone:", action, data);
  showNotification(`Undone: ${action}`);
});

table.on("historyRedo", function(action, component, data) {
  console.log("Redone:", action, data);
  showNotification(`Redone: ${action}`);
});

// Track specific operation types
table.on("historyUndo", function(action, component, data) {
  switch(action) {
    case "cellEdit":
      console.log(`Undid cell edit: ${data.oldValue} -> ${data.newValue}`);
      break;
    case "rowAdd":
      console.log("Undid row addition");
      break;
    case "rowDelete":  
      console.log("Undid row deletion");
      break;
    case "rowMove":
      console.log(`Undid row move: position ${data.posFrom} -> ${data.posTo}`);
      break;
  }
});

Advanced History Features

History State Management

Track and display history state information.

function getHistoryStatus() {
  const undoSize = table.getHistoryUndoSize();
  const redoSize = table.getHistoryRedoSize();
  
  return {
    canUndo: undoSize > 0,
    canRedo: redoSize > 0,
    undoCount: undoSize,
    redoCount: redoSize,
    totalOperations: undoSize + redoSize
  };
}

// Update UI based on history state
function updateHistoryDisplay() {
  const status = getHistoryStatus();
  
  document.getElementById("history-status").innerHTML = `
    <div>Operations: ${status.totalOperations}</div>
    <div>Can undo: ${status.undoCount} operations</div>
    <div>Can redo: ${status.redoCount} operations</div>
  `;
}

// Monitor all data changes
table.on("dataChanged", updateHistoryDisplay);
table.on("historyUndo", updateHistoryDisplay);
table.on("historyRedo", updateHistoryDisplay);

Conditional History Clearing

Clear history based on specific conditions or events.

// Clear history on major operations
table.on("dataLoaded", function() {
  table.clearHistory(); // Clear when new data is loaded
});

// Clear history periodically
setInterval(() => {
  const undoSize = table.getHistoryUndoSize();
  if (undoSize > 100) { // Limit history size
    table.clearHistory();
    console.log("History cleared - too many operations");
  }
}, 60000); // Check every minute

// Clear history on specific user actions
document.getElementById("reset-data").addEventListener("click", () => {
  table.clearData();
  table.clearHistory(); // Clear history when resetting data
});

History Integration with Other Features

History works seamlessly with other Tabulator features:

const table = new Tabulator("#table", {
  history: true,
  clipboard: true,        // Clipboard operations are tracked
  movableRows: true,      // Row movements are tracked
  pagination: false,      // Works better with history enabled
  dataTree: true,         // Tree operations are tracked
  groupBy: "department"   // Group changes are tracked
});

// Grouped data operations are tracked
table.setGroupBy("age");    // Tracked
table.setGroupBy(false);    // Tracked

// Tree operations are tracked
const row = table.getRow(1);
row.addTreeChild({ name: "New Child" }); // Tracked

Memory Management

For large datasets, consider history memory usage:

// Monitor history memory usage
function getHistoryMemoryUsage() {
  const undoSize = table.getHistoryUndoSize();
  const redoSize = table.getHistoryRedoSize();
  
  // Estimate memory usage (approximate)
  const estimatedSize = (undoSize + redoSize) * 100; // bytes per operation
  
  return {
    operations: undoSize + redoSize,
    estimatedBytes: estimatedSize
  };
}

// Clear history when memory usage is high
table.on("dataChanged", function() {
  const usage = getHistoryMemoryUsage();
  
  if (usage.operations > 500) { // Threshold
    console.warn("High history usage, consider clearing");
    // Optionally auto-clear oldest operations
    table.clearHistory();
  }
});

Custom History Actions

For advanced use cases, you can create custom undoable actions:

// Note: This is internal API - use with caution
// Custom actions would need to be implemented through table extensions

// Example of tracking custom operations
let customHistory = [];

function trackCustomOperation(description, undoAction, redoAction) {
  customHistory.push({
    description,
    undoAction,
    redoAction,
    timestamp: Date.now()
  });
}

// Custom bulk operation with undo
function customBulkUpdate(updates) {
  const originalData = updates.map(update => {
    const row = table.getRow(update.id);
    return { id: update.id, data: row.getData() };
  });
  
  // Perform updates
  table.updateData(updates);
  
  // Track for custom undo (this would need integration with Tabulator's history)
  trackCustomOperation(
    "Bulk update",
    () => {
      // Restore original data
      originalData.forEach(original => {
        table.updateRow(original.id, original.data);
      });
    },
    () => {
      // Reapply updates
      table.updateData(updates);
    }
  );
}

Types

interface HistoryOptions {
  history?: boolean;
}

interface HistoryAction {
  type: "cellEdit" | "rowAdd" | "rowDelete" | "rowMove";
  component: any;
  data: any;
}

interface CellEditData {
  oldValue: any;
  newValue: any;
}

interface RowAddData {
  data: any;
  pos: boolean;
  index: any;
}

interface RowDeleteData {
  data: any;
  pos: boolean;
  index: any;
}

interface RowMoveData {
  posFrom: number;
  posTo: number;
  to: any;
  after: boolean;
}

Install with Tessl CLI

npx tessl i tessl/npm-tabulator-tables

docs

cell-editing.md

clipboard.md

column-management.md

data-management.md

data-sorting.md

data-trees.md

event-system.md

filtering-search.md

history-undo.md

import-export.md

index.md

pagination.md

range-selection.md

row-grouping.md

table-construction.md

validation.md

tile.json