Undo Functionality with the Command Pattern in JavaScript

In object-oriented programming, the command pattern is a behavioural design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters. - Wikipedia

To summarise the Command pattern is a way of separating methods from classes. The reason to do this is so that commands can be reused and by doing so classes that rely on similar functionality do not depend on each other. 

So how does the Command pattern help implement undo functionality? A Command is a set of 2 methods:

  • Execute: A function that does the action of the command
  • Undo: A function that reverses the action of the command 

We can undo all the executions by calling the undo method from each command. Lets look at a simple example:

var num = 1;

var command = {
  execute: ()=> num++, // add num
  undo: ()=> num-- // sub num
};

command.execute(); // 2
command.execute(); // 3
command.execute(); // 4
command.undo(); // 3
command.undo(); // 2
command.undo(); // 1 <-- original value

Breaking it down to core concepts we can see that the command is just an object with an action and a reverse of that action. 

A way we can integrate this into classes is by making classes into Invokers. Invokers execute commands and manage the history of the executions.

function NumInvoker(n) {
  this.data = n; // our changing value
  this.history = []; // commands executed
}

NumInvoker.prototype.execute = function(cmd) {
  this.history.push(cmd); // add to history
  cmd.execute.call(this); // execute with this === Num
};

NumInvoker.prototype.undo = function() {
  var cmd = this.history.pop(); // get last executed command
  cmd.undo.call(this); // call undo 
};

var Add = {
  execute: function() {
    this.data += 1;
  },
  undo: function() {
    this.data -= 1;
  }
};

var three = new NumInvoker(3);
three.execute(Add); // 4
three.execute(Add); // 5
three.execute(Add); // 6
three.undo(); three.undo(); three.undo(); // 3

Real World Use Case

We can use the Command pattern to easily keep a track of changes to a input field: 

function Invoker() {
  this.history = [];
}

Invoker.prototype.execute = function(cmd) {
  this.history.push(cmd);
  console.log(cmd);
  return cmd.execute(this);
};

Invoker.prototype.undo = function() {
  var cmd = this.history.pop();
  return cmd.undo(this);
};

function TextArea(value) {
  this.value = value || "";
  Invoker.call(this);
}

TextArea.prototype = Object.create(Invoker.prototype);

TextArea.prototype.render = function() {
  $("input").val(this.value);
};

function InputCommand(key, value) {
  this.key = key;
  this.value = value;
  this.cache = "";
}

InputCommand.prototype.execute = function(ctx){
  this.cache = ctx[this.key];
  ctx[this.key] = this.value;
};

InputCommand.prototype.undo = function(ctx) {
  ctx[this.key] = this.cache;
};

var txt = new TextArea();

$("input").keyup(function(){
  txt.execute(new InputCommand("value", $(this).val()));
  txt.render();
});

$("#undo").click(function(){
  txt.undo();
  txt.render();
});

See the Pen ozyaPb by kameron tanseli (@kamerontanseli) on CodePen.

On the input change the TextArea class executes the InputCommand. This sets the current value of the  input as the TextArea value and pushes the command to the history. When we call undo we basically set the TextArea value to the last value the command had before execution.   

What can I do with the command pattern?

Now that you know how to implement the command pattern you can:

  • Decouple similar logic from classes 
  • Implement a rollback for certain methods 

On a final note if you have any questions or improvements to the code or explanations above please comment below so I can make this article more informative to the community.