removeArrayItem trouble

I am extending the dynamic ports example. I am trying to remove a port and get this exception:

Upon running the command again (I understand that the transaction did not complete) the element is actually deleted from the array as such:

Any ideas?
Thanks!

That first call did remove that item from that array.

Can you tell what the List is? (of length 2) What is the value of i? If you look further down the stack, can you tell what is going on?

Ahh. So you are thinking that “List” is mine. I will going hunting for it.
CBH

Not necessarily. I am suggesting that the List of the error is definitely not the same thing as the Array in the call to Model.removeArrayItem.

OK the List in this case appears to refer to a separate iterable list of the same items as the array passed to the function. The array has its element removed. Next the function fiddles with the index based on Spot or Panel and tries to remove the now incorrect index from the list. At least this is what I can glean from reading obfuscated code.
So…
Could the problem be that I am nesting the port in two spot panels?
Here is a snippet of the node template:

 $$(go.Panel, "Spot",
            new go.Binding("itemArray", "snapGlueArray"),

            {
                name: "gluePortPanel",
                //cursor: "move",

                itemTemplate:
                    // each port is an "X" shape whose alignment spot and port ID are given by the item data
                    $$(go.Panel, "Spot",

                        //new go.Binding("portId", "portId"),
                        new go.Binding("desiredSize", "", function (s, e) {
                            var main = e.part.findMainElement();
                            return main.desiredSize;
                        }),
                        new go.Binding("position", "", function (s, e) {
                            var main = e.part.findMainElement();
                            return main.position;
                        }),
                       
                        $$(go.Shape, "Rectangle",
                            {

                                
                                stroke: "red",
                                fill: "transparent",
                                visible: false

                            }),
                            //new go.Binding("portId", "portId")),

                            $$(go.Shape, "XLine",
                                { contextMenu: gluePortContextMenu, name: "Port XLine", width: gluePortSize, height: gluePortSize, background: "transparent", fill: null, stroke: gluePortColor, strokeWidth: .5, cursor: "move" },
                                new go.Binding("figure", "portId", portFigure),
                                new go.Binding("portId", "portId"),
                                new go.Binding("visible", "portVisible"),
                                //new go.Binding("alignmentFocus", "spotFocus", go.Spot.parse),
                                new go.Binding("alignment", "spot", go.Spot.parse).makeTwoWay(go.Spot.stringify),
                                new go.Binding("angle", "angle")),
                            $$(go.TextBlock,
                                { font: "normal normal normal 3px Segoe UI, san-serif", alignmentFocus: go.Spot.BottomLeft, editable: true },
                                new go.Binding("text", "portConn").makeTwoWay(),
                                new go.Binding("alignment", "spot", go.Spot.parse).makeTwoWay(go.Spot.stringify),
                                new go.Binding("visible", "portVisible"))
                                //new go.Binding("portId", "portId"))

                        )
               })

The problem is that the Panel with the Panel.itemArray is a “Spot” Panel. Such panels always have a main element and then one or more subsidiary elements that are positioned at spots relative to the main element. GoJS Panels -- Northwoods Software

But your template is replacing all of the elements of the Spot Panel. Did you want to position the second and third ports relative to the first port? Probably not. Either change the panel type to something else or add a first element marked with isPanelMain set to true, which will keep it there rather than being replaced when the itemArray gets an Array. Please read carefully GoJS Item Arrays-- Northwoods Software

Thanks for the info! I am off to fix my stupid.

Not a stupid error – just a complication resulting from several independent features operating at the same time.

All of the rest of my code is working perfectly. I am wondering if there is another way around removing the port given the current node template (which is working in the wild right now).
Any ideas?
Thanks again for all of your help.
CBH

I see that you have each item Panel be full-size, so that they overlay each other. It seems to me that the red rectangle Shape should be the main element of the overall Panel. It should not be repeated with each item/port. Then you wouldn’t need those two weird bindings on desiredSize and position, either, since the overall Panel should be an Auto Panel, not a Spot Panel. And you would set or stretch the overall panel, so that panel’s main element, the red rectangle, would fill the area of that panel. Then each item would be aligned by spot within that large red rectangle.

Note that I’m just guessing here what it looks like and how you are using it.

