GoJS组织结构图2

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>组织结构图</title>
  <meta name="description" content="An organization chart editor -- edit details and change relationships." />
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- Copyright 1998-2019 by Northwoods Software Corporation. -->

  <style>
    .inspector {
      display: inline-block;
      font: bold 14px helvetica, sans-serif;
      background-color: #212121;
      /* Grey 900 */
      color: #F5F5F5;
      /* Grey 100 */
      cursor: default;
    }

    .inspector table {
      border-collapse: separate;
      border-spacing: 2px;
    }

    .inspector td,
    th {
      padding: 2px;
    }

    .inspector input {
      background-color: #424242;
      /* Grey 800 */
      color: #F5F5F5;
      /* Grey 100 */
      font: bold 12px helvetica, sans-serif;
      border: 0px;
      padding: 2px;
    }

    .inspector input:disabled {
      background-color: #BDBDBD;
      /* Grey 400 */
      color: #616161;
      /* Grey 700 */
    }

    .inspector select {
      background-color: #424242;
    }
  </style>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/gojs/1.8.13/go-debug.js"></script>
  <!-- this is only for the GoJS Samples framework -->

</head>

<body onload="init()">
  <div id="sample">
    <div id="myDiagramDiv" style="background-color: #34343C; border: solid 1px black; height: 570px;"></div>
    <p>
      <button id="zoomToFit">Zoom to Fit</button>
      <button id="centerRoot">Center on root</button>
    </p>

    <div>
      <div id="myInspector">
      </div>
    </div>
    <p>
      This editable organizational chart sample color-codes the Nodes according to the tree level in the hierarchy.
    </p>
    <p>
      Double click on a node in order to add a person or the diagram background to add a new boss. Double clicking the diagram
      uses the
      <a>ClickCreatingTool</a>
      with a custom
      <a>ClickCreatingTool.insertPart</a> to scroll to the new node and start editing the
      <a>TextBlock</a> for its name .
    </p>
    <p>
      Drag a node onto another in order to change relationships. You can also draw a link from a node's background to other nodes
      that have no "boss". Links can also be relinked to change relationships. Right-click or tap-hold a Node to bring up
      a context menu which allows you to:
      <ul>
        <li>Vacate Position - remove the information specfic to the current person in that role</li>
        <li>Remove Role - removes the role entirely and reparents any children</li>
        <li>Remove Department - removes the role and the whole subtree</li>
      </ul>
      Deleting a Node or Link will orphan the child Nodes and generate a new tree. A custom SelectionDeleting
      <a>DiagramEvent</a> listener will clear out the boss info when the parent is removed.
    </p>
    <p>
      Select a node to edit/update node data values. This sample uses the
      <a href="../extensions/dataInspector.html">Data Inspector</a> extension to display and modify Part data.
    </p>
    <p>
      To learn how to build an org chart from scratch with GoJS, see the
      <a href="../learn/index.html">Getting Started tutorial</a>.
    </p>
    <p>
      If you want to have some "assistant" nodes on the side, above the regular reports, see the
      <a href="orgChartAssistants.html">Org Chart Assistants</a> sample, which is a copy of this sample that uses a custom
      <a>TreeLayout</a> to position "assistants" that way.
    </p>
    <div>
      <div>
        <button id="SaveButton" onclick="save()">Save</button>
        <button onclick="load()">Load</button>
        Diagram Model saved in JSON format:
      </div>
      <textarea id="mySavedModel" style="100%; height:270px;">
{ "class": "go.TreeModel",
  "nodeDataArray": [
{"key":1, "name":"中心主题"}
 ]
}
    </textarea>
    </div>
  </div>
</body>

