How to refactor GoJS code?

We’re trying to refactor GoJS code by break it to separate files in this way:

  1. 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
     };
    

    })();

  2. 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.

First of all, is everything working correctly?

What feature do you need to implement that requires such a reorganization? It’s all just JavaScript, so you should be able to rearrange to your heart’s desire.

But remember that defining templates is considered part of the initialization of the Diagram and independent of the Model. Defining and initializing Tools and the CommandHandler are also part of initializing the Diagram.