PortShiftingTool and DynamicPorts with itemTemplate (again)

Hi there!

Somehow the PortShiftingTool and using DynamicPorts with itemTemplate seem incompatible to me…

Please correct me if I’m wrong, but here’s how I understood the two:
The PortShiftingTool requires the ports (no matter if the ports are panels or shapes) to be in a Spot Panel, but the port must not be the “main element”…
Whereas an itemTemplate must be a Panel (at least that’s what my typescript editor tells me).

I started with a nodeTemplate that could be used in a diagram, links between ports could be drawn, everything worked nice. It’s the template at the end, except for the portId binding of the itemTemplate’s Panel, which I didn’t need yet.
Then I wanted to add the ability to move ports around, and added the PortShiftingTool, which…just didn’t work.
The tool’s findPort function returned null - why? because my port (marked by portId) was the itemTemplate’s Shape (Rectangle) inside the itemTemplate’s Panel, and findPort only looks one layer up for a Spot Panel.
Next I tried to make the itemTemplate’s Panel a Spot Panel, but that didn’t work either, because the port must not be the “main element”, but findMainElement defaults to the first child.
What worked was moving the portId binding up a level, then the PortShiftingTool suddenly was content and findPort returned a result.
And I was really happy to have found such a simple solution… until I wanted to create a Link, which - all of a sudden - did not work any more (wtf! ;)). For this to work the portId had to be bound to the Shape, moving the binding back and forth between the two illustrates the behaviour.

Now I wonder if it’s acceptable to bind the portId to both, like so:

$(go.Node, "Vertical",
    new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
    $(go.Panel, "Spot",
        $(go.Panel, "Auto", { padding: 4 },
            $(go.Picture, new go.Binding("source", "imageUrl"))
        ),
        new go.Binding("itemArray", "contactpoints"),
        {
            itemTemplate: $(go.Panel, { margin: 0 },
                new go.Binding("alignment", "", (obj) => new go.Spot(obj.x, obj.y)),
                new go.Binding("portId", "id", (id) => "" + id), // portId necessary for the PortShiftingTool
                $(go.Shape, "Rectangle",
                    {
                        width: 4, height: 4, fill: "blue", stroke: null, strokeWidth: 0,
                        fromLinkable: true, toLinkable: true, cursor: "pointer"
                    },
                    new go.Binding("portId", "id", (id) => "" + id) // portId necessary for creating links
                )
            )
        }
    )
);

…or if I should tweak the PortShiftingTool to look two layers up (from my Rectangle to the Spot Panel).
Or if there’s another solution the gojs-team deems “right” in this case…? :)

Thanks again in advance for your time and effort!
Florian

The portShiftingTool extension works by modifying a GraphObject.alignment property, which only makes sense in some panels (like Spot panel). The tool could be modified to work in other panels, like a Position panel, if it modifies the GraphObject.position instead. But this is non-trivial work.

Yes, but there’s no reason you cannot have a Spot Panel which uses an item Array, and each item (themselves a Panel) is a Port.

The DynamicPorts sample is set up with each Node having 4 panels, all of them Vertical or Horizontal (rather than Spot) holding the ports. If you made a node that had a Spot panel holding the ports (as an item array) it should work as you expect.

I just mangled the Dynamic Port node template to see if it would work, and it does:

    myDiagram.nodeTemplate =
      $(go.Node, "Spot",
        {
          locationObjectName: "BODY",
          locationSpot: go.Spot.Center,
          selectionObjectName: "BODY",

          itemTemplate:
            $(go.Panel,
              {
                _side: "left",  // internal property to make it easier to tell which side it's on
                fromSpot: go.Spot.Left, toSpot: go.Spot.Left,
                // fromLinkable: true, toLinkable: true,
                cursor: "pointer",
                contextMenu: portMenu
              },
              new go.Binding("portId", "portId"),
              $(go.Shape, "Rectangle",
                {
                  stroke: null, strokeWidth: 0,
                  desiredSize: portSize,
                  margin: new go.Margin(1, 0)
                },
                new go.Binding("fill", "portColor"))
            )  // end itemTemplate

        },
        new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
        new go.Binding("itemArray", "leftArray"),

        // the body
        $(go.Panel, "Auto",
          {
            row: 1, column: 1, name: "BODY",
            stretch: go.Stretch.Fill
          },
          $(go.Shape, "Rectangle",
            {
              fill: "#dbf6cb", stroke: null, strokeWidth: 0,
              minSize: new go.Size(60, 60)
            }),
          $(go.TextBlock,
            { margin: 10, textAlign: "center", font: "bold 14px Segoe UI,sans-serif", stroke: "#484848", editable: true },
            new go.Binding("text", "name").makeTwoWay())
        )
      );  // end Node

All ports start in the center of the node, but shift-clicking them allows one to move them as expected around the Spot panel (I changed the whole node to be a spot panel for simplicity of the demo). As-is this requires zero modification of the PortShiftingTool

1 Like

ohmy… I’m so sorry for wasting your time!

At least comparing your nodeTemplate to mine opened my eyes:
The real reason I could not draw links between my nodes was that I did not move fromLinkable: true, toLinkable: true from the itemTemplate’s Rectangle to its Panel

now the itemTemplate looks (kind of) like this, and everything’s fine: :)

itemTemplate: $(go.Panel, {margin: 0, fromLinkable: true, toLinkable: true, cursor: "pointer", fromSpot: go.Spot.Left, toSpot: go.Spot.Left},
	new go.Binding("alignment", "", obj => new go.Spot(obj.x, obj.y)),
	new go.Binding("portId", "id", id => "" + id),
	$(go.Shape, "Rectangle", {width: 4, height: 4, stroke: null, strokeWidth: 0, fill: "blue"})
)

That’s all right of course. Often its good to ask “how would you do…” if what you feel you are doing starts to feel too convoluted.

1 Like