<script src="https://gojs.net/beta/release/go-debug.js"></script>
<body onload="init()">

  <div id="myDiagram" style="border: solid 1px gray; width:500px; height:500px;"></div>
 
</body>
function init() {
  if (window.goSamples) goSamples(); // init for these samples -- you don't need to call this
  var $ = go.GraphObject.make; // for conciseness in defining templates

  myDiagram = $(go.Diagram, "myDiagram", // create a Diagram for the DIV HTML element
    {
      initialContentAlignment: go.Spot.Center, // center the content
      "undoManager.isEnabled": true // enable undo & redo
    });

  var nodeContextMenu =
    $(go.Adornment, "Vertical",
      $("ContextMenuButton", {
          "ButtonBorder.fill": "white",
          "ButtonBorder.stroke": null,
          "_buttonFillOver": "dodgerblue"
        },
        $(go.TextBlock, "Body Context Menu")),
      $("ContextMenuButton", $(go.TextBlock, "Copy")),
      $("ContextMenuButton", $(go.TextBlock, "Cut")),
      $("ContextMenuButton", $(go.TextBlock, "Delete"))

    ); // end Adornment

  var portContextMenu = $(go.Adornment, "Vertical",
      $("ContextMenuButton", {
          "ButtonBorder.fill": "white",
          "ButtonBorder.stroke": null,
          "_buttonFillOver": "dodgerblue"
        },
        $(go.TextBlock, "Port Context Menu")),
      $("ContextMenuButton", $(go.TextBlock, "Add Port"), {
        click: function(e, obj) {
          addRemovePort(e, obj, true)
        }
      }),
      $("ContextMenuButton", $(go.TextBlock, "Remove Port"), {
        click: function(e, obj) {
          addRemovePort(e, obj, false)
        }
      })
    ) // end Adornment

  function addRemovePort(e, obj, isAdd) {
		var panelPort = obj.part.adornedObject;
		var portSide = panelPort.column == 0 ? "inPort" : "outPort";
		if(isAdd){
		var index = panelPort.itemArray.length
		e.diagram.model.insertArrayItem(panelPort.itemArray, -1, {name: portSide+index})
		}
		else e.diagram.model.removeArrayItem(panelPort.itemArray)
  }

  function fieldTemplate(isRight) {
    var spot = isRight ? go.Spot.Right : go.Spot.Left //If left, left, else right.
    return $(go.Panel, "TableRow",
      new go.Binding("portId", "name"), {
        fromSpot: spot,
        toSpot: spot,
        fromLinkable: isRight,
        toLinkable: !isRight
      },
      $(go.Shape, {
        desiredSize: new go.Size(8, 8),
        margin: new go.Margin(1, 0),
        figure: "Rectangle",
      })
    )
  }
  myDiagram.groupTemplate = $(go.Group, "Table", {
      layout: $(go.TreeLayout, {
        arrangement: go.TreeLayout.ArrangementVertical,
      })
    },
    new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify),
    $(go.Panel, "Table", {
        row: 0,
        column: 0,
        margin: new go.Margin(10, 0),
        itemTemplate: fieldTemplate(false),
        contextMenu: portContextMenu
      },

      new go.Binding("itemArray", "inPort", function(numPort) {
        var portArray = [];
        for (var n = 0; n < numPort; n++) portArray.push({
          name: ("left" + n)
        })
        return portArray;
      })
    ),
    //Body
    $(go.Panel, "Auto", {
        row: 0,
        column: 1,
        //stretch: go.GraphObject.Fill,
        contextMenu: nodeContextMenu
      },
      new go.Binding("minSize", "", function(d) {
        var mostports = Math.max(d.inPort, d.outPort);
        return new go.Size(1, mostports*11)}
      ),
      $(go.Shape, "RoundedRectangle", {
        //geometryStretch: go.GraphObject.Fill,
        fill: "white"
      }),
      $(go.Panel, "Horizontal", {},
        $(go.TextBlock, new go.Binding("text", "name"))
      ),
      $(go.Placeholder)

    ),
    $(go.Panel, "Table", {
        row: 0,
        column: 2,
        margin: new go.Margin(10, 0),
        itemTemplate: fieldTemplate(true),
        contextMenu: portContextMenu
      },
      new go.Binding("itemArray", "outPort", function(numPort) {
        var portArray = [];
        for (var n = 0; n < numPort; n++) portArray.push({
          name: ("right" + n)
        })
        return portArray;
      }))
  );

  myDiagram.linkTemplate = $(go.Link, {
      adjusting: go.Link.Scale,
      routing: go.Link.AvoidsNodes,
      corner: 10,
      resegmentable: true,
      reshapable: true,
      relinkableFrom: true,
      relinkableTo: true,
    }, // let user reconnect links
    $(go.Shape, {
      strokeWidth: 2,
      stroke: "gray"
    })
  );

  var _model = $(go.GraphLinksModel, {
    linkFromPortIdProperty: "fromPort",
    linkToPortIdProperty: "toPort",
  });
  var nodeDataArray = [{
    key: "1",
    name: "PortNode Object",
    inPort: 19,
    outPort: 5,
    category: "portObject",
    isGroup: true
  }, {
    key: "2",
    name: "PortNode Object 2",
    inPort: 5,
    outPort: 5,
    error: false,
    category: "portObject",
    isGroup: true
  }, {
    key: "3",
    name: "Object 1",
    group: "1"
  }, {
    key: "4",
    name: "Object 2",
    group: "1"
  }, {
    key: "5",
    name: "Object 3",
    group: "1"
  },{
    key: "6",
    name: "Object 4",
    group: "1"
  }];
  _model.nodeDataArray = nodeDataArray;
  _model.linkDataArray = [{
    from: "1",
    to: "2",
    fromPort: "right0",
    toPort: "left2"
  }];

  myDiagram.model = _model;
}
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.