Search code examples
javascriptelementonchangerestrictionjointjs

How to prevent changes to links or elements?


I need to restrict the user interaction of certain links and elements. For example, if the user moves or deletes the item, the last state of this item will show up immediately and the graph will seem unaltered.

Ideally I am looking for a simple property that can be added when the element is created which prevents all changes to happen, e.g. isModifiable: false.

Since I couldn't find one, I used the change event and a not very elegant recursive solution for links (added as an answer below, now edited with an alternative and better solution).

Is there an ideal method for stopping or restricting changes to happen?


Solution

  • Individual Element/Link restriction

    I managed to solve this problem partially by modifying the events associated to the cells, e.g. for joint.dia.Link:

    1. Add a custom function to the change event;
    2. Once a change is triggered, remove the newly changed link from the graph;
    3. Use an intact clone of the initial link and clone it again for using it as the newly restored link;
    4. Add the same function to the cloned link change event;
    5. Add the cloned link to the graph.

    So these would be:

    let mylink = new joint.dia.Link({
      source: sourceObj,
      target: targetObj,
      attrs : attrsObj
    });
    let mylinkClone = mylink.clone();
    
    function restoreLink(link, changeStats) {
      link.remove();
      let rlink = mylinkClone.clone();
      rlink.on('change', restoreLink);
      rlink.addTo(graph);
    }
    
    mylink.on('change', restoreLink);
    

    Once the user clicks the delete button, or tries to drag the link out, or anything else, there is literally no visual change.

    This may not be a very elegant or high performance solution (due to repeated cloning) and I noticed during the period in which a token is sent through the link, the user is still able to delete it. But if you have very specific links/elements once they are created that you need to restrict, it's still effective.

    Paper interactive property restrictions

    This is an alternative better way to restrict specific actions on all elements or links on the paper by using joint.Paper.prototype.options.interactive, as suggested by Roman in another answer.

    Restrict Links

    So when the paper is instantiated, if you want all the interactions with links to be permanently restricted, you can just do:

    var paper = new joint.dia.Paper({
      interactive: {
        useLinkTools: false,
        labelMove: false,
        vertexRemove: false,
        vertexAdd: false
        vertexMove: false,
        arrowheadMove: false,
      },
      gridSize: 10,
      drawGrid: true,
      model: graph,
      defaultLink: new joint.shapes.app.Link,
      // other properties ....
    })
    

    Restrict Elements

    If you want to only restrict all elements' interactions, then:

    var paper = new joint.dia.Paper({
      interactive: {
        elementMove: false,
        addLinkFromMagnet: false,
      },
      // other properties ....
    })
    

    Providing a function to interactive (high customisation)

    Or, if you use a function for the interactive property value, you just need to distinguish elements from links and then operate on them. For restricting only links interactions you just need to return false when these are detected:

    var paper = new joint.dia.Paper({
      interactive: function (cellView) {
        if(cellView.model instanceof joint.dia.Link) {
          console.log('Link interaction');
          return false;
        }
        else {
          console.log('Element interaction of type:', cellView.model.get('type'));
          return true;
        }
      },
      // other properties ....
    })
    

    My preferred method of restriction for my specific case was simply done by identifying the links connected to certain sources and returning false for them:

    var paper = new joint.dia.Paper({
      interactive: function (cellView) {
        if(cellView.model instanceof joint.dia.Link) {
          console.log('Link interaction');
          var lSource = cellView.model.getSourceElement();
          if(lSource instanceof joint.shapes.app.AwesomeShape){
            // do not allow links connected to these sources to be modified
            console.log(`Links with ${lSource.get('type')} sources cannot be modified!`);
            return false;
          }
          // allow other links to be modified
          return true;
        }
        console.log('Element interaction of type:', cellView.model.get('type'));
        return true;
      },
      // other properties ....
    })