javascriptundo-redo

How to make a undo/redo function


I want to add an undo/redo function in my script. I have looked around and see some suggestions, most of them recommended to use the command pattern.

The function must work over one page - after a reload of the page the function must able to redo/undo the last things.

I don't know how the command pattern works, I think about to create a object, to store the a name of the function, the old and the new value - but I'm not sure if this is a efficient way to do this or not.

Maybe somebody could give me a small example how the code for an undo/redo function should look.


Solution

  • While there's many different ways to conceptuatlize undo/redo, by far the most established are the Memento Pattern and the Command Pattern.

    Both are part of the original GoF:Behavioral Design Patterns. and they are so strongly related to undo/redo that some just call them the "undo patterns".

    note: these are OOP-y patterns; if you're doing functional programming, you're better off looking into how Redux supports time-travel.

    Theory 101:

    Almost any software can be modelled as a state machine, which is a conceptual machine that changes its state in reaction to a transition.

    Simply put: you ask it to do something, which it executes (transition), and now there's a different/new thing (state) on your screen/disk etc.

    So in essence, the interaction with the system takes place via transitions. The end effect is a change (or not) of the program state in one way or another.

    Now it's easy to conceptualise:

    Their difference stems from what they process/store in order to return to a previous state.

    The Memento pattern

    In the Memento Pattern you capture the whole current state:

    When you want to undo:

    Pros:

    Cons:

    The Command Pattern

    Each action is coded as a Command with 2 methods:

    Example:

    
    // framework must provide at the very
    // least:
    // - a CommandManager (or UndoManager)
    // - an abstract Command that you extend into
    //   your own custom commands
    
    
    class MoveCommand extends Command {
      constructor(name, x, y) {
        super()
        this.name = name
        this.x = x
        this.y = y
      }
      
      execute() {
        const person = this.find()
        if (person) {
          person.moveBy(this.x, this.y) // action
        }
      }
      
      undo() {
        const person = this.find() // inverse action
        if (person) {
          person.moveBy(-this.x, -this.y)
        }
      }
    }
    
    // more commands...
    // e.g: ScaleCommand, RotateCommand... 
    
    // your program 
    
    const manager = new CommandManager()
    
    items.push(new Person('Bob'))
    items.push(new Person('Alice'))
    
    manager.execute(new MoveCommand('Bob', 50, 50))
    manager.execute(new MoveCommand('Bob', 100, 50))
    manager.execute(new MoveCommand('Bob', 100, 50))
    
    manager.execute(new MoveCommand('Alice', 100, 50))
    manager.execute(new MoveCommand('Alice', 100, 50))
    
    console.log('-- thats far enough, lets go back --')
    
    manager.undo() // undo Alice's last move
    manager.undo() // undo Alice's last move - 1
    manager.undo() // undo Bob's last move
    manager.undo() // undo Bob's last move - 1
    manager.undo() // undo Bob's last move - 2
    manager.undo() // nothing to undo
    
    

    Pros:

    Cons: