3

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?

4 Answers4

9

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

Mike Goodwin
  • 8,810
  • 2
  • 35
  • 50
  • Hello, Mike. Is it possible for you to upload the code somewhere? I am not exactly sure how to piece everything together – Alexey May 22 '15 at 11:12
  • Works amazing!! Is it possible to somehow extend all of elements to have this toolTip at the top? It is amazing that this works, but I don't like the idea that all of my objects have to have Actor as the parent – Alexey May 25 '15 at 10:25
  • For example there is the dev.Model extension. I would like to extend that too. – Alexey May 25 '15 at 10:35
  • The elements would have to be extended from toolElement rather than Actor, but I get your point. Sorry, but I'm not sure how to do it any other way. – Mike Goodwin May 25 '15 at 11:52
  • Sorry to bother again, Mike. Can you give me some tips on how to create the link button? Did you assign it magnetic property, or did you write the functionality from ground zero? – Alexey May 25 '15 at 12:37
  • I'm not sure what you mean. Maybe you could post a new question, then it would be easier for other people to find it in the future. If you tag it with jointjs, then I'll see it. – Mike Goodwin May 27 '15 at 15:18
  • http://stackoverflow.com/questions/30504323/creating-a-toolelement-for-every-object-jointjs I made the question. Would be great if you could take a look – Alexey May 28 '15 at 10:46
  • Works like a charm, Thank you very much – Sajith Edirisinghe May 05 '16 at 18:01
  • the .element-tools display none in the css does not seem to work in the current jointjs download for meteor. Is there any work around for the same? – Vinay Prabhakaran Nov 27 '16 at 19:24
2

Have a look at the HTML example on the JointJS website.

As you can see the elements have a close button there, so there's no need to complicate things by creating your own. Simply create a view for your element that contains the HTML code for the button, as well as the event handling. It's all in the source code of the example.

Note that the example doesn't provide you the CSS file for the HTML elements, but you also need it: http://resources.jointjs.com/tutorials/joint/tutorials/css/html-elements.css

marczoid
  • 1,365
  • 2
  • 12
  • 20
1

A more native approach could be using the provided elementTools:

const view = element.findView(paper);
const removeButton = new joint.elementTools.Remove({
    focusOpacity: 0.5,
    rotate: true,
    x: '50%',
    y: '0%',
    offset: { x: 10, y: 10 }
});

const toolsView = new joint.dia.ToolsView({
    name: 'basic-tools',
    tools: [removeButton]
});

view.addTools(toolsView);
Ali Havasi
  • 591
  • 6
  • 8
-1
joint.shapes.devs.ModelView = joint.dia.ElementView.extend(_.extend({},joint.shapes.basic.PortsViewInterface,{
         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) {
        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);
}

}));

diiN__________
  • 7,393
  • 6
  • 42
  • 69
Atish Aryan
  • 77
  • 1
  • 2