在 GoJS 中,如何防止端口的创建传播到所有对象?

How to prevent the creation of a port from spreading to all objects, in GoJS?

当用户交互在对象中创建端口时(右键单击对象然后 "add left port"),所有对象都会添加相同的端口,调色板中的对象也是如此。

如何防止端口的创建传播到所有对象?

    <!DOCTYPE html>
    <html>
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>LLDynp</title>
    <meta name="description" content="Nodes with varying lists of ports on each of four sides." />
    <!-- Copyright 1998-2017 by Northwoods Software Corporation. -->
    <meta charset="UTF-8">
    <script src="../release/go-debug.js"></script>
    <span id="diagramEventsMsg" style="color: red"></span>
    <span id="changeMethodsMsg" style="color: red"></span>
    <span id="BackgroundDoubleClicked" style="color: red"></span>
    <p>
    <script id="code">
      function init() {
        var $ = go.GraphObject.make;  //for conciseness in defining node templates
        myDiagram =
          $(go.Diagram, "myDiagramDiv",
            {
              allowDrop: true, // from Palette
              mouseDrop: function(e) { finishDrop(e, null); },
              "commandHandler.archetypeGroupData": { isGroup: true, category: "OfGroups" },
              "undoManager.isEnabled": true
            });

            // this function is used to highlight a Group that the selection may be dropped into
            function highlightGroup(e, grp, show) {
              if (!grp) return;
              e.handled = true;
              if (show) {
                // cannot depend on the grp.diagram.selection in the case of external drag-and-drops;
                // instead depend on the DraggingTool.draggedParts or .copiedParts
                var tool = grp.diagram.toolManager.draggingTool;
                var map = tool.draggedParts || tool.copiedParts;  // this is a Map
                // now we can check to see if the Group will accept membership of the dragged Parts
                if  (grp.canAddMembers(map.toKeySet())) {
                  grp.isHighlighted = true;
                  return;
                }
              }
              grp.isHighlighted = false;
            }
            function finishDrop(e, grp) {
              var ok = (grp !== null
                        ? grp.addMembers(grp.diagram.selection, true)
                        : e.diagram.commandHandler.addTopLevelParts(e.diagram.selection, true));
              if (!ok) e.diagram.currentTool.doCancel();
            }
            // LL
            function showMessage(s) {
              document.getElementById("diagramEventsMsg").textContent = s;
            }
        // To simplify this code we define a function for creating a context menu button:
        function makeButton(text, action, visiblePredicate) {
          return $("ContextMenuButton",
                   $(go.TextBlock, text),
                   { click: action },
                  //{showMessage( "button: " + myDiagram.lastInput.button);
                   // don't bother with binding GraphObject.visible if there's no predicate
                   visiblePredicate ? new go.Binding("visible", "", function(o, e) { return o.myDiagram ? visiblePredicate(o, e) : false; }).ofObject() : {});
                  //}
                }
        var nodeMenu =  // context menu for each Node
          $(go.Adornment, "Horizontal",
                      makeButton("Add left port",
                                 function (e, obj) { addPort("left"); }),
                      makeButton("Add right port",
                                 function (e, obj) { addPort("right"); }),

          ); //Leon end nodeMenu
            // Add a port to the specified side of the selected nodes.
        function addPort(side) {
              myDiagram.startTransaction("addPort");
              myDiagram.selection.each(function(node) {
                // skip any selected Links
                if (!(node instanceof go.Node)) return;
                // compute the next available index number for the side
                var i = 0;
                while (node.findPort(side + i.toString()) !== node) i++;
                // now this new port name is unique within the whole Node because of the side prefix
                var name = side + i.toString();
                // get the Array of port data to be modified
                var arr = node.data[side + "Array"];
                showMessage ("node: " + node + ";name: " + name + ";arr: " + arr + ";node.data: " + node.data + "; node.data[side=" + node.data[side + "Array"]);
                if (arr) { console.log("arr is true")
                  // create a new port data object
                  var newportdata = {
                    portId: name,
                    portColor: "rgb(180, 0, 0)" //go.Brush.randomColor()
                    // if you add port data properties here, you should copy them in copyPortData above
                  };
                  // and add it to the Array of port data
                  myDiagram.model.insertArrayItem(arr, -1, newportdata);
                }
              });
              myDiagram.commitTransaction("addPort");
            }
        var portSize = new go.Size(10, 10);

        myDiagram.linkTemplate =
          $(go.Link,
         {
           routing: go.Link.Orthogonal, corner: 5,
           relinkableFrom: true, relinkableTo: true
         },
         $(go.Shape, { stroke: "gray", strokeWidth: 2 }),
         $(go.Shape, { stroke: "gray", fill: "gray", toArrow: "Standard" })
       );
    //////////////   groupTemplateMap   ///////////////////////////////////
          myDiagram.groupTemplateMap.add("OfGroups",
          $(go.Group, "Table",// left ports+ placeHolder+right ports
            { locationObjectName: "BODY",
              selectionObjectName: "PH",
              resizable: true,
              resizeObjectName: "PH",
              contextMenu: nodeMenu,
              background: "transparent",
              mouseDragEnter: function(e, grp, prev) { highlightGroup(e, grp, true); },
              mouseDragLeave: function(e, grp, next) { highlightGroup(e, grp, false); },
              computesBoundsAfterDrag: false,
              mouseDrop: finishDrop,
              handlesDragDropForMembers: true  // don't need to define handlers on member Nodes and Links
            },
            new go.Binding("background", "isHighlighted", function(h) { return h ? "rgba(255,0,0,0.2)" : "transparent"; }).ofObject(),
            new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),

            // the body
            $(go.Panel, "Auto",
              { row: 1, column: 1, name: "BODY"
                //stretch: go.GraphObject.Fill
              },
              $(go.Shape, "Rectangle",
                { fill: null, stroke: "rgb(0, 0, 200)", strokeWidth: 3,name: "PH",
                  minSize: new go.Size(56, 56) }),
                  $(go.Panel, "Vertical",  // title above Placeholder
                  {alignment: go.Spot.Top},
                    $(go.Panel, "Horizontal",  // button next to TextBlock
                      { stretch: go.GraphObject.Horizontal, background: "#FFDD33" },
                      $("SubGraphExpanderButton",
                        { alignment: go.Spot.Right, margin: 5 }),
                      $(go.TextBlock,
                        {
                          alignment: go.Spot.Left,
                          editable: true,
                          margin: 5,
                          font: "bold 18px sans-serif",
                          opacity: 0.75,
                          stroke: "#404040"
                        },
                        new go.Binding("text", "text").makeTwoWay())
                    ),  // end Horizontal Panel
                    $(go.Placeholder,
                      { padding: 5
                        , alignment: go.Spot.TopLeft // no change
                      },
                      new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify))
                  )  // end Vertical Panel
            ),  // end Auto Panel body
            // the Panel holding the left port elements, which are themselves Panels,
            // created for each item in the itemArray, bound to data.leftArray
            $(go.Panel, "Vertical",
              new go.Binding("itemArray", "leftArray"),
              { row: 1, column: 0,
                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"},
                    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
              }
            ),  // end Vertical Panel
            // the Panel holding the right port elements, which are themselves Panels,
            // created for each item in the itemArray, bound to data.rightArray
            $(go.Panel, "Vertical",
              new go.Binding("itemArray", "rightArray"),
              { row: 1, column: 2,
                itemTemplate:
                  $(go.Panel,
                    { _side: "right",
                      fromSpot: go.Spot.Right, toSpot: go.Spot.Right,
                      fromLinkable: true, toLinkable: true, cursor: "pointer"},
                    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
              }
            ),  // end Vertical Panel
          )
         );  // end groupTemplateMap.add("OfGroups"
          var nodeDataArray = [];
          var linkDataArray = [];
          myDiagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray);

          // initialize the Palette and its contents
          myPalette =
            $(go.Palette, "myPaletteDiv",
              {
                groupTemplateMap: myDiagram.groupTemplateMap
              //  ,layout: $(go.GridLayout, { wrappingColumn: 1, alignment: go.GridLayout.Position })
              });

          myPalette.model = new go.GraphLinksModel([
                        { //key: 101,
                          text: 'UNIT',
                        leftArray: [], rightArray: [],
                        isGroup: true, isSubProcess: true,
                            category: "OfGroups", isAdHoc: true,
                             loc: '0 0' },
              ]);
            }
    </script>
    </head>
    <body onload="init()">
    <!-- LL -->
    <body onload="goIntro()">
    <div id="container" class="container-fluid">
    <div id="content">
    <div id="sample">
      <div style="width: 100%; display: flex; justify-content: space-between">
          <div id="myPaletteDiv" style="width: 100px; margin-right: 2px; background-color: whitesmoke; border: solid 1px black"></div>
          <div id="myDiagramDiv" style="flex-grow: 1; height: 480px; border: solid 1px black"></div>
        </div>
      </div>
    </div>
    </body>
    </html>

请阅读 https://gojs.net/latest/samples/dynamicPorts.html 上的动态端口示例代码。注意 load 函数中的注释。

    function load() {
      myDiagram.model = go.Model.fromJson(. . .);

      // When copying a node, we need to copy the data that the node is bound to.
      // This JavaScript object includes properties for the node as a whole, and
      // four properties that are Arrays holding data for each port.
      // Those arrays and port data objects need to be copied too.
      // Thus Model.copiesArrays and Model.copiesArrayObjects both need to be true.

      // Link data includes the names of the to- and from- ports;
      // so the GraphLinksModel needs to set these property names:
      // linkFromPortIdProperty and linkToPortIdProperty.
    }

并在模型的 JSON 表示中注意它如何设置这些属性:

{ "class": "go.GraphLinksModel",
  "copiesArrays": true,
  "copiesArrayObjects": true,
  "linkFromPortIdProperty": "fromPort",
  "linkToPortIdProperty": "toPort",
  "nodeDataArray": [
{"key":1, "name":"Unit One", "loc":"101 204",
   . . .

因此,当您以编程方式创建 GraphLinksModel 时,请务必将这些相同的属性设置为适合您的数据的值。