Skip to main content
Version: 4.1

Selection

JointJS+ provides a Selection plugin that enables the ability to select elements on the paper.

Installation​

import { ui } from '@joint/plus';

const selection = new ui.Selection({
paper: paper,
// optional, a custom collection that inherits from mvc.Collection
collection: new MyCollection
});
There is also a UMD version available

Include joint.ui.selection.js and joint.ui.selection.css in your HTML:

index.html
<link rel="stylesheet" type="text/css" href="joint.ui.selection.css">
<script src="joint.ui.selection.js"></script>

Access Selection through the joint.ui namespace:

index.js
const selection = new joint.ui.Selection({
paper: paper,
// optional, a custom collection that inherits from mvc.Collection
collection: new MyCollection
});

How does Selection work?​

Selection implements elements selection, moving the selection in one go and manipulating the selection in terms of selecting/deselecting individual elements.

Usage​

This section will guide you through the process of setting up the Selection plugin.

Initialize Selection​

The Selection internally stores selected cells in a collection. This is a normal mvc Collection. It can be accessed via collection attribute. This view takes care of rendering the bounding rectangle during the selection action, determining which elements fall into this rectangle and also provides methods for selecting/deselecting individual elements. The view also listens to the underlying collection and updates selection boxes when an element is removed/added/reset i.e. selection.collection.add(element); finds the view for the element on the paper and render a selection box above it.

const selection = new joint.ui.Selection({
paper: paper
});

The next step is to hook the selection actions to the relevant events triggered on the paper and selection:

// Initiate selecting when the user grabs the blank area of the paper.
paper.on('blank:pointerdown', selection.startSelecting);

// Select an element if CTRL/Meta key is pressed while the element is clicked.
paper.on('element:pointerup', (cellView, evt) => {
if (evt.ctrlKey || evt.metaKey) {
selection.collection.add(cellView.model);
}
});

// Unselect an element if the CTRL/Meta key is pressed while a selected element is clicked.
selection.on('selection-box:pointerdown', (elementView, evt) => {
if (evt.ctrlKey || evt.metaKey) {
this.selection.collection.remove(elementView.model);
}
});
note

The selection-box:pointerdown event is triggered on the selection view when the mouse cursor is pressed above a selected element.

Selection Collection​

As our selection collection is just a normal mvc Collection, we can take advantage of the mvc methods.

To select an element:

selection.collection.add(element);
selection.collection.add(element, { silent: true }); // add element to the collection, but renders no selection box.

To deselect an element:

selection.collection.remove(element);

To deselect all elements:

selection.collection.reset([]);

To select multiple elements and deselect all the other elements:

selection.collection.reset([element1, element2]);

To identify elements that are currently in the selection:

selection.collection.on('reset add', () => {
// Print types of all the elements in the selection.
console.log(selection.collection.map((cell) => cell.get('type')));
});

Selection Example​

This example combines the above snippets to create a simple selection mechanism. Users can click and hold while dragging to select multiple elements with a selection rectangle. Holding the CTRL key while clicking on an element will add/remove it from the selection depending on its current state.

Selection tools​

Disabling selection tools​

As you might have noticed, each selection is surrounded by a rectangle and offers three built-in icon tools by default: remove, rotate and resize. To disable any of these tools you may add a line similar to the following to your css:

.joint-selection .handle.rotate {
display: none; /* disables the rotate tool */
}

Another way to remove tool handles is via JavaScript:

selection.removeHandle('rotate');

To quickly disable all the tools (hide them) and also to hide the rectangular box around all the selected elements, you can simply do:

.selection-wrapper {
display: none;
}

Customizing selection tools​

Selection provides three methods for adding, removing and changing custom tools: addHandle(), removeHandle() and changeHandle(). Use the addHandle() method to add new tools to your selection:

In the example above, we added a new tool named myaction, positioned the tool to the south (bottom-center) and used our own icon represented as a base64. When the user clicks on our tool, Selection triggers an event named action:[name]:pointerdown. We can handle the event by listening on the Selection object. Similarly, the Selection triggers action:[name]:pointermove and action:[name]:pointerup events. This gives us a high flexibility in implementing our own actions. You might have noticed that this API is exactly the same as in the ui.Halo plugin. The difference is that in Selection, we can have actions that manipulate multiple elements in one go.

For adding a tool, use the addHandle() method:

selection.addHandle({
name: 'myaction',
position: Selection.HandlePosition.S,
icon: './myaction.png',
});

To bind an action to the tool, listen to the action:[name]:pointerdown event (alternatively pointermove and pointerup):

selection.on('action:myaction:pointerdown', (evt) => {
console.log('My action was clicked!');
});

For removing a tool, use the removeHandle(name) method:

selection.removeHandle('myaction');

changeHandle() method allows us to change tools. For instance, if we want to change a position of the remove tool, we could use:

selection.changeHandle('remove', { position: 'ne' });

In order to add a default tool back, you can use the getDefaultHandle() static method with the default tool name:

note

As of v4.1 of JointJS+, the built-in tools include a clone tool, which is not enabled by default.

The default tools are: remove, rotate, resize, clone.

selection.addHandle(Selection.getDefaultHandle('clone'));
// change the default `ne` position to `se`
selection.changeHandle('clone', { position: 'se' });

Selection frames​

