Port click event without disabling new link drag&drop

I have to make ports selectable.

image

If I set the .click() property on the port container Panel or on the port Shape, dragging a new link from a port gets disabled. Most likely I’m overriding it by setting the .click() prop.

Temporarily I made the port label actionable and I can select ports by clicking on the label, but I need to make the port shape single click working as well.

You might be interested in how this sample is implemented: https://gojs.net/latest/samples/selectableFields.html

Thank you Walter, that helped!
I’ve set the .click() prop on the port container Horizontal part and the fromLinkable and toLinkable props on not just the port container part, but also the Shape and the TextBox parts and now it’s working correctly, I can select by clicking anywhere and create a new link by dragging from anywhere on the port.

But I have a new problem: by selecting the port this way, the node itself also gets selected. I don’t want that. Actually I would like the node to be selectable (and movable) only on its header (the node has a header part). Is there a way to do that? (I was trying to search the forum, but did not found a similar question.)

Thanks!

The ClickSelectingTool.doMouseUp method first calls Tool.standardMouseSelect to select the clicked Part (or not, depending on modifier keys and selection rules), and then calls Tool.standardMouseClick to raise any appropriate click events. At the time of the GraphObject.click event handler the Node has already been selected.

So an easy solution is to just make sure the part is not selected:

  {
    click: function(e, port) {
      port.part.isSelected = false;
      . . . do whatever you want to do with the port upon a click . . .
    }
  }

If that isn’t flexible enough for you, you can override ClickSelectingTool.standardMouseSelect to do what you want. I can help you with that, but first why don’t you just try the easy solution, above, to see if it meets your requirements.

Thanks a lot for your quick answer Walter!

The thing is that I’m listening to the ChangedSelection event to display more info about the currently selected node on a sidebar next to the diagram itself. It gets called indeed as it’s earlier in the call stack, and I have no way to know if it really needs to be selected or not. Or is there any? Can I get actually which item was clicked in a ChangedSelection listener? I see that there is a parameter prop on DiagramEvent, is there a way to set that (I could check that in the listener)?

No, the “ChangedSelection” DiagramEvent happens whenever any command or tool changes the Diagram.selection collection, for any number of reasons. There might not even be a click involved at all. And you cannot modify the selection collection for any reason in such a listener.

But of course the ClickSelectingTool does involve a click, so it would make sense to customize that. The standard code is here: How to trigger selection event You could override that method to do what you want for each of the cases it handles.

I was thinking about setting the parameter prop earlier (somewhere on the node template) and just checking (not modifying) in the listener.

Just to make sure I understand correctly: I need to override the ClickSelectingTool.standardMouseSelect method, get the clicked part from diagram.lastInput and if that was a port (or the node background - not the header), just don’t call diagram.raiseDiagramEvent('ChangedSelection')? In this case the node won’t get into the selection collection neither, right?

Whether or not a Part is selected is entirely a matter of the value of Part.isSelected property.

Hmm, this doesn’t seem to be working. I did override the ClickSelectingTool, but diagram.findPartAt(diagram.lastInput.documentPoint, false) gives back the selected node and not the port. How could I find out whether a node header or a port was clicked?

The name of the method should give you a clue: Diagram.findPartAt. You could instead call Diagram.findObjectAt.

However, I think you don’t need to call either method – just look at the InputEvent.targetObject.

Where do I get the InputEvent.targetObject from?
standardMouseSelect doesn’t have any parameters.

Never mind – the this.diagram.lastInput.targetObject is null then anyway.

Try evaluating thisdiagram.findObjectAt(this.diagram.lastInput.documentPoint).

Yes, this should work. I’ve succeeded getting the name of the clicked object out. :)

Thanks again Walter, I was able to make it work the way I wanted. :) Here is my final code:

class CustomClickSelectingTool extends go.ClickSelectingTool {
standardMouseSelect = () => {
    const diagram = this.diagram;
    if (diagram === null || !diagram.allowSelect) return;
    const e = diagram.lastInput;
    const curobj = diagram.findObjectAt(e.documentPoint);

    if (curobj?.part) {
        // Make only the node headers selectable
        if (curobj.name !== "HeaderPanel" &&
            curobj.name !== "HeaderShortName" &&
            curobj.name !== "HeaderProductName" &&
            (curobj.part?.data as DiagramModelTypes)?.diagramModelType === DiagramModelType.DeviceHeader) // this line here is obviously a custom check (I have multiple node templates, and this rule does only apply to one type)
            return;

        if (e.meta || e.control) {
            // toggle the part's selection
            diagram.raiseDiagramEvent('ChangingSelection');
            let part = curobj.part;
            while (part !== null && !part.canSelect()) part = part.containingGroup;
            if (part !== null) part.isSelected = !part.isSelected;
            diagram.raiseDiagramEvent('ChangedSelection');
        } else if (e.shift) {
            // add the part to the selection
            if (!curobj.part.isSelected) {
                diagram.raiseDiagramEvent('ChangingSelection');
                let part = curobj.part;
                while (part !== null && !part.canSelect()) part = part.containingGroup;
                if (part !== null) part.isSelected = true;
                diagram.raiseDiagramEvent('ChangedSelection');
            }
        } else {
            if (!curobj.part.isSelected) {
                let part = curobj.part;
                while (part !== null && !part.canSelect()) part = part.containingGroup;
                if (part !== null) diagram.select(part);  // also raises ChangingSelection/Finished
            }
        }
    } else if (e.left && !(e.meta || e.control) && !e.shift) {
        // left click on background with no modifier: clear selection
        diagram.clearSelection();  // also raises ChangingSelection/Finished
    }
};

}