Skip to main content

Data validity

In order to keep the data in the graph valid, you can use the Validator class. It allows you to register validation functions for specific commands. When a command is executed, the validator checks if there is a validation function for the command and if so, it calls the function. If the function returns an error, the command is reverted and the error message can be displayed to the user.

Installation

Import the CommandManager from the dia namespace and create a new instance of it. Pass an instance of a dia.Graph to the constructor.

import { dia, shapes } from '@joint/plus';

const graph = new dia.Graph({}, { cellNamespace: shapes });
const commandManager = new dia.CommandManager({ graph });
const validator = new dia.Validator({ commandManager });
There is also a UMD version available

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

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

And access the Validator through the joint.dia namespace:

index.js
const graph = new joint.dia.Graph({}, { cellNamespace: joint.shapes });
const commandManager = new joint.dia.CommandManager({ graph });
const validator = new joint.dia.Validator({ commandManager });

Setup validation functions

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

For any action, you can register an arbitrary number of validation functions. The validation functions are called in the order they were registered. If the last validation function calls the next() function with an error, the command is reverted.

validator.validate('remove',
function preserveRectangles(err, command, next) {
if (command.data.type === 'standard.Rectangle') {
return next('Rectangles cannot be removed.');
}
return next();
},
function logError(err, command, next) {
if (err) console.log(err);
return next(err);
}
);

In the example above, we register a validation function for the remove action. The function preserveRectangles checks if the cell being removed is of type standard.Rectangle.

  • If so, it calls the next() function with an error message. The second callback logs the error message to the console and calls the next() function with the error. The rectangle removal is then reverted.
  • If not, it calls the next() function without an error, logError calls the next() function without error and the rectangle is removed.

Here's a working example of the above code:


Errors

The next() first argument is an error.

If the err is a falsy value, the validation is considered successful. If it's a truthy value, the validation is considered failed.

note

You can define your errors as strings, objects, or instances of Errors. It's up to you how you want to handle the errors.

Commands

The command parameter is a record from the CommandManager stack. It contains information about the change (delta) that was made to the graph.

There are 3 types of commands that are registered in the CommandManager stack:

  • cell change: e.g. change:position, change:size
  • cell addition and removal: add and remove actions
  • graph change: e.g change:background, change:diagramName

Cell change

The following command structure is generated when a cell is changed. This command says that a standard.Rectangle element has been moved by 50px down.

{
"action": "change:position",
"data": {
// id of the cell it was manipulated with
"id": "e0552cf2-fb05-4444-a9eb-3636a4589d64",
// type of the cell
"type": "standard.Rectangle",
// change:attribute events always have
// these two attributes filled
"previous": { "position": { "x": 50, "y": 50 }},
"next": { "position": { "x": 50, "y": 100 }},
},
// is command part of a batch command?
"batch": false,
// is this graph or cell change?
"graphChange": false,
// mvc options that are passed through the listeners
// when passed as the last argument to the mvc Model set() method.
"options": {
"myOption": "myValue"
}
}

This command can be generated for instance like this:

const rect = graph.getCell('e0552cf2-fb05-4444-a9eb-3636a4589d64');

rect.position(50, 100, { myOption: 'myValue' });

Add and remove cell

The add and remove actions have empty previous and next objects. 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": "standard.Ellipse",
// empty attributes
"previous": {},
"next": {},
// all cells attributes
"attributes": {
"type": "standard.Ellipse",
"size": {
"width": 50,
"height": 30
},
"position": { "x": 1220, "y": 680 },
"id": "28de715b-62a7-4130-a729-1bcf7dbb1f2b",
"attrs": {
"body": {
"fill": "red"
}
}
}
},
"batch": false,
"graphChange": false,
"options": {
"add": true,
"merge": false,
"remove": false
}
}

This command can be generated like this:

const ellipse = new shapes.standard.Ellipse({
position: { x: 1220, y: 680 },
size: { width: 50, height: 30 },
attrs: {
body: {
fill: 'red'
}
}
});

graph.addCell(ellipse);

Graph change

The change on the graph is the same as cell change but it generated when you change the graph attributes.

{
"action": "change:background",
"data": {
"previous": { "color": "white" },
"next": { "color": "black" }
},
"batch": false,
"graphChange": true,
"options": {}
}

This command can be generated like this:

graph.set('background', { color: 'black' });

Advanced example

This section shows a more advanced example of the Validator usage.

  • We check if 2 elements overlapping
  • We allow only certain elements to be connected to each other
  • We limit number of connections to a certain number

Stay in the know

Be where thousands of diagramming enthusiasts meet

Star us on GitHub