halo! Opps I spelled it wrong. Let me Ctrl+z this. Ohh wait your app doesn't support undo and redo, what a shame :( Okay in this article lets fix that.
But instead of working with a big and complex project lets use this simple class that does some calculations.
class Calculate { constructor(initialValue){ this.value = initialValue } add(val){ this.value += val } sub(val){ this.value -= val } mul(val){ this.value *= val } div(val){ this.value /= val } } const num = new Calculate(0) num.add(10) // Value: 0 + 10 = 10 num.add(20) // Value: 10 + 20 = 30 num.mul(2) // Value: 30 * 2 = 60 num.sub(100) // Value: 60 - 100 = -40 console.log(num.value) // Output: -40
Now we want to add undo and redo functionality to our class.
So basically we should be able to so this
const num = new Calculate(0) num.add(10) num.add(20) num.mul(2) num.sub(100) num.undo() num.undo() num.redo() console.log(num.value) // Expected output: 60
Okay so first we will create another class called Executor
that will have a execute
function that take two functions:
- Function that does the indented operation
- Function that undo the operation
class Executor { constructor(){ // Stores the undo functions this.undoQueue = [] // Stores the redo functions this.redoQueue = [] } execute(fn, undoFn){ fn() this.undoQueue.push(() => { undoFn() // The redo will be added to queue only after the undo is executed this.redoQueue.push(fn) // calling fn after undoFn will be redoing the same operation }) } undo(){ if(this.undoQueue.length > 0){ this.undoQueue.pop()() } } redo(){ if(this.redoQueue.length > 0){ this.redoQueue.pop()() } } }
So now lets use the Executor
in our Calculate
class.
class Calculate { constructor(initialValue){ this.value = initialValue this.executor = new Executor() } add(val){ this.executor.execute( () => this.value += val, //Function to execute () => this.value -= val //Function to undo ) } sub(val){ this.executor.execute( () => this.value -= val, //Function to execute () => this.value += val //Function to undo ) } mul(val){ this.executor.execute( () => this.value *= val, //Function to execute () => this.value /= val //Function to undo ) } div(val){ this.executor.execute( () => this.value /= val, //Function to execute () => this.value *= val //Function to undo ) } redo(){ this.executor.redo() } undo(){ this.executor.undo() } }
So now we can simply use num.undo()
to undo the operation and num.redo()
to redo it.
So here is the full code
class Calculate { constructor(initialValue){ this.value = initialValue this.executor = new Executor() } add(val){ this.executor.execute( () => this.value += val, () => this.value -= val ) } sub(val){ this.executor.execute( () => this.value -= val, () => this.value += val ) } mul(val){ this.executor.execute( () => this.value *= val, () => this.value /= val ) } div(val){ this.executor.execute( () => this.value /= val, () => this.value *= val ) } redo(){ this.executor.redo() } undo(){ this.executor.undo() } } class Executor { constructor(){ this.undoQueue = [] this.redoQueue = [] } execute(fn, undoFn){ fn() this.undoQueue.push(() => { undoFn() this.redoQueue.push(fn) }) } undo(){ if(this.undoQueue.length > 0){ this.undoQueue.pop()() } } redo(){ if(this.redoQueue.length > 0){ this.redoQueue.pop()() } } } const num = new Calculate(0) num.add(10) num.add(20) num.mul(2) num.sub(100) num.undo() num.undo() num.redo() console.log(num.value) // Output: 60
Top comments (2)
Maybe you should also clean the redoQueue in execute method.
Might be a good idea