Skip to content

The Command Stack

The Command Stack is built into Common Canvas and automatically provides functionality to support do/undo/redo of commands performed in the flow editor. In addition, if needed, applications can add their own commands to the command stack.

The Command Stack maintains an internal stack of commands with a cursor that moves up and down when commands are undone or redone. Commands are JavaScript classes that implement a simple interface.

The canvas controller automatically creates an instance of the command stack. Common Canvas provides command objects for each of the commands that are performed by the user, such as: create node, delete comment, link nodes together, etc. which it adds to the command stack.

To allow the user to activate the undo and redo actions, Common Canvas provides: * undo/redo buttons on the default toolbar and * undo/redo options in its default context menu for the flow editor background, * keyboard shortcuts: ctrl+z (undo) and ctrl+shift+z (redo) when keyboard focus is on the canvas.

If the applicaiton specifies its own canvas toolbar or its own context menu/toolbar for the flow editor canvas and it must include defintions for the undo and redo internal actions.

API control of the command stack (optional)

You can implement your own undo and redo UI, if required, using the canvas controller API.

The canvas controller has a number of methods that allows the application to interact with the command stack if necessary.

Building a command (optional)

Each command that is added to the command stack is a JavaScript class that needs to implement these methods:

   constructor()
   do()
   undo()
   redo()
   getLabel()

constructor() - Initial setup

do() - Performs all actions necessary to execute the command

undo() - Performs all actions necessary to reverse the actions performed in do()

redo() - Performs all actions necessary to re-execute the command. For some commands this is the same as do() but for others it is different.

getLabel() - Returns a label that descibes the action.

Here is some sample code that shows how a ‘create node’ command might be written:

   export default class CreateNodeAction extends Action {

        constructor(data, canvasController) {
            super(data);
            this.canvasController = canvasController;
            this.newNode = createNode(data);
         }

         do() {
            this.canvasController.addNode(this.newNode);
         }

         undo() {
            this.canvasController.deleteNode(this.newNode.id);
         }

         redo() {
            this.canvasController.addNode(this.newNode);
         }

         getLabel() {
            return "Add 1 node"
         }
   }
Note that the command has to keep a reference to the new node to allow the node to be added back to the canvas in redo() even though it was deleted in undo().

Here is an example showing how to create a command action and push it on the stack using the canvas-conttoller:

   const command = new CreateNodeAction(data, this.canvasController);
   this.canvasController.do(command);

Exported common-canvas action classes

Some of the internal action classes have been exported from Common Canvas and can be extended with additional functionality, if necessaey. The classes that are exported are:

  • CreateAutoNodeAction
  • CreateNodeAction
  • CreateNodeLinkAction
  • DeleteObjectsAction
  • DisconnectObjectsAction
  • PasteAction

The constructors for these classes all take the same two parameters. The data object that descrbes the command and a reference to the canvas controller.

Applications can extend these classes to augment their basic behavior with application specific behavior. It is the application’s responsibility to add the extended object to the command stack when the user performs the corresponding action.

Although there are no plans to alter the internal workings of these six command action classes, there is always the chance that a change in the future might alter a field name or two. If you extend these classes, it is therefore recommended that you have sufficient regression tests for your extensions that would highlight such a problem, should it occur.