Possible to click endpoints to create link via LinkingTool?

Thanks.

I can place links between my placeholders when not clicking on ports just fine.

However, I have two questions:

  1. What constitutes a mouseDownTool vs a mouseUpTool?
  2. When I select a port, it actually grabs the node which the port belongs to and starts dragging the node around, rather than just setting the temporaryFromPort.

A Tool should be in the ToolManager.mouseDownTools list if you want the ToolManager to consider the tool for running on an initial mouse-down event. Basically – whether to call Tool.canStart in the order that the tools are listed.

Same for an initial mouse-move event for the tools in the mouseMoveTools list, and for an initial mouse-up event for the tools in the mouseUpTools list.

I don’t have time right now to check your code, so I don’t have a comment on that.

Ah, okay. So that’s why my tool works in mouseDownTools but not mouseUpTools, because both ClickSelecting and ClickCreating canStart() before my ClickLinking? I would have to reorder the list of tools (or disable the others) to change that?

Yes, re-order the tools or disable other tools earlier in the list.

Note also that once a tool is running (i.e. Diagram.currentTool is that tool) then it receives all further events (mouse, keyboard, touch) until it decides to stop itself either by calling this.stopTool() or by starting some other tool.

That’s why the ToolManager can just be another Tool – it’s just the Diagram.defaultTool.

Sorry, another ClickCreating question. I’m not sure how to differentiate between single and double-click when just using a doMouseUp() override. Is there a straightforward way to handle this like the ClickCreatingTool does?

Good call, thanks

I’m a little bit confused about the doActivate() method in the LinkingTool, perhaps you can shed some light on it.

“It then start a transaction, captures the mouse, and changes the cursor. Next it initializes and adds the LinkingBaseTool.temporaryFromNode, LinkingBaseTool.temporaryToNode, and LinkingBaseTool.temporaryLink to the diagram. The temporary nodes that are positioned and sized to be like the real LinkingBaseTool.originalFromPort and LinkingBaseTool.originalToPort ports. The temporary link connects the two temporary ports, of course.”

Starting the transaction is obvious, but I’m not sure what is meant by “captures the mouse”. Additionally, is initializing the temporaryFromNode and temporaryToNode as simple as assigning them to a port.part? How would I reset them? How is the temporaryLink set up from this information?

Any clarification would be most helpful!

Here are the complete implementations of LinkingTool.doActivate and LinkingTool.doDeactivate. Pardon me if there are any references to internal properties or methods.

LinkingTool.prototype.doActivate = function() {
  var diagram = this.diagram;
  if (diagram === null) return;
  var startPort = this.findLinkablePort();
  if (startPort === null) return;

  this.startTransaction(this.name);

  diagram.isMouseCaptured = true;
  diagram.currentCursor = 'pointer';

  if (this.isForwards) {
    if (this.temporaryToNode !== null && !this.temporaryToNode.location.isReal()) {
      this.temporaryToNode.location = diagram.lastInput.documentPoint;
    }
    this.originalFromPort = startPort;
    var node = this.originalFromPort.part;
    if (node instanceof Node) this.originalFromNode = /** @type {Node} */ (node);
    this.copyPortProperties(this.originalFromNode, this.originalFromPort, this.temporaryFromNode, this.temporaryFromPort, false);
  } else {
    if (this.temporaryFromNode !== null && !this.temporaryFromNode.location.isReal()) {
      this.temporaryFromNode.location = diagram.lastInput.documentPoint;
    }
    this.originalToPort = startPort;
    var node = this.originalToPort.part;
    if (node instanceof Node) this.originalToNode = /** @type {Node} */ (node);
    this.copyPortProperties(this.originalToNode, this.originalToPort, this.temporaryToNode, this.temporaryToPort, true);
  }

  diagram.add(this.temporaryFromNode);
  diagram.add(this.temporaryToNode);

  if (this.temporaryLink !== null) {
    if (this.temporaryFromNode !== null) {
      this.temporaryLink.fromNode = this.temporaryFromNode;
    }
    if (this.temporaryToNode !== null) {
      this.temporaryLink.toNode = this.temporaryToNode;
    }
    this.temporaryLink.invalidateRoute();
    diagram.add(this.temporaryLink);
  }

  this.isActive = true;
};

