Products

History Tracker

The Jspreadsheet History Tracker automatically records all changes to the spreadsheet and provides undo/redo functionality through Ctrl+Z and Ctrl+Y keyboard shortcuts. Developers can monitor interactions through historical events and manage the history stack programmatically, including options to ignore specific operations or cascade multiple actions into a single history entry.

Documentation

Methods

The following methods provide programmatic control over the history tracker. The undo() and redo() methods are automatically bound to Ctrl+Z and Ctrl+Y keyboard shortcuts:

Method Description
history.undo Undo the last spreadsheet changes.
jspreadsheet.history.undo() : void
history.redo Redo the most recent spreadsheet changes.
jspreadsheet.history.redo() : void
history.reset Clear all history entries.
jspreadsheet.history.reset() : void

Advanced usage

Method Description
history(object) Add a new entry to the history tracker.
jspreadsheet.history(changes: Object) : void

Events

The following events are triggered during history operations:

Event Description
onredo onredo(worksheet: Object, info: Object) : null
Triggered after a redo operation is performed.
onundo onundo(worksheet: Object, info: Object) : null
Triggered after an undo operation is performed.

Interface

The history tracker interface provides the following properties and methods:

Property Description
index: number Current position in the history stack
actions: [] Array of history items
cascade: boolean When true, the next action is cascaded into the same existing history element
ignore: boolean When true, no history will be added to the tracker
progress: string Indicates if a history process is ongoing
undo: () => void Undo the last action
redo: () => void Redo the most recent action
reset: () => void Clear all history entries

Examples

Programmatic History Control

This example demonstrates how to control undo/redo operations programmatically using the global jspreadsheet.history object.

<html>
<script src="https://jspreadsheet.com/v12/jspreadsheet.js"></script>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jspreadsheet.com/v12/jspreadsheet.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons" />

<div id='spreadsheet'></div>

<p><input type="button" value="Undo" id="btn1" />
<input type="button" value="Redo" id="btn2" />
<input type="button" value="Reset" id="btn3" /></p>

<script>
// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

// Create the spreadsheet
let spreadsheet = jspreadsheet(document.getElementById('spreadsheet'), {
    worksheets: [{
        minDimensions: [6, 6],
        data: [
            ['Sample', 'Data', 'Here'],
            ['Row 2', 'Value', '123'],
            ['Test', 'More', 'Data']
        ]
    }],
    onundo: function(worksheet, info) {
        console.log('Undo action:', info);
    },
    onredo: function(worksheet, info) {
        console.log('Redo action:', info);
    }
});

document.getElementById("btn1").onclick = () => {
    console.log('History index before undo:', jspreadsheet.history.index);
    jspreadsheet.history.undo();
}
document.getElementById("btn2").onclick = () => {
    console.log('History index before redo:', jspreadsheet.history.index);
    jspreadsheet.history.redo();
}
document.getElementById("btn3").onclick = () => {
    console.log('Clearing', jspreadsheet.history.actions.length, 'history items');
    jspreadsheet.history.reset();
}
</script>
</html>
import React, {useRef} from "react";
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

export default function App() {
    // Spreadsheet array of worksheets
    const spreadsheet = useRef();

    const handleUndo = () => {
        console.log('History before undo:', jspreadsheet.history.index);
        jspreadsheet.history.undo();
    };

    const handleRedo = () => {
        console.log('History before redo:', jspreadsheet.history.index);
        jspreadsheet.history.redo();
    };

    const handleReset = () => {
        console.log('Clearing', jspreadsheet.history.actions.length, 'history items');
        jspreadsheet.history.reset();
    };

    const onundo = (worksheet, info) => {
        console.log('Undo event:', info);
    };

    const onredo = (worksheet, info) => {
        console.log('Redo event:', info);
    };

    // Render component
    return (
        <>
            <Spreadsheet ref={spreadsheet} onundo={onundo} onredo={onredo}>
                <Worksheet
                    minDimensions={[8, 8]}
                    data={[['Sample', 'Data', 'Here'], ['Row 2', 'Value', '123']]}
                />
            </Spreadsheet>
            <input type="button" value="Undo" onClick={handleUndo}/>
            <input type="button" value="Redo" onClick={handleRedo}/>
            <input type="button" value="Reset" onClick={handleReset}/>
        </>
    );
}
<template>
    <Spreadsheet ref="spreadsheet" :onundo="onundo" :onredo="onredo">
        <Worksheet :minDimensions="[8,8]" :data="data" />
    </Spreadsheet>
    <input type="button" value="Undo" @click="undo" />
    <input type="button" value="Redo" @click="redo" />
    <input type="button" value="Reset" @click="reset" />
