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