Node Template Editor

Hi,
I’m trying to implement a Node Template editor for network elements.
In general, i would like to let the user the ability ability to drag some picture of network elements and to to locate its ports dynamically.
In the following picture :


I have implemented a simple Group Template with some Cisco router picture.
On click, somewhere on the picture, the applications adds a re-sizable rectangular red node.
The red nodes represents the device ports.
At some point, the diagram is saved to disk as JSON string.
When the user wants to add a node of this type, the JSON string is loaded from disk as a group node. The application rolls over the group member parts (the red nodes) and adds port “panel” to the template, for each node, as follows:

var nodeTemplate = diagram.nodeTemplateMap.get(“cisco2950”);
var group = diagram.findNodeForKey(1);
var i = 0;

    for (var it = group.memberParts.iterator; it.next();) {
        var node = it.value; 
        var s = node.findObject("SHAPE");
        var x = group.position.x + node.position.x;
        var y = group.position.y + node.position.y;
        var portId = "Eth";
        nodeTemplate.add(
                $(go.Panel,
                        {position: new go.Point(x, y)},
                        $(go.Shape,
                                {width: s.width, height: s.height, 
                                 portId: portId + i, fill: "green" })
                ));
        i++;
    };

The reason that i selected the group–>nodes relationship to be the source of my template is because the red nodes can be moved and located on the group surface, and because they are resize-able.
I couldn’t implement this solution with node–>ports relationship because the ports (itemarray) in the node are not movable nor resize-able.
My question is if there is a smarter way to implement a node template editor using other object type rather than group->nodes relationship.
Recall, that the ports should be able to be moved and resized.
Thanks
Tany

Yes, you can implement it either way. Consider the Draggable Ports sample, which does what you have done: https://gojs.net/latest/samples/draggablePorts.html

Note how the model is handled, and how selection is handled.

I see, the ports are movable but are they also re-sizable ?

Not in that sample, but you could modify it by setting Node.resizable and Node.resizeObjectName and adding a TwoWay Binding of “desiredSize” on the Shape.

I’m mot sure i’m clear.
If i use the node–>ports model,instead of group–>nodes model, namely, model the ports as “red” rectangles shapes, will i be able to resize the port objects ?

I was referring to that sample using the group-and-node-as-port design.

Yes, it should be possible to implement those behaviors, including resizing, in the node-and-simple-object-as-port design. Have you seen this sample? https://gojs.net/latest/extensions/PortShifting.html

Back to this point.
I tried to implement the node editor by using node->port relationship.
Namely, every time the user presses the mouse a port should be added to the node exactly where the mouse was clicked.
But i couldn’t set the port position. I tried to bind the itemArray panel with position and set the position to new go.Point( e.documentPoint.x, e.documentPoint.y), but the port position maintains on a single position, disregarding the binded position.
I guess i have to bind the port spot value relative to the node, but how calculate the spot value relative to the mouse location ?

We’re going to need some more details of what you have implemented.

OK,
Let’s forget for one moment the template editor subject.
I have implemented a node template with an itemArray (ports).
Whenever a mouse is clicked somewhere on the node, i would like to add a port to the node on the mouse (x,y) location.
How can i set the port position within the node ?

It depends on what kind of Panel you are using.

If it’s a “Position” Panel, you’ll need to set the port’s position. It’s easy to compute the offset from the top-left corner of the panel.

If it’s a “Spot” Panel, you’ll need to set the port’s alignment. The advantage of a “Spot” Panel is that you have a choice as to whether the positioning is fractional or absolute in the area of the panel. Fractional is good if you expect the panel’s size to change, either directly because of the user resizing it or indirectly due to stretching to some other object’s size. But there are times when you might need the absolute positioning, which you can achieve using Spot.offsetX and Spot.offsetY. And the absolute positioning can be relative to any fractional point – it doesn’t have to be relative to the top-left corner as the “Position” Panel requires.

In both cases you’ll want to put a Binding on it, getting the value from the item data.

I tried to implement a “position” node :

diagram.nodeTemplate =
        $(go.Node,  "Position",  {   click: addPort },

            $(go.Panel, "Spot",
                $(go.Picture, { name : "PIC" },
                        new go.Binding("source"),
                        new go.Binding("width"),
                        new go.Binding("height")

                ),
                new go.Binding("itemArray", "ports"),
                {
                    itemTemplate : $(go.Panel,
                                new go.Binding("portId", "id"),
                                new go.Binding("position"),
                        { fromLinkable: true,  toLinkable: true, toMaxLinks: 10 },
                        $(go.Shape, "Rectangle", { desiredSize: new go.Size(15, 15), fill: "yellow", cursor: "pointer", })
                    )
                }
            )
        );