LinkingTool.prototype.doDeactivate = function() {
  this.isActive = false;
  var diagram = this.diagram;
  if (diagram === null) return;
  diagram.remove(this.temporaryLink);
  diagram.remove(this.temporaryFromNode);
  diagram.remove(this.temporaryToNode);
  diagram.isMouseCaptured = false;
  diagram.currentCursor = '';
  this.stopTransaction();
};

Thank you, that helps a ton!

Okay, I’m making progress here. I appreciate the assistance.

Another question, however. When the LinkingTool calls insertLink(...) in its doMouseUp method. I’m curious as to where the parameters are from. Are they this.originalToNode or this.temporaryToNode (and their counterparts, of course)?

What I’m doing is, in my doMouseUp, pulling in the original nodes and ports, which I’ve assigned, to order to create the link:

insertLink(fromNode = this.originalFromNode, fromPort = this.originalFromPort, toNode = this.originalToNode, toPort = this.originalToPort) {
    let link = super.insertLink(fromNode, fromPort, toNode, toPort);
    return link;
}```

However, when I stop the transaction (which `this.transactionResult` of `this.name`), only the final placeholder is created. The starting placeholder and link disappear.

Let me know if you need more information.

Here’s the definition of LinkingTool.doMouseUp:

LinkingTool.prototype.doMouseUp = function() {
  if (this.isActive) {
    var diagram = this.diagram;
    if (diagram === null) return;
    this.transactionResult = null;

    /** @ignore @type {Node} */ var fromnode = null;
    /** @ignore @type {GraphObject} */ var fromport = null;
    /** @ignore @type {Node} */ var tonode = null;
    /** @ignore @type {GraphObject} */ var toport = null;

    this.targetPort = this.findTargetPort(this.isForwards);
    var targetport = this.targetPort;
    if (targetport !== null) {
      var tpart = targetport.part;
      if (tpart instanceof Node) {
        var targetnode = /** @type {Node} */ (tpart);
        if (this.isForwards) {
          if (this.originalFromNode !== null) {
            fromnode = this.originalFromNode;
            fromport = this.originalFromPort;
          }
          tonode = targetnode;
          toport = targetport;
        } else {
          fromnode = targetnode;
          fromport = targetport;
          if (this.originalToNode !== null) {
            tonode = this.originalToNode;
            toport = this.originalToPort;
          }
        }
      }
    } else {  // not connecting to a port; set FROMNODE or TONODE, but not both
      if (this.isForwards) {
        if (this.originalFromNode !== null && this.isUnconnectedLinkValid) {
          fromnode = this.originalFromNode;
          fromport = this.originalFromPort;
        }
      } else {
        if (this.originalToNode !== null && this.isUnconnectedLinkValid) {
          tonode = this.originalToNode;
          toport = this.originalToPort;
        }
      }
    }
    if (fromnode !== null || tonode !== null) {
      var link = this.insertLink(fromnode, fromport, tonode, toport);
      if (link !== null) {
        if (targetport === null) {
          // need to tell Link that it ought to end at Diagram.lastInput.documentPoint
          if (this.isForwards) {
            link.defaultToPoint = diagram.lastInput.documentPoint;
          } else {
            link.defaultFromPoint = diagram.lastInput.documentPoint;
          }
        }
        if (diagram.allowSelect) {
          diagram.select(link);
        }
        // set the TransactionResult before raising event, in case it changes the result or cancels the tool
        this.transactionResult = this.name;
        diagram.raiseDiagramEvent('LinkDrawn', link);
      } else {  // insertLink returned null
        diagram.model.clearUnresolvedReferences();
        this.doNoLink(fromnode, fromport, tonode, toport);
      }
    } else {  // no fromnode or tonode
      if (this.isForwards) {
        this.doNoLink(this.originalFromNode, this.originalFromPort, null, null);
      } else {
        this.doNoLink(null, null, this.originalToNode, this.originalToPort)
      }
    }
  }
  this.stopTool();
};

Again, pardon me if there are any references to internal data members or methods.