Very good guess. However the red rectangle was just a way to visualize my geometry.

Here is an abbreviated version of the full template.
The node is created as a spot so that the various text elements may be positioned outside the main SHAPE. So to with the ports. I have avoided the Auto panel due to its constraints.

/*
Loosely based on the pipes and planogram examples
We have modified the resizing and dragging tools to 
behave with this template as well.
*/ 
 
 $$(go.Node, "Spot", //a spot panel because the textblocks below MAY be positioned outside the main rectangle.
        {

            resizable: true, resizeObjectName: "SHAPE",
            contextMenu: panelContextMenu,
            resizeAdornmentTemplate: barnyPanelAdornment,
            resizeCellSize: new go.Size(1, 17.5),
            
            locationSpot: new go.Spot(0, 0, cellSize.width / 2, cellSize.height / 2),
            
            mouseDragLeave: function (e, node) {
                node.updateTargetBindings();
            },
            
            name: "Barny",

            // hide a port when it is connected
            linkConnected: function (node, link, port) {
                port.visible = false;
            },
            linkDisconnected: function (node, link, port) {
                port.visible = true;
            }

        },
        new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
        
        // this is the primary thing people see
        $$(go.Shape, "Rectangle",
            {
                name: "SHAPE",
                fill: "transparent",
                minSize: cellSize,
                desiredSize: cellSize,  
                strokeWidth: .5,
                
            },
           /*Other bindings go here*/
            new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
            new go.Binding("position", "pos", go.Point.parse).makeTwoWay(go.Point.stringify)),
       
		//optional picture
        $$(go.Picture, { /*imageStretch: go.GraphObject.Fill */ },
            new go.Binding("source", "imageSrc").makeTwoWay(),
            new go.Binding("imageStretch", "imageStretch", go.Binding.parseEnum(go.GraphObject, go.GraphObject.Fill)).makeTwoWay(go.Binding.toString),
            new go.Binding("desiredSize", "", function (p, o) {
                var main = o.part.findMainElement();
                return main.desiredSize;
            }).makeTwoWay()),

        $$(go.TextBlock,
            { _kind: "descriptor_text", _isNodeLabel: true, cursor: "move", alignmentFocus: go.Spot.TopLeft,  font: "normal normal normal 9px Segoe UI,sans-serif", stroke: "black", editable: true, isMultiline: false, name: "ManuName" },
			
            /*Other bindings go here*/

            new go.Binding("alignment", "ManuAlignment", function (p) {
                if (p == null) {
                    return new go.Spot(0, 0, 0, 0);
                }
                else {
                    return go.Spot.parse(p);
                }
            }).makeTwoWay(go.Spot.stringify)),
			
        /*Many other TextBlocks go here. Removed for brevity*/
        

       //And now for the ports which MAY also be positioned outside the "SHAPE"
       $$(go.Panel, "Spot",
            new go.Binding("itemArray", "snapGlueArray"),

            {
                name: "gluePortPanel",
               
                itemTemplate:
                    // each port is an "X" shape whose alignment spot and port ID are given by the item data
                    $$(go.Panel, "Spot",

                        new go.Binding("portId", "portId"),
                        new go.Binding("desiredSize", "", function (s, e) {
                            var main = e.part.findMainElement();
                            return main.desiredSize;
                        }),
                        new go.Binding("position", "", function (s, e) {
                            var main = e.part.findMainElement();
                            return main.position;
                        }),
                        
                        $$(go.Shape, "Rectangle",
                            {
                                //desiredSize: cellSize,
                                stroke: "red",
                                fill: "transparent",
                                visible: true //just so we can see what we are creating. We will set this to false later.

                            },
                            new go.Binding("portId", "portId")),

                        $$(go.Shape, "XLine",
                            { contextMenu: gluePortContextMenu, name: "Port XLine", width: gluePortSize, height: gluePortSize, background: "transparent", fill: null, stroke: gluePortColor, strokeWidth: .5, cursor: "move" },
                            new go.Binding("figure", "portId", portFigure),//from the pipes example
                            new go.Binding("portId", "portId"),
                            new go.Binding("alignmentFocus", "spotFocus", go.Spot.parse),
                            new go.Binding("alignment", "spot", go.Spot.parse).makeTwoWay(go.Spot.stringify),
                            new go.Binding("angle", "angle")),
                        $$(go.TextBlock,
                            { font: "normal normal normal 3px Segoe UI, san-serif", alignmentFocus: go.Spot.BottomLeft, editable: true },
                            new go.Binding("text", "portConn").makeTwoWay(),
                            new go.Binding("alignment", "spot", go.Spot.parse).makeTwoWay(go.Spot.stringify))

                    )
            })

     );  // end Node

