Undo
The Undo class allows to undo and redo the execution of a series of function calls with or without arguments. NOTE: The Undo class is an independent code which can be used without Objective.js in any project in JavaScript.
See Editor for a basic example and the articles Editing a video clip and Configure charts by Plotly among others for examples of more complete editors.
An instance of Undo keeps a stack of function calls.
The push
method adds a function call to the top of the stack.
The undo
method pops the function call at the top of the stack and runs it.
The redo
method pops the function call added to the stack by the undo
method and runs it.
A function which can be undone pushes the function call which cancels its execution.
An instance of Model can undo and redo changes to its data.
An instance of Editor has actions to undo and redo modifications it does on a model.
- function Undo(size = 100) {
- if (! Number.isInteger(size))
- throw new TypeError();
- if (size < 1)
- throw new RangeError();
- this._size = size;
- this._undo = [];
- this._redo = null;
- }
Creates an instance of the class Undo.
size
defines the number of function calls the stack can contain, 100 by default.
- Object.defineProperty(Undo.prototype, 'size', {
- get: function() {
- return this._size;
- },
- set: function(n) {
- if (! Number.isInteger(n))
- throw new TypeError();
- if (n < 1)
- throw new RangeError();
- if (this._size > n)
- this._undo = this._undo.slice(-n);
- this._redo = null;
- this._size = n;
- }
- });
size
is an accessor which returns or changes the size of the stack of function calls managed by this
.
- Object.defineProperty(Undo.prototype, 'undoLength', {
- get: function() {
- return this._undo.length;
- }
- });
undoLength
is an accessor which returns the number of function calls which this
can undo.
- Object.defineProperty(Undo.prototype, 'redoLength', {
- get: function() {
- return this._redo === null ? 0 : this._redo.length;
- }
- });
redoLength
is an accessor which returns the number of function calls which this
can redo.
- Undo.prototype.push = function(f, ...args) {
- if (this._undo.length >= this._size)
- this._undo.shift();
- this._undo.push(args.length ? [f, args] : f);
- this._redo = null;
- return this;
- };
push
adds the function f
with the arguments args
to the stack of function calls to undo of this
.
If the stack of this
is full, the function call at the bottom of the stack of functions to undo is deleted.
IMPORTANT: Pushing a function to undo empties the stack of functions to redo.
- Undo.prototype.pop = function() {
- this._undo.pop();
- this._redo = null;
- return this;
- };
pop
removes the function call at the top of the stack of function calls to undo of this
.
NOTE: pop
is normally called only by undo
.
- Undo.prototype.undo = function() {
- let exp = this._undo.pop();
- if (exp === undefined)
- return false;
- let redo = this._redo;
- if (Array.isArray(exp))
- exp[0](...exp[1]);
- else
- exp();
- exp = this._undo.pop();
- if (exp === undefined)
- return true;
- this._redo = redo;
- if (this._redo === null)
- this._redo = [];
- else if (this._redo.length >= this._size)
- this._redo.shift();
- this._redo.push(exp);
- return true;
- };
undo
pops the function call at the top of the stack of functions to undo of this
and runs it.
A function which can be undone pushes the function call which cancels its execution.
undo
pops this call and adds it to stack of functions to redo of this
.
- Undo.prototype.redo = function() {
- if (! this._redo)
- return false;
- let exp = this._redo.pop();
- if (exp === undefined)
- return false;
- let redo = this._redo;
- if (Array.isArray(exp))
- exp[0](...exp[1]);
- else
- exp();
- this._redo = redo;
- return true;
- };
redo
pops the function call at the top of the stack of functions to redo this
and runs it.
- Undo.prototype.clear = function() {
- this._undo = [];
- this._redo = null;
- return this;
- };
clear
empties the stack of function calls to undo and redo.
Test
Download the code of the class Calculator and the test program of the class Undo:
- <?php head('javascript', '/objectivejs/Undo.js'); ?>
Adds the tag <script src="/objectivejs/Undo.js"></script>
to the <head>
section of the HTML document.
head
is a function of iZend.
Adapt the code to your development environment.
- <?php head('javascript', '/objectivejs/tests/Calculator.js'); ?>
Adds the tag <script src="/objectivejs/tests/Calculator.js"></script>
to the <head>
section of the HTML document.
- var undo = new Undo();
- console.log(undo.size); // 100
- undo.size = 20;
- console.log(undo.size); // 20
- console.log(undo.undoLength); // 0
Creates an instance of the class Undo, displays the size of its stack of function calls to undo, changes it and displays it.
- var calc = new Calculator();
Creates an instance of the class Calculator. See Objective.
- function fmul(val) {
- undo.push(() => fdiv(val));
- calc.mul(val);
- }
- function fdiv(val) {
- undo.push((v) => fmul(v), val); // context
- calc.div(val);
- }
fmul
pushes the division of the value of the calculator by val
before multiplying it by val
.
fdiv
pushes the multiplication of the value of the calculator by val
before dividing it by val
.
fmul
pushes a function. div
pushes a function and an argument.
- calc.value = 3;
- fmul(calc.value);
- fmul(4);
- console.log(calc.value); // 36
- console.log(undo.undoLength); // 2
- undo.undo();
- undo.undo();
- console.log(calc.value); // 3
- console.log(undo.undoLength); // 0
- console.log(undo.redoLength); // 2
- undo.undo(); // one too many
- undo.redo();
- undo.redo();
- console.log(calc.value); // 36
- console.log(undo.undoLength); // 2
- console.log(undo.redoLength); // 0
- undo.redo(); // one too many
- undo.undo();
- undo.undo();
- console.log(calc.value); // 3
Display the page generated by the file testUndo.phtml and check the trace in the console of the browser:
100
20
0
36
2
3
0
2
36
2
0
3
Comments