Undo functionality in paint applications

I was working on a personal project that involved painting with an HTML canvas and wondered how easy it would be to incorporate 'undo functionality'? So I did a little research to see what people were proposing, and whilst I came across a few suggestions, none of them included an example of an implementation. Therefore I decided to look into the matter myself and share the result.

Example

Download the code for the example Paint application on GitHub.


    Choosing the pattern

    As with most challenges, there are multiple ways to approach the problem. In this case, the two most frequently suggested methods are to use either the Command or Memento design patterns. The Memento pattern is designed to save the state ('mementos') of an object, whilst the Command pattern is designed to change the state of the object by using 'Commands', which can be invoked repeatably.

    After some deliberation, I chose the Command pattern as it provides an elegant solution for encapsulating the drawing logic. Added to this, it doesn't consume nearly as much memory as the Memento pattern, in which I was required to save the state of the canvas each time a modification was made.

    Implementing the Command pattern

    As previously touched upon, the Command pattern is used as a means to encapsulate behavioural logic in order for it to be executed at a later date. This pattern, explained in greater detail on wikipedia, consists of four parts; the Client, Receiver, Command and Invoker. All of these are described below in further detail:

    UML diagram of the Command design pattern

    Client

    The Client is where most of the application's logic is stored. The example application's Client is responsible for creating the commands to draw the shapes and clear the canvas. It then adds them to the invoker, which we'll come to later.

    The code for the Client includes the 'undo' function; this is where the application uses the Invoker to undo the last executed command. The 3 steps below show how the logic is executed when the 'undo' button is clicked...

    1. Remove the last Command added to the Invoker.
    2. Clear the canvas by executing the ClearCanvasCommand, without adding it to the Invoker.
    3. Execute all the Commands in the order they were added to the Invoker.

    Alternative implementations

    It is worth noting that there are other ways to implement undo functionality with the Command pattern. For example, providing each Command with their own 'undo' function in order to reverse the actions they carried out against the Receiver when the 'execute' function was called. However, the problem with this is that the Commands in the example application will be modifying an HTML Canvas and therefore make reversing an action against it very difficult.

    FlyingTopHat.PaintApplication = (function (SquarePaintCommand,
        CirclePaintCommand,
        ClearCanvasCommand) {
    
        function PaintApplication(context2d, commandInvoker) {...}
    
        var p = PaintApplication.prototype;
    
        p.initialise = function(context2d, commandInvoker) {
            this._context2d = context2d;
            this._commandInvoker = commandInvoker;
        };
    
        p.drawRandomShape = function (x, y) {
            var cmd = (Math.random() > 0.5)
                ? new SquarePaintCommand(this._context2d)
                : new CirclePaintCommand(this._context2d);
    
            cmd.x = x;
            cmd.y = y;
    
            this._commandInvoker.addAndExecute(cmd);
        };
    
        p.clearAll = function() {
            this._commandInvoker.addAndExecute(
                new ClearCanvasCommand(this._context2d)
            );
        };
    
        p.undo = function() {
            this._commandInvoker.removeLast();
    
            new ClearCanvasCommand(this._context2d).execute();
    
            this._commandInvoker.executeAll();
        };
    
        p.getExecutedCommandNames = function () {...};
    
        return PaintApplication;
    
    }(FlyingTopHat.Command.SquarePaintCommand,
      FlyingTopHat.Command.CirclePaintCommand,
      FlyingTopHat.Command.ClearCanvasCommand));

    Receiver

    The Receiver, which in this instance is the Canvas, is acted upon by commands.

    The Command section provides an example of how a command is used to draw a square on the Canvas (Receiver). As with all the Commands, when this command is instantiated it is provided with a reference to the canvas. The command's 'execute' function, once invoked, will then use the canvas' 'beginPath', 'rect' and 'stroke' functions to draw the square.

    Invoker

    The Invoker exposes functions for executing and managing a stack of Commands. The two most important functions for the undo functionality are the 'removeLast' and 'executeAll' functions.

    FlyingTopHat.Command.CommandInvoker = (function(){
    
        function CommandInvoker(){...}
    
        var p = CommandInvoker.prototype;
    
        p.initialise = function (){
            this._commands = new Array();
        };
    
        p.getCommands = function (){...};
    
        p.addAndExecute = function (command){
            if (command.execute !== undefined) {
                this._commands.push(command);
    
                command.execute();
            }
        };
    
        p.executeAll = function(){
            for (var i = 0; i < this._commands.length; i++) {
                this._commands[i].execute();
            }
        };
    
        p.removeLast = function(){
            var length = this._commands.length;
            if (length > 0) {
                this._commands = this._commands.slice(0, length-1);
            }
        };
    
        return CommandInvoker;
    }());

    Command

    The Command modifies the state of the Receiver and at the point of instantiation, a reference is provided for the Receiver, which then alters it when executed. This process can be seen in the code below, which is responsible for drawing a square of a fixed dimension at the co-ordinate it's given. This occurs when its 'execute' function is called by the Invoker that it has been added to.

    FlyingTopHat.Command.SquarePaintCommand = (function(CanvasCommand) {
    
        function SquarePaintCommand(context2d) {...}
    
        var p = SquarePaintCommand.prototype = new CanvasCommand();
    
        p.x = 0;
    
        p.y = 0;
    
        p.execute = function() {
            this._context2d.beginPath();
            this._context2d.rect(this.x, this.y, 20, 20);
            this._context2d.stroke();
        };
    
        p.toString = function () {...};
    
        return SquarePaintCommand;
    }(FlyingTopHat.Command.CanvasCommand));

    Conclusion

    Overall, there are many different ways to implement undo functionality, such as using the Momento pattern or different variations of the Command pattern. However, I believe the aforementioned implementation of the Command pattern, which can be found on GitHub, was by far the simplest and most efficient method when working with the HTML Canvas.