Search code examples
javascriptjquerygraphjointjs

How to give JointJS elements a remove tool?


In JointJS, links come with a handy responsive tool for removing links (when you hover over the link, an "x" appears, and clicking it removes the link). Elements, on the other hand, have a remove() method in the API, but don't have the UI "x" to expose that method to users.

Is there a straightforward way to give users the ability to delete elements in the UI?


Solution

  • In my project I define a custom shape - toolElement - that encapsulates this behaviour and then extend this with other custom shapes as required.

    Full disclosure: This technique leans heavily on the jointjs code for links - I just adapted it :o)

    Here is a jsfiddle showing it working:

    http://jsfiddle.net/kj4bqczd/3/

    The toolElement is defined like this:

    joint.shapes.tm.toolElement = joint.shapes.basic.Generic.extend({
    
        toolMarkup: ['<g class="element-tools">',
            '<g class="element-tool-remove"><circle fill="red" r="11"/>',
            '<path transform="scale(.8) translate(-16, -16)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z"/>',
            '<title>Remove this element from the model</title>',
            '</g>',
            '</g>'].join(''),
    
        defaults: joint.util.deepSupplement({
            attrs: {
                text: { 'font-weight': 400, 'font-size': 'small', fill: 'black', 'text-anchor': 'middle', 'ref-x': .5, 'ref-y': .5, 'y-alignment': 'middle' },
            },
        }, joint.shapes.basic.Generic.prototype.defaults)
    
    });
    

    You can add more markup if you need other tools as well as the remove button.

    The remove behaviour is encapsulated in a custom view:

    joint.shapes.tm.ToolElementView = joint.dia.ElementView.extend({
    
        initialize: function() {
    
            joint.dia.ElementView.prototype.initialize.apply(this, arguments);
        },
    
        render: function () {
    
            joint.dia.ElementView.prototype.render.apply(this, arguments);
    
            this.renderTools();
            this.update();
    
            return this;
        },
    
        renderTools: function () {
    
            var toolMarkup = this.model.toolMarkup || this.model.get('toolMarkup');
    
            if (toolMarkup) {
    
                var nodes = V(toolMarkup);
                V(this.el).append(nodes);
    
            }
    
            return this;
        },
    
        pointerclick: function (evt, x, y) {
    
            this._dx = x;
            this._dy = y;
            this._action = '';
    
            var className = evt.target.parentNode.getAttribute('class');
    
            switch (className) {
    
                case 'element-tool-remove':
                    this.model.remove();
                    return;
                    break;
    
                default:
            }
    
            joint.dia.CellView.prototype.pointerclick.apply(this, arguments);
        },
    });
    

    You can then extend these to make your custom shapes. In my project, I am doing data flow diagrams and here is the definition of the Process shape:

    joint.shapes.tm.Process = joint.shapes.tm.toolElement.extend({
    
        markup: '<g class="rotatable"><g class="scalable"><circle class="element-process"/><title class="tooltip"/></g><text/></g>',
    
        defaults: joint.util.deepSupplement({
            type: 'tm.Process',
            attrs: {
                '.element-process': { 'stroke-width': 1, r: 30, stroke: 'black', transform: 'translate(30, 30)' },
                text: { ref: '.element-process'}
            },
            size: { width: 100, height: 100 }
        }, joint.shapes.tm.toolElement.prototype.defaults)
    });
    

    and view:

    joint.shapes.tm.ProcessView = joint.shapes.tm.ToolElementView;
    

    I show and hide the tool markup, depending whether the element is highlighted using CSS. You could do the same when hovering (like the links do) if you like:

    .element .element-tools {
        display: none;
        cursor: pointer
    }
    
    .element.highlighted .element-tools {
        display: inline;
    }
    

    When rendered, it looks like this (note: in my case, I have another button in the tools, not just the remove - that is what the green chevron button is. I removed this from the code samples above to make them simpler):

    When the element is not highlighted:

    element tool unhighlighted render

    When it is highlighted:

    rendering of the tool element

    I can then define other shapes really easily by extending toolElement. Here are the data flow diagram shapes for data stores:

    enter image description here

    and external actors:

    enter image description here