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 joint.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 by creating a simple, custom Rectangle
definition which extends joint.dia.Element
.
class Rectangle extends joint.dia.Element {
defaults() {
return {
...super.defaults,
type: 'Rectangle',
position: { x: 10, y: 10 },
attrs: {
body: {
width: 100,
height: 70
}
}
};
}
preinitialize() {
this.markup = joint.util.svg/* xml */ `
<rect @selector="body" />
`;
}
}
We will also declare a variable which will contain our shapes, and act as our cell namespace.
// Built-in JointJS shapes and our custom Rectangle are added
const namespace = { ...joint.shapes, Rectangle };
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 joint.dia.Element {
defaults() {
return {
...
type: 'custom.Rectangle',
...
};
}
...
}
const namespace = { ...joint.shapes, custom: { Rectangle }};
Now that we have created a cell namespace, how do we tell JointJS which namespace to use? There are 2 important options to be aware
of when creating your diagrams. The first is the graph
option
cellNamespace
, and the second is the paper
option cellViewNamespace
. In
the following example, for a cell of type 'standard.Rectangle'
, the graph
looks up the
'joint.shapes.standard.Rectangle'
path to find the correct constructor. If you don't plan on creating custom shapes,
or playing around with namespaces, the following setup should be fine for your application.
const namespace = joint.shapes;
const graph = new joint.dia.Graph({}, { cellNamespace: namespace });
const paper = new joint.dia.Paper({
...
cellViewNamespace: namespace
...
});
graph.fromJSON({
cells: [
{
type: 'standard.Rectangle',
size: { width: 80, height: 50 },
position: { x: 10, y: 10 }
}
]
});
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 joint.shapes
. 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 joint.shapes.standard.Rectangle {
defaults() {
return {
...super.defaults,
type: 'custom.RectangleTwoLabels'
};
}
preinitialize() {
this.markup = joint.util.svg/* xml */ `
<rect @selector="body" />
<text @selector="label" />
<text @selector="labelSecondary" />
`;
}
}
const namespace = { ...joint.shapes, custom: { RectangleTwoLabels }};
const graph = new joint.dia.Graph({}, { cellNamespace: namespace });
new joint.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: 140, 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
}
}
},
]
});
JointJS source code: cell-namespace.js
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.
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 joint.shapes
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
'joint.shapes.example.RectangleInput'
and 'joint.shapes.example.RectangleInputView'
respectively.
const namespace = joint.shapes;
const graph = new joint.dia.Graph({}, { cellNamespace: namespace });
const paper = new joint.dia.Paper({
...
cellViewNamespace: namespace,
...
});
class RectangleInput extends joint.dia.Element {
defaults() {
return {
...super.defaults,
type: 'example.RectangleInput',
attrs: {
foreignObject: {
width: 'calc(w)',
height: 'calc(h)'
}
}
};
}
preinitialize() {
this.markup = joint.util.svg/* xml */`
<foreignObject @selector="foreignObject">
<div
xmlns="http:www.w3.org/1999/xhtml"
style="background:white;border:1px solid black;height:100%;display:flex;justify-content:center;align-items:center;"
>
<input
placeholder="Type something"
/>
</div>
</foreignObject>
`;
}
}
const RectangleInputView = joint.dia.ElementView.extend({
events: {
// Name of event + CSS selector : custom view method name
'input input': 'onInput'
},
onInput: function(evt) {
console.log('Input Value:', evt.target.value);
this.model.attr('name/props/value', evt.target.value);
}
});
Object.assign(namespace, {
example: {
RectangleInput,
RectangleInputView
}
});
const rectangleInput = new RectangleInput();
rectangleInput.position(10, 10);
rectangleInput.resize(200, 120);
rectangleInput.addTo(graph);
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. If you would like to explore any of the features mentioned here in more detail, you can find more information in our JointJS documentation.