215

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.

  1. function Undo(size = 100) {
  2.     if (! Number.isInteger(size))
  3.         throw new TypeError();
  4.  
  5.     if (size < 1)
  6.         throw new RangeError();
  7.  
  8.     this._size = size;
  9.  
  10.     this._undo = [];
  11.     this._redo = null;
  12. }

Creates an instance of the class Undo. size defines the number of function calls the stack can contain, 100 by default.

  1. Object.defineProperty(Undo.prototype, 'size', {
  2.     get:    function() {
  3.         return this._size;
  4.     },
  5.     set:    function(n) {
  6.         if (! Number.isInteger(n))
  7.             throw new TypeError();
  8.  
  9.         if (n < 1)
  10.             throw new RangeError();
  11.  
  12.         if (this._size > n)
  13.             this._undo = this._undo.slice(-n);
  14.  
  15.         this._redo = null;
  16.  
  17.         this._size = n;
  18.     }
  19. });

size is an accessor which returns or changes the size of the stack of function calls managed by this.

  1. Object.defineProperty(Undo.prototype, 'undoLength', {
  2.     get:    function() {
  3.         return this._undo.length;
  4.     }
  5. });

undoLength is an accessor which returns the number of function calls which this can undo.

  1. Object.defineProperty(Undo.prototype, 'redoLength', {
  2.     get:    function() {
  3.         return this._redo === null ? 0 : this._redo.length;
  4.     }
  5. });

redoLength is an accessor which returns the number of function calls which this can redo.

  1. Undo.prototype.push = function(f, ...args) {
  2.     if (this._undo.length >= this._size)
  3.         this._undo.shift();
  4.  
  5.     this._undo.push(args.length ? [f, args] : f);
  6.  
  7.     this._redo = null;
  8.  
  9.     return this;
  10. };

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.

  1. Undo.prototype.pop = function() {
  2.     this._undo.pop();
  3.  
  4.     this._redo = null;
  5.  
  6.     return this;
  7. };

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.

  1. Undo.prototype.undo = function() {
  2.     let exp = this._undo.pop();
  3.  
  4.     if (exp === undefined)
  5.         return false;
  6.  
  7.     let redo = this._redo;
  8.  
  9.     if (Array.isArray(exp))
  10.         exp[0](...exp[1]);
  11.     else
  12.         exp();
  13.  
  14.     exp = this._undo.pop();
  15.  
  16.     if (exp === undefined)
  17.         return true;
  18.  
  19.     this._redo = redo;
  20.  
  21.     if (this._redo === null)
  22.         this._redo = [];
  23.     else if (this._redo.length >= this._size)
  24.         this._redo.shift();
  25.  
  26.     this._redo.push(exp);
  27.  
  28.     return true;
  29. };

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.

  1. Undo.prototype.redo = function() {
  2.     if (! this._redo)
  3.         return false;
  4.  
  5.     let exp = this._redo.pop();
  6.  
  7.     if (exp === undefined)
  8.         return false;
  9.  
  10.     let redo = this._redo;
  11.  
  12.     if (Array.isArray(exp))
  13.         exp[0](...exp[1]);
  14.     else
  15.         exp();
  16.  
  17.     this._redo = redo;
  18.  
  19.     return true;
  20. };

redo pops the function call at the top of the stack of functions to redo this and runs it.

  1. Undo.prototype.clear = function() {
  2.     this._undo = [];
  3.     this._redo = null;
  4.  
  5.     return this;
  6. };

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:

  1. <?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.

  1. <?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.

  1. var undo = new Undo();
  2.  
  3. console.log(undo.size);         // 100
  4.  
  5. undo.size = 20;
  6.  
  7. console.log(undo.size);         // 20
  8.  
  9. 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.

  1. var calc = new Calculator();

Creates an instance of the class Calculator. See Objective.

  1. function fmul(val) {
  2.     undo.push(() => fdiv(val));
  3.     calc.mul(val);
  4. }
  5.  
  6. function fdiv(val) {
  7.     undo.push((v) => fmul(v), val); // context
  8.     calc.div(val);
  9. }

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.

  1. calc.value = 3;
  2.  
  3. fmul(calc.value);
  4. fmul(4);
  5.  
  6. console.log(calc.value);        // 36
  7.  
  8. console.log(undo.undoLength);   // 2
  9.  
  10. undo.undo();
  11. undo.undo();
  12.  
  13. console.log(calc.value);        // 3
  14.  
  15. console.log(undo.undoLength);   // 0
  16. console.log(undo.redoLength);   // 2
  17.  
  18. undo.undo();    // one too many
  19.  
  20. undo.redo();
  21. undo.redo();
  22.  
  23. console.log(calc.value);        // 36
  24.  
  25. console.log(undo.undoLength);   // 2
  26. console.log(undo.redoLength);   // 0
  27.  
  28. undo.redo();    // one too many
  29.  
  30. undo.undo();
  31. undo.undo();
  32.  
  33. 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
SEE ALSO

Editor, Model, UndoPanel

Comments

Your comment:
[p] [b] [i] [u] [s] [quote] [pre] [br] [code] [url] [email] strip help 2000

Enter a maximum of 2000 characters.
Improve the presentation of your text with the following formatting tags:
[p]paragraph[/p], [b]bold[/b], [i]italics[/i], [u]underline[/u], [s]strike[/s], [quote]citation[/quote], [pre]as is[/pre], [br]line break,
[url]http://www.izend.org[/url], [url=http://www.izend.org]site[/url], [email]izend@izend.org[/email], [email=izend@izend.org]izend[/email],
[code]command[/code], [code=language]source code in c, java, php, html, javascript, xml, css, sql, bash, dos, make, etc.[/code].