</template>

<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    methods: {
        undo() {
            console.log('History before undo:', jspreadsheet.history.index);
            jspreadsheet.history.undo();
        },
        redo() {
            console.log('History before redo:', jspreadsheet.history.index);
            jspreadsheet.history.redo();
        },
        reset() {
            console.log('Clearing', jspreadsheet.history.actions.length, 'history items');
            jspreadsheet.history.reset();
        },
        onundo(worksheet, info) {
            console.log('Undo event:', info);
        },
        onredo(worksheet, info) {
            console.log('Redo event:', info);
        }
    },
    data() {
        return {
            data: [['Sample', 'Data', 'Here'], ['Row 2', 'Value', '123']]
        }
    }
}
</script>
import { Component, ViewChild, ElementRef } from '@angular/core';
import jspreadsheet from 'jspreadsheet';
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

// Create component
@Component({
  standalone: true,
  selector: 'app-root',
  template: `<div #spreadsheet></div>
        <input type="button" value="Undo" (click)="undo()" />
        <input type="button" value="Redo" (click)="redo()" />
        <input type="button" value="Reset" (click)="reset()" />`,
})
export class AppComponent {
  @ViewChild('spreadsheet') spreadsheet: ElementRef;
  // Worksheets
  worksheets: jspreadsheet.worksheetInstance[];
  // Create a new data grid
  ngAfterViewInit() {
    // Create spreadsheet
    this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
      worksheets: [
        {
          minDimensions: [8, 8],
          data: [
            ['Sample', 'Data', 'Here'],
            ['Row 2', 'Value', '123'],
            ['Test', 'More', 'Data']
          ]
        },
      ],
      onundo: (worksheet, info) => {
        console.log('Undo event:', info);
      },
      onredo: (worksheet, info) => {
        console.log('Redo event:', info);
      }
    });
  }
  undo(): void {
    console.log('History before undo:', jspreadsheet.history.index);
    jspreadsheet.history.undo();
  }

  redo(): void {
    console.log('History before redo:', jspreadsheet.history.index);
    jspreadsheet.history.redo();
  }

  reset(): void {
    console.log('Clearing', jspreadsheet.history.actions.length, 'history items');
    jspreadsheet.history.reset();
  }
}

Ignoring History for Specific Operations

This example shows how to make changes without adding them to the history stack using the ignore flag.

<html>
<script src="https://jspreadsheet.com/v12/jspreadsheet.js"></script>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jspreadsheet.com/v12/jspreadsheet.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />

<div id='spreadsheet'></div>

<p><input type="button" value="Set A1 (untracked)" id="btn1" />
<input type="button" value="Undo" id="btn2" /></p>

<script>
// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

// Create the spreadsheet
let spreadsheet = jspreadsheet(document.getElementById('spreadsheet'), {
    worksheets: [{
        minDimensions: [6, 6],
    }],
});

document.getElementById("btn1").onclick = () => {
    // Ignored operation - will not be tracked in history
    jspreadsheet.history.ignore = true;
    spreadsheet[0].setValue('A1', Math.random()*1000);
    jspreadsheet.history.ignore = false;
}

document.getElementById("btn2").onclick = () => {
    jspreadsheet.history.undo();
}
</script>
</html>
import React, {useRef} from "react";
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

export default function App() {
    const spreadsheet = useRef();

    const setUntrackedValue = () => {
        if (spreadsheet.current) {
            jspreadsheet.history.ignore = true;
            spreadsheet.current[0].setValue('A1', Math.random() * 1000);
            jspreadsheet.history.ignore = false;
        }
    };

    const handleUndo = () => {
        jspreadsheet.history.undo();
    };

    return (
        <>
            <Spreadsheet ref={spreadsheet}>
                <Worksheet minDimensions={[6, 6]} />
            </Spreadsheet>
            <input type="button" value="Set A1 (untracked)" onClick={setUntrackedValue}/>
            <input type="button" value="Undo" onClick={handleUndo}/>
        </>
    );
}
<template>
    <Spreadsheet ref="spreadsheet">
        <Worksheet :minDimensions="[6,6]" />
    </Spreadsheet>
    <input type="button" value="Set A1 (untracked)" @click="setUntrackedValue" />
    <input type="button" value="Undo" @click="undo" />
</template>

