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:
<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:
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);
}
});
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:
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.
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.
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;
}
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.
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);
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);
});