There are several selection frame types that can be used to change the way the selection items are rendered.

Overlay selection frame list​

It is a simple rectangle that is frame around the selected elements. It is rendered above/under the elements.

It comes in two implementations: HTMLSelectionFrameList or SVGSelectionFrameList.

As the name suggests, the HTMLSelectionFrameList uses HTML elements to render the selection frame, while the SVGSelectionFrameList uses SVG elements.

tip

Both implementations are using the same API and are interchangeable. The only difference is the way the selection frame is rendered. So how to decide which one to use?

Use HTMLSelectionFrameList if you want:

  • to style the selection frame using CSS (including pseudo-elements).
  • the border of the selection frame to stay at the same width regardless of the zoom level.

Use SVGSelectionFrameList if you want:

  • the selection frames to be present in an SVG/PNG export.
  • the selection frames to be rendered under the elements.

HTMLSelectionFrameList​

A basic example of using the HTMLSelectionFrameList:

const selection = new ui.Selection({
paper: paper,
frames: new ui.HTMLSelectionFrameList({
margin: 5,
rotate: true,
/* ... */
})
});

SVGSelectionFrameList​

The SVGSelectionFrameList can be configured to render under the elements and use non-uniform margin.

const selection = new ui.Selection({
paper: paper,
frames: new ui.SVGSelectionFrameList({
margin: { horizontal: 5, top: 5, bottom: 25 },
layer: dia.Paper.Layers.BACK,
rotate: true,
/* ... */
})
});

Highlighter selection frame list​

The HighlighterSelectionFrameList is a selection frame that is using the dia.HighlighterView to highlight the selected cells. You can use any of the built-in highlighters or create your own.

To use the HighlighterSelectionFrameList, you need to pass it to the Selection constructor:

Using mask highlighter​

This is a convenient way to show tight selection frames around the selected elements.

const selection = new ui.Selection({
paper: paper,
frames: new ui.HighlighterSelectionFrameList({
highlighter: highlighters.mask,
options: { padding: 1 }
})
});

Using addClass highlighter​

This is a way to add a class to the selected elements and style them using CSS. It's useful when you want to highlight the selected elements in a different way than their default style.

note

This could be the preferred way if you planning on selecting a large number of elements.

Adding a class is faster than rendering an extra element for each selected element, keeping the number of elements in the DOM low.

const selection = new ui.Selection({
paper: paper,
frames: new ui.HighlighterSelectionFrameList({
highlighter: highlighters.addClass,
selector: 'body',
options: { className: 'highlighted' }
})
});

Styling Selection​

The look of the selection can be customized using CSS or using JavaScript.

Styling frames​

You can change the style of the selection frames using the attributes / styles options.

new ui.SVGSelectionFrameList({
attributes: {
stroke: 'red',
strokeWidth: 1
}
})

new ui.HTMLSelectionFrameList({
styles: {
border: '1px solid red'
}
})

Or you can style the selection frames using CSS, as each selection frame has a class joint-selection-frame.

/* for SVGSelectionFrameList */
rect.joint-selection-frame {
stroke: red;
stroke-width: 1;
fill: none;
}

/* for HTMLSelectionFrameList */
div.joint-selection-frame {
border: 1px solid red;
}
note

The default selection frame is not the HTMLSelectionFrameList or SVGSelectionFrameList, but a LegacySelectionFrameList for backward compatibility.

The frames are nearly identical to the HTMLSelectionFrameList, but has class selection-box and using margins and paddings to create the gap between the cell and the frame.

Styling wrapper​

The rectangle surrounding all the selected element is a <div> with class .joint-selection-wrapper.

It can also be styled in JavaScript using various options:

wrapper: {
margin: 5,
style: {
border: '1px dotted #8e44ad'
borderRadius: '5px'
}
}

All wrapper options can be found in the Selection documentation.

note

The default selection wrapper is a div element with class selection-wrapper for backwards compatibility.

Styling selection region​

The selection region to select cells is a <div> element with the .joint-selection class.

Common Setup of the Selection in Applications Explained​

The following is a common setup of the Selection in applications.

Cherry picking elements​

For cherry picking elements - when the user clicks on an element holding the CTRL key, we register a handler for the cell:pointerup event on the paper, which is triggered when the user releases his mouse above a cell (either element or a link - links get filtered out in our case). In this handler, we add the element to our selection collection and create a selection box around that element.

paper.on('element:pointerup', (elementView, evt) => {
if (evt.ctrlKey || evt.metaKey) {
selection.collection.add(elementView.model);
}
});

Releasing selection on cherry-picked elements​

To implement the reverse action of cherry-picking, i.e. when the user clicks a selected element while holding the CTRL key, we register a handler for the selection-box:pointerdown event on the Selection (see Selection events for reference). In this handler, we remove the element from our selection collection and destroy the selection box.

Initiating bulk selection​

Last thing we want to setup is the bulk selection that should happen when the user drags a blank area in the paper:

paper.on('blank:pointerdown', selection.startSelecting);
tip

Some applications might not want the user to be able to create selections when dragging a blank area in the paper. This is because they might have a different action for this, let's say panning the paper. In that case, you can, for example, start bulk selection only when the SHIFT key is being hold.

paper.on('blank:pointerdown', (evt) => {
if (evt.shiftKey) selection.startSelecting(evt);
});