<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    methods: {
        setUntrackedValue() {
            if (this.$refs.spreadsheet.current[0]) {
                jspreadsheet.history.ignore = true;
                this.$refs.spreadsheet.current[0].setValue('A1', Math.random() * 1000);
                jspreadsheet.history.ignore = false;
            }
        },
        undo() {
            jspreadsheet.history.undo();
        }
    }
}
</script>
import { Component, ViewChild, ElementRef } from '@angular/core';
import jspreadsheet from 'jspreadsheet';
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

@Component({
  standalone: true,
  selector: 'app-root',
  template: `<div #spreadsheet></div>
        <input type="button" value="Set A1 (untracked)" (click)="setUntrackedValue()" />
        <input type="button" value="Undo" (click)="undo()" />`,
})
export class AppComponent {
  @ViewChild('spreadsheet') spreadsheet: ElementRef;
  worksheets: jspreadsheet.worksheetInstance[];

  ngAfterViewInit() {
    this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
      worksheets: [
        {
          minDimensions: [6, 6],
        },
      ],
    });
  }

  setUntrackedValue(): void {
    jspreadsheet.history.ignore = true;
    this.worksheets[0].setValue('A1', Math.random() * 1000);
    jspreadsheet.history.ignore = false;
  }

  undo(): void {
    jspreadsheet.history.undo();
  }
}

Cascading History Actions

This example shows how to group multiple changes into a single undo action using the cascade flag.

<html>
<script src="https://jspreadsheet.com/v12/jspreadsheet.js"></script>
<script src="https://jsuites.net/v6/jsuites.js"></script>
<link rel="stylesheet" href="https://jspreadsheet.com/v12/jspreadsheet.css" type="text/css" />
<link rel="stylesheet" href="https://jsuites.net/v6/jsuites.css" type="text/css" />

<div id='spreadsheet'></div>

<p><input type="button" value="Set Value & Style (cascaded)" id="btn1" class="jss_object" />
<input type="button" value="Undo" id="btn2" class="jss_object" /></p>

<script>
// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

// Create the spreadsheet
let spreadsheet = jspreadsheet(document.getElementById('spreadsheet'), {
    worksheets: [{
        minDimensions: [6, 6],
    }],
});

document.getElementById("btn1").onclick = () => {
    // Generate random cell coordinates
    let col = Math.floor(Math.random() * 6);
    let row = Math.floor(Math.random() * 6);
    let cellName = jspreadsheet.helpers.getCellNameFromCoords(col, row);

    // Enable cascade to group multiple actions
    spreadsheet[0].setValue(cellName, 'test');
    jspreadsheet.history.cascade = true;
    spreadsheet[0].setStyle(cellName, 'background-color: red');
    jspreadsheet.history.cascade = false;
}

document.getElementById("btn2").onclick = () => {
    jspreadsheet.history.undo();
}
</script>
</html>
import React, {useRef} from "react";
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/react";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

export default function App() {
    const spreadsheet = useRef();

    const setCascadedValueAndStyle = () => {
        if (spreadsheet.current) {
            // Generate random cell coordinates
            let col = Math.floor(Math.random() * 6);
            let row = Math.floor(Math.random() * 6);
            let cellName = jspreadsheet.helpers.getCellNameFromCoords(col, row);

            // Enable cascade to group multiple actions
            spreadsheet.current[0].setValue(cellName, 'test');
            jspreadsheet.history.cascade = true;
            spreadsheet.current[0].setStyle(cellName, 'background-color: red');
            jspreadsheet.history.cascade = false;
        }
    };

    const handleUndo = () => {
        jspreadsheet.history.undo();
    };

    return (
        <>
            <Spreadsheet ref={spreadsheet}>
                <Worksheet minDimensions={[6, 6]} />
            </Spreadsheet>
            <input type="button" value="Set Value & Style (cascaded)" onClick={setCascadedValueAndStyle} className="jss_object"/>
            <input type="button" value="Undo" onClick={handleUndo} className="jss_object"/>
        </>
    );
}
<template>
    <Spreadsheet ref="spreadsheet">
        <Worksheet :minDimensions="[6,6]" />
    </Spreadsheet>
    <input type="button" value="Set Value & Style (cascaded)" @click="setCascadedValueAndStyle" class="jss_object" />
    <input type="button" value="Undo" @click="undo" class="jss_object" />
</template>

<script>
import { Spreadsheet, Worksheet, jspreadsheet } from "@jspreadsheet/vue";
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