I have tried eliminating the red rectangle and marking SHAPE as isPanelMain:true.
I have tried binding the itemArray directly to the parent spot node.
No joy.
How would you structure this?
Thanks again.

I still don’t have an idea of how the node should look, with various configurations.

Here is a screen grab of two nodes snapped together.

I hope this helps.
CBH

What do you mean by “two nodes snapped together”?

If the ports might go outside of the panel (which I had thought would have a red outline), then yes, you cannot use an “Auto” Panel because it would clip at the border.

I thought it would be helpful to see how the two nodes relate to each other. They work much like the pipes example. Two ports are linked by dragging them together.
The ports may need to move to just outside the edge to accomplish a visually appealing linkage.
In this example the red outline is hidden from view. I set the stroke on the interior node to red, though I probably should have used a different color.

Ah, blue and red nodes – not a blue node and the red shape holding the items/ports.

OK, so you have the Node, which is a “Spot” Panel, that holds the main and resizable Shape along with some other stuff. First, I’m surprised the setting stretch: go.GraphObject.Fill wouldn’t cause the Picture to fill/shrink to the size of the main Shape.

And you have the inner “Spot” Panel, which currently only holds item panels that have ports in them, but needs to hold a “main” element as well.

If that’s basically the right idea of what you want, I can see if there’s a natural solution.

Try this:

  function init() {
    var $ = go.GraphObject.make;

    myDiagram =
      $(go.Diagram, "myDiagramDiv",
        { "undoManager.isEnabled": true });

    myDiagram.nodeTemplate =
      $(go.Node, "Spot",
        {
          selectionObjectName: "SHAPE",
          resizable: true, resizeObjectName: "SHAPE"
        },
        $(go.Shape,  // the main resizable element
          { name: "SHAPE", fill: "lightgray" },
          new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)),
        $(go.TextBlock,
          new go.Binding("text")),
        $(go.Panel, "Spot",
          { // this panel's main element stretches to the size
            // of the main element in the outer Spot panel
            alignmentFocusName: "INNER",
            stretch: go.GraphObject.Fill
          },
          $(go.Shape,  // some main element is required by the inner Spot panel
            { name: "INNER", fill: null, strokeWidth: 0 }),
          {
            itemTemplate:
              $(go.Panel,
                new go.Binding("alignment", "spot", go.Spot.parse),
                $(go.TextBlock, new go.Binding("text"))
              )
          },
          new go.Binding("itemArray", "items")
        )
      );

    myDiagram.model = new go.GraphLinksModel(
    [
      { key: 1, text: "Alpha", items:
        [
          { text: "a", spot: "0.3 0.2" },
          { text: "bb", spot: "0.8 1.0" },
          { text: "ccc", spot: "1.0 0.5 20 0" },
          { text: "dddd", spot: "0.0 0.0 -30 -30" },
          { text: "eeeee", spot: "0.5 0.5 0 12" }
        ]
      },
    ]);
  }

The “magic” comes from specifying Panel.alignmentFocusName, Panel | GoJS API. That is a rather obscure new feature we added a few years ago, to cover more complicated demands than the usual cases.

EDIT: note that there are no weird Bindings in this node template, despite its effective complexity.

Thanks again for all of your help.
I am starting to get this working.
I am curious. When creating a panel such as this where you do not declare a type:

itemTemplate:
          $(go.Panel,
            new go.Binding("alignment", "spot", go.Spot.parse),
            $(go.TextBlock, new go.Binding("text"))
          )

What panel type is created? The documentation states: “When you construct a Panel you usually specify its Panel.type as the constructor argument.” It kind of leaves me hanging (I added the emphasis).

PS. I have enough of a proof together to show that I can add/remove ports. Which was, after all, the main reason for the thread.
Thanks,
CBH

Thanks again for the help.