shopping24 tech blog

s is for shopping

May 27, 2015 / by Kim Hogeling / Web developer / @kimhogeling

JavaScript command pattern for undo and redo

Being able to undo and redo actions is awesome! I already knew the command pattern, but as always everything is different in JavaScript. After figuring it out I want to share how it works.

Very short theory

The idea behind the command pattern is to sort of “thingify” actions. Instead of adding countless methods to objects, countless command classes are made. These can be passed to a subjects execute method. With “subject” I mean the object, that actually performs the command. If a subject keeps track of the commands which are passed it’s execute method, a history is created. This makes it possible to add undo and redo methods to those objects.

This complete changes the way I normally code. But I like it! I’m building my own JS game engine in my spare time, so tackling the command pattern is super helpful for that.

Example

Enough theory, lets get our hands dirty!

The supertype command class

To keep the commands short, readable and maintainable, I created a supertype called Command.

/**
 * The command supertype, which is inherited by subtype commands.
 * @constructor
 * @param {type} normalAction The normal action.
 * @param {type} undoAction The opposite of the normal action.
 * @param {type} actionValue The value, which will get passed to the action.
 * @returns {Command}
 */
function Command(normalAction, undoAction, actionValue) {
    this.execute = normalAction;
    this.undo = undoAction;
    this.value = actionValue;
}

More concrete commands will inherit this, but that comes later.

Subjects

Next we need something for performing commands. In my example I create one subject called Mountaineer, because I like mountaineers. Those brave people deserve a JS Class! Instances of mountaineers will keep track of their commands by themselves, so every mountaineer can undo and redo independently. The following example is already complete, containing the needed methods called execute, undo and redo.

/**
 * Class for creating brave mountain climbers.
 * @constructor
 * @param {number} start The starting height.
 * @returns {Mountaineer}
 */
function Mountaineer(start) {
    // The achieved height.
    this._currentProgress = start || 0;
    // Contains instances of inherited Commands.
    this._commandsList = [];
    // We do not push and pop commands,
    // instead we keep them all and remember the current.
    // It start with -1, because this._commands is zero based.
    this._currentCommand = -1;
}

// Add the methods for mountaineers
Mountaineer.prototype = {
    constructor: Mountaineer,
    /**
     * Execute a new command.
     * @param {type} command Instance of a command.
     * @returns {undefined}
     */
    execute: function (command) {
        this._currentProgress = command.execute(this._currentProgress);
        this._currentCommand++;
        this._commandsList[this._currentCommand] = command;
        if (this._commandsList[this._currentCommand + 1]) {
            this._commandsList.splice(this._currentCommand + 1);
        }
    },
    /**
     * Undo the current command.
     * @returns {undefined}
     */
    undo: function () {
        var cmd = this._commandsList[this._currentCommand];
        if (!cmd) {
            console.error('Nothing to undo');
            return;
        }
        this._currentProgress = cmd.undo(this._currentProgress);
        this._currentCommand--;
    },
    /**
     * Redo the undone command.
     * @returns {undefined}
     */
    redo: function () {
        var cmd = this._commandsList[this._currentCommand + 1];
        if (!cmd) {
            console.error('Nothing to redo');
            return;
        }
        this._currentProgress = cmd.execute(this._currentProgress);
        this._currentCommand++;
    },
    /**
     * Simple getter of the current progress i.e. the achieved height.
     * @returns {Number}
     */
    getCurrentProgress: function () {
        return this._currentProgress;
    }
};

Of course, many more and different kinds of subjects can be created, but I want to keep this article short.

Actions

Because commands could actually contain more than one and also combinations of actions, I create the actions separately. The mountaineer will be able to climb and fall.

/**
 * Climb up action. One of the actions which will be used in the commands.
 * @param {type} actionValue Amount of climbed progress.
 * @returns {climb.value}
 */
function climbUp(actionValue) {
    // `this` is the instance of a command.
    return actionValue + this.value;
}

/**
 * Fall down action. One of the actions which will be used in the commands.
 * @param {type} actionValue Amount of falling regression.
 * @returns {fall.value}
 */
function fallDown(actionValue) {
    // `this` is the instance of a command.
    return actionValue - this.value;
}
Concrete commands

Finally the actual commands! They are really small and super easy to read. All what happens here is choosing the actions for the command. In these cases one action for both the normal and the undo function.

/**
 * Command subtype for climbing up.
 * @constructor
 * @extends Command
 * @param {type} value The value, which will be passed to the action.
 * @returns {undefined}
 */
function CommandClimbUp(value) {
    // Constructor stealing for inheritance.
    // The order of climb and fall defines which is the normal vs undo action.
    Command.call(this, climbUp, fallDown, value);
}
// Prototype chaining for inheritance.
CommandClimbUp.prototype = Object.create(Command.prototype);

/**
 * Command subtype for falling down.
 * @constructor
 * @extends Command
 * @param {type} value The value, which will be passed to the action.
 * @returns {undefined}
 */
function CommandFallDown(value) {
    // Constructor stealing for inheritance.
    // The order of climb and fall defines which is the normal vs undo action.
    Command.call(this, fallDown, climbUp, value);
}
// Prototype chaining of inheritance.
CommandFallDown.prototype = Object.create(Command.prototype);

// Lots of other commands e.g. jumping, eating, dancing, planking can be added here

As imagination is limitless, commands are too.

Showing of the undo and redo magic

That is all. It is really that simple! Let us see it in action.

var reinhold = new Mountaineer(); // start at 0m
reinhold.execute(new ClimbUpCommand(1)); // climb 1m. (progress = 1m)
reinhold.undo(); // fall down 1m. (progress = 0m)
reinhold.execute(new ClimbUpCommand(2)); // climb 2m. (progress = 2m)
reinhold.undo(); // fall down 2m. (progress = 0m)
reinhold.execute(new FallDownCommand(3)); // fall 3m. (progress = -3m)
reinhold.undo(); // climb up 3m. (progress = 0m)
reinhold.execute(new ClimbUpCommand(4)); // climb 4m. (progress = 4m)
reinhold.execute(new FallDownCommand(5)); // fall 5m. (progress = -1m)
reinhold.execute(new ClimbUpCommand(6)); // climb 6m. (progress = 0m)
reinhold.undo(); // fall 6m. (progress = -1m)
reinhold.undo(); // climb 5m. (progress = 4m)
reinhold.undo(); // fall 4m. (progress = 0m)
reinhold.undo(); // Do nothing, because there are no actions left to undo
reinhold.redo(); // climb 4m. (progress = 4m)
reinhold.redo(); // fall 5m. (progress = -1m)
reinhold.redo(); // climb 6m. (progress = 5m)
reinhold.redo(); // Do nothing, because there are no actions left to redo
reinhold.undo(); // fall 6m. (progress = -1m)
reinhold.redo(); // climb 6m. (progress = 5m)
reinhold.execute(new ClimbUpCommand(7)); // climb 7m. (progress = 12m)
reinhold.getCurrentProgress(); // 12m

Performance issues

Creating many instances of commands could become a serious memory issue! In that case, simply limiting the subjects _commandsList should do the trick. A lot of desktop applications (used to) do exactly that.

Found a mistake? Have a question or suggestion?

I love feedback! Please contact me at https://twitter.com/KimHogeling