export default {
    components: {
        Spreadsheet,
        Worksheet,
    },
    methods: {
        setCascadedValueAndStyle() {
            if (this.$refs.spreadsheet.current[0]) {
                // Generate random cell coordinates
                let col = Math.floor(Math.random() * 6);
                let row = Math.floor(Math.random() * 6);
                let cellName = jspreadsheet.helpers.getCellNameFromCoords(col, row);

                // Enable cascade to group multiple actions
                this.$refs.spreadsheet.current[0].setValue(cellName, 'test');
                jspreadsheet.history.cascade = true;
                this.$refs.spreadsheet.current[0].setStyle(cellName, 'background-color: red');
                jspreadsheet.history.cascade = false;
            }
        },
        undo() {
            jspreadsheet.history.undo();
        }
    }
}
</script>
import { Component, ViewChild, ElementRef } from '@angular/core';
import jspreadsheet from 'jspreadsheet';
import "jsuites/dist/jsuites.css";
import "jspreadsheet/dist/jspreadsheet.css";

// Set your JSS license key (The following key only works for one day)
jspreadsheet.setLicense('YTRmYTJiNDQ0NDk5Mzc5MDUwNjc0ZDQ3NDAyYjZkY2NiYTFkZmFmZGI2NTk1ZDhiNTIwY2E0Njk0MTlmOTg5MzBiYWFmOGNlMWYwZDNjOGRjOGMzOTJhNGMwZWEyOGZjZDllODA0MWQxZTg0ZjE3N2UyY2ZlOTY5ZDkxOWYxNmEsZXlKamJHbGxiblJKWkNJNklpSXNJbTVoYldVaU9pSktjM0J5WldGa2MyaGxaWFFpTENKa1lYUmxJam94TnpVNU56azJOell5TENKa2IyMWhhVzRpT2xzaWFuTndjbVZoWkhOb1pXVjBMbU52YlNJc0ltTnZaR1Z6WVc1a1ltOTRMbWx2SWl3aWFuTm9aV3hzTG01bGRDSXNJbU56WWk1aGNIQWlMQ0p6ZEdGamEySnNhWFI2TG1sdklpd2lkMlZpWTI5dWRHRnBibVZ5TG1sdklpd2lkMlZpSWl3aWJHOWpZV3hvYjNOMElsMHNJbkJzWVc0aU9pSXpOQ0lzSW5OamIzQmxJanBiSW5ZM0lpd2lkamdpTENKMk9TSXNJbll4TUNJc0luWXhNU0lzSW1Ob1lYSjBjeUlzSW1admNtMXpJaXdpWm05eWJYVnNZU0lzSW5CaGNuTmxjaUlzSW5KbGJtUmxjaUlzSW1OdmJXMWxiblJ6SWl3aWFXMXdiM0owWlhJaUxDSmlZWElpTENKMllXeHBaR0YwYVc5dWN5SXNJbk5sWVhKamFDSXNJbkJ5YVc1MElpd2ljMmhsWlhSeklpd2lZMnhwWlc1MElpd2ljMlZ5ZG1WeUlpd2ljMmhoY0dWeklpd2labTl5YldGMElsMHNJbVJsYlc4aU9uUnlkV1Y5');

@Component({
  standalone: true,
  selector: 'app-root',
  template: `<div #spreadsheet></div>
        <input type="button" value="Set Value & Style (cascaded)" (click)="setCascadedValueAndStyle()" class="jss_object" />
        <input type="button" value="Undo" (click)="undo()" class="jss_object" />`,
})
export class AppComponent {
  @ViewChild('spreadsheet') spreadsheet: ElementRef;
  worksheets: jspreadsheet.worksheetInstance[];

  ngAfterViewInit() {
    this.worksheets = jspreadsheet(this.spreadsheet.nativeElement, {
      worksheets: [
        {
          minDimensions: [6, 6],
        },
      ],
    });
  }

  setCascadedValueAndStyle(): void {
    // Generate random cell coordinates
    let col = Math.floor(Math.random() * 6);
    let row = Math.floor(Math.random() * 6);
    let cellName = jspreadsheet.helpers.getCellNameFromCoords(col, row);

    // Enable cascade to group multiple actions
    this.worksheets[0].setValue(cellName, 'test');
    jspreadsheet.history.cascade = true;
    this.worksheets[0].setStyle(cellName, 'background-color: red');
    jspreadsheet.history.cascade = false;
  }

  undo(): void {
    jspreadsheet.history.undo();
  }
}