<script id="code">

  function init() {
    if (window.goSamples) goSamples();  // init for these samples -- you don't need to call this
    var $ = go.GraphObject.make;  //GraphObject是所有图形基类,这里简洁定义模板,$太敏感,为避免与jQuery冲突,此处使用ds

    //图表由节点、文字、线、箭头组成。
    myDiagram =
      $(go.Diagram, "myDiagramDiv", // must be the ID or reference to div
        {
          "initialContentAlignment": go.Spot.Center, // 将图表在画布中居中显示
          maxSelectionCount: 1, // 一次允许选择一个部件,
          //"isReadOnly": true, // 只读
          "allowZoom": true, //画布缩放
          //"InitialLayoutCompleted":loadDiagramProperties这是函数名,  //一个DiagramEvent侦听器
          //鼠标滚轮事件放大和缩小,而不是向上和向下滚动
          "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom,
          "undoManager.isEnabled": true, // 操作支持Ctrl-Z撤销、Ctrl-Y重做
          validCycle: go.Diagram.CycleDestinationTree, // make sure users can only create trees
          //"clickCreatingTool.archetypeNodeData": { // 默认单根节点,这段代码允许多根节点,双击背景创建新的根节点
          //  name: "(自由节点)"
          //},
          "clickCreatingTool.insertPart": function (loc) {  // scroll to the new node
            var node = go.ClickCreatingTool.prototype.insertPart.call(this, loc);
            if (node !== null) {
              this.diagram.select(node);
              this.diagram.commandHandler.scrollToPart(node);
              this.diagram.commandHandler.editTextBlock(node.findObject("NAMETB"));
            }
            return node;
          },
          layout:
          $(go.TreeLayout,
            {
              treeStyle: go.TreeLayout.StyleLastParents,
              arrangement: go.TreeLayout.ArrangementHorizontal,
              // properties for most of the tree:
              angle: 90,
              layerSpacing: 35,
              // properties for the "last parents":
              alternateAngle: 90,
              alternateLayerSpacing: 35,
              alternateAlignment: go.TreeLayout.AlignmentBus,
              alternateNodeSpacing: 20
            })
        });

    // 当文档被修改时,在标题中添加“*”并启用“Save”按钮
    myDiagram.addDiagramListener("Modified", function (e) {
      var button = document.getElementById("SaveButton");
      if (button) button.disabled = !myDiagram.isModified;
      var idx = document.title.indexOf("*");
      if (myDiagram.isModified) {
        if (idx < 0) document.title += "*";
      } else {
        if (idx >= 0) document.title = document.title.substr(0, idx);
      }
    });

    // manage boss info manually when a node or link is deleted from the diagram
    myDiagram.addDiagramListener("SelectionDeleting", function (e) {
      var part = e.subject.first(); // e.subject is the myDiagram.selection collection,
      // so we'll get the first since we know we only have one selection
      myDiagram.startTransaction("clear boss");
      if (part instanceof go.Node) {
        var it = part.findTreeChildrenNodes(); // find all child nodes
        while (it.next()) { // now iterate through them and clear out the boss information
          var child = it.value;
          var bossText = child.findObject("boss"); // since the boss TextBlock is named, we can access it by name
          if (bossText === null) return;
          bossText.text = "";
        }
      } else if (part instanceof go.Link) {
        var child = part.toNode;
        var bossText = child.findObject("boss"); // since the boss TextBlock is named, we can access it by name
        if (bossText === null) return;
        bossText.text = "";
      }
      myDiagram.commitTransaction("clear boss");
    });

    var levelColors = ["#AC193D", "#2672EC", "#8C0095", "#5133AB",
      "#008299", "#D24726", "#008A00", "#094AB2"];

    // override TreeLayout.commitNodes to also modify the background brush based on the tree depth level
    myDiagram.layout.commitNodes = function () {
      go.TreeLayout.prototype.commitNodes.call(myDiagram.layout);  // do the standard behavior
      // then go through all of the vertexes and set their corresponding node's Shape.fill
      // to a brush dependent on the TreeVertex.level value
      myDiagram.layout.network.vertexes.each(function (v) {
        if (v.node) {
          var level = v.level % (levelColors.length);
          var color = levelColors[level];
          var shape = v.node.findObject("SHAPE");
          if (shape) shape.stroke = $(go.Brush, "Linear", { 0: color, 1: go.Brush.lightenBy(color, 0.05), start: go.Spot.Left, end: go.Spot.Right });
        }
      });
    };

    // when a node is double-clicked, add a child to it
    function createNode(e, obj) {
      //拿到节点的对象,后面要拿什么值就直接拿
      var clicked = obj.part;
      if (clicked !== null) {
        var thisemp = clicked.data;
        myDiagram.startTransaction("createNode");
        var newemp = {
          name: "(新节点)",
          parent: thisemp.key
        };
        //console.log(thisemp.key);
        console.log(myDiagram.model.toJson());
        myDiagram.model.addNodeData(newemp);
        myDiagram.commitTransaction("createNode");
      }
    }

    // this is used to determine feedback during drags
    function mayWorkFor(node1, node2) {
      if (!(node1 instanceof go.Node)) return false;  // must be a Node
      if (node1 === node2) return false;  // cannot work for yourself
      if (node2.isInTreeOf(node1)) return false;  // cannot work for someone who works for you
      return true;
    }

    // 为大多数文本块提供通用样式
    // Some of these values may be overridden in a particular TextBlock.某些值可能在特定的文本块中被覆盖
    function textStyle() {
      //strokez颜色,添加textAlign: "center"好像效果可能是被覆盖了
      return { font: "9pt  Segoe UI,sans-serif", stroke: "white" };
    }

    // This converter is used by the Picture.
    function findHeadShot(key) {
      if (key < 0 || key > 16) return "images/HSnopic.jpg"; // There are only 16 images on the server
      return "images/HS" + key + ".jpg"
    }

    // define the Node template 定义节点模板 描述如何构建每个节点
    myDiagram.nodeTemplate =
      $(go.Node, "Auto",//形状自动填充适合 与css设置auto同样效果
        //{ doubleClick: createNode },
        { // handle dragging a Node onto a Node to (maybe) change the reporting relationship
          mouseDragEnter: function (e, node, prev) {
            var diagram = node.diagram;
            var selnode = diagram.selection.first();
            if (!mayWorkFor(selnode, node)) return;
            var shape = node.findObject("SHAPE");
            if (shape) {
              shape._prevFill = shape.fill;  // remember the original brush
              shape.fill = "darkred";
            }
          },
          mouseDragLeave: function (e, node, next) {
            var shape = node.findObject("SHAPE");
            if (shape && shape._prevFill) {
              shape.fill = shape._prevFill;  // restore the original brush
            }
          },
          mouseDrop: function (e, node) {
            var diagram = node.diagram;
            var selnode = diagram.selection.first();  // assume just one Node in selection
            if (mayWorkFor(selnode, node)) {
              // find any existing link into the selected node
              var link = selnode.findTreeParentLink();
              if (link !== null) {  // 重新连接所有已有连接
                link.fromNode = node;
              } else {  // 新建连接
                diagram.toolManager.linkingTool.insertLink(node, node.port, selnode, selnode.port);
              }
            }
          }
        },
        // for sorting, have the Node.text be the data.name
        new go.Binding("text", "name"),
        // bind the Part.layerName to control the Node's layer depending on whether it isSelected
        new go.Binding("layerName", "isSelected", function (sel) { return sel ? "Foreground" : ""; }).ofObject(),
        //设置节点形状:长方形
        $(go.Shape, "Rectangle",
          {
            name: "SHAPE", fill: "#333333", stroke: 'white', strokeWidth: 3.5,
            // set the port properties: 是否可连接fromLinkable、toLinkable
            portId: "", fromLinkable: false, toLinkable: false, cursor: "pointer"
          }),
        // Panel 有不同的类型,每个类型表示一种布局,通过不同的坐标系统排列    
        $(go.Panel, "Horizontal",
          // 定义文本显示框
          $(go.Panel, "Table",
            {
              minSize: new go.Size(130, NaN),
              maxSize: new go.Size(150, NaN),
              margin: new go.Margin(30),//设置文本和边框距离
              defaultAlignment: go.Spot.Center
            },
            $(go.RowColumnDefinition, { column: 2,  4 }),
            // 设置文本节点
            $(go.TextBlock, textStyle(),  // the name
              {
                row: 0, column: 0, columnSpan: 5,
                font: "12pt Segoe UI,sans-serif",
                editable: true, isMultiline: false,// editable文本是否可编辑
                minSize: new go.Size(10, 16)
              },
              //将节点数据nodeDataArray.name与text建立联系
              new go.Binding("text", "name").makeTwoWay())
          )  // end Table Panel
        ) // end Horizontal Panel
      );  // end Node

    // 选中的节点显示用于添加子节点的按钮
    myDiagram.nodeTemplate.selectionAdornmentTemplate =
      $(go.Adornment, "Spot",
        $(go.Panel, "Auto",
          $(go.Placeholder, {
            margin: new go.Margin(4, -20, 50, 4)
          })
        ),
        // 所选节点的删除按钮
        $("Button", {
          alignment: go.Spot.Right,
          alignmentFocus: go.Spot.Left,
          click: function (e, obj) {
            var node = obj.part.adornedPart;
            //console.log(node.data.key);
            if (node !== null) {
              myDiagram.startTransaction("remove dept");
              // 删除单个节点myDiagram.model.removeNodeData(node.data)只要能拿到node.data对象就能删除了;
              //删除整个子树,包括节点本身
              myDiagram.removeParts(node.findTreeParts());
              myDiagram.commitTransaction("remove dept");
            }
          }
          // 定义装饰中此按钮的单击行为
        },
          $(go.TextBlock, "-", // 按钮的内容
            {
              font: "bold 8pt sans-serif"
            })
        ),
        // 所选节点的新增按钮
        $("Button", {
          alignment: go.Spot.Right,
          alignmentFocus: go.Spot.Right,
          click: createNode
        },
          $(go.TextBlock, "+", // 按钮的内容
            {
              font: "bold 8pt sans-serif"
            })
        )
      );

    //监听键盘事件
    myDiagram.commandHandler.doKeyDown = function () {
      var e = myDiagram.lastInput;
      var control = e.control || e.meta;
      var key = e.key;

      console.log('key' + key);//Tab和Enter键好像这里取到的名字是空

      if (control && (key === 'Z' || key === 'Y')) {
        console.log('Ctrl+Z/Y');
      };

      // 取消Del/Backspace删除键的命令关联:
      //if (key === 'Del' || key === 'Backspace') return;

      go.CommandHandler.prototype.doKeyDown.call(this);
    };

    // 设置线条和箭头,是否允许拖动连接relinkableFrom,relinkableTo
    myDiagram.linkTemplate =
      $(go.Link, go.Link.Orthogonal,
        { corner: 5, relinkableFrom: false, relinkableTo: false },
        $(go.Shape, { strokeWidth: 1.5, stroke: "#F5F5F5" }));  // the link shape

    // 拖拽框选功能
    myDiagram.toolManager.dragSelectingTool.box =
      $(go.Part,
        { layerName: "Tool", selectable: true },
        $(go.Shape,
          { name: "SHAPE", fill: null, stroke: "chartreuse", strokeWidth: 3 }));

    // read in the JSON-format data from the "mySavedModel" element
    load();

    // support editing the properties of the selected person in HTML
    if (window.Inspector) myInspector = new Inspector("myInspector", myDiagram,
      {
        properties: {
          "key": { readOnly: true },
          "comments": {}
        }
      });

    // Setup zoom to fit button
    document.getElementById('zoomToFit').addEventListener('click', function () {
      myDiagram.commandHandler.zoomToFit();
    });

    document.getElementById('centerRoot').addEventListener('click', function () {
      myDiagram.scale = 1;
      myDiagram.commandHandler.scrollToPart(myDiagram.findNodeForKey(1));
    });

  } // end init

  // Show the diagram's model in JSON format
  function save() {
    document.getElementById("mySavedModel").value = myDiagram.model.toJson();
    myDiagram.isModified = false;
  }
  function load() {
    // model中的数据每一个js对象都代表着一个相应的模型图中的元素
    myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value);
    // make sure new data keys are unique positive integers
    var lastkey = 1;
    myDiagram.model.makeUniqueKeyFunction = function (model, data) {
      var k = data.key || lastkey;
      while (model.findNodeDataForKey(k)) k++;
      data.key = lastkey = k;
      return k;
    };
  }

  "use strict";//严格模式,在开发中使用严格模式能帮助我们早发现错误

  function Inspector(divid, diagram, options) {
    var mainDiv = document.getElementById(divid);
    mainDiv.className = "inspector";
    mainDiv.innerHTML = "";
    this._div = mainDiv;
    this._diagram = diagram;
    this._inspectedProperties = {};
    this._multipleProperties = {};

    // Either a GoJS Part or a simple data object, such as Model.modelData
    this.inspectedObject = null;

    // Inspector options defaults:
    this.includesOwnProperties = true;
    this.declaredProperties = {};
    this.inspectsSelection = true;
    this.propertyModified = null;
    this.multipleSelection = false;
    this.showAllProperties = false;
    this.showSize = 0;

    if (options !== undefined) {
      if (options["includesOwnProperties"] !== undefined) this.includesOwnProperties = options["includesOwnProperties"];
      if (options["properties"] !== undefined) this.declaredProperties = options["properties"];
      if (options["inspectSelection"] !== undefined) this.inspectsSelection = options["inspectSelection"];
      if (options["propertyModified"] !== undefined) this.propertyModified = options["propertyModified"];
      if (options['multipleSelection'] !== undefined) this.multipleSelection = options['multipleSelection'];
      if (options['showAllProperties'] !== undefined) this.showAllProperties = options['showAllProperties'];
      if (options['showSize'] !== undefined) this.showSize = options['showSize'];
    }

    var self = this;
    diagram.addModelChangedListener(function (e) {
      if (e.isTransactionFinished) self.inspectObject();
    });
    if (this.inspectsSelection) {
      diagram.addDiagramListener("ChangedSelection", function (e) { self.inspectObject(); });
    }
  }

  // Some static predicates to use with the "show" property.
  Inspector.showIfNode = function (part) { return part instanceof go.Node };
  Inspector.showIfLink = function (part) { return part instanceof go.Link };
  Inspector.showIfGroup = function (part) { return part instanceof go.Group };

  // Only show the property if its present. Useful for "key" which will be shown on Nodes and Groups, but normally not on Links
  Inspector.showIfPresent = function (data, propname) {
    if (data instanceof go.Part) data = data.data;
    return typeof data === "object" && data[propname] !== undefined;
  };

    /**
    * Update the HTML state of this Inspector given the properties of the {@link #inspectedObject}.
    * @param {Object} object is an optional argument, used when {@link #inspectSelection} is false to
    *                        set {@link #inspectedObject} and show and edit that object's properties.
    */
  Inspector.prototype.inspectObject = function (object) {
    var inspectedObject = null;
    var inspectedObjects = null;
    if (object === null) return;
    if (object === undefined) {
      if (this.inspectsSelection) {
        if (this.multipleSelection) { // gets the selection if multiple selection is true
          inspectedObjects = this._diagram.selection;
        } else { // otherwise grab the first object
          inspectedObject = this._diagram.selection.first();
        }
      } else { // if there is a single inspected object
        inspectedObject = this.inspectedObject;
      }
    } else { // if object was passed in as a parameter
      inspectedObject = object;
    }
    if (inspectedObjects && inspectedObjects.count === 1) {
      inspectedObject = inspectedObjects.first();
    }
    if (inspectedObjects && inspectedObjects.count <= 1) {
      inspectedObjects = null;
    }

    // single object or no objects
    if (!inspectedObjects || !this.multipleSelection) {
      if (inspectedObject === null) {
        this.inspectedObject = inspectedObject;
        this.updateAllHTML();
        return;
      }

      this.inspectedObject = inspectedObject;
      if (this.inspectObject === null) return;
      var mainDiv = this._div;
      mainDiv.innerHTML = '';

      // use either the Part.data or the object itself (for model.modelData)
      var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
      if (!data) return;
      // Build table:
      var table = document.createElement('table');
      var tbody = document.createElement('tbody');
      this._inspectedProperties = {};
      this.tabIndex = 0;
      var declaredProperties = this.declaredProperties;

      // Go through all the properties passed in to the inspector and show them, if appropriate:
      for (var name in declaredProperties) {
        var desc = declaredProperties[name];
        if (!this.canShowProperty(name, desc, inspectedObject)) continue;
        var val = this.findValue(name, desc, data);
        tbody.appendChild(this.buildPropertyRow(name, val));
      }
      // Go through all the properties on the model data and show them, if appropriate:
      if (this.includesOwnProperties) {
        for (var k in data) {
          if (k === '__gohashid') continue; // skip internal GoJS hash property
          if (this._inspectedProperties[k]) continue; // already exists
          if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
          tbody.appendChild(this.buildPropertyRow(k, data[k]));
        }
      }

      table.appendChild(tbody);
      mainDiv.appendChild(table);
    } else { // multiple objects selected
      var mainDiv = this._div;
      mainDiv.innerHTML = '';
      var shared = new go.Map(); // for properties that the nodes have in common
      var properties = new go.Map(); // for adding properties
      var all = new go.Map(); // used later to prevent changing properties when unneeded
      var it = inspectedObjects.iterator;
      // Build table:
      var table = document.createElement('table');
      var tbody = document.createElement('tbody');
      this._inspectedProperties = {};
      this.tabIndex = 0;
      var declaredProperties = this.declaredProperties;
      it.next();
      inspectedObject = it.value;
      this.inspectedObject = inspectedObject;
      var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
      if (data) { // initial pass to set shared and all
        // Go through all the properties passed in to the inspector and add them to the map, if appropriate:
        for (var name in declaredProperties) {
          var desc = declaredProperties[name];
          if (!this.canShowProperty(name, desc, inspectedObject)) continue;
          var val = this.findValue(name, desc, data);
          if (val === '' && desc && desc.type === 'checkbox') {
            shared.add(name, false);
            all.add(name, false);
          } else {
            shared.add(name, val);
            all.add(name, val);
          }
        }
        // Go through all the properties on the model data and add them to the map, if appropriate:
        if (this.includesOwnProperties) {
          for (var k in data) {
            if (k === '__gohashid') continue; // skip internal GoJS hash property
            if (this._inspectedProperties[k]) continue; // already exists
            if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
            shared.add(k, data[k]);
            all.add(k, data[k]);
          }
        }
      }
      var nodecount = 2;
      while (it.next() && (this.showSize < 1 || nodecount <= this.showSize)) { // grabs all the properties from the other selected objects
        properties.clear();
        inspectedObject = it.value;
        if (inspectedObject) {
          // use either the Part.data or the object itself (for model.modelData)
          data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
          if (data) {
            // Go through all the properties passed in to the inspector and add them to properties to add, if appropriate:
            for (var name in declaredProperties) {
              var desc = declaredProperties[name];
              if (!this.canShowProperty(name, desc, inspectedObject)) continue;
              var val = this.findValue(name, desc, data);
              if (val === '' && desc && desc.type === 'checkbox') {
                properties.add(name, false);
              } else {
                properties.add(name, val);
              }
            }
            // Go through all the properties on the model data and add them to properties to add, if appropriate:
            if (this.includesOwnProperties) {
              for (var k in data) {
                if (k === '__gohashid') continue; // skip internal GoJS hash property
                if (this._inspectedProperties[k]) continue; // already exists
                if (declaredProperties[k] && !this.canShowProperty(k, declaredProperties[k], inspectedObject)) continue;
                properties.add(k, data[k]);
              }
            }
          }
        }
        if (!this.showAllProperties) {
          // Cleans up shared map with properties that aren't shared between the selected objects
          // Also adds properties to the add and shared maps if applicable
          var addIt = shared.iterator;
          var toRemove = [];
          while (addIt.next()) {
            if (properties.has(addIt.key)) {
              var newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
              all.set(addIt.key, newVal);
              if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
                && declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
                || !declaredProperties[addIt.key]) { // for non-string properties i.e color
                newVal = shared.get(addIt.key) + '|' + properties.get(addIt.key);
                shared.set(addIt.key, newVal);
              }
            } else { // toRemove array since addIt is still iterating
              toRemove.push(addIt.key);
            }
          }
          for (var i = 0; i < toRemove.length; i++) { // removes anything that doesn't showAllPropertiess
            shared.remove(toRemove[i]);
            all.remove(toRemove[i]);
          }
        } else {
          // Adds missing properties to all with the correct amount of seperators
          var addIt = properties.iterator;
          while (addIt.next()) {
            if (all.has(addIt.key)) {
              if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
                && declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
                || !declaredProperties[addIt.key]) { // for non-string properties i.e color
                var newVal = all.get(addIt.key) + '|' + properties.get(addIt.key);
                all.set(addIt.key, newVal);
              }
            } else {
              var newVal = '';
              for (var i = 0; i < nodecount - 1; i++) newVal += '|';
              newVal += properties.get(addIt.key);
              all.set(addIt.key, newVal);
            }
          }
          // Adds bars in case properties is not in all
          addIt = all.iterator;
          while (addIt.next()) {
            if (!properties.has(addIt.key)) {
              if ((declaredProperties[addIt.key] && declaredProperties[addIt.key].type !== 'color'
                && declaredProperties[addIt.key].type !== 'checkbox' && declaredProperties[addIt.key].type !== 'select')
                || !declaredProperties[addIt.key]) { // for non-string properties i.e color
                var newVal = all.get(addIt.key) + '|';
                all.set(addIt.key, newVal);
              }
            }
          }
        }
        nodecount++;
      }
      // builds the table property rows and sets multipleProperties to help with updateall
      var mapIt;
      if (!this.showAllProperties) mapIt = shared.iterator;
      else mapIt = all.iterator;
      while (mapIt.next()) {
        tbody.appendChild(this.buildPropertyRow(mapIt.key, mapIt.value)); // shows the properties that are allowed
      }
      table.appendChild(tbody);
      mainDiv.appendChild(table);
      var allIt = all.iterator;
      while (allIt.next()) {
        this._multipleProperties[allIt.key] = allIt.value; // used for updateall to know which properties to change
      }
    }
  };

    /**
    * @ignore
    * This predicate should be false if the given property should not be shown.
    * Normally it only checks the value of "show" on the property descriptor.
    * The default value is true.
    * @param {string} propertyName the property name
    * @param {Object} propertyDesc the property descriptor
    * @param {Object} inspectedObject the data object
    * @return {boolean} whether a particular property should be shown in this Inspector
    */
  Inspector.prototype.canShowProperty = function (propertyName, propertyDesc, inspectedObject) {
    if (propertyDesc.show === false) return false;
    // if "show" is a predicate, make sure it passes or do not show this property
    if (typeof propertyDesc.show === "function") return propertyDesc.show(inspectedObject, propertyName);
    return true;
  }

    /**
    * @ignore
    * This predicate should be false if the given property should not be editable by the user.
    * Normally it only checks the value of "readOnly" on the property descriptor.
    * The default value is true.
    * @param {string} propertyName the property name
    * @param {Object} propertyDesc the property descriptor
    * @param {Object} inspectedObject the data object
    * @return {boolean} whether a particular property should be shown in this Inspector
    */
  Inspector.prototype.canEditProperty = function (propertyName, propertyDesc, inspectedObject) {
    if (this._diagram.isReadOnly || this._diagram.isModelReadOnly) return false;
    // assume property values that are functions of Objects cannot be edited
    var data = (inspectedObject instanceof go.Part) ? inspectedObject.data : inspectedObject;
    var valtype = typeof data[propertyName];
    if (valtype === "function") return false;
    if (propertyDesc) {
      if (propertyDesc.readOnly === true) return false;
      // if "readOnly" is a predicate, make sure it passes or do not show this property
      if (typeof propertyDesc.readOnly === "function") return !propertyDesc.readOnly(inspectedObject, propertyName);
    }
    return true;
  }

    /**
     * @ignore
     * @param {any} propName
     * @param {any} propDesc
     * @param {any} data
     * @return {any}
     */
  Inspector.prototype.findValue = function (propName, propDesc, data) {
    var val = '';
    if (propDesc && propDesc.defaultValue !== undefined) val = propDesc.defaultValue;
    if (data[propName] !== undefined) val = data[propName];
    if (val === undefined) return '';
    return val;
  }

    /**
    * @ignore
    * This sets this._inspectedProperties[propertyName] and creates the HTML table row:
    *    <tr>
    *      <td>propertyName</td>
    *      <td><input value=propertyValue /></td>
    *    </tr>
    * @param {string} propertyName the property name
    * @param {*} propertyValue the property value
    * @return the table row
    */
  Inspector.prototype.buildPropertyRow = function (propertyName, propertyValue) {
    var mainDiv = this._div;
    var tr = document.createElement("tr");

    var td1 = document.createElement("td");
    td1.textContent = propertyName;
    tr.appendChild(td1);

    var td2 = document.createElement("td");
    var decProp = this.declaredProperties[propertyName];
    var input = null;
    var self = this;
    function updateall() { self.updateAllProperties(); }

    if (decProp && decProp.type === "select") {
      input = document.createElement("select");
      this.updateSelect(decProp, input, propertyName, propertyValue);
      input.addEventListener("change", updateall);
    } else {
      input = document.createElement("input");

      input.value = this.convertToString(propertyValue);
      if (decProp) {
        var t = decProp.type;
        if (t !== 'string' && t !== 'number' && t !== 'boolean' &&
          t !== 'arrayofnumber' && t !== 'point' && t !== 'size' &&
          t !== 'rect' && t !== 'spot' && t !== 'margin') {
          input.setAttribute("type", decProp.type);
        }
        if (decProp.type === "color") {
          if (input.type === "color") {
            input.value = this.convertToColor(propertyValue);
            // input.addEventListener("input", updateall);
            input.addEventListener("change", updateall);
          }
        } if (decProp.type === "checkbox") {
          input.checked = !!propertyValue;
          input.addEventListener("change", updateall);
        }
      }
      if (input.type !== "color") input.addEventListener("blur", updateall);
    }

    if (input) {
      input.tabIndex = this.tabIndex++;
      input.disabled = !this.canEditProperty(propertyName, decProp, this.inspectedObject);
      td2.appendChild(input);
    }
    tr.appendChild(td2);

    this._inspectedProperties[propertyName] = input;
    return tr;
  };

    /**
    * @ignore
    * HTML5 color input will only take hex,
    * so var HTML5 canvas convert the color into hex format.
    * This converts "rgb(255, 0, 0)" into "#FF0000", etc.
    * @param {string} propertyValue
    * @return {string}
    */
  Inspector.prototype.convertToColor = function (propertyValue) {
    var ctx = document.createElement("canvas").getContext("2d");
    ctx.fillStyle = propertyValue;
    return ctx.fillStyle;
  };

    /**
    * @ignore
    * @param {string}
    * @return {Array.<number>}
    */
  Inspector.prototype.convertToArrayOfNumber = function (propertyValue) {
    if (propertyValue === "null") return null;
    var split = propertyValue.split(' ');
    var arr = [];
    for (var i = 0; i < split.length; i++) {
      var str = split[i];
      if (!str) continue;
      arr.push(parseFloat(str));
    }
    return arr;
  };

    /**
    * @ignore
    * @param {*}
    * @return {string}
    */
  Inspector.prototype.convertToString = function (x) {
    if (x === undefined) return "undefined";
    if (x === null) return "null";
    if (x instanceof go.Point) return go.Point.stringify(x);
    if (x instanceof go.Size) return go.Size.stringify(x);
    if (x instanceof go.Rect) return go.Rect.stringify(x);
    if (x instanceof go.Spot) return go.Spot.stringify(x);
    if (x instanceof go.Margin) return go.Margin.stringify(x);
    if (x instanceof go.List) return this.convertToString(x.toArray());
    if (Array.isArray(x)) {
      var str = "";
      for (var i = 0; i < x.length; i++) {
        if (i > 0) str += " ";
        var v = x[i];
        str += this.convertToString(v);
      }
      return str;
    }
    return x.toString();
  };

    /**
    * @ignore
    * Update all of the HTML in this Inspector.
    */
  Inspector.prototype.updateAllHTML = function () {
    var inspectedProps = this._inspectedProperties;
    var diagram = this._diagram;
    var isPart = this.inspectedObject instanceof go.Part;
    var data = isPart ? this.inspectedObject.data : this.inspectedObject;
    if (!data) {  // clear out all of the fields
      for (var name in inspectedProps) {
        var input = inspectedProps[name];
        if (input instanceof HTMLSelectElement) {
          input.innerHTML = "";
        } else if (input.type === "color") {
          input.value = "#000000";
        } else if (input.type === "checkbox") {
          input.checked = false;
        } else {
          input.value = "";
        }

      }
    } else {
      for (var name in inspectedProps) {
        var input = inspectedProps[name];
        var propertyValue = data[name];
        if (input instanceof HTMLSelectElement) {
          var decProp = this.declaredProperties[name];
          this.updateSelect(decProp, input, name, propertyValue);
        } else if (input.type === "color") {
          input.value = this.convertToColor(propertyValue);
        } else if (input.type === "checkbox") {
          input.checked = !!propertyValue;
        } else {
          input.value = this.convertToString(propertyValue);
        }
      }
    }
  }

    /**
    * @ignore
    * Update an HTMLSelectElement with an appropriate list of choices, given the propertyName
    */
  Inspector.prototype.updateSelect = function (decProp, select, propertyName, propertyValue) {
    select.innerHTML = "";  // clear out anything that was there
    var choices = decProp.choices;
    if (typeof choices === "function") choices = choices(this.inspectedObject, propertyName);
    if (!Array.isArray(choices)) choices = [];
    decProp.choicesArray = choices;  // remember list of actual choice values (not strings)
    for (var i = 0; i < choices.length; i++) {
      var choice = choices[i];
      var opt = document.createElement("option");
      opt.text = this.convertToString(choice);
      select.add(opt, null);
    }
    select.value = this.convertToString(propertyValue);
  }

    /**
    * @ignore
    * Update all of the data properties of {@link #inspectedObject} according to the
    * current values held in the HTML input elements.
    */
  Inspector.prototype.updateAllProperties = function () {
    var inspectedProps = this._inspectedProperties;
    var diagram = this._diagram;
    if (diagram.selection.count === 1 || !this.multipleSelection) { // single object update
      var isPart = this.inspectedObject instanceof go.Part;
      var data = isPart ? this.inspectedObject.data : this.inspectedObject;
      if (!data) return;  // must not try to update data when there's no data!

      diagram.startTransaction('set all properties');
      for (var name in inspectedProps) {
        var input = inspectedProps[name];
        var value = input.value;

        // don't update "readOnly" data properties
        var decProp = this.declaredProperties[name];
        if (!this.canEditProperty(name, decProp, this.inspectedObject)) continue;

        // If it's a boolean, or if its previous value was boolean,
        // parse the value to be a boolean and then update the input.value to match
        var type = '';
        if (decProp !== undefined && decProp.type !== undefined) {
          type = decProp.type;
        }
        if (type === '') {
          var oldval = data[name];
          if (typeof oldval === 'boolean') type = 'boolean'; // infer boolean
          else if (typeof oldval === 'number') type = 'number';
          else if (oldval instanceof go.Point) type = 'point';
          else if (oldval instanceof go.Size) type = 'size';
          else if (oldval instanceof go.Rect) type = 'rect';
          else if (oldval instanceof go.Spot) type = 'spot';
          else if (oldval instanceof go.Margin) type = 'margin';
        }

        // convert to specific type, if needed
        switch (type) {
          case 'boolean': value = !(value === false || value === 'false' || value === '0'); break;
          case 'number': value = parseFloat(value); break;
          case 'arrayofnumber': value = this.convertToArrayOfNumber(value); break;
          case 'point': value = go.Point.parse(value); break;
          case 'size': value = go.Size.parse(value); break;
          case 'rect': value = go.Rect.parse(value); break;
          case 'spot': value = go.Spot.parse(value); break;
          case 'margin': value = go.Margin.parse(value); break;
          case 'checkbox': value = input.checked; break;
          case 'select': value = decProp.choicesArray[input.selectedIndex]; break;
        }

        // in case parsed to be different, such as in the case of boolean values,
        // the value shown should match the actual value
        input.value = value;

        // modify the data object in an undo-able fashion
        diagram.model.setDataProperty(data, name, value);

        // notify any listener
        if (this.propertyModified !== null) this.propertyModified(name, value, this);
      }
      diagram.commitTransaction('set all properties');
    } else { // selection object update
      diagram.startTransaction('set all properties');
      for (var name in inspectedProps) {
        var input = inspectedProps[name];
        var value = input.value;
        var arr1 = value.split('|');
        var arr2 = [];
        if (this._multipleProperties[name]) {
          // don't split if it is union and its checkbox type
          if (this.declaredProperties[name] && this.declaredProperties[name].type === 'checkbox' && this.showAllProperties) {
            arr2.push(this._multipleProperties[name]);
          } else {
            arr2 = this._multipleProperties[name].toString().split('|');
          }
        }
        var it = diagram.selection.iterator;
        var change = false;
        if (this.declaredProperties[name] && this.declaredProperties[name].type === 'checkbox') change = true; // always change checkbox
        if (arr1.length < arr2.length // i.e Alpha|Beta -> Alpha procs the change
          && (!this.declaredProperties[name] // from and to links
            || !(this.declaredProperties[name] // do not change color checkbox and choices due to them always having less
              && (this.declaredProperties[name].type === 'color' || this.declaredProperties[name].type === 'checkbox' || this.declaredProperties[name].type === 'choices')))) {
          change = true;
        } else { // standard detection in change in properties
          for (var j = 0; j < arr1.length && j < arr2.length; j++) {
            if (!(arr1[j] === arr2[j])
              && !(this.declaredProperties[name] && this.declaredProperties[name].type === 'color' && arr1[j].toLowerCase() === arr2[j].toLowerCase())) {
              change = true;
            }
          }
        }
        if (change) { // only change properties it needs to change instead all of them
          for (var i = 0; i < diagram.selection.count; i++) {
            it.next();
            var isPart = it.value instanceof go.Part;
            var data = isPart ? it.value.data : it.value;

            if (data) { // ignores the selected node if there is no data
              if (i < arr1.length) value = arr1[i];
              else value = arr1[0];

              // don't update "readOnly" data properties
              var decProp = this.declaredProperties[name];
              if (!this.canEditProperty(name, decProp, it.value)) continue;

              // If it's a boolean, or if its previous value was boolean,
              // parse the value to be a boolean and then update the input.value to match
              var type = '';
              if (decProp !== undefined && decProp.type !== undefined) {
                type = decProp.type;
              }
              if (type === '') {
                var oldval = data[name];
                if (typeof oldval === 'boolean') type = 'boolean'; // infer boolean
                else if (typeof oldval === 'number') type = 'number';
                else if (oldval instanceof go.Point) type = 'point';
                else if (oldval instanceof go.Size) type = 'size';
                else if (oldval instanceof go.Rect) type = 'rect';
                else if (oldval instanceof go.Spot) type = 'spot';
                else if (oldval instanceof go.Margin) type = 'margin';
              }

              // convert to specific type, if needed
              switch (type) {
                case 'boolean': value = !(value === false || value === 'false' || value === '0'); break;
                case 'number': value = parseFloat(value); break;
                case 'arrayofnumber': value = this.convertToArrayOfNumber(value); break;
                case 'point': value = go.Point.parse(value); break;
                case 'size': value = go.Size.parse(value); break;
                case 'rect': value = go.Rect.parse(value); break;
                case 'spot': value = go.Spot.parse(value); break;
                case 'margin': value = go.Margin.parse(value); break;
                case 'checkbox': value = input.checked; break;
                case 'select': value = decProp.choicesArray[input.selectedIndex]; break;
              }

              // in case parsed to be different, such as in the case of boolean values,
              // the value shown should match the actual value
              input.value = value;

              // modify the data object in an undo-able fashion
              diagram.model.setDataProperty(data, name, value);

              // notify any listener
              if (this.propertyModified !== null) this.propertyModified(name, value, this);
            }
          }
        }
      }
      diagram.commitTransaction('set all properties');
    }
  };


</script>

</html>

参考 学习文档  https://liuxiaofan.com/2018/03/16/3521.html

原文地址:https://www.cnblogs.com/Alwaysbecoding/p/11898305.html