Temporary Ports with No Real Port

We would like to be able to start dragging a link towards a node and create an input port to connect to automatically on that node if one is not available. I would like to show our “temporary port” if 1) there is not a port created yet or 2) there are ports, but they already have connections. (Ports in our world should only have one incoming connection, but a node can have multiple input ports if necessary). If the user decides to not link the two nodes, the temporary port goes away, or if they do, it should update our node model to have another port in its port item array.
Screenshot 2023-05-11 at 1.11.07 PM
Screenshot 2023-05-11 at 1.05.16 PM
Example: in the screen shots above, as I am dragging the second bottom link towards node 2, it adds a second “temporary input port” for the user to connect to.

I’ve started by looking within our custom LinkingTool- we have a “portTargeted” function that works for highlighting ports that are found, but I’m thinking that might be the right place for this functionality to exist as well. If a port is not found available in the portTargeted method, I search the area to see if a node is at least nearby, and what its port situation is like. This is the part where I’m struggling- it feels incorrect to dynamically create a port here, add it to our nodes itemarray, store a reference to it elsewhere, and then have to know to delete that port if the user decides to not connect to it when moving away. Before I went any further, I wanted to reach out and see if there are better suggestions for accomplishing this behavior.

Thanks!

Example code of what I’m thinking:

private portTarget(
    node: GoJSNode | null,
    port: GraphObject | null,
    _tempNode: GoJSNode,
    _tempPort: GraphObject,
    _toEnd: boolean
  ) {
    if (port !== null) {
       this.temporaryToPort = LinkTemplate.TemporaryToPort(port); // looks just like our normal ports, but with a black highlight
    } else {
      const node = this.diagram.findObjectsNear(this.diagram.lastInput.viewPoint, 2, (x) => {
        const p = x.part;
        return p instanceof GoJSNode ? p : null;
      }).first();
      if (node) { // there's a node near, see if we should display a temp port for them
        // 1) create temp port
        // 2) add temp port to node, save as class variable here for reference
        // 3) recall this method to find it and highlight?
      } else {
        // delete port reference here and from node, if the temp port reference here exists
      }
    }
  }

If I understand your requirements correctly, it seems that each Node has a default port that is used for all outgoing links. Presumably its fromLinkable property is true.

You would define an “LinkDrawn” DiagramEvent listener that adds a port to the “from” node (by calling Model.addArrayItem) and reconnects the newly drawn Link (i.e. the DiagramEvent.subject) to come from that new port by assigning its Link.fromPortId property.

So there would be no temporary output port at all. Your screenshots don’t seem to show a temporary port so I think what I suggest will still work visually.

I’m not sure what you want to do for input ports. Again, you could allow the drawing of a new link to an existing port, even though there’s already one link coming into it, and then fix it up in the “LinkDrawn” DiagramEvent listener by adding an input port and reconnecting the newly drawn Link.

Or you could use that same default port to also support any link that is coming in during the drawing of a new link. The same fix-up in the “LinkDrawn” event would apply.

Sorry I wasn’t clear about the criteria, here’s some information to hopefully help:

  • Circular nodes have links going to the body of the node/are the ports themselves; square nodes have square ports that have to be defined in order to connect them. You can’t connect to the body of a square node, and this temporary port business doesnt apply to circular nodes since they can’t add ports.
  • Square nodes can have an array of ports (0…n) defined on them on both sides. A user can add or remove as many as they want on square nodes, depending on the node. Some have restrictions like “can only have one input port” in which case we don’t want to let them to create and connect to another port.

The “temporary port” is just for input ports on the square node like in the screenshots, and each input port can only accept one link. In this case, as the user is dragging a link to node 2, a second port should appear for them to connect to since the first port is occupied. Before they start drawing that link, it looks like the first screen shot where there is only one port. The second port appears to let them know a link can be made.

I think the LinkDrawn event happens too late for this. Visually, we need the port to appear on the node as the user starts to drag to it to let them know that a connection can be made and is valid. UX is not a fan of letting them connect to one port and then splitting them a part after the fact.

Here’s an old sample demonstrating a variation on what I suggested: Automatically Creating Ports Just one type of node, though.

Ah, so that is why you were looking into LinkingBaseTool.portTargeted.
Still, it might not be needed. Take a look at Automatically Creating Ports

As always, the complete source code for each sample is in the page itself.

That’s the behavior I’m after. I think I can adapt this to work for us, thanks Walter!