The Command Stack¶
The Command Stack is built into Common Canvas and the application does not need to configure or customize it unless specialized behavior is required.
The Command Stack maintains an internal stack of commands with a cursor that moves up and down when commands are done, undone or redone. Commands are JavaScript classes that implement a simple interface.
Common Canvas provides one command for each action that can be perfomed in the flow editor. The application can optionally add its own commands to the command stack.
The canvas controller automatically creates an instance of the command stack. When actions, such as Create Node
or Delete Link
, are performed by the user in the Flow Editor, Common Canvas creates an instance of an internal command action, adds it to the command stack and executes the command.
To allow the user to activate the undo and redo actions, Common Canvas provides:
- undo/redo buttons on the default toolbar,
- undo/redo options in its default context menu for the flow editor canvas,
- keyboard shortcuts: ctrl+z (undo) and ctrl+shift+z (redo) when keyboard focus is on the canvas.
API control of the command stack (optional)¶
If the application specifies its own canvas toolbar, or its own context menu/toolbar for the flow editor canvas, it can include defintions for the undo
and redo
actions if needed.
Alternatively, the application can provide its own undo and redo UI, if required, which calls 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 an application specific 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()
getFocusObject()
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.
getFocusObject() - Returns a canvas object (node, link or comment) or the string “CanvasFocus” to indicate where keyboard focus should be set when the command completes.
Here is some sample code that shows how a ‘create node’ command might be written:
import { Action } from "@elyra/canvas";
export default class MyCreateNodeAction extends Action {
constructor(data, canvasController) {
super(data);
this.canvasController = canvasController;
this.newNode = createNode(data);
}
do() {
this.canvasController.addNode(this.newNode);
this.focusObject = this.newNode;
}
undo() {
this.canvasController.deleteNode(this.newNode.id);
this.focusObject = "CanvasFocus";
}
redo() {
this.do();
}
getLabel() {
return "Add 1 node"
}
getFocusObject() {
return this.focusObject;
}
}
Here is an example showing how the command action would be created and pushed onto the stack using the canvas-contoller:
const command = new MyCreateNodeAction(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.