JointJS+ Selection

ui.Selection

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

Installation

Include joint.ui.selection.js and joint.ui.selection.css files to your HTML:

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

Initialize Selection

The Selection internally stores selected cells in a collection. This is a normal Backbone 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.

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

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', function(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', function(elementView, evt) {
    if (evt.ctrlKey || evt.metaKey) {
        this.selection.collection.remove(elementView.model);
    }
});

Note that 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 Backbone Collection, we can take advantage of the Backbone methods. For instance:

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', function() {
    // Print types of all the elements in the selection.
    console.log(selection.collection.pluck('type'));
});

Customizing the look of Selection

The bounding rectangle of the active selection, the rectangles above the selected elements and the rectangle around all the elements once the selection is made can all be styled in CSS. The selection bounding rectangle is a <div> element with the .joint-selection class. The rectangles above the selected elements are also <div> elements having the .selection-box class and the final rectangle surrounding all the selected element is a <div> with class .selection-wrapper. An example of a custom styling might look like the following:

.joint-selection {
   background-color: red;
   border: 2px dashed red;
   opacity: .7;
}
.selection-box {
   border: 1px solid #8e44ad;
   margin-top: -4px;
   margin-left: -4px;
   box-shadow: 2px 2px 5px #8e44ad;
}
.selection-wrapper {
   border: 1px dotted #8e44ad;
}

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:

selection.addHandle({ name: 'myaction', position: 's', icon: 'myaction.png' });
selection.on('action:myaction:pointerdown', function(evt) {
    evt.stopPropagation();
    alert('My custom action.');
});

In the example above, we added a new tool named myaction, positioned the tool to the south (bottom-center) and used our own icon myaction.png. 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 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' });

Selection Configuration

paper (required) The joint.dia.Paper or joint.ui.PaperScroller the Selection should operate on. If a PaperScroller object is provided, then its paper option is used as the basis for this Selection; in addition, the behavior specified by the PaperScroller's scrollWhileDragging option is automatically applied for relevant Selection activities (selecting, translating, resizing).
graph The JointJS graph the Selection should operate on. If no graph is passed paper.model is used.
collection A Backbone Collection object used to store selected cells. If no collection is passed new Backbone.Collection is used.
filter Either an array in which case it can contain (even intermixed) cells or cell types that will be ignored by the selection. This is useful if you have certain elements in the diagram that are not supposed to get selected. It can also be function in which case it will be called with a cell as an argument and should return true if that cell should be filtered out of the selection. The function gives you more flexibility in deciding whether an element can or cannot be selected. Example:
filter: ['standard.Rectangle', 'mymodule.HiddenShape']
boxContent(cellView, boxDOMElement) A function that returns an HTML string with the content that will be used in the information box below the selection. By default, the box shows the number of selected elements.
useModelGeometry If set to true, the cells position and dimensions will be used as a basis for the Selection wrapping rectangle position. By default, this is set to false which causes the Selection wrapping rectangle position be based on the bounding box of the selected element views. Sometimes though, your shapes can have certain SVG sub elements that stick out of the view and you don't want these sub elements to affect the Selection wrapping rectangle position. In this case, set the useModelGeometry to true.
strictSelection If set to true, only elements that are fully contained in the selection rectangle will be selected as opposed to the default behaviour that selects elements whose bounding box intersects the selection rectangle.
allowTranslate If set to false, users are not allowed to move selected elements around by dragging selection boxes. It defaults to true.
preserveAspectRatio Set to true if you want the resizing to preserve the aspect ratio of the elements. The default is false.
rotateAngleGrid When selected elements are rotated the resulting angles are snapped to this value. It defaults to 15 degrees.
handles A array of objects defining the tools around the selection. This array defaults to:
[
     { name: 'remove', position: 'nw', events: {
         pointerdown: 'removeElements'
     } },
     { name: 'rotate', position: 'sw', events: {
         pointerdown: 'startRotating',
         pointermove: 'doRotate',
         pointerup: 'stopBatch' }
     }
]
Normally, you do not need to set this array. It's better to use the addHandle(), removeHandle() and changeHandle() methods described below in the API section.
translateConnectedLinks When translating selected elements, should the connected links also be translated? Based on the selected value of the ui.Selection.ConnectedLinksTranslation enumerator, the behavior is as follows:
  • NONE - none of the links are translated
  • SUBGRAPH - only links whose both ends are connected to one of the selected elements are translated.
  • ALL - all connected links are translated
The default is ui.Selection.ConnectedLinksTranslation.ALL.
allowCellInteraction Allows interaction with underlying element's features, e. g. buttons or ports. In this case selection-box events will be triggered by browser event from the cell instead of the selection-box html element. It defaults to false.

Selection API

addHandle(opt) Add a custom tool to the Selection. opt.name is the name of the custom tool. This name will be also set as a CSS class to the handle DOM element making it easy to select it your CSS stylesheet. opt.position is a string that specifies a position of the tool handle. Possible values are n, nw, w, sw, s, se, e and ne. opt.icon is a URL of the icon used to render the tool. This icons is set as a background image on the tool handle DOM element.
removeHandle(name) Remove a tool handle named name from the Selection.
changeHandle(name, opt) Change a tool handle named name in the Selection. The opt object can contain the same parameters as with the addHandle() method except of the name property. opt parameters will be merged with those defined previously.
on(event, callback) Register a handler (callback) for an event See the Selection Events section for the list of events the Selection object triggers.
startSelecting(evt) Initiate bulk selection. See below in the Common Setup section how that can be used.
createSelectionBox(element) Create a single selection box above an element.
destroySelectionBox(element) Destroy a single selection box, which is rendered above an element.

Selection Events

The Selection object triggers events when the user manipulates its tools. These events can be handled by using the Selection on() method.

action:[name]:pointerdown Triggered when the user clicks (touches) on a tool handle named [name]. The handler is called with the mousedown event object, and x and y of the pointer in local coordinates.
action:[name]:pointermove Triggered when the user moves with mouse cursor after a tool handle named [name] was mousedowned (touched). The handler is called with the mousemove event object, and x and y of the pointer in local coordinates.
action:[name]:pointerup Triggered when the user releases his mouse cursor after a tool handle named [name] was mousedowned (touched). The handler is called with the mouseup event object, and x and y of the pointer in local coordinates.
action:[name]:contextmenu Triggered when the user invokes contextmenu on a tool handle named [name]. The handler is called with the mousedown event object, and x and y of the pointer in local coordinates.
selection-box:pointerdown Triggered when the user starts interacting with a selected element, an element that has a selection box above it. The handler gets passed the element view, the DOM event object, x and y paper local coordinates. Use elementView.model if you wish to access the actual element model. In the case of allowCellInteraction: true the DOM event will be triggered by the element instead of selection-box. See below in the Common Setup section how that can be used.
selection-box:pointermove Triggered when the user is moving with a selected element, an element that has a selection box above it. The handler gets passed the element view, the DOM event object, x and y paper local coordinates.
selection-box:pointerup Triggered when the user stops interacting with a selected element, an element that has a selection box above it. The handler gets passed the element view, the DOM event object, x and y paper local coordinates.

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', function(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.

selection.on('selection-box:pointerdown', function(elementView, evt) {
    if (evt.ctrlKey || evt.metaKey) {
        selection.collection.remove(elementView.model);
    }
});

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', function(evt) {
    if (evt.shiftKey) selection.startSelecting(evt);
});