Cell namespaces
Basics
A simple, but important aspect of working with JointJS is to ensure that JointJS knows where to look for built-in and custom shapes. In order to achieve this, it's a requirement to tell JointJS where to read cell view definitions. Failure to do so will result in an error in our application. Built-in shapes are usually located in the shapes
namespace, so this is a common namespace to use. It's possible to add custom shapes to this namespace, or alternatively, you may like to use a different namespace completely. The choice is yours, but you need to state the namespace at the outset when using JointJS.
Let's begin from using built-in shapes. To use built-in shapes you should provide shapes
namespace as a cellNamespace
graph and cellViewNamespace
paper options:
import { dia, shapes } from '@joint/core';
const namespace = {
...shapes
}
const graph = new dia.Graph({}, { cellNamespace: namespace });
const paper = new dia.Paper({
/* ... */
cellViewNamespace: namespace
});
This approach allows you to use built-in shapes in your application. You can look at the built-in shapes in our dedicated learn section.
Type
Cell namespace is a key:value
map, where key is a type of a cell and value is a cell's constructor. The type
property is a key part of the cell definition. It is used to identify the cell type, and is used by JointJS to find the correct constructor for the cell. The type
property is used to locate the correct constructor in the cell namespace. For example, if you have a cell with a type
of 'standard.Rectangle'
, JointJS will look for the constructor in the shapes.standard
namespace. If you have a cell with a type
of 'custom.Rectangle'
, JointJS will look for the constructor in the shapes.custom
namespace.
The other use case is creating your own custom shapes. Let's begin by creating a simple, custom Rectangle
definition which extends base dia.Element
class. To know more about creating custom shapes, you can visit our dedicated learn section.
import { dia, util } from '@joint/core';
class Rectangle extends dia.Element {
defaults() {
return {
type: 'Rectangle',
/* ... */
};
}
/* ... */
}
The most important part of the custom shapes declaration is the type
property. In this case, we set the type
property to 'Rectangle'
. This means that JointJS will look for the constructor in the shapes
namespace by the 'Rectangle'
key. So for our purposes we will add our custom Rectangle
constructor to the namespace object:
import { shapes } from '@joint/core';
// Built-in JointJS shapes and our custom Rectangle are added
const namespace = { ...shapes, Rectangle };
Notice, that we are using the spread operator to copy all the properties of the shapes
object into the namespace
object. This is a common pattern in JavaScript to create a new object with the same properties as the original object. In addition the way we are adding our custom Rectangle
constructor to the namespace
object will be converted into a key-value pair with the key 'Rectangle'
and the value as Rectangle
constructor.
If you want a little more organization and nesting in your cell namespace, you can define a type
using dot notation, and structure your shape definitions how you would like.
class Rectangle extends dia.Element {
defaults() {
return {
type: 'custom.Rectangle',
/* ... */
};
}
/* ... */
}
const namespace = { ...shapes, custom: { Rectangle }};
Detailed look
With the intention of strengthening this concept in our minds, let's define another shape, so that we are more familiar with this process. Below, we create a class RectangleTwoLabels
with a type
property of 'custom.RectangleTwoLabels'
. JointJS will now expect that our custom RectangleTwoLabels
element will be located within the custom
namespace.
As we want our custom
namespace to be at the same level of nesting as built-in JointJS shapes, we will structure our cell namespace accordingly. First, we declare a namespace
variable, then using the spread operator, ensure that namespace
contains all of the properties of shapes
namespace. These properties correspond to shape namespaces such as standard
.
Afterwards, we also place our new custom
namespace which contains our custom shape definition RectangleTwoLabels
alongside our built-in shapes. As a result, standard
and custom
are both defined at the same level in our namespace
object. Lastly, we make sure that namespace
is set as the value of our cellNamespace
and cellViewNamespace
options respectively.
class RectangleTwoLabels extends shapes.standard.Rectangle {
defaults() {
return {
...super.defaults,
type: 'custom.RectangleTwoLabels'
};
}
preinitialize() {
this.markup = util.svg/* xml */ `
<rect @selector="body" />
<text @selector="label" />
<text @selector="labelSecondary" />
`;
}
}
const namespace = { ...shapes, custom: { RectangleTwoLabels }};
const graph = new dia.Graph({}, { cellNamespace: namespace });
new dia.Paper({
...
cellViewNamespace: namespace
...
});
With the objective of defining our custom
namespace at the same nesting level of standard
taken care of, it's now possible to add cells to our graph
with the confidence that we shouldn't run into any errors regarding cell namespaces.
graph.fromJSON({
cells: [
{
type: 'standard.Rectangle',
size: { width: 100, height: 60 },
position: { x: 50, y: 50 },
attrs: { body: { fill: '#C9ECF5' }, label: { text: 'standard.Rectangle', textWrap: { width: 'calc(w-10)' }}}
},
{
type: 'custom.RectangleTwoLabels',
size: { width: 160, height: 80 },
position: { x: 200, y: 30 },
attrs: {
body: {
fill: '#F5BDB0'
},
label: {
text: 'custom.RectangleTwoLabels',
textWrap: { width: 'calc(w-10)' }
},
labelSecondary: {
text: 'SecondaryLabel',
x: 'calc(w/2)',
y: 'calc(h+15)',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
fontSize: 14
}
}
},
]
});
Discovering your cell namespaces are not organized correctly should result in a common JointJS error. If you see the dreaded Uncaught Error: dia.ElementView: markup required
appearing in your console, it's likely your namespace is not set up correctly, and JointJS cannot find the correct shape.
Adding custom views
Last but not least, the topics covered so far also apply to our custom views. Placing a custom view in the correct location is necessary, because the JointJS paper
will search for any model types with a suffix of 'View' in our provided namespace.
In this snippet, we create a simple rectangle shape with text input. We also define a custom view that on user input, sets the input value on the model, and also logs the value to the console. This time around, we choose shapes
namespace as our cellNamespace
and cellViewNamespace
values, and 'example.RectangleInput'
as the type
for our custom element. Those things combined mean JointJS assumes our custom element & view will be located at 'shapes.example.RectangleInput'
and 'shapes.example.RectangleInputView'
respectively.
import { dia, util, shapes } from '@joint/core';
const namespace = shapes;
const graph = new dia.Graph({}, { cellNamespace: namespace });
const paper = new dia.Paper({
...
cellViewNamespace: namespace,
...
});
class RectangleInput extends dia.Element {
defaults() {
return {
type: 'example.RectangleInput',
/* ... */
};
}
/* ... */
}
const RectangleInputView = dia.ElementView.extend({
/* ... */
});
Object.assign(namespace, {
example: {
RectangleInput,
RectangleInputView
}
});
const rectangleInput = new RectangleInput();
rectangleInput.position(10, 10);
rectangleInput.resize(200, 120);
rectangleInput.addTo(graph);
Validation tips
If you are experimenting with cell namespaces, you may like to perform some quick validation, or double-check exactly what type
values you are working with. Taking advantage of the prop()
method on both Elements & Links allows you to quickly access the type
value, so it can be useful to keep track of where your shapes are located.
const rect = new Rectangle({
size: { width: 80, height: 50 },
position: { x: 10, y: 10 }
});
console.log(rect.prop('type')); // standard.Rectangle
A concise way to check if your namespaces are set up correctly is to overwrite the graph
via graph.toJSON()
passing it the value returned from graph.toJSON()
. If no error occurs, you can be more confident that your namespaces are organized correctly.
graph.fromJSON(graph.toJSON());
That's all we will cover in this tutorial. Thanks for staying with us if you got this far, and we hope you will have more confidence when working with cell namespaces in JointJS.