Rappid - dia

dia.CommandManager

CommandManager keeps track of graph changes and allows you to travel the history of those changes back and forth. There is no limitation put into the number of levels one can undo/redo.

Installation

Include joint.dia.command.js to your HTML:

<script src="joint.dia.command.js"></script>

Creating CommandManager

var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({ el: $('#paper'), width: 500, height: 500, model: graph });

var commandManager = new joint.dia.CommandManager({ graph: graph });

$('#undo-button').click(function() { commandManager.undo(); });
$('#redo-button').click(function() { commandManager.redo(); });

How does CommandManager work?

CommandManager listens to graph changes and when any cell's attribute is changed it stores the difference (command). CommandManager stores every change made on graph's cells into undoStack and every reverted change into redoStack. It allows you to revert the changes stored in those stacks.

CommandManager API

constructor

The joint.dia.CommandManager constructor takes up to three parameters.

graph

Is the graph the CommandManager listens to.

cmdBeforeAdd

A function evaluated before any command is added. If the function returns false, the command does not get stored. This way you can control which commands do not get registered for undo/redo.

var commandManager = new joint.dia.CommandManager({
    graph: graph,
    cmdBeforeAdd: function(cmdName, cell, graph, options) {
        options = options || {};
        return !options.ignoreCommandManager;
    }
});

// ...

// Note that the last argument to set() is an options object that gets passed to the cmdBeforeAdd() function.
element.set({ foo: 'bar' }, { ignoreCommandManager: true });

cmdNameRegex

A regular expression specifying what cell's attributes the CommandManager listens to. Default regex is /^(?:add|remove|change:\w+)$/.

revertOptionsList

An array of options property names that are meant to be passed in undo actions. It defaults to ['propertyPath'].

commandManager = new joint.dia.CommandManager({
    revertOptionsList: ['myOptionAttribute1']
})
element.set('attribute', 'value', { myOptionAttribute1: 5, myOptionAttribute2: 6 });
commandManager.undo(); // -> calls element.set('attribute', 'prevValue', { myOptionAttribute1: 5 });

Alternatively the revertOptionList can be defined as a function.

revertOptionsList: function(attrValue, attrName) {
    return attrName !== 'doNotPassMe'; /* pass over everything except `doNotPassMe` attribute */
}

applyOptionsList

An array of options property names that are meant to be passed in redo actions. It defaults to ['propertyPath'].

commandManager = new joint.dia.CommandManager({
    applyOptionsList: ['myOptionAttribute1']
})
element.set('attribute', 'value', { myOptionAttribute1: 5, myOptionAttribute2: 6 });
commandManager.undo();
commandManager.redo(); // -> calls element.set('attribute', 'value', { myOptionAttribute1: 5 });

Alternatively the applyOptionList can be defined as a function.

applyOptionsList: function(attrValue, attrName) {
    return attrName !== 'doNotPassMe'; /* pass over everything except `doNotPassMe` attribute */
}

undo

Function undo(opt) travels the history one command back. It accepts an option parameter (opt) that is applied when function makes changes to the graph. e.g commandManager.undo({ undoneBy: 'user123' })

redo

Function redo(opt) go back to whatever was previously undo()ed.

cancel

Function cancel(opt) same as undo() but does not store the undo-ed command to the redoStack. Canceled command therefore cannot be redo-ed.

hasUndo

Function hasUndo() true if there is something in the undoStack.

hasRedo

Function hasRedo() true if there is something in the redoStack

reset

Function reset() clears both undoStack and redoStack.

initBatchCommand

Function initBatchCommand() gives you the ability to gather multiple changes into a single command. These commands could be revert with single undo() call.

From the moment the function is called every change made on graph is not stored into the undoStack. Changes are temporarily kept in the CommandManager until storeBatchCommand() is called.

storeBatchCommand

Calling function storeBatchCommand() tells the CommandManager to store all changes temporarily kept in the undoStack. In order to store changes you have to call this function as many times as initBatchCommand() had been called.

Events

The plugin fires the following events.

Event Name Handler Signature Description
stack:undo (commandUndone, opt) Triggered when a command was undone.
stack:redo (commandRedone, opt) Triggered when a command were redone.
stack:push (commandPushedToUndoStack, opt) Triggered when a command were added to the stack.
stack:cancel (commandCanceled, opt) Triggered when a command was canceled.
stack:reset (opt) Triggered when all commands were reset.
stack (opt) Triggered when any change was made to stacks.
commandManager.on('stack', function(opt) {
    if (this.hasUndo()) { /* do something */ }
    if (this.hasRedo()) { /* do something else */ }
});

dia.GraphUtils

dia.GraphUtils adds additional methods to the joint.dia.Graph. Currently, it adds only two methods: constructTree() and shortestpath().

Installation

Include joint.dia.graphUtils.js file to your HTML:

<script src="joint.dia.graphUtils.js"></script>

If you plan to use shortestPath() method, you're going to also need to include joint.alg.priorityQueue.js and joint.alg.dijkstra.js plugins:

<script src="joint.alg.priorityQueue.js"></script>
<script src="joint.alg.dijkstra.js"></script>

This will add more methods to the joint.dia.Graph object:

Methods

constructTree(parent, opt, parentElement)

