We’re trying to refactor GoJS code by break it to separate files in this way:
-
Main functionality (we apply module pattern):
var SchemaEditor = (function() {
/* Private members */ var black = "black"; var transparent = "transparent"; var sd = { mode: "pointer", // Set to default mode. Alternatives are "node" and "link", for adding a new node or a new link respectively. itemType: "pointer", // Set when user clicks on a node or link button. nodeCounter: {} }; var myDiagram; var $ = go.GraphObject.make; /* Private methods */ // update the diagram every 250 milliseconds function loop() { setTimeout(function () { updateStates(); loop(); }, 250); } // update the value and appearance of each node according to its type and input values function updateStates() { var oldskip = myDiagram.skipsUndoManager; myDiagram.skipsUndoManager = true; // do all "input" nodes first myDiagram.nodes.each(function (node) { if (node.category === "input") { doInput(node); } }); // now we can do all other kinds of nodes myDiagram.nodes.each(function (node) { switch (node.category) { case "image1": doImage1(node); break; case "image2": doImage2(node); break; case "image3": doImage3(node); break; case "image4": doImage4(node); break; case "image5": doImage5(node); break; case "image6": doImage6(node); break; case "image7": doImage7(node); break; case "image8": doImage8(node); break; case "image9": doImage9(node); break; case "image10": doImage10(node); break; case "table": doTable(node); break; case "hBar": dohBar(node); break; } }); myDiagram.skipsUndoManager = oldskip; } // update nodes by the specific function for its type // determine the color of links coming out of this node based on those coming in and node type function doImage1(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage2(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage3(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage4(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage5(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage6(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage7(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage8(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage9(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doImage10(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function doTable(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function dohBar(node) { // assume there is just one input link // we just need to update the node's Shape.fill node.linksConnected.each(function (link) { node.findObject("NODESHAPE1").fill = link.findObject("SHAPE").stroke; }); } function BarLink() { go.Link.call(this); } go.Diagram.inherit(BarLink, go.Link); /** @override */ BarLink.prototype.getLinkPoint = function (node, port, spot, from, ortho, othernode, otherport) { if (node.category === "hBar") { var op = go.Link.prototype.getLinkPoint.call(this, othernode, otherport, this.computeSpot(!from), !from, ortho, node, port); var r = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft), port.getDocumentPoint(go.Spot.BottomRight)); var y = (op.y > r.centerY) ? r.bottom : r.top; if (op.x < r.left) return new go.Point(r.left, y); if (op.x > r.right) return new go.Point(r.right, y); return new go.Point(op.x, y); } else { return go.Link.prototype.getLinkPoint.call(this, node, port, spot, from, ortho, othernode, otherport); } }; /** @override */ BarLink.prototype.getLinkDirection = function (node, port, linkpoint, spot, from, ortho, othernode, otherport) { var p = port.getDocumentPoint(go.Spot.Center); var op = otherport.getDocumentPoint(go.Spot.Center); var below = op.y > p.y; return below ? 90 : 270; }; // end BarLink class var setMode = function (mode, itemType) { myDiagram.startTransaction(); sd.mode = mode; sd.itemType = itemType; if (mode === "link") { if (itemType === 'default') { if (document.getElementById("defaultMode").classList.contains('non-pushed')) { document.getElementById("defaultMode").classList.remove('non-pushed'); document.getElementById("defaultMode").classList.add('pushed'); document.getElementById("orthoMode").classList.add('non-pushed'); document.getElementById("cornerMode").classList.add('non-pushed'); } } if (itemType === 'ortho') { if (document.getElementById("orthoMode").classList.contains('non-pushed')) { document.getElementById("orthoMode").classList.remove('non-pushed'); document.getElementById("orthoMode").classList.add('pushed'); document.getElementById("defaultMode").classList.add('non-pushed'); document.getElementById("cornerMode").classList.add('non-pushed'); } } if (itemType === 'corner') { if (document.getElementById("cornerMode").classList.contains('non-pushed')) { document.getElementById("cornerMode").classList.remove('non-pushed'); document.getElementById("cornerMode").classList.add('pushed'); document.getElementById("defaultMode").classList.add('non-pushed'); document.getElementById("orthoMode").classList.add('non-pushed'); } } myDiagram.allowLink = true; myDiagram.nodes.each(function (n) { n.port.cursor = "pointer"; }); } myDiagram.commitTransaction("mode changed"); }; // save a model to and load a model from JSON text, displayed below the Diagram var save = function () { document.getElementById("mySavedModel").value = myDiagram.model.toJson(); myDiagram.isModified = false; }; var load = function () { myDiagram.model = go.Model.fromJson(document.getElementById("mySavedModel").value); }; function addLinkTemplateMaps() { // creates relinkable Links that will avoid crossing Nodes when possible and will jump over other Links in their paths myDiagram.linkTemplateMap.add("default", $(go.Link, { relinkableFrom: true, relinkableTo: true, selectionAdorned: false, // Links are not adorned when selected so that their color remains visible. shadowOffset: new go.Point(0, 0), shadowBlur: 5, shadowColor: "black" }, new go.Binding("isShadowed", "isSelected").ofObject(), $(go.Shape, { name: "SHAPE", strokeWidth: 2, stroke: black }))); myDiagram.linkTemplateMap.add("corner", $(go.Link, { reshapable: true, resegmentable: true, routing: go.Link.AvoidsNodes }, new go.Binding("points").makeTwoWay(), // TwoWay Binding of Link.points $(go.Shape) )); myDiagram.linkTemplateMap.add("ortho", $(BarLink, // subclass defined below { routing: go.Link.Orthogonal, relinkableFrom: true, relinkableTo: true, toPortChanged: function (link, oldport, newport) { if (newport instanceof go.Shape) link.path.stroke = newport.fill; } }, $(go.Shape, { strokeWidth: 2 }) )); }; var initSchemaEditor = function () { myDiagram = $(go.Diagram, "myDiagramDiv", { initialContentAlignment: go.Spot.Center, allowDrop: true, // Nodes from the Palette can be dropped into the Diagram "undoManager.isEnabled": true, "grid.visible": true, "linkingTool.portGravity": 0, // no snapping while drawing new links "linkingTool.doActivate": function () { // change the curve of the LinkingTool.temporaryLink this.temporaryLink.curve = (sd.itemType === "default") ? go.Link.Normal : go.Link.Orthogonal; go.LinkingTool.prototype.doActivate.call(this); }, // override the link creation process "linkingTool.insertLink": function (fromnode, fromport, tonode, toport) { // to control what kind of Link is created, // change the LinkingTool.archetypeLinkData's category myDiagram.model.setCategoryForLinkData(this.archetypeLinkData, sd.itemType); // also change the text indicating the condition, which the user can edit this.archetypeLinkData.text = sd.itemType; return go.LinkingTool.prototype.insertLink.call(this, fromnode, fromport, tonode, toport); }, "clickCreatingTool.archetypeNodeData": {}, // enable ClickCreatingTool "clickCreatingTool.isDoubleClick": false, // operates on a single click in background "clickCreatingTool.canStart": function () { // but only in "node" creation mode return sd.mode === "node" && go.ClickCreatingTool.prototype.canStart.call(this); }, "clickCreatingTool.insertPart": function (loc) { // customize the data for the new node sd.nodeCounter[sd.itemType] += 1; var newNodeId = sd.itemType + sd.nodeCounter[sd.itemType]; this.archetypeNodeData = { key: newNodeId, category: sd.itemType, label: newNodeId }; return go.ClickCreatingTool.prototype.insertPart.call(this, loc); } }); myDiagram.requestUpdate(); // when the document is modified, add a "*" to the title and enable the "Save" button myDiagram.addDiagramListener("Modified", function (e) { var button = document.getElementById("saveModel"); 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); } }); myDiagram.model = $(go.GraphLinksModel, { linkFromPortIdProperty: "fromPort", // required information: linkToPortIdProperty: "toPort" // identifies data property names }); addLinkTemplateMaps(); loadNodes(); // add the templates created above to myDiagram and palette myDiagram.nodeTemplateMap.add("image1", image1Template); myDiagram.nodeTemplateMap.add("image2", image2Template); myDiagram.nodeTemplateMap.add("image3", image3Template); myDiagram.nodeTemplateMap.add("image4", image4Template); myDiagram.nodeTemplateMap.add("image5", image5Template); myDiagram.nodeTemplateMap.add("image6", image6Template); myDiagram.nodeTemplateMap.add("image7", image7Template); myDiagram.nodeTemplateMap.add("image8", image8Template); myDiagram.nodeTemplateMap.add("image9", image9Template); myDiagram.nodeTemplateMap.add("image10", image10Template); myDiagram.nodeTemplateMap.add("table", tableTemplate); myDiagram.nodeTemplateMap.add("hBar", hBarTemplate); var palette = new go.Palette("palette"); // create a new Palette in the HTML DIV element "palette" // share the template map with the Palette palette.nodeTemplateMap = myDiagram.nodeTemplateMap; //palette.groupTemplateMap = myDiagram.groupTemplateMap; palette.model.nodeDataArray = [ { category: "image1" }, { category: "image2" }, { category: "image3" }, { category: "image4" }, { category: "image5" }, { category: "image6" }, { category: "image7" }, { category: "image8" }, { category: "image9" }, { category: "image10" }, { category: "table" }, { category: "hBar" } ]; myDiagram.addDiagramListener("ExternalObjectsDropped", function (e) { if (myDiagram.currentTool instanceof go.TextEditingTool) { myDiagram.currentTool.acceptText(go.TextEditingTool.LostFocus); } myDiagram.commandHandler.ungroupSelection(); jQuery.ajax({ type: "GET", url: '/Home/GetRandomObjectProperties' }).done(function (data) { // loop through selection to find table node and populate properties myDiagram.selection.each(function (p) { if (p instanceof go.Node && p.category === "table") { var nodedata = p.data; var properties = data.map(function (item) { return { "property_name": item.Item1.toString(), "property_value": item.Item2.toString() }; }); myDiagram.model.setDataProperty(nodedata, "properties", properties); return; } }); }); }); // load the initial diagram load(); // continually update the diagram loop(); }; /* Public methods */ return { initSchemaEditor: initSchemaEditor, setMode: setMode, save: save, load: load };
})();
-
The file where we want to keep all node templates (we don’t apply module pattern):
function loadNodes() {
// node template helpers
this.sharedToolTip =
$(go.Adornment, “Auto”,
$(go.Shape, “RoundedRectangle”, { fill: “lightyellow” }),
$(go.TextBlock, { margin: 2 },
new go.Binding(“text”, “”, function (d) { return d.category; })));// define some common property settings function nodeStyle() { return [ new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), new go.Binding("isShadowed", "isSelected").ofObject(), { selectionAdorned: false, shadowOffset: new go.Point(0, 0), shadowBlur: 15, shadowColor: "blue", toolTip: sharedToolTip } ]; } function portStyle0(input) { return { desiredSize: new go.Size(3, 3), fill: "black", fromLinkable: !input, toLinkable: input, cursor: "pointer" }; } function portStyle1() { return { desiredSize: new go.Size(3, 3), fill: "black", toLinkable: true, cursor: "pointer", fromLinkable: true, fromSpot: go.Spot.TopBottomSides, toSpot: go.Spot.TopBottomSides }; } this.image1Template = $(go.Node, "Vertical", nodeStyle(), $(go.Picture, "Images/ElectricalElements/Cell_1.svg"), $(go.Shape, "Rectangle", portStyle1(), { portId: "", alignment: new go.Spot(0.18, 0) }) ); this.image2Template = $(go.Node, "Vertical", nodeStyle(), $(go.Picture, "Images/ElectricalElements/Cell_2.svg"), $(go.Shape, "Rectangle", portStyle1(), { portId: "", alignment: new go.Spot(0.33, 0) }) ); this.image3Template = $(go.Node, "Vertical", nodeStyle(), $(go.Picture, "Images/ElectricalElements/GTU.svg"), $(go.Shape, "Rectangle", portStyle1(), { portId: "", alignment: new go.Spot(0.215, 0) }) ); this.image4Template = $(go.Node, "Vertical", nodeStyle(), $(go.Shape, "Rectangle", portStyle0(false), { portId: "1", alignment: new go.Spot(0.125, 0) }), $(go.Picture, "Images/ElectricalElements/Sec_1.svg"), $(go.Shape, "Rectangle", portStyle1(), { portId: "2", alignment: new go.Spot(0.125, 0) }) ); this.image5Template = $(go.Node, "Vertical", nodeStyle(), $(go.Shape, "Rectangle", portStyle0(true), { portId: "3", alignment: new go.Spot(0.523, 0) }), $(go.Picture, "Images/ElectricalElements/Sec_2.svg"), $(go.Shape, "Rectangle", portStyle1(), { portId: "4", alignment: new go.Spot(0.523, 0) }) ); this.image6Template = $(go.Node, "Vertical", nodeStyle(), $(go.Shape, "Rectangle", portStyle0(true), { portId: "", alignment: new go.Spot(0.12, 0) }), $(go.Picture, "Images/ElectricalElements/Sec_3.svg"), $(go.Shape, "Rectangle", portStyle1(), { portId: "", alignment: new go.Spot(0.12, 0) }) ); this.image7Template = $(go.Node, "Vertical", nodeStyle(), $(go.Picture, "Images/ElectricalElements/Tr_1.svg"), $(go.Shape, "Rectangle", portStyle1(), { portId: "", alignment: new go.Spot(0.42, 0) }) ); this.image8Template = $(go.Node, "Vertical", nodeStyle(), $(go.Shape, "Rectangle", portStyle1(), { portId: "", alignment: new go.Spot(0.59, 0) }), $(go.Picture, "Images/ElectricalElements/Tr_2.svg") ); this.image9Template = $(go.Node, "Vertical", nodeStyle(), { resizable: true, resizeObjectName: "SHAPE", selectionObjectName: "SHAPE" }, $(go.Shape, "Rectangle", { name: "SHAPE", fill: transparent, width: 60, height: 40, stroke: black, strokeWidth: 1, strokeDashArray: [5, 5] }, new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)) ); this.image10Template = $(go.Node, "Vertical", nodeStyle(), $(go.TextBlock, { text: "text", editable: true, isMultiline: true }, new go.Binding("text", "text").makeTwoWay()) ); this.tableTemplate = $(go.Node, go.Panel.Auto, nodeStyle(), $(go.Shape, { fill: "white", stroke: "gray", strokeWidth: 1 }), { movable: true }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Panel, "Table", new go.Binding("itemArray", "properties"), { defaultAlignment: go.Spot.Left, defaultColumnSeparatorStroke: "black", itemTemplate: $(go.Panel, "TableRow", $(go.TextBlock, new go.Binding("text", "property_name"), { column: 0, margin: 1, font: "bold 7pt sans-serif" }), $(go.TextBlock, new go.Binding("text", "property_value"), { column: 1, margin: 1 }) ) }, $(go.Panel, "TableRow", { isPanelMain: true }, $(go.TextBlock, "Name", { column: 0, margin: new go.Margin(1, 1, 0, 1), font: "bold 7pt sans-serif" }), $(go.TextBlock, "Value", { column: 1, margin: new go.Margin(1, 1, 0, 1), font: "bold 7pt sans-serif" }) ), $(go.RowColumnDefinition, { row: 0, background: "lightgray" }), $(go.RowColumnDefinition, { row: 1, separatorStroke: "black" }) ) ); this.hBarTemplate = $(go.Node, new go.Binding("location", "location", go.Point.parse).makeTwoWay(go.Point.stringify), { layerName: "Background", // special resizing: just at the ends resizable: true, resizeObjectName: "SHAPE", resizeAdornmentTemplate: $(go.Adornment, "Spot", $(go.Placeholder), $(go.Shape, // left resize handle { alignment: go.Spot.Left, cursor: "col-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "dodgerblue" }), $(go.Shape, // right resize handle { alignment: go.Spot.Right, cursor: "col-resize", desiredSize: new go.Size(6, 6), fill: "lightblue", stroke: "dodgerblue" })), rotatable: true }, $(go.Shape, "Rectangle", { name: "SHAPE", fill: "black", stroke: null, strokeWidth: 0, width: 60, height: 5, minSize: new go.Size(50, 5), maxSize: new go.Size(Infinity, 5) }, new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify), new go.Binding("fill"), { portId: "", toLinkable: true }) );
};
We call directly loadNodes function from main file, but the palette is empty.
Ideally, we’d like to have separate file with node templates organized according to module pattern as well.