Changing Selectable Parts

Hello! I am currently working on implementing a Node object that can have different selectable parts depending on whether or not the ports are expanded.
This is our base node, which should be selected with our circular port like below:
Screen Shot 2022-08-17 at 4.20.19 PM selected → Screen Shot 2022-08-17 at 4.20.25 PM

In order to achieve this, I had to add a hidden rectangular object on top of the node body and the circular port and set that as my selection object. This is fine, but the problem I’m facing happens when I want to expand the ports and the selection object should change to just be the node body.
Screen Shot 2022-08-17 at 4.20.31 PM should look likeScreen Shot 2022-08-17 at 4.20.38 PM

When the ports are expanded, updateTargetBindings is called on the body part to recalculate port positions, but the selection remains how it was originally. I have bindings set to both selectionObjectName and selectionAdornment to change up the selected object and adornment, but I have to first unselect the node, then reselect it in order to get this behavior. I’ve been searching through the API, and I feel like there must be a call I may be overlooking that should handle updating it at the same time the ports are being redrawn. (I’ve looked at updateAdornments, and that didn’t work either.)

Here is my code trimmed below if this helps:

public static HybridTemplate(
    inputCircle: boolean = true,
    handlers: Map<string, (...args: any) => void>
  ): GoNode {
    return GO(
      GoNode,
      'Spot',
      new Binding('selectionObjectName', '', (node: Node, _part: Part) => {
        if ((inputCircle && node.inPorts?.expanded) || (!inputCircle && node.outPorts?.expanded)) {
          return 'SHAPE';
        }
        return 'EXPANDEDSHAPE';
      }),
      new Binding('selectionAdornmentTemplate', '', (node: Node, _part: Part) => {
        if ((inputCircle && node.inPorts?.expanded) || (!inputCircle && node.outPorts?.expanded)) {
          return AdornmentTemplate.SquareNodeSelectionAdornment(handlers);
        }
        return AdornmentTemplate.HybridNodeSelectionAdornment(inputCircle, handlers);
      }),
    );
    /** Other node properties */
  }

Any help would be very appreciated- thanks!

[EDIT] Never mind – I just tried it, and if you can assume that the distance is fixed, it’s pretty easy to implement using a regular selection adornment and data binding.

image

<!DOCTYPE html>
<html>
<head>
  <title>Minimal GoJS Sample</title>
  <!-- Copyright 1998-2022 by Northwoods Software Corporation. -->
</head>
<body>
  <div id="myDiagramDiv" style="border: solid 1px black; width:100%; height:600px"></div>
  <button id="myTestButton">Test</button>
  <textarea id="mySavedModel" style="width:100%;height:250px"></textarea>

  <script src="https://unpkg.com/gojs"></script>
  <script id="code">
const $ = go.GraphObject.make;

const myDiagram =
  $(go.Diagram, "myDiagramDiv",
    {
      "undoManager.isEnabled": true,
      "ModelChanged": e => {     // just for demonstration purposes,
        if (e.isTransactionFinished) {  // show the model data in the page's TextArea
          document.getElementById("mySavedModel").textContent = e.model.toJson();
        }
      }
    });

myDiagram.nodeTemplate =
  $(go.Node, "Spot",
    {
      selectionObjectName: "BODY",
      selectionAdornmentTemplate:
        $(go.Adornment, "Auto",
          $(go.Shape, { fill: null, stroke: "dodgerblue", strokeWidth: 4 }),
          $(go.Placeholder, { margin: new go.Margin(2, 17, 2, 2) },
            new go.Binding("margin", "apart", a => a ? new go.Margin(2) : new go.Margin(2, 17, 2, 2)))
        )
    },
    $(go.Shape,
      { name: "BODY", fill: "white", width: 40, height: 40 },
      new go.Binding("fill", "color")),
    $(go.Shape, "Circle",
      { alignment: go.Spot.Right, width: 30, height: 30 },
      new go.Binding("fill", "color"),
      new go.Binding("alignmentFocus", "apart", a => a ? new go.Spot(0, 0.5, -20, 0) : go.Spot.Center)),
    $(go.TextBlock,
      { alignment: go.Spot.Bottom, alignmentFocus: new go.Spot(0.5, 0, 0, -4) },
      new go.Binding("text"))
  );

myDiagram.model = new go.GraphLinksModel(
[
  { key: 1, text: "Alpha", color: "lightblue" },
  { key: 2, text: "Beta", color: "orange", apart: true },
]);

document.getElementById("myTestButton").addEventListener("click", e => {
  const node = myDiagram.selection.first();
  if (!node) return;
  myDiagram.model.commit(m => m.set(node.data, "apart", !node.data.apart));
});
  </script>
</body>
</html>

The margin binding worked perfectly, and much cleaner than what I was doing. Thanks!