and the addPort function :
indent preformatted text by 4 spaces

function addPort(e) {
        diagram.startTransaction("addPort");
        var nodeData = diagram.model.nodeDataArray[0];
        var arr = nodeData["ports"];
        var node = e.targetObject.part;
        if (arr) {
            // create a new port data object
            var newportdata = { id: + nodeData.key + "," + portKey++ , position: node.getLocalPoint(e.documentPoint) };
            // and add it to the Array of port data
            diagram.model.insertArrayItem(arr, -1, newportdata);
        }
        diagram.commitTransaction("addPort");

    }

Still, i cannot set the port position.
Where have i gone wrong ? (Sorry, i cannot recall how to format the code)

Surround code with lines consisting of only triple backquotes.

I’ll look into this later.

function addPort(e) {
        diagram.startTransaction("addPort");
        var nodeData = diagram.model.nodeDataArray[0];
        var arr = nodeData["ports"];
        var node = e.targetObject.part;
        if (arr) {
            // create a new port data object
            var newportdata = { id: + nodeData.key + "," + portKey++ , position: node.getLocalPoint(e.documentPoint) };
            // and add it to the Array of port data
            diagram.model.insertArrayItem(arr, -1, newportdata);
        }
        diagram.commitTransaction("addPort");

    }

OK, let’s look at your node template.

The whole Node is a “Position” panel. OK, but it only ever has exactly one element in it – a “Spot” Panel. If that is the intention, the default value for the element’s position is at (0, 0), so there’s no reason to have that “Position” panel. Get rid of it and make the Node a “Spot” panel. On the other hand, if you have simplified the template for this forum post and do have other elements in the Node and those elements have their position set or bound, then you do need that “Position” panel.

The “Spot” panel, as I just described, should be deleted and the Node made to be a “Spot” panel. This is where the binding of Panel.itemArray is. So the elements of the “Spot” panel will be ports, copies of the itemTemplate. But that means that each port will have a binding of position, which is useless in a “Spot” panel, which expects alignment (and maybe alignmentFocus) to be set or bound on each element other than the main element, which in your case is the Picture.

It’s good that you have a binding of portId to turn each element into a port. I assume you make sure that each port descriptor object in the data.ports Array has a string id property value.

Looking at the addPort function, it is odd to see that you are only adding ports (i.e. port descriptor objects to the data.ports Array) to the first node in the diagram. Wouldn’t you expect to be able to add ports to the first selected node, or perhaps to a node chosen in some other manner?

Oh, but now I see that that other manner must be the case because e must be an InputEvent. Is that correct? OK, so now you have the Node, so the data must be node.data, not the first item of Model.nodeDataArray.

Shouldn’t you create an Array if the data.ports property isn’t an Array, and then Model.set data.ports? Otherwise call Model.insertArrayItem as you do if the Array is present.

But the data that you are pushing onto the Array (for which calling Model.addArrayItem is easier to understand) sets the position property, which as I said above isn’t appropriate for a “Spot” Panel element. This is where you want to set a property that is the source for a Binding targeting the GraphObject.alignment. And perhaps another one targeting the GraphObject.alignmentFocus.

Exactly what values will depend on what positioning that you want. I think you are correctly calling GraphObject.getLocalPoint to turn the point in document coordinates into an offset in the Node’s Panel coordinates. But as I discussed previously, you will need to decide exactly what Spot values to use for the alignment (and maybe alignmentFocus).

The template is as simple as it is.
It is only a picture of a network element (cisco router).
Every time the user clicks the mouse, i would like to add a port with a yellow rectangular shape exactly where the mouse is located.
I figure i have to use a “Spot” panel and bind the alignment value, but i don’t know how to convert the mouse (X,Y) values into Spot(x,y).

As I said above, it depends on what you want to achieve.

OK,
I changed the code bind aligment and the port is created with :

alignment: new go.Spot(0,0,e.documentPoint.x,e.documentPoint.y);

The port is created just next to the mouse position, as shown :


But, when i move the node and then click the mouse again, the new port is created out of bound :

I already confirmed to you that you were doing the right thing in your code.

I thought you are relating the getLocalPoint to “position” method.
i changed the code to :

 alignment: new go.Spot(0,0,node.getLocalPoint(e.documentPoint).x,node.getLocalPoint(e.documentPoint).y) 

And it works as expected.
Thanks and have a safe corona time.