Construct a tree from a JSON object (parent, i.e. the top level node). This method returns an array of cells (elements and links) that constitute the tree. The parent parameter must be a JSON of the form:

{
  name: 'my label',
  children: [ { name: 'my label 2', children: [...] }, ...]
}
	

opt.children is the property specifying the children array (default is 'children'). If opt.children is a function, it will be called with the current node as an argument and must return an array of its child nodes. opt.makeElement() is a function that is passed the current tree node and returns a JointJS element for it. opt.makeLink() is a function that is passed a parent and child nodes and returns a JointJS link for the edge. Example:

var cells = graph.constructTree(
    {
        name: 'my top,
        children: [ { name: 'my child 1' }, { name: 'my child 2' } ]
    }, {
    makeElement: function(node) {
	    return new joint.shapes.basic.Rect({
		size: { width: 80, height: 30 },
		attrs: { text: { text: node.name } }
	    });
    },
    makeLink: function(parentElement, childElement) {
	    return new joint.dia.Link({
		source: { id: parentElement.id },
		target: { id: childElement.id }
	    });
    }
});
graph.addCells(cells);
	

This method is used in the JavaScript AST visualizer demo.

shortestPath(source, target [, opt]) Return an array of IDs of nodes on the shortest path between source and target. source and target can either be elements or IDs of elements. opt.weight is an optional function returning a distance between two nodes. It defaults to function(u, v) { return 1 }. If opt.directed is true, the algorithm will take link direction into account. The implementation uses the joint.alg.Dijkstra plugin internally. Please refer to the plugin documentation for more information.

dia.Validator

Validator runs a set of callbacks to determine if a command is valid. This is useful for checking if a certain action in your application does lead to an invalid state of the diagram (from your application specific perspective). You can not only cancel such command but e.g. also give the user a hint that this action is not allowed).

Installation

Include joint.dia.command.js and joint.dia.validator.js to your HTML:

<script src="joint.dia.command.js"></script>
<script src="joint.dia.vaidator.js"></script>

Creating Validator

var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({ el: $('#paper'), width: 500, height: 500, model: graph });

var commandManager = new joint.dia.CommandManager({ graph: graph });
var validator = new joint.dia.Validator({ commandManager: commandManager });
validator.validate('remove',
    function(err, command, next) {
        if (command.data.type === 'basic.Rect') return next('Rectangles cannot be removed.');
        return next();
    },
    function(err, command, next) {
        if (err) console.log(err);
        return next(err);
    }
);

(Those that know the great ExpressJS application framework might have recognized a similar API to what ExpressJS provides for registering URL route handlers.)

Validator API

constructor

The joint.dia.Validator constructor takes two parameters.

commandManager

The CommandManager the validator listens to.

cancelInvalid

Determine whether to cancel an invalid command or not. If set to false, only the invalid event is triggered.

Default is true.

validate

Function validate(action [, callback]*) registers callbacks for a given action.

The validator listens on commands added to the CommandManager and runs this set of callbacks registered for the action. If the last callback returns an error the command is canceled (see joint.dia.CommandManager.cancel()). This behaviour can be suppressed by setting the cancelInvalid to false in options passed to the Validator constructor.

action

Action is the name of the event triggered on the graph (see List of triggered events). Multiple actions may be given separated by whitespaces.
validate("change:source change:target", function() { .. });

callback

callback(err, command, next)

The validation function. An arbitrary number of validation functions can be passed to validate(). Every callback has the following signature:

Where err is an error from the previous callback.

The command parameter is a record from the CommandManager stack. It holds information about an action in the graph.

See below for the structure of a command. This command says that a basic.Rect element has been moved by 50px down.

{
  "action" : "change:position"
  "data": {
    "id":"e0552cf2-fb05-4444-a9eb-3636a4589d64", // id of the cell it was manipulated with
    "type":"basic.Rect", // type of the cell

    // change:attribute events have always these two attributes filled
    "previous": { "position":{"x":50,"y":50} },
    "next": { "position": {"x":50,"y":100}},
  },
  "batch":true, // is command part of a batch command?
  "options":{} // Backbone options that are passed through the listeners when passed as the last argument to the Backbone Model set() method.
}

The add and remove actions have previous and next object empty. They hold all the cell attributes in the attributes object so that the CommandManager is able to reconstruct the whole cell if it has to. See below for an example of such a command structure:

{
  "action": "add",
  "data" : {
    "id" : "28de715b-62a7-4130-a729-1bcf7dbb1f2b",
    "type":"basic.Circle",

    //empty attributes
    "previous":{},
    "next":{},

    // all cells attributes
    "attributes": {
      "type":"basic.Circle",
      "size": {
        "width":50,
       "height":30
      },
     "position":{"x":1220,"y":680},
     "id":"28de715b-62a7-4130-a729-1bcf7dbb1f2b",
     "attrs" : {
       "circle" : {
         "width":50,
         "height":30
       }
     }
  "batch":true,
  "options" : {
    "add":true,
    "merge":false,
    "remove":false
  }
}

The next parameter is a function accepting one argument - an error passed to the next callback in a row.

Calling next() function means going to the next callback in the order passed to the validate() method. If a call to the next() function is omitted, the validation for the specified action stops.