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:
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 thenext()
function with the error. The rectangle removal is then reverted. - If not, it calls the
next()
function without an error,logError
calls thenext()
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.
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
andremove
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