Skip to main content

Events

JointJS provides multiple events for reacting to user interactions with the diagram. These events can be used to trigger custom actions, such as updating the diagram or displaying additional information.

General information​

JointJS objects like shapes, paper, graph etc. are all descendants of either mvc.Model or mvc.View classes. These classes implement mvc.Events methods, which allow you to create and listen for built-in and custom events.

The most important methods are on() and trigger(). The on() method is used to listen for built-in and custom events, while the trigger() method is used to trigger custom events if you want to create them.

JointJS provides a set of a built-in events that can be listened to. For example, when any model attribute is updated the 'change:[attribute]' event is triggered alongside with the generic 'change' event. Here is the snippet:

const rect = new joint.shapes.standard.Rectangle();

rect.on('change:position', (model, position) => {
// react on the position
});

To manage your event listeners in a more controlled way you can use the mvc.Listener class:

const listener = new mvc.Listener();

listener.listenTo(paper, 'element:pointerclick', (elementView) => {});
listener.listenTo(paper, 'link:pointerclick', (linkView) => {});

// when your listeners are not needed you can disable them all at once
listener.stopListening();

You can look at the advanced use of listeners in the View/Edit mode demo.

Graph and Cell events​

The Graph model comes with several built-in events. These events react to changes in the cell model's properties, including position, size, attributes, and status of JointJS transitions. JointJS documentation contains a generic list of Graph events you can react to (alongside more specific lists for Element events and Link events).

To react to Graph events, add a listener on your Graph model.

In the following example, the event 'change:position' is triggered on the element when it is moved; we determine the new position of the element's center and write it into its text label. The event 'change:target' is triggered on the link when its target is moved; we use that event to write the new target position into the link label.

The callback functions of the cell graph change events have the signature callback(cell, newValue). Inside the listeners, the graph itself is available as this. In our example, we were able to assume that the received cell model is of the type Element and Link, respectively, because Link objects do not trigger 'change:position' events, and Element objects do not trigger 'change:target'. Keep in mind that some Graph events can be triggered on both data types (e.g. 'change:attrs') - depending on what you are trying to do, you might need to check the actual cell data type first (e.g. cell.isElement() / cell.isLink()).

Other graph event listeners are provided with different parameters. The generic 'change' event listeners receive only the changed cell but not the new value (i.e. callback(cell)). Meanwhile, the 'add' event listeners receive the added cell and the updated cells array (callback(cell, newCells)), whereas the 'remove' event listeners receive the removed cell and the original cells array (callback(cell, oldCells)).

These events are also triggered on the individual cell (element or link model) the user interacted with. Therefore, you could achieve the same functionality as above by adding a listener on every element and link separately. While this approach has its uses, we recommend using graph listeners; having a single listener cover all interaction options is better practice in JavaScript than having dozens of listeners for individual models.

The graph can also react on changes in its own properties. For example, calling graph.set('property', true) would trigger 'change:property' on the graph. Event listeners for graph attribute changes receive a reference to the graph and the new value as their parameters (callback(graph, newValue)).

Beware that due to backwards-compatibility considerations, this can lead to confusion if we are careless in choosing custom graph property names! If we named our custom graph property position instead of property, the triggered event would be identified as 'change:position', and would thus be captured by the event listener in our example - but the callback would have to contend with an unexpected set of arguments. To avoid such name collisions, we strongly recommend adopting a naming convention for custom graph properties - e.g. starting their variable names with the word graph (i.e. graphProperty and graphPosition). If that is not an option, and you need to support custom graph attributes, you can make yourself safe by asserting that the cell parameter is not actually a Graph:

graph.on('change:position', (cell) => {
if (cell instanceof joint.dia.Graph) return;
const center = cell.getBBox().center();
const label = center.toString();
cell.attr('label/text', label);
});

Paper and CellView events​

Paper automatically triggers several built-in events upon user interaction. These include pointerdown, double click and right click events, as well as link connection or cell highlighting events. You can find the full list in the Paper API reference. Reacting to any of these events is as simple as adding a listener on your paper; this is the easiest way to detect a single type of user interaction on all instances of a JointJS object.

For example, the event 'link:pointerdblclick' is triggered when a double click is detected on any link in the diagram. A corresponding event for elements ('element:pointerdblclick'), cells in general ('cell:pointerdblclick'), and for blank areas on the paper ('blank:pointerdblclick') is provided as well. In the following example, the cell event listener shows an element with a message whenever an Element or Link is clicked. The more specific element and link event listeners change the color of their model's stroke. Finally, the blank event listener hides the message element and changes the color of the Paper background:

The event listener callback functions have the signature callback([cellView,] eventObject, eventX, eventY) (cellView is not provided for 'blank:…' events, for obvious reasons). Inside the listeners, the paper itself is available as this. The eventObject can be used to, for example, to call stopPropagation(). The eventX and eventY coordinates can be useful when placing markers at sites of user interaction. We have not used these three parameters in the above example, but you will encounter them frequently in JointJS event listeners.

