Dragging a link

The goal: When trying to drag a link, the nodes at both ends will be dragged (along with the link).
Look at this example, which is a copy of the minimal example except that a link is green, thick and selectable. If one would drag a node, the node would move. If you would drag the link between alpha and gamma, both nodes would move (if I could get put it to work).

note: the example incorporates all the work laid out below. Just save it to obtain your copy.

Solution A:
Overriding the method go.DraggingTool.prototype.computeEffectiveCollection and not only returning the link but also the fromNode and toNode.

    var tool = myDiagram.toolManager.draggingTool;
    tool.computeEffectiveCollection = function(parts) {
      var functhis = this;
      var map = go.DraggingTool.prototype.computeEffectiveCollection.call(this, parts);
      console.log("computeEffective start on parts:");
      parts.each(function(p){
        console.log("part "+p.toString());
      }); 
      map.each(function(n) {
        console.log("start map component: "+n.key.toString()+ " : "+n.value.point.toString());
        console.assert(n.key.canMove(),"Wooooo is us, cant move "+n.toString());
      });
      parts.each(function(link) {
        if (!link.isTreeLink) return;
        console.log("link found to move");
//        map.clear();
//        var myparts= new go.Set;
//        myparts.add(link.fromNode);
//        tool.dragsTree=true;
//        map = go.DraggingTool.prototype.computeEffectiveCollection.call(functhis, myparts);
        map.add(link.fromNode,{point: link.fromNode.location});
        map.add(link.toNode,{point: link.toNode.location});
        map.add(link,{point: new go.Point(0,0)});
      });
      map.each(function(n) {
        console.log("map component: "+n.key.toString()+ " : "+n.value.point.toString());
        console.assert(n.key.canMove(),"Wooooo is us, cant move "+n.toString());
      });
      return map; 
    };

Alas, nothing moves. If you set draggingTool.dragsTree to true, then dragging gamma does result in dragging delta and the link as well. Dragging a tree works and standard computeEffectiveCollection function returns the same map as my revised function does. Yet despite the similar returned map, the dragging is not the same. I tried to alter the “parts” variable but you cant alter it anymore in this function.
Mark that simply turning on dragsTree doesnt work as I want to drag each node individually.
As indicated in the extentions page in the api, one could resort to subclassing iso overriding methods. I did do that but the results are identical to overriding.

Solution B:
If changing computeEffectiveCollection is not enough, we ll look at functions that fire there after.

    tool.computeMove = function(n, newloc, draggedparts, result) {
      console.log("start computeMove on "+n.toString());
      draggedparts.each(function(n) {
        console.log("start map component: "+n.key.toString()+ " : "+n.value.point.toString());
      });
      var ret = go.DraggingTool.prototype.computeMove.call(this,n, newloc, draggedparts, result);
//      tool.dragsTree=false;
      return ret;
    };

Utterly useless because this function is not even executed when dragging a link. Only when dragging a node you can see it firing. So something in the draggingtool must be deciding to NOT execute computeMove in this case.

Solution C:
Lets try to “trick” the dragtool before he realises that we are handling a link. When finding what needs dragging, we return not the link but a node related to the link. By turning on temporarly the dragsTree property, the 2 nodes (and the whole subtree) will be dragged, I hope.

    tool.findDraggablePart = function() {
      console.log("findDraggablePart start");
      var diagram = this.diagram;
      var obj = diagram.findObjectAt(diagram.lastInput.documentPoint);
      if (obj !== null) {
        console.log("this "+obj.toString());
        console.log(" is from part "+obj.part.toString());
        obj=obj.part;
        if (obj !== null && obj.isTreeLink) {
          console.log("yay! returning "+obj.fromNode.toString());
          return obj.fromNode;
        } else {
          var newobj = go.DraggingTool.prototype.findDraggablePart.call(this);
          console.log("trad returns "+newobj.toString());
          return newobj;
        }
      }
      return null;
    };

The code looks for the object to drag and when it is a link, we simply return the fromNode. This node would then be fed to computeEffectiveCollection I presumed, resulting in behaviour identical to a dragged tree. Alas, this function doesnt really did what I thought it would do because it has no influence on the input of computeEffectiveCollection. Even though returning the node in findDraggablePart , the computeEffectiveCollection function is called with only the link in the parameter parts.

Is there anyone who can shine his or her light as to what I might be misunderstanding or what the way to go could be?

