"use strict"; /* * Copyright (C) 1998-2018 by Northwoods Software Corporation. All Rights Reserved. */ // This file holds all of the JavaScript code specific to the BPMN.html page. // Setup all of the Diagrams and what they need. // This is called after the page is loaded. function init() { function checkLocalStorage() { try { window.localStorage.setItem('item', 'item'); window.localStorage.removeItem('item'); return true; } catch (e) { return false; } } if (!checkLocalStorage()) { var currentFile = document.getElementById("currentFile"); currentFile.textContent = "Sorry! No web storage support. If you're using Internet Explorer / Microsoft Edge, you must load the page from a server for local storage to work."; } // setup the menubar jQuery( "#menuui" ).menu(); jQuery(function() { jQuery( "#menuui" ).menu({ position: { my: "left top", at: "left top+30" } }); }); jQuery( "#menuui" ).menu({ icons: { submenu: "ui-icon-triangle-1-s" } }); // hides open HTML Element var openDocument = document.getElementById("openDocument"); openDocument.style.visibility = "hidden"; // hides remove HTML Element var removeDocument = document.getElementById("removeDocument"); removeDocument.style.visibility = "hidden"; var $ = go.GraphObject.make; // for more concise visual tree definitions // constants for design choices var GradientYellow = $(go.Brush, "Linear", { 0: "LightGoldenRodYellow", 1: "#FFFF66" }); var GradientLightGreen = $(go.Brush, "Linear", { 0: "#E0FEE0", 1: "PaleGreen" }); var GradientLightGray = $(go.Brush, "Linear", { 0: "White", 1: "#DADADA" }); var ActivityNodeFill = $(go.Brush, "Linear", { 0: "OldLace", 1: "PapayaWhip" }); var ActivityNodeStroke = "#CDAA7D"; var ActivityMarkerStrokeWidth = 1.5; var ActivityNodeWidth = 120; var ActivityNodeHeight = 80; var ActivityNodeStrokeWidth = 1; var ActivityNodeStrokeWidthIsCall = 4; var SubprocessNodeFill = ActivityNodeFill; var SubprocessNodeStroke = ActivityNodeStroke; var EventNodeSize = 42; var EventNodeInnerSize = EventNodeSize - 6; var EventNodeSymbolSize = EventNodeInnerSize - 14; var EventEndOuterFillColor = "pink"; var EventBackgroundColor = GradientLightGreen; var EventSymbolLightFill = "white"; var EventSymbolDarkFill = "dimgray"; var EventDimensionStrokeColor = "green"; var EventDimensionStrokeEndColor = "red"; var EventNodeStrokeWidthIsEnd = 4; var GatewayNodeSize = 80; var GatewayNodeSymbolSize = 45; var GatewayNodeFill = GradientYellow; var GatewayNodeStroke = "darkgoldenrod"; var GatewayNodeSymbolStroke = "darkgoldenrod"; var GatewayNodeSymbolFill = GradientYellow; var GatewayNodeSymbolStrokeWidth = 3; var DataFill = GradientLightGray; // custom figures for Shapes go.Shape.defineFigureGenerator("Empty", function(shape, w, h) { return new go.Geometry(); }); var annotationStr = "M 150,0L 0,0L 0,600L 150,600 M 800,0"; var annotationGeo = go.Geometry.parse(annotationStr); annotationGeo.normalize(); go.Shape.defineFigureGenerator("Annotation", function(shape, w, h) { var geo = annotationGeo.copy(); // calculate how much to scale the Geometry so that it fits in w x h var bounds = geo.bounds; var scale = Math.min(w / bounds.width, h / bounds.height); geo.scale(scale, scale); return geo; }); var gearStr = "F M 391,5L 419,14L 444.5,30.5L 451,120.5L 485.5,126L 522,141L 595,83L 618.5,92L 644,106.5" + "L 660.5,132L 670,158L 616,220L 640.5,265.5L 658.122,317.809L 753.122,322.809L 770.122,348.309L 774.622,374.309" + "L 769.5,402L 756.622,420.309L 659.122,428.809L 640.5,475L 616.5,519.5L 670,573.5L 663,600L 646,626.5" + "L 622,639L 595,645.5L 531.5,597.5L 493.192,613.462L 450,627.5L 444.5,718.5L 421.5,733L 393,740.5L 361.5,733.5" + "L 336.5,719L 330,627.5L 277.5,611.5L 227.5,584.167L 156.5,646L 124.5,641L 102,626.5L 82,602.5L 78.5,572.5" + "L 148.167,500.833L 133.5,466.833L 122,432.5L 26.5,421L 11,400.5L 5,373.5L 12,347.5L 26.5,324L 123.5,317.5" + "L 136.833,274.167L 154,241L 75.5,152.5L 85.5,128.5L 103,105.5L 128.5,88.5001L 154.872,82.4758L 237,155" + "L 280.5,132L 330,121L 336,30L 361,15L 391,5 Z M 398.201,232L 510.201,275L 556.201,385L 505.201,491L 399.201,537" + "L 284.201,489L 242.201,385L 282.201,273L 398.201,232 Z"; var gearGeo = go.Geometry.parse(gearStr); gearGeo.normalize(); go.Shape.defineFigureGenerator("BpmnTaskService", function(shape, w, h) { var geo = gearGeo.copy(); // calculate how much to scale the Geometry so that it fits in w x h var bounds = geo.bounds; var scale = Math.min(w / bounds.width, h / bounds.height); geo.scale(scale, scale); // text should go in the hand geo.spot1 = new go.Spot(0, 0.6, 10, 0); geo.spot2 = new go.Spot(1, 1); return geo; }); var handGeo = go.Geometry.parse("F1M18.13,10.06 C18.18,10.07 18.22,10.07 18.26,10.08 18.91," + "10.20 21.20,10.12 21.28,12.93 21.36,15.75 21.42,32.40 21.42,32.40 21.42," + "32.40 21.12,34.10 23.08,33.06 23.08,33.06 22.89,24.76 23.80,24.17 24.72," + "23.59 26.69,23.81 27.19,24.40 27.69,24.98 28.03,24.97 28.03,33.34 28.03," + "33.34 29.32,34.54 29.93,33.12 30.47,31.84 29.71,27.11 30.86,26.56 31.80," + "26.12 34.53,26.12 34.72,28.29 34.94,30.82 34.22,36.12 35.64,35.79 35.64," + "35.79 36.64,36.08 36.72,34.54 36.80,33.00 37.17,30.15 38.42,29.90 39.67," + "29.65 41.22,30.20 41.30,32.29 41.39,34.37 42.30,46.69 38.86,55.40 35.75," + "63.29 36.42,62.62 33.47,63.12 30.76,63.58 26.69,63.12 26.69,63.12 26.69," + "63.12 17.72,64.45 15.64,57.62 13.55,50.79 10.80,40.95 7.30,38.95 3.80," + "36.95 4.24,36.37 4.28,35.35 4.32,34.33 7.60,31.25 12.97,35.75 12.97," + "35.75 16.10,39.79 16.10,42.00 16.10,42.00 15.69,14.30 15.80,12.79 15.96," + "10.75 17.42,10.04 18.13,10.06z "); handGeo.rotate(90, 0, 0); handGeo.normalize(); go.Shape.defineFigureGenerator("BpmnTaskManual", function(shape, w, h) { var geo = handGeo.copy(); // calculate how much to scale the Geometry so that it fits in w x h var bounds = geo.bounds; var scale = Math.min(w / bounds.width, h / bounds.height); geo.scale(scale, scale); // guess where text should go (in the hand) geo.spot1 = new go.Spot(0, 0.6, 10, 0); geo.spot2 = new go.Spot(1, 1); return geo; }); // define the appearance of tooltips, shared by various templates var tooltiptemplate = $(go.Adornment, go.Panel.Auto, $(go.Shape, "RoundedRectangle", { fill: "whitesmoke", stroke: "gray" }), $(go.TextBlock, { margin: 3, editable: true }, new go.Binding("text", "", function (data) { if (data.item !== undefined) return data.item; return "(unnamed item)"; })) ); // conversion functions used by data Bindings function nodeActivityTaskTypeConverter(s) { var tasks = ["Empty", "BpmnTaskMessage", "BpmnTaskUser", "BpmnTaskManual", // Custom hand symbol "BpmnTaskScript", "BpmnTaskMessage", // should be black on white "BpmnTaskService", // Custom gear symbol "InternalStorage"]; if (s < tasks.length) return tasks[s]; return "NotAllowed"; // error } // location of event on boundary of Activity is based on the index of the event in the boundaryEventArray function nodeActivityBESpotConverter(s) { var x = 10 + (EventNodeSize / 2); if (s === 0) return new go.Spot(0, 1, x, 0); // bottom left if (s === 1) return new go.Spot(1, 1, -x, 0); // bottom right if (s === 2) return new go.Spot(1, 0, -x, 0); // top right return new go.Spot(1, 0, -x - (s - 2) * EventNodeSize, 0); // top ... right-to-left-ish spread } function nodeActivityTaskTypeColorConverter(s) { return (s == 5) ? "dimgray" : "white"; } function nodeEventTypeConverter(s) { // order here from BPMN 2.0 poster var tasks = [ "NotAllowed", "Empty", "BpmnTaskMessage", "BpmnEventTimer", "BpmnEventEscalation", "BpmnEventConditional", "Arrow", "BpmnEventError", "ThinX", "BpmnActivityCompensation", "Triangle", "Pentagon", "ThickCross", "Circle"]; if (s < tasks.length) return tasks[s]; return "NotAllowed"; // error } function nodeEventDimensionStrokeColorConverter(s) { if (s === 8) return EventDimensionStrokeEndColor; return EventDimensionStrokeColor; } function nodeEventDimensionSymbolFillConverter(s) { if (s <= 6) return EventSymbolLightFill; return EventSymbolDarkFill; } //------------------------------------------ Activity Node Boundary Events ---------------------------------------------- var boundaryEventMenu = // context menu for each boundaryEvent on Activity node $(go.Adornment, "Vertical", $("ContextMenuButton", $(go.TextBlock, "Remove event"), // in the click event handler, the obj.part is the Adornment; its adornedObject is the port { click: function(e, obj) { removeActivityNodeBoundaryEvent(obj.part.adornedObject); } }) ); // removing a boundary event doesn't not reposition other BE circles on the node // just reassigning alignmentIndex in remaining BE would do that. function removeActivityNodeBoundaryEvent(obj) { myDiagram.startTransaction("removeBoundaryEvent"); var pid = obj.portId; var arr = obj.panel.itemArray; for (var i = 0; i < arr.length; i++) { if (arr[i].portId === pid) { myDiagram.model.removeArrayItem(arr, i); break; } } myDiagram.commitTransaction("removeBoundaryEvent"); } var boundaryEventItemTemplate = $(go.Panel, "Spot", { contextMenu: boundaryEventMenu, alignmentFocus: go.Spot.Center, fromLinkable: true, toLinkable: false, cursor: "pointer", fromSpot: go.Spot.Bottom, fromMaxLinks: 1, toMaxLinks: 0 }, new go.Binding("portId", "portId"), new go.Binding("alignment", "alignmentIndex", nodeActivityBESpotConverter), $(go.Shape, "Circle", { desiredSize: new go.Size(EventNodeSize, EventNodeSize) }, new go.Binding("strokeDashArray", "eventDimension", function(s) { return (s === 6) ? [4, 2] : null; }), new go.Binding("fromSpot", "alignmentIndex", function(s) { // nodeActivityBEFromSpotConverter, 0 & 1 go on bottom, all others on top of activity if (s < 2) return go.Spot.Bottom; return go.Spot.Top; }), new go.Binding("fill", "color")), $(go.Shape, "Circle", { alignment: go.Spot.Center, desiredSize: new go.Size(EventNodeInnerSize, EventNodeInnerSize), fill: null }, new go.Binding("strokeDashArray", "eventDimension", function(s) { return (s === 6) ? [4, 2] : null; }) ), $(go.Shape, "NotAllowed", { alignment: go.Spot.Center, desiredSize: new go.Size(EventNodeSymbolSize, EventNodeSymbolSize), fill: "white" }, new go.Binding("figure", "eventType", nodeEventTypeConverter) ) ); //------------------------------------------ Activity Node contextMenu ---------------------------------------------- var activityNodeMenu = $(go.Adornment, "Vertical", $("ContextMenuButton", $(go.TextBlock, "Add Email Event", { margin: 3 }), { click: function(e, obj) { addActivityNodeBoundaryEvent(2, 5); } }), $("ContextMenuButton", $(go.TextBlock, "Add Timer Event", { margin: 3 }), { click: function(e, obj) { addActivityNodeBoundaryEvent(3, 5); } }), $("ContextMenuButton", $(go.TextBlock, "Add Escalation Event", { margin: 3 }), { click: function(e, obj) { addActivityNodeBoundaryEvent(4, 5); } }), $("ContextMenuButton", $(go.TextBlock, "Add Error Event", { margin: 3 }), { click: function(e, obj) { addActivityNodeBoundaryEvent(7, 5); } }), $("ContextMenuButton", $(go.TextBlock, "Add Signal Event", { margin: 3 }), { click: function(e, obj) { addActivityNodeBoundaryEvent(10, 5); } }), $("ContextMenuButton", $(go.TextBlock, "Add N-I Escalation Event", { margin: 3 }), { click: function(e, obj) { addActivityNodeBoundaryEvent(4, 6); } }), $("ContextMenuButton", $(go.TextBlock, "Rename", { margin: 3 }), { click: function(e, obj) { rename(obj); } })); // sub-process, loop, parallel, sequential, ad doc and compensation markers in horizontal array function makeSubButton(sub) { if (sub) return [$("SubGraphExpanderButton"), { margin: 2, visible: false }, new go.Binding("visible", "isSubProcess")]; return []; } // sub-process, loop, parallel, sequential, ad doc and compensation markers in horizontal array function makeMarkerPanel(sub, scale) { return $(go.Panel, "Horizontal", { alignment: go.Spot.MiddleBottom, alignmentFocus: go.Spot.MiddleBottom }, $(go.Shape, "BpmnActivityLoop", { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: ActivityMarkerStrokeWidth }, new go.Binding("visible", "isLoop")), $(go.Shape, "BpmnActivityParallel", { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: ActivityMarkerStrokeWidth }, new go.Binding("visible", "isParallel")), $(go.Shape, "BpmnActivitySequential", { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: ActivityMarkerStrokeWidth }, new go.Binding("visible", "isSequential")), $(go.Shape, "BpmnActivityAdHoc", { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: ActivityMarkerStrokeWidth }, new go.Binding("visible", "isAdHoc")), $(go.Shape, "BpmnActivityCompensation", { width: 12 / scale, height: 12 / scale, margin: 2, visible: false, strokeWidth: ActivityMarkerStrokeWidth, fill: null }, new go.Binding("visible", "isCompensation")), makeSubButton(sub) ); // end activity markers horizontal panel } var activityNodeTemplate = $(go.Node, "Spot", { locationObjectName: "SHAPE", locationSpot: go.Spot.Center, resizable: true, resizeObjectName: "PANEL", toolTip: tooltiptemplate, selectionAdorned: false, // use a Binding on the Shape.stroke to show selection contextMenu: activityNodeMenu, itemTemplate: boundaryEventItemTemplate }, new go.Binding("itemArray", "boundaryEventArray"), new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), // move a selected part into the Foreground layer, so it isn"t obscured by any non-selected parts new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(), $(go.Panel, "Auto", { name: "PANEL", minSize: new go.Size(ActivityNodeWidth, ActivityNodeHeight), desiredSize: new go.Size(ActivityNodeWidth, ActivityNodeHeight) }, new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify), $(go.Panel, "Spot", $(go.Shape, "RoundedRectangle", // the outside rounded rectangle { name: "SHAPE", fill: ActivityNodeFill, stroke: ActivityNodeStroke, parameter1: 10, // corner size portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer", fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide }, new go.Binding("fill", "color"), new go.Binding("strokeWidth", "isCall", function(s) { return s ? ActivityNodeStrokeWidthIsCall : ActivityNodeStrokeWidth; }) ), // $(go.Shape, "RoundedRectangle", // the inner "Transaction" rounded rectangle // { margin: 3, // stretch: go.GraphObject.Fill, // stroke: ActivityNodeStroke, // parameter1: 8, fill: null, visible: false // }, // new go.Binding("visible", "isTransaction") // ), // task icon $(go.Shape, "BpmnTaskScript", // will be None, Script, Manual, Service, etc via converter { alignment: new go.Spot(0, 0, 5, 5), alignmentFocus: go.Spot.TopLeft, width: 22, height: 22 }, new go.Binding("fill", "taskType", nodeActivityTaskTypeColorConverter), new go.Binding("figure", "taskType", nodeActivityTaskTypeConverter) ), // end Task Icon makeMarkerPanel(false, 1) // sub-process, loop, parallel, sequential, ad doc and compensation markers ), // end main body rectangles spot panel $(go.TextBlock, // the center text { alignment: go.Spot.Center, textAlign: "center", margin: 12, editable: true }, new go.Binding("text").makeTwoWay()) ) // end Auto Panel ); // end go.Node, which is a Spot Panel with bound itemArray // ------------------------------- template for Activity / Task node in Palette ------------------------------- var palscale = 2; var activityNodeTemplateForPalette = $(go.Node, "Vertical", { locationObjectName: "SHAPE", locationSpot: go.Spot.Center, selectionAdorned: false }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Panel, "Spot", { name: "PANEL", desiredSize: new go.Size(ActivityNodeWidth / palscale, ActivityNodeHeight / palscale) }, $(go.Shape, "RoundedRectangle", // the outside rounded rectangle { name: "SHAPE", fill: ActivityNodeFill, stroke: ActivityNodeStroke, parameter1: 10 / palscale // corner size (default 10) }, new go.Binding("strokeWidth", "isCall", function(s) { return s ? ActivityNodeStrokeWidthIsCall : ActivityNodeStrokeWidth; })), $(go.Shape, "RoundedRectangle", // the inner "Transaction" rounded rectangle { margin: 3, stretch: go.GraphObject.Fill, stroke: ActivityNodeStroke, parameter1: 8 / palscale, fill: null, visible: false }, new go.Binding("visible", "isTransaction")), // task icon $(go.Shape, "BpmnTaskScript", // will be None, Script, Manual, Service, etc via converter { alignment: new go.Spot(0, 0, 5, 5), alignmentFocus: go.Spot.TopLeft, width: 22 / palscale, height: 22 / palscale }, new go.Binding("fill", "taskType", nodeActivityTaskTypeColorConverter), new go.Binding("figure", "taskType", nodeActivityTaskTypeConverter)), makeMarkerPanel(false, palscale) // sub-process, loop, parallel, sequential, ad doc and compensation markers ), // End Spot panel $(go.TextBlock, // the center text { alignment: go.Spot.Center, textAlign: "center", margin: 2 }, new go.Binding("text")) ); // End Node var subProcessGroupTemplateForPalette = $(go.Group, "Vertical", { locationObjectName: "SHAPE", locationSpot: go.Spot.Center, isSubGraphExpanded: false, selectionAdorned: false }, $(go.Panel, "Spot", { name: "PANEL", desiredSize: new go.Size(ActivityNodeWidth / palscale, ActivityNodeHeight / palscale) }, $(go.Shape, "RoundedRectangle", // the outside rounded rectangle { name: "SHAPE", fill: ActivityNodeFill, stroke: ActivityNodeStroke, parameter1: 10 / palscale // corner size (default 10) }, new go.Binding("strokeWidth", "isCall", function(s) { return s ? ActivityNodeStrokeWidthIsCall : ActivityNodeStrokeWidth; }) ), $(go.Shape, "RoundedRectangle", // the inner "Transaction" rounded rectangle { margin: 3, stretch: go.GraphObject.Fill, stroke: ActivityNodeStroke, parameter1: 8 / palscale, fill: null, visible: false }, new go.Binding("visible", "isTransaction")), makeMarkerPanel(true, palscale) // sub-process, loop, parallel, sequential, ad doc and compensation markers ), // end main body rectangles spot panel $(go.TextBlock, // the center text { alignment: go.Spot.Center, textAlign: "center", margin: 2 }, new go.Binding("text")) ); // end go.Group //------------------------------------------ Event Node Template ---------------------------------------------- var eventNodeTemplate = $(go.Node, "Vertical", { locationObjectName: "SHAPE", locationSpot: go.Spot.Center, toolTip: tooltiptemplate }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), // move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(), // can be resided according to the user's desires { resizable: false, resizeObjectName: "SHAPE" }, $(go.Panel, "Spot", $(go.Shape, "Circle", // Outer circle { strokeWidth: 1, name: "SHAPE", desiredSize: new go.Size(EventNodeSize, EventNodeSize), portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer", fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide }, // allows the color to be determined by the node data new go.Binding("fill", "eventDimension", function(s) { return (s === 8) ? EventEndOuterFillColor : EventBackgroundColor; }), new go.Binding("strokeWidth", "eventDimension", function(s) { return s === 8 ? EventNodeStrokeWidthIsEnd : 1; }), new go.Binding("stroke", "eventDimension", nodeEventDimensionStrokeColorConverter), new go.Binding("strokeDashArray", "eventDimension", function(s) { return (s === 3 || s === 6) ? [4, 2] : null; }), new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify) ), // end main shape $(go.Shape, "Circle", // Inner circle { alignment: go.Spot.Center, desiredSize: new go.Size(EventNodeInnerSize, EventNodeInnerSize), fill: null }, new go.Binding("stroke", "eventDimension", nodeEventDimensionStrokeColorConverter), new go.Binding("strokeDashArray", "eventDimension", function(s) { return (s === 3 || s === 6) ? [4, 2] : null; }), // dashes for non-interrupting new go.Binding("visible", "eventDimension", function(s) { return s > 3 && s <= 7; }) // inner only visible for 4 thru 7 ), $(go.Shape, "NotAllowed", { alignment: go.Spot.Center, desiredSize: new go.Size(EventNodeSymbolSize, EventNodeSymbolSize), stroke: "black" }, new go.Binding("figure", "eventType", nodeEventTypeConverter), new go.Binding("fill", "eventDimension", nodeEventDimensionSymbolFillConverter) ) ), // end Auto Panel $(go.TextBlock, { alignment: go.Spot.Center, textAlign: "center", margin: 5, editable: true }, new go.Binding("text").makeTwoWay()) ); // end go.Node Vertical //------------------------------------------ Gateway Node Template ---------------------------------------------- function nodeGatewaySymbolTypeConverter(s) { var tasks = ["NotAllowed", "ThinCross", // 1 - Parallel "Circle", // 2 - Inclusive "AsteriskLine", // 3 - Complex "ThinX", // 4 - Exclusive (exclusive can also be no symbol, just bind to visible=false for no symbol) "Pentagon", // 5 - double cicle event based gateway "Pentagon", // 6 - exclusive event gateway to start a process (single circle) "ThickCross"] // 7 - parallel event gateway to start a process (single circle) if (s < tasks.length) return tasks[s]; return "NotAllowed"; // error } // tweak the size of some of the gateway icons function nodeGatewaySymbolSizeConverter(s) { var size = new go.Size(GatewayNodeSymbolSize, GatewayNodeSymbolSize); if (s === 4) { size.width = size.width / 4 * 3; size.height = size.height / 4 * 3; } else if (s > 4) { size.width = size.width / 1.6; size.height = size.height / 1.6; } return size; } function nodePalGatewaySymbolSizeConverter(s) { var size = nodeGatewaySymbolSizeConverter(s); size.width = size.width / 2; size.height = size.height / 2; return size; } var gatewayNodeTemplate = $(go.Node, "Vertical", { locationObjectName: "SHAPE", locationSpot: go.Spot.Center, toolTip: tooltiptemplate }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), // move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts new go.Binding("layerName", "isSelected", function(s) { return s ? "Foreground" : ""; }).ofObject(), // can be resided according to the user's desires { resizable: false, resizeObjectName: "SHAPE" }, $(go.Panel, "Spot", $(go.Shape, "Diamond", { strokeWidth: 1, fill: GatewayNodeFill, stroke: GatewayNodeStroke, name: "SHAPE", desiredSize: new go.Size(GatewayNodeSize, GatewayNodeSize), portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer", fromSpot: go.Spot.NotLeftSide, toSpot: go.Spot.MiddleLeft }, new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)), // end main shape $(go.Shape, "NotAllowed", { alignment: go.Spot.Center, stroke: GatewayNodeSymbolStroke, fill: GatewayNodeSymbolFill }, new go.Binding("figure", "gatewayType", nodeGatewaySymbolTypeConverter), //new go.Binding("visible", "gatewayType", function(s) { return s !== 4; }), // comment out if you want exclusive gateway to be X instead of blank. new go.Binding("strokeWidth", "gatewayType", function(s) { return (s <=4 ) ? GatewayNodeSymbolStrokeWidth : 1; }), new go.Binding("desiredSize", "gatewayType", nodeGatewaySymbolSizeConverter)), // the next 2 circles only show up for event gateway $(go.Shape, "Circle", // Outer circle { strokeWidth: 1, stroke: GatewayNodeSymbolStroke, fill: null, desiredSize: new go.Size(EventNodeSize, EventNodeSize) }, new go.Binding("visible", "gatewayType", function(s) { return s >= 5; }) // only visible for > 5 ), // end main shape $(go.Shape, "Circle", // Inner circle { alignment: go.Spot.Center, stroke: GatewayNodeSymbolStroke, desiredSize: new go.Size(EventNodeInnerSize, EventNodeInnerSize), fill: null }, new go.Binding("visible", "gatewayType", function(s) { return s === 5; }) // inner only visible for == 5 ) ), $(go.TextBlock, { alignment: go.Spot.Center, textAlign: "center", margin: 5, editable: true }, new go.Binding("text").makeTwoWay()) ); // end go.Node Vertical //-------------------------------------------------------------------------------------------------------------- var gatewayNodeTemplateForPalette = $(go.Node, "Vertical", { toolTip: tooltiptemplate, resizable: false, locationObjectName: "SHAPE", locationSpot: go.Spot.Center, resizeObjectName: "SHAPE" }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Panel, "Spot", $(go.Shape, "Diamond", { strokeWidth: 1, fill: GatewayNodeFill, stroke: GatewayNodeStroke, name: "SHAPE", desiredSize: new go.Size(GatewayNodeSize / 2, GatewayNodeSize / 2) }), $(go.Shape, "NotAllowed", { alignment: go.Spot.Center, stroke: GatewayNodeSymbolStroke, strokeWidth: GatewayNodeSymbolStrokeWidth, fill: GatewayNodeSymbolFill }, new go.Binding("figure", "gatewayType", nodeGatewaySymbolTypeConverter), //new go.Binding("visible", "gatewayType", function(s) { return s !== 4; }), // comment out if you want exclusive gateway to be X instead of blank. new go.Binding("strokeWidth", "gatewayType", function(s) { return (s <=4 ) ? GatewayNodeSymbolStrokeWidth : 1; }), new go.Binding("desiredSize", "gatewayType", nodePalGatewaySymbolSizeConverter)), // the next 2 circles only show up for event gateway $(go.Shape, "Circle", // Outer circle { strokeWidth: 1, stroke: GatewayNodeSymbolStroke, fill: null, desiredSize: new go.Size(EventNodeSize/2, EventNodeSize/2) }, //new go.Binding("desiredSize", "gatewayType", new go.Size(EventNodeSize/2, EventNodeSize/2)), new go.Binding("visible", "gatewayType", function(s) { return s >= 5; }) // only visible for > 5 ), // end main shape $(go.Shape, "Circle", // Inner circle { alignment: go.Spot.Center, stroke: GatewayNodeSymbolStroke, desiredSize: new go.Size(EventNodeInnerSize/2, EventNodeInnerSize/2), fill: null }, new go.Binding("visible", "gatewayType", function(s) { return s === 5; }) // inner only visible for == 5 )), $(go.TextBlock, { alignment: go.Spot.Center, textAlign: "center", margin: 5, editable: false }, new go.Binding("text")) ); //-------------------------------------------------------------------------------------------------------------- var annotationNodeTemplate = $(go.Node, "Auto", { background: GradientLightGray, locationSpot: go.Spot.Center }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, "Annotation", // A left bracket shape { portId: "", fromLinkable: true, cursor: "pointer", fromSpot: go.Spot.Left, strokeWidth: 2, stroke: "gray" }), $(go.TextBlock, { margin: 5, editable: true }, new go.Binding("text").makeTwoWay()) ); var dataObjectNodeTemplate = $(go.Node, "Vertical", { locationObjectName: "SHAPE", locationSpot: go.Spot.Center }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, "File", { name: "SHAPE", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer", fill: DataFill, desiredSize: new go.Size(EventNodeSize * 0.8, EventNodeSize)}), $(go.TextBlock, { margin: 5, editable: true }, new go.Binding("text").makeTwoWay()) ); var dataStoreNodeTemplate = $(go.Node, "Vertical", { locationObjectName: "SHAPE", locationSpot: go.Spot.Center }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, "Database", { name: "SHAPE", portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer", fill: DataFill, desiredSize: new go.Size(EventNodeSize, EventNodeSize) }), $(go.TextBlock, { margin: 5, editable: true }, new go.Binding("text").makeTwoWay()) ); //------------------------------------------ private process Node Template Map ---------------------------------------------- var privateProcessNodeTemplate = $(go.Node, "Auto", { layerName: "Background", resizable: true, resizeObjectName: "LANE" }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, "Rectangle", { fill: null }), $(go.Panel, "Table", // table with 2 cells to hold header and lane { desiredSize: new go.Size(ActivityNodeWidth * 6, ActivityNodeHeight), background: DataFill, name: "LANE", minSize: new go.Size(ActivityNodeWidth, ActivityNodeHeight * 0.667) }, new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify), $(go.TextBlock, { row: 0, column: 0, angle: 270, margin: 5, editable: true, textAlign: "center" }, new go.Binding("text").makeTwoWay()), $(go.RowColumnDefinition, { column: 1, separatorStrokeWidth: 1, separatorStroke: "black" }), $(go.Shape, "Rectangle", { row: 0, column: 1, stroke: null, fill: "transparent", portId: "", fromLinkable: true, toLinkable: true, fromSpot: go.Spot.TopBottomSides, toSpot: go.Spot.TopBottomSides, cursor: "pointer", stretch: go.GraphObject.Fill }) ) ); var privateProcessNodeTemplateForPalette = $(go.Node, "Vertical", { locationSpot: go.Spot.Center }, $(go.Shape, "Process", { fill: DataFill, desiredSize: new go.Size(GatewayNodeSize / 2, GatewayNodeSize / 4) }), $(go.TextBlock, { margin: 5, editable: true }, new go.Binding("text")) ); var poolTemplateForPalette = $(go.Group, "Vertical", { locationSpot: go.Spot.Center, computesBoundsIncludingLinks: false, isSubGraphExpanded: false }, $(go.Shape, "Process", { fill: "white", desiredSize: new go.Size(GatewayNodeSize / 2, GatewayNodeSize / 4) }), $(go.Shape, "Process", { fill: "white", desiredSize: new go.Size(GatewayNodeSize / 2, GatewayNodeSize / 4) }), $(go.TextBlock, { margin: 5, editable: true }, new go.Binding("text")) ); var swimLanesGroupTemplateForPalette = $(go.Group, "Vertical" ); // empty in the palette var subProcessGroupTemplate = $(go.Group, "Spot", { locationSpot: go.Spot.Center, locationObjectName: "PH", //locationSpot: go.Spot.Center, isSubGraphExpanded: false, memberValidation: function(group, part) { return !(part instanceof go.Group) || (part.category !== "Pool" && part.category !== "Lane"); }, mouseDrop: function(e, grp) { var ok = grp.addMembers(grp.diagram.selection, true); if (!ok) grp.diagram.currentTool.doCancel(); }, contextMenu: activityNodeMenu, itemTemplate: boundaryEventItemTemplate }, new go.Binding("itemArray", "boundaryEventArray"), new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), // move a selected part into the Foreground layer, so it isn't obscured by any non-selected parts // new go.Binding("layerName", "isSelected", function (s) { return s ? "Foreground" : ""; }).ofObject(), $(go.Panel, "Auto", $(go.Shape, "RoundedRectangle", { name: "PH", fill: SubprocessNodeFill, stroke: SubprocessNodeStroke, minSize: new go.Size(ActivityNodeWidth, ActivityNodeHeight), portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer", fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide }, new go.Binding("strokeWidth", "isCall", function(s) { return s ? ActivityNodeStrokeWidthIsCall : ActivityNodeStrokeWidth; }) ), $(go.Panel, "Vertical", { defaultAlignment: go.Spot.Left }, $(go.TextBlock, // label { margin: 3, editable: true }, new go.Binding("text", "text").makeTwoWay(), new go.Binding("alignment", "isSubGraphExpanded", function(s) { return s ? go.Spot.TopLeft : go.Spot.Center; })), // create a placeholder to represent the area where the contents of the group are $(go.Placeholder, { padding: new go.Margin(5, 5) }), makeMarkerPanel(true, 1) // sub-process, loop, parallel, sequential, ad doc and compensation markers ) // end Vertical Panel ) ); // end Group //** need this in the subprocess group template above. // $(go.Shape, "RoundedRectangle", // the inner "Transaction" rounded rectangle // { margin: 3, // stretch: go.GraphObject.Fill, // stroke: ActivityNodeStroke, // parameter1: 8, fill: null, visible: false // }, // new go.Binding("visible", "isTransaction") // ), function groupStyle() { // common settings for both Lane and Pool Groups return [ { layerName: "Background", // all pools and lanes are always behind all nodes and links background: "transparent", // can grab anywhere in bounds movable: true, // allows users to re-order by dragging copyable: false, // can't copy lanes or pools avoidable: false // don't impede AvoidsNodes routed Links }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify) ]; } // hide links between lanes when either lane is collapsed function updateCrossLaneLinks(group) { group.findExternalLinksConnected().each(function(l) { l.visible = (l.fromNode.isVisible() && l.toNode.isVisible()); }); } var laneEventMenu = // context menu for each lane $(go.Adornment, "Vertical", $("ContextMenuButton", $(go.TextBlock, "Add Lane"), // in the click event handler, the obj.part is the Adornment; its adornedObject is the port {click: function(e, obj) { addLaneEvent(obj.part.adornedObject); } }) ); // Add a lane to pool (lane parameter is lane above new lane) function addLaneEvent(lane) { myDiagram.startTransaction("addLane"); if (lane != null && lane.data.category === "Lane") { // create a new lane data object var shape = lane.findObject("SHAPE"); var size = new go.Size(shape.width, MINBREADTH); //size.height = MINBREADTH; var newlanedata = { category: "Lane", text: "New Lane", color: "white", isGroup: true, loc: go.Point.stringify(new go.Point(lane.location.x, lane.location.y + 1)), // place below selection size: go.Size.stringify(size), group: lane.data.group }; // and add it to the model myDiagram.model.addNodeData(newlanedata); } myDiagram.commitTransaction("addLane"); } var swimLanesGroupTemplate = $(go.Group, "Spot", groupStyle(), { name: "Lane", contextMenu: laneEventMenu, minLocation: new go.Point(NaN, -Infinity), // only allow vertical movement maxLocation: new go.Point(NaN, Infinity), selectionObjectName: "SHAPE", // selecting a lane causes the body of the lane to be highlit, not the label resizable: true, resizeObjectName: "SHAPE", // the custom resizeAdornmentTemplate only permits two kinds of resizing layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph { isInitial: false, // don't even do initial layout isOngoing: false, // don't invalidate layout when nodes or links are added or removed direction: 0, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }), computesBoundsAfterDrag: true, // needed to prevent recomputing Group.placeholder bounds too soon computesBoundsIncludingLinks: false, // to reduce occurrences of links going briefly outside the lane computesBoundsIncludingLocation: true, // to support empty space at top-left corner of lane handlesDragDropForMembers: true, // don't need to define handlers on member Nodes and Links mouseDrop: function(e, grp) { // dropping a copy of some Nodes and Links onto this Group adds them to this Group // don't allow drag-and-dropping a mix of regular Nodes and Groups if (!e.diagram.selection.any(function(n) { return (n instanceof go.Group && n.category !== "subprocess") || n.category === "privateProcess"; })) { var ok = grp.addMembers(grp.diagram.selection, true); if (ok) { updateCrossLaneLinks(grp); relayoutDiagram(); } else { grp.diagram.currentTool.doCancel(); } } }, subGraphExpandedChanged: function(grp) { var shp = grp.resizeObject; if (grp.diagram.undoManager.isUndoingRedoing) return; if (grp.isSubGraphExpanded) { shp.height = grp._savedBreadth; } else { grp._savedBreadth = shp.height; shp.height = NaN; } updateCrossLaneLinks(grp); } }, //new go.Binding("isSubGraphExpanded", "expanded").makeTwoWay(), $(go.Shape, "Rectangle", // this is the resized object {name: "SHAPE", fill: "white", stroke: null }, // need stroke null here or you gray out some of pool border. new go.Binding("fill", "color"), new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify)), // the lane header consisting of a Shape and a TextBlock $(go.Panel, "Horizontal", { name: "HEADER", angle: 270, // maybe rotate the header to read sideways going up alignment: go.Spot.LeftCenter, alignmentFocus: go.Spot.LeftCenter }, $(go.TextBlock, // the lane label {editable: true, margin: new go.Margin(2, 0, 0, 8) }, new go.Binding("visible", "isSubGraphExpanded").ofObject(), new go.Binding("text", "text").makeTwoWay()), $("SubGraphExpanderButton", { margin: 4, angle: -270 }) // but this remains always visible! ), // end Horizontal Panel $(go.Placeholder, { padding: 12, alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.TopLeft }), $(go.Panel, "Horizontal", { alignment: go.Spot.TopLeft, alignmentFocus: go.Spot.TopLeft }, $(go.TextBlock, // this TextBlock is only seen when the swimlane is collapsed { name: "LABEL", editable: true, visible: false, angle: 0, margin: new go.Margin(6, 0, 0, 20) }, new go.Binding("visible", "isSubGraphExpanded", function(e) { return !e; }).ofObject(), new go.Binding("text", "text").makeTwoWay()) ) ); // end swimLanesGroupTemplate // define a custom resize adornment that has two resize handles if the group is expanded //myDiagram.groupTemplate.resizeAdornmentTemplate = swimLanesGroupTemplate.resizeAdornmentTemplate = $(go.Adornment, "Spot", $(go.Placeholder), $(go.Shape, // for changing the length of a lane { alignment: go.Spot.Right, desiredSize: new go.Size(7, 50), fill: "lightblue", stroke: "dodgerblue", cursor: "col-resize" }, new go.Binding("visible", "", function(ad) { if (ad.adornedPart === null) return false; return ad.adornedPart.isSubGraphExpanded; }).ofObject()), $(go.Shape, // for changing the breadth of a lane { alignment: go.Spot.Bottom, desiredSize: new go.Size(50, 7), fill: "lightblue", stroke: "dodgerblue", cursor: "row-resize" }, new go.Binding("visible", "", function(ad) { if (ad.adornedPart === null) return false; return ad.adornedPart.isSubGraphExpanded; }).ofObject()) ); var poolGroupTemplate = $(go.Group, "Auto", groupStyle(), { computesBoundsIncludingLinks: false, // use a simple layout that ignores links to stack the "lane" Groups on top of each other layout: $(PoolLayout, { spacing: new go.Size(0, 0) }) // no space between lanes }, new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify), $(go.Shape, { fill: "white" }, new go.Binding("fill", "color")), $(go.Panel, "Table", { defaultColumnSeparatorStroke: "black" }, $(go.Panel, "Horizontal", { column: 0, angle: 270 }, $(go.TextBlock, { editable: true, margin: new go.Margin(5, 0, 5, 0) }, // margin matches private process (black box pool) new go.Binding("text").makeTwoWay()) ), $(go.Placeholder, { background: "darkgray", column: 1 }) ) ); // end poolGroupTemplate //------------------------------------------ Template Maps ---------------------------------------------- // create the nodeTemplateMap, holding main view node templates: var nodeTemplateMap = new go.Map("string", go.Node); // for each of the node categories, specify which template to use nodeTemplateMap.add("activity", activityNodeTemplate); nodeTemplateMap.add("event", eventNodeTemplate); nodeTemplateMap.add("gateway", gatewayNodeTemplate); nodeTemplateMap.add("annotation", annotationNodeTemplate); nodeTemplateMap.add("dataobject", dataObjectNodeTemplate); nodeTemplateMap.add("datastore", dataStoreNodeTemplate); nodeTemplateMap.add("privateProcess", privateProcessNodeTemplate); // for the default category, "", use the same template that Diagrams use by default // this just shows the key value as a simple TextBlock var groupTemplateMap = new go.Map("string", go.Group); groupTemplateMap.add("subprocess", subProcessGroupTemplate); groupTemplateMap.add("Lane", swimLanesGroupTemplate); groupTemplateMap.add("Pool", poolGroupTemplate); // create the nodeTemplateMap, holding special palette "mini" node templates: var palNodeTemplateMap = new go.Map("string", go.Node); palNodeTemplateMap.add("activity", activityNodeTemplateForPalette); palNodeTemplateMap.add("event", eventNodeTemplate); palNodeTemplateMap.add("gateway", gatewayNodeTemplateForPalette); palNodeTemplateMap.add("annotation", annotationNodeTemplate); palNodeTemplateMap.add("dataobject", dataObjectNodeTemplate); palNodeTemplateMap.add("datastore", dataStoreNodeTemplate); palNodeTemplateMap.add("privateProcess", privateProcessNodeTemplateForPalette); var palGroupTemplateMap = new go.Map("string", go.Group); palGroupTemplateMap.add("subprocess", subProcessGroupTemplateForPalette); palGroupTemplateMap.add("Pool", poolTemplateForPalette); palGroupTemplateMap.add("Lane", swimLanesGroupTemplateForPalette); //------------------------------------------ Link Templates ---------------------------------------------- var sequenceLinkTemplate = $(go.Link, { contextMenu: $(go.Adornment, "Vertical", $("ContextMenuButton", $(go.TextBlock, "Default Flow"), // in the click event handler, the obj.part is the Adornment; its adornedObject is the port { click: function(e, obj) { setSequenceLinkDefaultFlow(obj.part.adornedObject); } }), $("ContextMenuButton", $(go.TextBlock, "Conditional Flow"), // in the click event handler, the obj.part is the Adornment; its adornedObject is the port { click: function(e, obj) { setSequenceLinkConditionalFlow(obj.part.adornedObject); } }) ), routing: go.Link.AvoidsNodes, curve: go.Link.JumpGap, corner: 10, //fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide, reshapable: true, relinkableFrom: true, relinkableTo: true, toEndSegmentLength: 20 }, new go.Binding("points").makeTwoWay(), $(go.Shape, { stroke: "black", strokeWidth: 1 }), $(go.Shape, { toArrow: "Triangle", scale: 1.2, fill: "black", stroke: null }), $(go.Shape, { fromArrow: "", scale: 1.5, stroke: "black", fill: "white" }, new go.Binding("fromArrow", "isDefault", function(s) { if (s === null) return ""; return s ? "BackSlash" : "StretchedDiamond"; }), new go.Binding("segmentOffset", "isDefault", function(s) { return s ? new go.Point(5, 0) : new go.Point(0, 0); })), $(go.TextBlock, { // this is a Link label name: "Label", editable: true, text: "label", segmentOffset: new go.Point(-10, -10), visible: false }, new go.Binding("text", "text").makeTwoWay(), new go.Binding("visible", "visible").makeTwoWay()) ); // set Default Sequence Flow (backslash From Arrow) function setSequenceLinkDefaultFlow(obj) { myDiagram.startTransaction("setSequenceLinkDefaultFlow"); var model = myDiagram.model; model.setDataProperty(obj.data, "isDefault", true); // Set all other links from the fromNode to be isDefault=null obj.fromNode.findLinksOutOf().each(function(link) { if (link !== obj && link.data.isDefault) { model.setDataProperty(link.data, "isDefault", null); } }); myDiagram.commitTransaction("setSequenceLinkDefaultFlow"); } // set Conditional Sequence Flow (diamond From Arrow) function setSequenceLinkConditionalFlow(obj) { myDiagram.startTransaction("setSequenceLinkConditionalFlow"); var model = myDiagram.model; model.setDataProperty(obj.data, "isDefault", false); myDiagram.commitTransaction("setSequenceLinkConditionalFlow"); } var messageFlowLinkTemplate = $(PoolLink, // defined in BPMNClasses.js { routing: go.Link.Orthogonal, curve: go.Link.JumpGap, corner: 10, fromSpot: go.Spot.TopBottomSides , toSpot: go.Spot.TopBottomSides, reshapable: true, relinkableTo: true, toEndSegmentLength: 20 }, new go.Binding("points").makeTwoWay(), $(go.Shape, { stroke: "black", strokeWidth: 1, strokeDashArray: [6, 2] }), $(go.Shape, { toArrow: "Triangle", scale: 1, fill: "white", stroke: "black" }), $(go.Shape, { fromArrow: "Circle", scale: 1, visible: true, stroke: "black", fill: "white" }), $(go.TextBlock, { editable: true, text: "label" }, // Link label new go.Binding("text", "text").makeTwoWay()) ); var dataAssociationLinkTemplate = $(go.Link, { routing: go.Link.AvoidsNodes, curve: go.Link.JumpGap, corner: 10, fromSpot: go.Spot.AllSides , toSpot: go.Spot.AllSides, reshapable: true, relinkableFrom: true, relinkableTo: true }, new go.Binding("points").makeTwoWay(), $(go.Shape, { stroke: "black", strokeWidth: 1, strokeDashArray: [1, 3] }), $(go.Shape, { toArrow: "OpenTriangle", scale: 1, fill: null, stroke: "blue" }) ); var annotationAssociationLinkTemplate = $(go.Link, { reshapable: true, relinkableFrom: true, relinkableTo: true, toSpot: go.Spot.AllSides, toEndSegmentLength: 20, fromEndSegmentLength: 40 }, new go.Binding("points").makeTwoWay(), $(go.Shape, { stroke: "black", strokeWidth: 1, strokeDashArray: [1, 3] }), $(go.Shape, { toArrow: "OpenTriangle", scale: 1, stroke: "black" }) ); var linkTemplateMap = new go.Map("string", go.Link); linkTemplateMap.add("msg", messageFlowLinkTemplate); linkTemplateMap.add("annotation", annotationAssociationLinkTemplate); linkTemplateMap.add("data", dataAssociationLinkTemplate); linkTemplateMap.add("", sequenceLinkTemplate); // default //------------------------------------------the main Diagram---------------------------------------------- window.myDiagram = $(go.Diagram, "myDiagramDiv", { nodeTemplateMap: nodeTemplateMap, linkTemplateMap: linkTemplateMap, groupTemplateMap: groupTemplateMap, allowDrop: true, // accept drops from palette commandHandler: new DrawCommandHandler(), // defined in DrawCommandHandler.js // default to having arrow keys move selected nodes "commandHandler.arrowKeyBehavior": "move", mouseDrop: function(e) { // when the selection is dropped in the diagram's background, // make sure the selected Parts no longer belong to any Group var ok = myDiagram.commandHandler.addTopLevelParts(myDiagram.selection, true); if (!ok) myDiagram.currentTool.doCancel(); }, linkingTool: new BPMNLinkingTool(), // defined in BPMNClasses.js "SelectionMoved": relayoutDiagram, // defined below "SelectionCopied": relayoutDiagram }); myDiagram.toolManager.mouseDownTools.insertAt(0, new LaneResizingTool()); myDiagram.addDiagramListener("LinkDrawn", function(e) { if (e.subject.fromNode.category === "annotation") { e.subject.category = "annotation"; // annotation association } else if (e.subject.fromNode.category === "dataobject" || e.subject.toNode.category === "dataobject") { e.subject.category = "data"; // data association } else if (e.subject.fromNode.category === "datastore" || e.subject.toNode.category === "datastore") { e.subject.category = "data"; // data association } }); // uncomment this if you want a subprocess to expand on drop. We decided we didn't like this behavior // myDiagram.addDiagramListener("ExternalObjectsDropped", function(e) { // // e.subject is the collection that was just dropped // e.subject.each(function(part) { // if (part instanceof go.Node && part.data.item === "end") { // part.move(new go.Point(part.location.x + 350, part.location.y)) // } // }); // myDiagram.commandHandler.expandSubGraph(); // }); // change the title to indicate that the diagram has been modified myDiagram.addDiagramListener("Modified", function(e) { var currentFile = document.getElementById("currentFile"); var idx = currentFile.textContent.indexOf("*"); if (myDiagram.isModified) { if (idx < 0) currentFile.textContent = currentFile.textContent + "*"; } else { if (idx >= 0) currentFile.textContent = currentFile.textContent.substr(0, idx); } }); //------------------------------------------ Palette ---------------------------------------------- // Make sure the pipes are ordered by their key in the palette inventory function keyCompare(a, b) { var at = a.data.key; var bt = b.data.key; if (at < bt) return -1; if (at > bt) return 1; return 0; } // initialize the first Palette, BPMN Spec Level 1 var myPaletteLevel1 = $(go.Palette, "myPaletteLevel1", { // share the templates with the main Diagram nodeTemplateMap: palNodeTemplateMap, groupTemplateMap: palGroupTemplateMap, layout: $(go.GridLayout, {cellSize: new go.Size(1, 1), spacing: new go.Size(5, 5), comparer: keyCompare }) }); // initialize the second Palette, BPMN Spec Level 2 var myPaletteLevel2 = $(go.Palette, "myPaletteLevel2", { // share the templates with the main Diagram nodeTemplateMap: palNodeTemplateMap, groupTemplateMap: palGroupTemplateMap, layout: $(go.GridLayout, {cellSize: new go.Size(1, 1), spacing: new go.Size(5, 5), comparer: keyCompare }) }); // initialize the third Palette, random other stuff var myPaletteLevel3 = $(go.Palette, "myPaletteLevel3", { // share the templates with the main Diagram nodeTemplateMap: palNodeTemplateMap, groupTemplateMap: palGroupTemplateMap, layout: $(go.GridLayout, {cellSize: new go.Size(1, 1), spacing: new go.Size(5, 5), comparer: keyCompare }) }); jQuery("#accordion").accordion({ activate: function(event, ui) { myPaletteLevel1.requestUpdate(); myPaletteLevel2.requestUpdate(); } }); myPaletteLevel1.model = $(go.GraphLinksModel, { copiesArrays: true, copiesArrayObjects: true, nodeDataArray: [ // -------------------------- Event Nodes { key: 101, category: "event", text: "Start", eventType: 1, eventDimension: 1, item: "start"}, { key: 102, category: "event", text: "Message", eventType: 2, eventDimension: 2, item: "Message"}, // BpmnTaskMessage { key: 103, category: "event", text: "Timer", eventType: 3, eventDimension: 3, item: "Timer"}, { key: 104, category: "event", text: "End", eventType: 1, eventDimension: 8, item: "End"}, { key: 107, category: "event", text: "Message", eventType: 2, eventDimension: 8, item: "Message"},// BpmnTaskMessage { key: 108, category: "event", text: "Terminate", eventType: 13, eventDimension: 8, item: "Terminate"}, // -------------------------- Task/Activity Nodes { key: 131, category: "activity", text: "Task", item: "generic task", taskType: 0}, { key: 132, category: "activity", text: "User Task", item: "User task", taskType: 2}, { key: 133, category: "activity", text: "Service\nTask", item: "service task", taskType: 6}, // subprocess and start and end { key: 134, category: "subprocess", loc: "0 0", text: "Subprocess", isGroup: true, isSubProcess: true, taskType: 0 }, { key: -802, category: "event", loc: "0 0", group: 134, text: "Start", eventType: 1, eventDimension: 1, item: "start" }, { key: -803, category: "event", loc: "350 0", group: 134, text: "End", eventType: 1, eventDimension: 8, item: "end", name: "end" }, // -------------------------- Gateway Nodes, Data, Pool and Annotation { key: 201, category: "gateway", text: "Parallel", gatewayType: 1}, { key: 204, category: "gateway", text: "Exclusive", gatewayType: 4}, { key: 301, category: "dataobject", text: "Data\nObject"}, { key: 302, category: "datastore", text: "Data\nStorage"}, { key: 401, category: "privateProcess", text: "Black Box"}, { key: "501", "text": "Pool 1", "isGroup": "true", "category": "Pool" }, { key: "Lane5", "text": "Lane 1", "isGroup": "true", "group": "501", "color": "lightyellow", "category": "Lane" }, { key: "Lane6", "text": "Lane 2", "isGroup": "true", "group": "501", "color": "lightgreen", "category": "Lane" }, { key: 701, category: "annotation", text: "note"} ] // end nodeDataArray }); // end model // an activity with a boundary event: // { // key: 1, // category: "activity", // text: "Message", // taskType: 1, // item: "Message Task", // boundaryEventArray: [{ "portId": "be0", alignmentIndex: 0, eventType: 2, color: "white" }] // portId # and alignmentIndex should match // }, myPaletteLevel2.model = $(go.GraphLinksModel, { copiesArrays: true, copiesArrayObjects: true, nodeDataArray: [ { key: 1, category: "activity", taskType: 1, text: "Receive Task", item: "Receive Task" }, { key: 2, category: "activity", taskType: 5, text: "Send Task", item: "Send Task" }, { key: 3, category: "activity", taskType: 7, text: "Business\nRule Task", item: "Business Rule Task" }, { key: 4, category: "activity", taskType: 2, text: "User Task", item: "User Task", isCall: true}, { key: 101, text: "Adhoc\nSubprocess", isGroup: true, isSubProcess: true, category: "subprocess", isAdHoc: true, taskType: 0, loc: "0 0" }, { key: -812, group: 101, category: "event", text: "Start", eventType: 1, eventDimension: 1, item: "start", loc: "0 0" }, { key: -813, group: 101, category: "event", text: "End", eventType: 1, eventDimension: 8, item: "end", name: "end" }, { key: 102, text: "Transactional\nSubprocess", isGroup: true, isSubProcess: true, category: "subprocess", isTransaction: true, taskType: 0, loc: "0 0" }, { key: -822, group: 102, category: "event", text: "Start", eventType: 1, eventDimension: 1, item: "start", loc: "0 0" }, { key: -823, group: 102, category: "event", text: "End", eventType: 1, eventDimension: 8, item: "end", name: "end", loc: "350 0" }, { key: 103, text: "Looping\nActivity", isGroup: true, isLoop: true, isSubProcess: true, category: "subprocess", taskType: 0, loc: "0 0" }, { key: -831, group: 103, category: "event", text: "Start", eventType: 1, eventDimension: 1, item: "start", loc: "0 0" }, { key: -832, group: 103, category: "event", text: "End", eventType: 1, eventDimension: 8, item: "end", name: "end", loc: "350 0" }, { key: 104, text: "Multi-Instance\nActivity", isGroup: true, isSubProcess: true, isParallel: true, category: "subprocess", taskType: 0, loc: "0 0" }, { key: -841, group: 104, category: "event", text: "Start", eventType: 1, eventDimension: 1, item: "start", loc: "0 0" }, { key: -842, group: 104, category: "event", text: "End", eventType: 1, eventDimension: 8, item: "end", name: "end", loc: "350 0" }, { key: 105, text: "Call\nSubprocess", isGroup: true, isSubProcess: true, category: "subprocess", isCall: true, taskType: 0, loc: "0 0" }, { key: -861, group: 105, category: "event", text: "Start", eventType: 1, eventDimension: 1, item: "start", loc: "0 0" }, { key: -862, group: 105, category: "event", text: "End", eventType: 1, eventDimension: 8, item: "end", name: "end", loc: "350 0" }, // gateway nodes { key: 301, category: "gateway", gatewayType: 2, text: "Inclusive" }, { key: 302, category: "gateway", gatewayType: 5, text: "Event\nGateway" }, // events { key: 401, category: "event", eventType: 5, eventDimension: 1, text: "Conditional\nStart", item: "BpmnEventConditional" }, { key: 402, category: "event", eventType: 10, eventDimension: 1, text: "Signal\nStart", item: "BpmnEventSignal" }, // start signal { key: 403, category: "event", eventType: 10, eventDimension: 8, text: "Signal\nEnd", item: "end signal" }, { key: 404, category: "event", eventType: 7, eventDimension: 8, text: "Error", item: "BpmnEventError" }, { key: 405, category: "event", eventType: 4, eventDimension: 8, text: "Escalation", item: "BpmnEventEscalation" }, // throwing / catching intermedicate events { key: 502, category: "event", eventType: 6, eventDimension: 4, text: "Catch\nLink", item: "BpmnEventOffPage" }, { key: 503, category: "event", eventType: 6, eventDimension: 7, text: "Throw\nLink", item: "BpmnEventOffPage" }, { key: 504, category: "event", eventType: 2, eventDimension: 4, text: "Catch\nMessage", item: "Message"}, { key: 505, category: "event", eventType: 2, eventDimension: 7, text: "Throw\nMessage", item: "Message"}, { key: 506, category: "event", eventType: 5, eventDimension: 4, text: "Catch\nConditional", item: ""}, { key: 507, category: "event", eventType: 3, eventDimension: 4, text: "Catch\nTimer", item: ""}, { key: 508, category: "event", eventType: 4, eventDimension: 7, text: "Throw\nEscalation", item: "Escalation"}, { key: 509, category: "event", eventType: 10, eventDimension: 4, text: "Catch\nSignal", item: "" }, { key: 510, category: "event", eventType: 10, eventDimension: 7, text: "Throw\nSignal", item: "" } ] // end nodeDataArray }); // end model myPaletteLevel3.model = $(go.GraphLinksModel, { copiesArrays: true, copiesArrayObjects: true, nodeDataArray: [ { key: 108, category: "event", eventType: 8, eventDimension: 5, text: "Cancel", item: "BpmnEventCancel" }, { key: 109, category: "event", eventType: 9, eventDimension: 5, text: "Compensation", item: "BpmnEventCompensation"}, { key: 111, category: "event", eventType: 11, eventDimension: 1, text: "Multiple", item: "Multiple" }, { key: 112, category: "event", eventType: 12, eventDimension: 1, text: "Parallel", item: "Parallel" }, // activity nodes { key: 203, category: "activity", taskType: 3, isAdHoc: true, text: "Manual", item: "Manual Task" }, { key: 204, category: "activity", taskType: 4, isSequential: true, text: "Script", item: "Script Task" }, { key: 205, category: "activity", taskType: 5, isParallel: true, text: "Send Msg", item: "Send Msg Task" }, { key: 206, category: "activity", taskType: 6, isLoop: true, isSubProcess: true, isTransaction: true, text: "Service", item: "service task" }, // gateway nodes not in Level 1 or Level 2 { key: 603, category: "gateway", text: "Complex", gatewayType: 3 }, { key: 606, category: "gateway", text: "Exclusive Start", gatewayType: 6 }, { key: 607, category: "gateway", text: "Parallel Start", gatewayType: 7 }, { key: 4, category: "activity", taskType: 2, text: "User Task", item: "User Task", isCall: true, isLoop: true, isParallel: true, isSequential: true } ] // end nodeDataArray }); // end model //------------------------------------------ Overview ---------------------------------------------- var myOverview = $(go.Overview, "myOverviewDiv", { observed: myDiagram, maxScale: 0.5, contentAlignment: go.Spot.Center }); // change color of viewport border in Overview myOverview.box.elt(0).stroke = "dodgerblue"; // start with a simple preset model: loadJSON("BPMNdata/OMG BPMN by Example Figure 5.1.json") } // end init //------------------------------------------ pools / lanes ---------------------------------------------- // swimlanes var MINLENGTH = 400; // this controls the minimum length of any swimlane var MINBREADTH = 20; // this controls the minimum breadth of any non-collapsed swimlane // some shared functions // this is called after nodes have been moved or lanes resized, to layout all of the Pool Groups again function relayoutDiagram() { myDiagram.layout.invalidateLayout(); myDiagram.findTopLevelGroups().each(function(g) { if (g.category === "Pool") g.layout.invalidateLayout(); }); myDiagram.layoutDiagram(); } // compute the minimum size of a Pool Group needed to hold all of the Lane Groups function computeMinPoolSize(pool) { // assert(pool instanceof go.Group && pool.category === "Pool"); var len = MINLENGTH; pool.memberParts.each(function(lane) { // pools ought to only contain lanes, not plain Nodes if (!(lane instanceof go.Group)) return; var holder = lane.placeholder; if (holder !== null) { var sz = holder.actualBounds; len = Math.max(len, sz.width); } }); return new go.Size(len, NaN); } // compute the minimum size for a particular Lane Group function computeLaneSize(lane) { // assert(lane instanceof go.Group && lane.category !== "Pool"); var sz = computeMinLaneSize(lane); if (lane.isSubGraphExpanded) { var holder = lane.placeholder; if (holder !== null) { var hsz = holder.actualBounds; sz.height = Math.max(sz.height, hsz.height); } } // minimum breadth needs to be big enough to hold the header var hdr = lane.findObject("HEADER"); if (hdr !== null) sz.height = Math.max(sz.height, hdr.actualBounds.height); return sz; } // determine the minimum size of a Lane Group, even if collapsed function computeMinLaneSize(lane) { if (!lane.isSubGraphExpanded) return new go.Size(MINLENGTH, 1); return new go.Size(MINLENGTH, MINBREADTH); } // define a custom ResizingTool to limit how far one can shrink a lane Group function LaneResizingTool() { go.ResizingTool.call(this); } go.Diagram.inherit(LaneResizingTool, go.ResizingTool); LaneResizingTool.prototype.isLengthening = function() { return (this.handle.alignment === go.Spot.Right); }; /** @override */ LaneResizingTool.prototype.computeMinSize = function() { var lane = this.adornedObject.part; // assert(lane instanceof go.Group && lane.category !== "Pool"); var msz = computeMinLaneSize(lane); // get the absolute minimum size if (this.isLengthening()) { // compute the minimum length of all lanes var sz = computeMinPoolSize(lane.containingGroup); msz.width = Math.max(msz.width, sz.width); } else { // find the minimum size of this single lane var sz = computeLaneSize(lane); msz.width = Math.max(msz.width, sz.width); msz.height = Math.max(msz.height, sz.height); } return msz; }; /** @override */ LaneResizingTool.prototype.canStart = function() { if (!go.ResizingTool.prototype.canStart.call(this)) return false; // if this is a resize handle for a "Lane", we can start. var diagram = this.diagram; if (diagram === null) return; var handl = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name); if (handl === null || handl.part === null || handl.part.adornedObject === null || handl.part.adornedObject.part === null) return false; return (handl.part.adornedObject.part.category === "Lane"); } /** @override */ LaneResizingTool.prototype.resize = function(newr) { var lane = this.adornedObject.part; if (this.isLengthening()) { // changing the length of all of the lanes lane.containingGroup.memberParts.each(function(lane) { if (!(lane instanceof go.Group)) return; var shape = lane.resizeObject; if (shape !== null) { // set its desiredSize length, but leave each breadth alone shape.width = newr.width; } }); } else { // changing the breadth of a single lane go.ResizingTool.prototype.resize.call(this, newr); } relayoutDiagram(); // now that the lane has changed size, layout the pool again }; // end LaneResizingTool class // define a custom grid layout that makes sure the length of each lane is the same // and that each lane is broad enough to hold its subgraph function PoolLayout() { go.GridLayout.call(this); this.cellSize = new go.Size(1, 1); this.wrappingColumn = 1; this.wrappingWidth = Infinity; this.isRealtime = false; // don't continuously layout while dragging this.alignment = go.GridLayout.Position; // This sorts based on the location of each Group. // This is useful when Groups can be moved up and down in order to change their order. this.comparer = function(a, b) { var ay = a.location.y; var by = b.location.y; if (isNaN(ay) || isNaN(by)) return 0; if (ay < by) return -1; if (ay > by) return 1; return 0; }; } go.Diagram.inherit(PoolLayout, go.GridLayout); /** @override */ PoolLayout.prototype.doLayout = function(coll) { var diagram = this.diagram; if (diagram === null) return; diagram.startTransaction("PoolLayout"); var pool = this.group; if (pool !== null && pool.category === "Pool") { // make sure all of the Group Shapes are big enough var minsize = computeMinPoolSize(pool); pool.memberParts.each(function(lane) { if (!(lane instanceof go.Group)) return; if (lane.category !== "Pool") { var shape = lane.resizeObject; if (shape !== null) { // change the desiredSize to be big enough in both directions var sz = computeLaneSize(lane); shape.width = (isNaN(shape.width) ? minsize.width : Math.max(shape.width, minsize.width)); shape.height = (!isNaN(shape.height)) ? Math.max(shape.height, sz.height) : sz.height; var cell = lane.resizeCellSize; if (!isNaN(shape.width) && !isNaN(cell.width) && cell.width > 0) shape.width = Math.ceil(shape.width / cell.width) * cell.width; if (!isNaN(shape.height) && !isNaN(cell.height) && cell.height > 0) shape.height = Math.ceil(shape.height / cell.height) * cell.height; } } }); } // now do all of the usual stuff, according to whatever properties have been set on this GridLayout go.GridLayout.prototype.doLayout.call(this, coll); diagram.commitTransaction("PoolLayout"); }; // end PoolLayout class //------------------------------------------ Commands for this application ---------------------------------------------- // Add a port to the specified side of the selected nodes. name is beN (be0, be1) // evDim is 5 for Interrupting, 6 for non-Interrupting function addActivityNodeBoundaryEvent(evType, evDim) { myDiagram.startTransaction("addBoundaryEvent"); myDiagram.selection.each(function(node) { // skip any selected Links if (!(node instanceof go.Node)) return; if (node.data && (node.data.category === "activity" || node.data.category === "subprocess")) { // compute the next available index number for the side var i = 0; var defaultPort = node.findPort(""); while (node.findPort("be" + i.toString()) !== defaultPort) i++; // now this new port name is unique within the whole Node because of the side prefix var name = "be" + i.toString(); if (!node.data.boundaryEventArray) {myDiagram.model.setDataProperty(node.data, "boundaryEventArray", []);} // initialize the Array of port data if necessary // create a new port data object var newportdata = { portId: name, eventType: evType, eventDimension: evDim, color: "white", alignmentIndex: i // if you add port data properties here, you should copy them in copyPortData above ** BUG... we don't do that. }; // and add it to the Array of port data myDiagram.model.insertArrayItem(node.data.boundaryEventArray, -1, newportdata); } }); myDiagram.commitTransaction("addBoundaryEvent"); } // changes the item of the object function rename(obj) { myDiagram.startTransaction("rename"); var newName = prompt("Rename " + obj.part.data.item + " to:"); myDiagram.model.setDataProperty(obj.part.data, "item", newName); myDiagram.commitTransaction("rename"); } // shows/hides gridlines // to be implemented onclick of a button function updateGridOption() { myDiagram.startTransaction("grid"); var grid = document.getElementById("grid"); myDiagram.grid.visible = grid.checked; myDiagram.commitTransaction("grid"); } // enables/disables snapping tools, to be implemented by buttons function updateSnapOption() { // no transaction needed, because we are modifying tools for future use var snap = document.getElementById("snap"); if (snap.checked) { myDiagram.toolManager.draggingTool.isGridSnapEnabled = true; myDiagram.toolManager.resizingTool.isGridSnapEnabled = true; } else { myDiagram.toolManager.draggingTool.isGridSnapEnabled = false; myDiagram.toolManager.resizingTool.isGridSnapEnabled = false; } } // user specifies the amount of space between nodes when making rows and column function askSpace() { var space = prompt("Desired space between nodes (in pixels):", "0"); return space; } var UnsavedFileName = "(Unsaved File)"; function getCurrentFileName() { var currentFile = document.getElementById("currentFile"); var name = currentFile.textContent; if (name[name.length - 1] === "*") return name.substr(0, name.length - 1); return name; } function setCurrentFileName(name) { var currentFile = document.getElementById("currentFile"); if (myDiagram.isModified) { name += "*"; } currentFile.textContent = name; } function newDocument() { // checks to see if all changes have been saved if (myDiagram.isModified) { var save = confirm("Would you like to save changes to " + getCurrentFileName() + "?"); if (save) { saveDocument(); } } setCurrentFileName(UnsavedFileName); // loads an empty diagram myDiagram.model = new go.GraphLinksModel(); resetModel(); } function resetModel() { myDiagram.model.undoManager.isEnabled = true; myDiagram.model.linkFromPortIdProperty = "fromPort"; myDiagram.model.linkToPortIdProperty = "toPort"; myDiagram.model.copiesArrays = true; myDiagram.model.copiesArrayObjects = true; myDiagram.isModified = false; } function checkLocalStorage() { return (typeof (Storage) !== "undefined") && (window.localStorage !== undefined); } // saves the current floor plan to local storage function saveDocument() { if (checkLocalStorage()) { var saveName = getCurrentFileName(); if (saveName === UnsavedFileName) { saveDocumentAs(); } else { saveDiagramProperties() window.localStorage.setItem(saveName, myDiagram.model.toJson()); myDiagram.isModified = false; } } } // saves floor plan to local storage with a new name function saveDocumentAs() { if (checkLocalStorage()) { var saveName = prompt("Save file as...", getCurrentFileName()); if (saveName && saveName !== UnsavedFileName) { setCurrentFileName(saveName); saveDiagramProperties() window.localStorage.setItem(saveName, myDiagram.model.toJson()); myDiagram.isModified = false; } } } // checks to see if all changes have been saved -> shows the open HTML element function openDocument() { if (checkLocalStorage()) { if (myDiagram.isModified) { var save = confirm("Would you like to save changes to " + getCurrentFileName() + "?"); if (save) { saveDocument(); } } openElement("openDocument", "mySavedFiles"); } } // shows the remove HTML element function removeDocument() { if (checkLocalStorage()) { openElement("removeDocument", "mySavedFiles2"); } } // these functions are called when panel buttons are clicked function loadFile() { var listbox = document.getElementById("mySavedFiles"); // get selected filename var fileName = undefined; for (var i = 0; i < listbox.options.length; i++) { if (listbox.options[i].selected) fileName = listbox.options[i].text; // selected file } if (fileName !== undefined) { // changes the text of "currentFile" to be the same as the floor plan now loaded setCurrentFileName(fileName); // actually load the model from the JSON format string var savedFile = window.localStorage.getItem(fileName); myDiagram.model = go.Model.fromJson(savedFile); loadDiagramProperties(); myDiagram.model.undoManager.isEnabled = true; myDiagram.isModified = false; // eventually loadDiagramProperties will be called to finish // restoring shared saved model/diagram properties } closeElement("openDocument"); } function loadJSON(file) { jQuery.getJSON(file, function (jsondata) { // set these kinds of Diagram properties after initialization, not now myDiagram.addDiagramListener("InitialLayoutCompleted", loadDiagramProperties); // defined below // create the model from the data in the JavaScript object parsed from JSON text //myDiagram.model = new go.GraphLinksModel(jsondata["nodes"], jsondata["links"]); myDiagram.model = go.Model.fromJson(jsondata); loadDiagramProperties(); myDiagram.model.undoManager.isEnabled = true; myDiagram.isModified = false; }); } // Store shared model state in the Model.modelData property // (will be loaded by loadDiagramProperties) function saveDiagramProperties() { myDiagram.model.modelData.position = go.Point.stringify(myDiagram.position); } // Called by loadFile and loadJSON. function loadDiagramProperties(e) { // set Diagram.initialPosition, not Diagram.position, to handle initialization side-effects var pos = myDiagram.model.modelData.position; if (pos) myDiagram.initialPosition = go.Point.parse(pos); } // deletes the selected file from local storage function removeFile() { var listbox = document.getElementById("mySavedFiles2"); // get selected filename var fileName = undefined; for (var i = 0; i < listbox.options.length; i++) { if (listbox.options[i].selected) fileName = listbox.options[i].text; // selected file } if (fileName !== undefined) { // removes file from local storage window.localStorage.removeItem(fileName); // the current document remains open, even if its storage was deleted } closeElement("removeDocument"); } function updateFileList(id) { // displays cached floor plan files in the listboxes var listbox = document.getElementById(id); // remove any old listing of files var last; while (last = listbox.lastChild) listbox.removeChild(last); // now add all saved files to the listbox for (var key in window.localStorage) { var storedFile = window.localStorage.getItem(key); if (!storedFile) continue; var option = document.createElement("option"); option.value = key; option.text = key; listbox.add(option, null); } } function openElement(id, listid) { var panel = document.getElementById(id); if (panel.style.visibility === "hidden") { updateFileList(listid); panel.style.visibility = "visible"; } } // hides the open/remove elements when the "cancel" button is pressed function closeElement(id) { var panel = document.getElementById(id); if (panel.style.visibility === "visible") { panel.style.visibility = "hidden"; } }