JointJS+ Halo

ui.Halo

Halo creates a control panel above an element with various tools. This gives the user control over your elements with tools located right at hand.

Installation

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

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

Creating a Halo

var graph = new joint.dia.Graph;
    var paper = new joint.dia.Paper({ el: $('#paper'), width: 500, height: 500, model: graph });

    paper.on('cell:pointerup', function(cellView) {
        // We don't want a Halo for links.
        if (cellView.model instanceof joint.dia.Link) return;

        var halo = new joint.ui.Halo({ cellView: cellView });
        halo.render();
    });

The joint.ui.Halo constructor takes a graph and paper objects + it also requires the view of a cell we want to display the halo above.

Default handles (tools)

The ui.Halo control panel has the following built-in tools. To add custom tools, please see the section Customizing halo tools.

remove Remove the element.
clone Drag to clone the element.
link Drag to link the element with another element. Note that the new link will be created based on the defaultLink option from the joint.dia.Paper object.
fork Drag to clone the element and connect it with cloned element in one go.
unlink Remove all the links coming in/out of the element.
resize Resize the element.
rotate Rotate the element.

Configuration

The joint.ui.Halo constructor function takes an options object as an argument. The options object can have the following parameters:

cellViewan element or link view. Previously, Halo accepted also paper and graph options but this is now deprecated as those can always be inferred from cellView.paper and cellView.paper.model properties.
typethe type of the Halo control panel. The default value is 'surrounding'. Another possible values are 'pie' in which case the Halo will be displayed as a pie menu and 'toolbar' in which case the Halo tools are displayed above the element in a small toolbar. See the demos below.
loopLinkPreferredSidethe preferred side for a self-loop link created from Halo ("top"|"bottom"|"left"|"right"), default is "top"
loopLinkWidththe self-loop link width in pixels, default is 40
rotateAngleGridthe angle increments the rotate action snaps to, default is 15
rotateEmbedsshould be elements embedded inside the cellView element rotated as well? Default is false
boxContenta string (text or HTML) or a function (of the form boxContent(cellView, boxDOMElement)) that returns an HTML string with the content that will be used in the information box below an element. By default, the box displayes the x, y coordinates and width and height dimensions of the element. If boxContent is set to false (or an empty string), the information box will be hidden
clearAllif set to true (the default value), clear all the existing halos from the page when a new halo is created. This is the most common behavior as it is assumed that there is only one halo visible on the page at a time. However, some applications might need to have more than one halo visible. In this case, set clearAll to false (and make sure to call remove() once you don't need a halo anymore)
clearOnBlankPointerdown if set to true (the default value), clear the halo when a user clicks the blank area of the paper.
useModelGeometryif set to true, the model position and dimensions will be used as a basis for the Halo tools position. By default, this is set to false which causes the Halo tools position be based on the bounding box of the element view. 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 Halo tools position. In this case, set the useModelGeometry to true.
clonea function with signature function(cell, opt). This function will be called when cloning or forking actions take place and it should return a clone of the original cell. This is useful e.g. if you want the clone to be moved by an offset after the user clicks the clone handle. The default function is: function(cell, opt) { return cell.clone().unset('z') }.

The callback may return an array of cells. This is useful if the element has nested cells that also need to be cloned. In this case the user drags the first cell in the array.

new ui.Halo({
  /* ... */
  clone: (cell) => {
    return cell.clone({ deep: true });
    // const [cellClone, ...embedsClones] = cell.clone({ deep: true });
    // return [cellClone, ...embedsClones];
  }
});

pieSliceAngle(only valid for the 'pie' type of Halo) The angle of one slice in the pie menu. It defaults to 45.
pieStartAngleOffset(only valid for the 'pie' type of Halo) The angular offset of the first handle in the pie menu. It defaults to 0.
pieIconSize(only valid for the 'pie' type of Halo) The size in pixels of the icon in the pie menu. It defaults to 14.
pieToggles(only valid for the 'pie' type of Halo) An array of pie toggle buttons. Usually, there's only one (the default) but you can have as many as you want. The default value is [{ name: 'default', position: 'e' }]. Each item in the array is an object of the form { name: [name of you toggle], position: [one of e/w/s/n] }. The name is passed in the options object in the state:close and state:open events triggered by the Halo when the pie toggle is clicked.
bboxA bounding box within which the Halo view will be rendered.
tinyThresholdif the set number value is higher than the Halo bbox width and height, a 'tiny' CSS class is applied to the Halo. This reduces the size of the Halo. For elements, the default value is 40. For links, the default Halo bbox width and height both equal 1. This means a value of 2 or higher is needed to apply the class.
smallThresholdif the set number value is higher than the Halo bbox width and height, a 'small' CSS class is applied to the Halo. This reduces the size of the Halo. A 'small' class will not be applied, if a 'tiny' class is already present. For elements, the default value is 80. For links, the default Halo bbox width and height both equal 1. This means a value of 2 or higher is needed to apply the class.
magnet A function accepting an elementView returning an SVGElement used as a magnet for links created via linking or forking handles.
new joint.dia.Halo({
  cellView: elementView,
  magnet: function(elementView, end, evt) {
    // where `end` is either "source" (linking and forking) or "target" (forking only)
    // and `evt` is a `mousedown` event
    // connect the link directly to a rectangle element as opposed to the element group
    return elementView.el.querySelector('rect') || elementView.el;
  }
})

Disabling halo tools

Built-in tools are remove, resize, rotate, clone, fork, link and unlink. You can call the removeHandle() method to remove any of these tools just by passing its name:

halo.removeHandle('clone');

Alternatively, you can disable tools via CSS:

.halo .handle.clone { display: none; } /* disables the clone tool */

Also, the same way you would hide halo tools, as explained above, you can reposition them. For example, to reposition the remove icon tool to the bottom left corner, you could do:

.halo .handle.remove {
       left: -25px;
       bottom: -25px;
       top: auto;
       right: auto;
    }
Note the auto values for the top/right coordinates are important in order to cancel out the default values defined in the Halo plugin.

Another way to remove/reposition tool handles in the Halo is in JavaScript:

halo.removeHandle('clone');
    halo.changeHandle('remove', { position: 'se' });

The code above removes the clone tool from the Halo and re-positions the remove tool to the bottom right (south-east).

Each Halo can also be styled in CSS (disabling tools, repositioning, changing icons, ...) based on the type of element the Halo is displayed for. The Halo <div> container stores the type of the element in its data-type attribute. This makes it easy to target a halo for a certain type of element in your CSS. For instance, let's say we want to hide the remove tool only for an element of type "basic.Rect". We can do this:

.halo[data-type="basic.Rect"] .remove { display: none; }

Customizing halo tools

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

halo.addHandle({ name: 'myaction', position: 's', icon: 'myaction.png' });
    halo.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, Halo triggers an event named action:[name]:pointerdown. We can handle the event by listening on the halo object. Similarly, the Halo triggers action:[name]:pointermove and action:[name]:pointerup events. This gives us a high flexibility in implementing our own actions.

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

halo.removeHandle('myaction')

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

halo.changeHandle('clone', { position: 'se' })

Pie menu type of Halo

To display the Halo control panel as a pie menu, just set the type option to 'pie':

new joint.ui.Halo({ cellView: myElementView, type: 'pie' })

You can still add your own actions as you would normally do with the default 'surrounding' type of Halo.

Toolbar type of Halo

To display the Halo control panel as a small toolbar above an element, just set the type option to 'toolbar':

new joint.ui.Halo({ cellView: myElementView, type: 'toolbar' })
You can still add your own actions as you would normally do with the default 'surrounding' type of Halo.

It is also possible to use the Halo plugin with links, as the following demo illustrates.

API

addHandle(opt) Add a custom tool to the Halo. 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.
addHandles(handles) Add multiple handles in one go. This calls addHandle() internally for each item of the handles array.
removeHandle(name) Remove a tool handle named name from the Halo.
removeHandles() Remove all handles from the Halo.
changeHandle(name, opt) Change a tool handle named name in the Halo. 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.
render() Render the Halo. This must be called after the Halo object is instantiated.
on(event, callback) Register a handler (callback) for an event See the Halo Events section for the list of events the Halo object triggers.
toggleState([toggleName]) Toggle (open/close) the sate of the Halo (applicable for the pie type of Halo). toggleName is the name of the pie toggle as defined in the pieToggles option. If toggleName is not passed, all pie menus change the state (the most common usage as there is usually only one pie toggle).
isOpen([toggleName]) Return true if the Halo is open (applicable for the pie type of Halo). toggleName is the name of the pie toggle as defined in the pieToggles option. If toggleName is not passed, return true is any of the pie menus is open.
remove() Remove/destroy the Halo. Note that by default, the Halo removes itself when the user clicks on a blank area in the paper. In some cases, however, it is useful to remove the Halo programmatically.
joint.ui.Halo.clear(paper) Remove all the Halo panels (without having to have a reference to a particular halo object) from the paper. Note that this is a static function in the joint.ui.Halo namespace rather than a method of a halo object.

Events

The Halo object triggers events when the user manipulates its tools. These events can be handled by using the Halo 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.
action:link:add

Triggered after the user finished creating a link with the default link tool. This is useful if you want to do something with the link after it has been created. For example, to prevent the user from creating loose links (links that do not have both source and target set), you can do:

halo.on('action:link:add', function(link) {
        if (!link.get('source').id || !link.get('target').id) {
            link.remove();
        }
    });
state:open Triggered when the user opens the Halo (applicable for the pie type of Halo). The handler is passed the pie toggle name as defined in pieToggles option.
state:close Triggered when the user closes the Halo (applicable for the pie type of Halo). The handler is passed the pie toggle name as defined in pieToggles option.
close Triggered when the halo gets closed.