Command
Command
is an implementation of the command
pattern, in which objects are
used to represent actions. A command object typically has an execute()
method that runs its action, and sometimes will have an undo()
method if it
can be reversed.
// In the browser JS.require('JS.Command', function(Command) { ... }); // In CommonJS var Command = require('jsclass/src/command').Command;
Creating commands
Here’s your basic ‘Hello world’ command:
var helloWorld = new Command({ execute: function() { alert('Hello world!'); } }); helloWorld.execute() // -> alerts "Hello world!"
If your action can be reversed and you want to implement an ‘undo’ function in your code, you need to tell the command how to reverse itself:
var incrementCounter = new Command({ execute: function() { someCounter += 1; }, undo: function() { someCounter -= 1; } }); var someCounter = 0; incrementCounter.execute(); incrementCounter.execute(); incrementCounter.undo(); // someCounter is now == 1
Creating your own command classes
You’ll often want to subclass Command
to provide your own types of command.
For example, you might want to create a type of command that drew a circle at
random on a canvas
element:
var DrawCircleCommand = new Class(Command, { initialize: function(ctx) { var x = Math.random() * 400, y = Math.random() * 300, r = Math.random() * 50; this.callSuper({ execute: function() { ctx.fillStyle = 'rgb(255,0,0)'; ctx.beginPath(); ctx.arc(x, y, r, 0, 2*Math.PI, true); ctx.fill(); } }); } }); var ctx = myCanvas.getContext('2d'); var randomCircle = new DrawCircleCommand(ctx); randomCircle.execute();
Note the general pattern here: we create a subclass of Command
. This new
subclass accepts a drawing context to instantiate it (ctx
), chooses some
random drawing points, then uses this.callSuper
to pass some command
properties up to the Command
initialize()
method. Note that the random
numbers are generated once, rather than every time the command is executed:
each new DrawCircleCommand
does something different, but an individual
DrawCircleCommand
instance ought to do the same thing every time it is
executed.
Important: do not overwrite the execute()
or undo()
methods in a
Command
sublcass. They contain hooks for talking to command stacks and must
not be modified. Always use callSuper()
as shown above to set up your
commands. Do not do this:
var BrokenCommand = new Class(Command, { execute: function() { doSomething(); }, undo: function() { undoSomething(); } });
This command will not work properly if you try to hook it up to a command stack as described below.
Command.Stack
If you have actions that can be undone and you want to store the command
history so you can step back and forth through it later, we have a class
called Command.Stack
. To use it, just create one and give it a name:
var counterStack = new Command.Stack();
You can then create commands that will automatically add themselves to this
stack whenever they are executed, using the stack
option:
var incrementCounter = new Command({ execute: function() { someCounter += 1; }, undo: function() { someCounter -= 1; }, stack: counterStack });
Now, whenever incrementCounter
is executed, it gets added to the stack
counterStack
. You can use the stack’s undo()
and redo()
methods to step
back and forth through the command history:
var someCounter = 0; incrementCounter.execute(); // someCounter == 1 incrementCounter.execute(); // someCounter == 2 incrementCounter.execute(); // someCounter == 3 counterStack.undo(); // someCounter == 2 counterStack.redo(); // someCounter == 3 counterStack.undo(); // someCounter == 2 counterStack.undo(); // someCounter == 1
Of course, this is more useful when you have lots of different types of commands that can all be undone, and you want to provide a means of stepping through the command stack.
Command stacks have a couple of other methods you ough to be aware of:
stepTo()
lets you revert to any point in the stack’s command history by
number. stepTo(0)
undoes the whole stack, and commands are numbered
sequentially from 1
upwards.
push()
lets you push a command onto the stack yourself—this is the method
Command
uses internally if you tell a command to associate itself with a
stack. When you push a command onto the stack, it is added at whichever point
in the stack you happen to be, and any previously undone commands after this
point are discarded. For example:
someCounter = 0; counterStack.clear(); // 0 commands in stack incrementCounter.execute(); // 1 command incrementCounter.execute(); // 2 commands incrementCounter.execute(); // 3 commands incrementCounter.execute(); // 4 commands // someCounter == 4 counterStack.stepTo(2); // still 4 commands in stack // stack pointer is after 2nd command // someCounter == 2 incrementCounter.execute(); // stack truncated // contains 3 commands // pointer at end of stack // someCounter == 3
Redo from start
Some commands cannot easily be undone, but you’d still like to store them in a
stack. One example is the circle-drawing command shown above: it’s hard to
‘unpaint’ a circle because you don’t know what filled its space before it was
there. In some situations it’s easier to implement ‘undo’ by having a stack
redo itself from the start up to a given point. If you pass a redo
command
when creating a stack, the stack will assume you want a redo-from-start stack
and will automatically call your redo
command to wipe the slate clean before
running its history again.
As an example, here’s a program that allows you to draw random squares on a canvas. Hooking this up to a GUI is trivial with this code in place:
// Canvas information var canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d'), W = 400, H = 300; // Command for clearing the drawing area var clearCanvas = new Command({ execute: function() { ctx.fillStyle = 'rgb(255,255,255)'; ctx.fillRect(0, 0, W, H); } }); // A redo-from-start stack var drawingStack = new Command.Stack({redo: clearCanvas}); // Command for drawing a random square var DrawSquareCommand = new Class(Command, { initialize: function(ctx) { var x = Math.random() * W, y = Math.random() * H, r = Math.random() * 30; this.callSuper({ execute: function() { ctx.fillStyle = 'rgb(0,0,255)'; ctx.fillRect(x, y, r, r); }, stack: drawingStack }); this.name = 'Draw square at ' + x + ', ' + y; } });
To undo a step, the drawing stack clears the canvas and then re-runs all its
commands up to the required step. Notice how the drawing command includes the
line stack: drawingStack
to make sure it is remembered by the stack. To draw
a square at random, you’d just do this:
new DrawSquareCommand(ctx).execute();
That could easily be hooked up to an interface button, as could a snippet of
code for calling drawingStack.undo()
.
The advantage of coding the application like this is that you can add as many different types of drawing command as you like without complicating the mechanism for undoing them – you just get all your commands to notify a single command stack and use that stack to revert changes.
Notice how the command gives itself a name
property inside its
initialize()
method – this will allow us to represent the command in the GUI
in the next example.
length
and pointer
All stacks have a length
property that tells you how many commands they
contain at any point in time. This property only changes when a new command is
push()
-ed onto the stack – an undo()
or redo()
does not change the stack
length. What does change is the stack’s pointer
– this number tells you what
point in the stack the most recently executed command is. For example:
var stack = new Command.Stack(); var command = new Command({ execute: function() { ... } }); // stack.length == 0 // stack.pointer == 0 stack.push(command); stack.push(command); stack.push(command); // stack.length == 3 // stack.pointer == 3 stack.undo(); stack.undo(); // stack.length == 3 // stack.pointer == 1 stack.push(command); // stack gets truncated // stack.length == 2 // stack.pointer == 2
They’re Observable
and Enumerable
Command.Stack
mixes in the Observable
and
Enumerable
modules. This means you can implement a
Photoshop-style action history with barely any code at all by observing the
stack:
<ul id="drawHistory"></ul> <script> drawingStack.subscribe(function(stack) { var list = document.getElementById('drawHistory'), str = ''; stack.forEach(function(command, i) { var color = (i >= stack.pointer) ? '#999' : '#000'; str += '<li style="color: ' + color + ';">' + command.name + '</li>'; }); list.innerHTML = str; }); </script>
This creates a list that updates itself in response to stack changes. Your
subscribe
callback is passed a reference to the stack whenever the stack
executes a new command or undoes/redoes a command. Stacks have a pointer
property that tells you what point in the stack the most recent command is. So
you can work out whether each command has been undone or not, and assign a
greyed-out color to commands after the current pointer
position. Your
forEach
callback is passed each command in the stack in turn, along with its
position in the stack (beginning at 0).