Skip to main content

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 };
note

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.

Stay in the know

Be where thousands of diagramming enthusiasts meet

Star us on GitHub