Actually, your first idea to override DraggingTool.computeEffectiveCollection is the right way to go: var tool = myDiagram.toolManager.draggingTool; tool.dragsLink = true; tool.computeEffectiveCollection = function(parts) { if (parts.count === 1 && parts.first() instanceof go.Link) { var link = parts.first(); var map = new go.Map(go.Part); map.add(link, { point: new go.Point() }); if (link.fromNode !== null) map.add(link.fromNode, { point: link.fromNode.location }); if (link.toNode !== null) map.add(link.toNode, { point: link.toNode.location }); return map; } else { return go.DraggingTool.prototype.computeEffectiveCollection.call(tool, parts); } };

Great minds think alike ;P The only difference in our implementations is the statement:

[code]tool.dragsLink = true;[/code]
I actually tried it before but because it decouples the links from the nodes it ran into a lot of problems (in a not-so-minimal program). Ill try to patch up the broken connections one more time.

For others trying to do the same thing:

    var tool = myDiagram.toolManager.draggingTool;
    tool.dragsLink = true;
    tool.computeEffectiveCollection = function(parts) {
      if (parts.count === 1 && parts.first() instanceof go.Link) {
        var link = parts.first();
        var map = new go.Map(go.Part);
        map.add(link, { point: new go.Point() });
        if (link.fromNode !== null) map.add(link.fromNode, { point: link.fromNode.location });
        if (link.toNode !== null) map.add(link.toNode, { point: link.toNode.location });
        //save the information of what was linked
        tool.savedLinks = new go.Map(Object,Object);
        tool.savedLinks.add(link.data, {from: link.fromNode.data, to: link.toNode.data });
        return map;
      } else {
        return go.DraggingTool.prototype.computeEffectiveCollection.call(tool, parts);
      }

    };

    tool.doDeactivate = function() {
      this.diagram.startTransaction("relinking after dragging");
      var it = tool.draggedParts.iteratorKeys;
      while (it.next()) {
        var n = it.value;
        if(typeof tool.savedLinks === "undefined") continue; //happens when we are not dragging a link but a link is moved (through a node)
        if(n instanceof go.Link && (typeof n.data.from === "undefined" || typeof n.data.to === "undefined")) {
          this.diagram.model.setDataProperty(n.data,"from",tool.savedLinks.getValue(n.data).from.key);
          this.diagram.model.setDataProperty(n.data,"to",tool.savedLinks.getValue(n.data).to.key);
        }
      }
      this.diagram.commitTransaction("relinking after dragging");
      return go.DraggingTool.prototype.doDeactivate.call(this);
    };

Thanks for posting your code. I hope you don’t take offense if I provide some commentary.

First, the “if (typeof tool.savedLinks === “undefined”) continue;” statement is independent of the contents of the DraggingTool.draggedParts collection that the loop is iterating over. So the code could be reorganized not to execute the loop at all when savedLinks is undefined.

Second, the code sets a “savedLinks” property on the tool but does not clear it. This will cause undesired side-effects on drags after the first one that drags a single Link.

Third, startTransaction/commitTransaction calls are unnecessary because DraggingTool.doActivate will already have started a transaction, and DraggingTool.doDeactivate makes sure that the transaction is terminated properly.

Fourth, what are you trying to accomplish by the override of DraggingTool.doDeactivate? Are you trying not to have dragged Links be disconnected? That shouldn’t be a problem since computeEffectiveCollection should always be including both Nodes that the Link is connected with.

Fifth, although setting the “from” property on the link data may work in your app, it won’t work in general because the GraphLinksModel might be using a different property name (which is controlled by the GraphLinksModel.linkFromKeyProperty property). It’s more robust to call GraphLinkModel.setFromKeyForLinkData. But again, I don’t think these calls are needed at all.

Thank you very much. As a newbie in js I could sure you some pointers and since js doesnt include any …

Only
the fourth point I do contest, because the dragged links DO get
disconnected, regardless of the nodes being included in the draggedParts
collection or not. I did upload the file again:
http://users.telenet.be/zeverzever/gojs/ so you can see for
yourself. after you move a link, drag one of the attached nodes.

The reworked code:

    tool.doDeactivate = function() {
      if(typeof tool.savedLinks !== "undefined") {
        var it = tool.draggedParts.iteratorKeys;
        while (it.next()) {
          var n = it.value;
          if(n instanceof go.Link && (typeof n.data.from === "undefined" || typeof n.data.to === "undefined")) {
            var myModel = this.diagram.model;
            var data = tool.savedLinks.getValue(n.data);
            myModel.setDataProperty(n.data,myModel.linkFromKeyProperty,data[myModel.linkFromKeyProperty].key);
            myModel.setDataProperty(n.data,myModel.linkToKeyProperty,data[myModel.linkToKeyProperty].key);
          }
        }
        tool.savedLinks.clear();
      }
      return go.DraggingTool.prototype.doDeactivate.call(this);
    };