Note, that all of these events are also triggered on the individual cellView (element or link view) the user interacted with. Therefore, you could achieve the same functionality as above by adding a listener on every elementView and linkView separately. While this approach has its uses, we recommend using paper listeners; having a single listener cover all interaction options is better practice in JavaScript than having dozens of listeners for individual views.

Subelement event attribute​

The easiest way to add a custom event to an individual component on your diagram is to use the event special attribute.

This special attribute is useful for creating elements with custom tool subelements (e.g. a minimize button). Then, you can simply attach a listener on your paper that gets called when your custom event is detected. Note that this functionality can also be achieved with JointJS highlighters.

In the following example, we define a custom element type with a button subelement. We attach a custom event 'element:button:pointerdown' to the button and listen for it. When the button is pressed, the element's body and label are hidden ("minimized"), and the symbol in the button is changed to indicate that the element can now be unminimized:

Custom view events​

For more advanced event customization, we need to delve into custom views. That is an advanced topic with many powerful options; here we will concentrate on extending our View objects with custom events.

Overriding built-in methods
note

This method is not very reliable as it is interferes with the out-of-the-box functionality of the library. It is recommended to use the event special attribute, the events() method, or a highlighter instead.

One of the ways to use custom views is to modify corresponding options of a paper. The Paper object has two options that determine the views used to render diagram components.

We will use these two options to provide extended versions of the default ElementView and LinkView. The following example removes double-clicked views:

import { dia } from '@joint/core';

const paper = new dia.Paper({
// ... other options
elementView: dia.ElementView.extend({
pointerdblclick: function(evt, x, y) {
this.model.remove();
}
}),
linkView: dia.LinkView.extend({
pointerdblclick: function(evt, x, y) {
this.model.remove();
}
})
})

Event propagation​

You can maintain recognition of the event by the built-in paper mechanism if you notify the CellView and Paper about the event. For example, in our custom ElementView pointerdblclick handler, we would include this:

dia.CellView.prototype.pointerdblclick.apply(this, arguments);
this.notify('element:pointerdblclick', evt, x, y);

This can be useful if you need to maintain default event handling behavior. The following example integrates showing a message element on Cell click from the paper events demo with removing the components from the previous example:

import { dia } from '@joint/core';

const paper = new dia.Paper({
// ... other options
elementView: dia.ElementView.extend({
pointerdblclick: function(evt, x, y) {
dia.CellView.prototype.pointerdblclick.apply(this, arguments);
this.notify('element:pointerdblclick', evt, x, y);
this.model.remove();
}
}),
linkView: dia.LinkView.extend({
pointerdblclick: function(evt, x, y) {
dia.CellView.prototype.pointerdblclick.apply(this, arguments);
this.notify('link:pointerdblclick', evt, x, y);
this.model.remove();
}
})
})

paper.on('cell:pointerdblclick', (cellView) => {
const isElement = cellView.model.isElement();
const message = (isElement ? 'Element' : 'Link') + ' removed';
info.attr('label/text', message);

info.attr('body/visibility', 'visible');
info.attr('label/visibility', 'visible');
});

Listening to DOM events in custom views​

When you create your custom views, you can add listeners to DOM events of the rendered elements. This can be useful for creating custom interactions with the custom shape. To achieve it you can use the events() method of the view. (In the case of declaration using extend() you may use events property instead.)

The events object is a key-value object where keys are event names and values are event handlers. The event handler is a function that will be called when the event is triggered. The event name consists of the event type and the selector of the element that should trigger the event. The selector is a string that is used to filter the elements that will trigger the event. You can look at the example in the custom shapes section.

export class ButtonNodeView extends dia.ElementView {

events() {
return {
'click button': (evt) => { this.onButtonClick(evt) }
}
}

onButtonClick(evt) {
const visibility = this.model.attr('label/visibility');
if (visibility === 'hidden') {
this.model.attr('label/visibility', 'visible');
evt.target.textContent = this.model.get('buttonTextVisible');
} else {
this.model.attr('label/visibility', 'hidden');
evt.target.textContent = this.model.get('buttonTextHidden');
}
}
}
Using mvc.View.extend()

When declaring a custom view using the extend() method, you can specify events like this:

const ButtonNodeView = dia.ElementView.extend({
events: {
'click button': (evt) => { this.onButtonClick(evt) }
},

onButtonClick(evt) {
const visibility = this.model.attr('label/visibility');
if (visibility === 'hidden') {
this.model.attr('label/visibility', 'visible');
evt.target.textContent = this.model.get('buttonTextVisible');
} else {
this.model.attr('label/visibility', 'hidden');
evt.target.textContent = this.model.get('buttonTextHidden');
}
}
});