/* * Copyright (C) 1998-2019 by Northwoods Software Corporation. All Rights Reserved. */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); (function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "../../extensionsTS/DrawCommandHandler", "../../extensionsTS/Figures", "../../release/go", "./BPMNClasses"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var DrawCommandHandler_1 = require("../../extensionsTS/DrawCommandHandler"); require("../../extensionsTS/Figures"); var go = require("../../release/go"); var BPMNClasses_1 = require("./BPMNClasses"); // This file holds all of the JavaScript code specific to the BPMN.html page. var myDiagram; // Setup all of the Diagrams and what they need. // This is called after the page is loaded. function init() { var $ = go.GraphObject.make; // for more concise visual tree definitions // 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 openDocumentDiv = document.getElementById('openDocument'); openDocumentDiv.style.visibility = 'hidden'; // hides remove HTML Element var removeDocumentDiv = document.getElementById('removeDocument'); removeDocumentDiv.style.visibility = 'hidden'; // 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 = $('ToolTip', $(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', 'BpmnTaskScript', 'BpmnTaskMessage', 'BpmnTaskService', '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) { 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 $('ContextMenu', $('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) { if (obj === null || obj.panel === null || obj.panel.itemArray === null) return; 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 = $('ContextMenu', $('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, 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, 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', 'Circle', 'AsteriskLine', 'ThinX', 'Pentagon', 'Pentagon', '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.NotRightSide }, 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) { if (!(grp instanceof go.Group) || grp.diagram === null) return; 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() { return [ { layerName: 'Background', background: 'transparent', movable: true, copyable: false, 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 !== null && l.fromNode.isVisible() && l.toNode !== null && l.toNode.isVisible()); }); } var laneEventMenu = // context menu for each lane $('ContextMenu', $('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 ? shape.width : MINLENGTH, 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)), 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), maxLocation: new go.Point(NaN, Infinity), selectionObjectName: 'SHAPE', resizable: true, resizeObjectName: 'SHAPE', layout: $(go.LayeredDigraphLayout, // automatically lay out the lane's subgraph { isInitial: false, isOngoing: false, direction: 0, columnSpacing: 10, layeringOption: go.LayeredDigraphLayout.LayerLongestPathSource }), computesBoundsAfterDrag: true, computesBoundsIncludingLinks: false, computesBoundsIncludingLocation: true, handlesDragDropForMembers: true, mouseDrop: function (e, grp) { // 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'; })) { if (!(grp instanceof go.Group) || grp.diagram === null) return; var ok = grp.addMembers(grp.diagram.selection, true); if (ok) { updateCrossLaneLinks(grp); relayoutDiagram(); } else { grp.diagram.currentTool.doCancel(); } } }, subGraphExpandedChanged: function (grp) { if (grp.diagram === null) return; if (grp.diagram.undoManager.isUndoingRedoing) return; var shp = grp.resizeObject; 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, 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(); // 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(); 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(); 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(); palGroupTemplateMap.add('subprocess', subProcessGroupTemplateForPalette); palGroupTemplateMap.add('Pool', poolTemplateForPalette); palGroupTemplateMap.add('Lane', swimLanesGroupTemplateForPalette); // ------------------------------------------ Link Templates ---------------------------------------------- var sequenceLinkTemplate = $(go.Link, { contextMenu: $('ContextMenu', $('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, { 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 if (obj.fromNode !== 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 = $(BPMNClasses_1.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(); linkTemplateMap.add('msg', messageFlowLinkTemplate); linkTemplateMap.add('annotation', annotationAssociationLinkTemplate); linkTemplateMap.add('data', dataAssociationLinkTemplate); linkTemplateMap.add('', sequenceLinkTemplate); // default // ------------------------------------------the main Diagram---------------------------------------------- myDiagram = $(go.Diagram, 'myDiagramDiv', { nodeTemplateMap: nodeTemplateMap, linkTemplateMap: linkTemplateMap, groupTemplateMap: groupTemplateMap, commandHandler: new DrawCommandHandler_1.DrawCommandHandler(), // 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 BPMNClasses_1.BPMNLinkingTool(), relinkingTool: new BPMNClasses_1.BPMNRelinkingTool(), 'SelectionMoved': relayoutDiagram, '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', { 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', { 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', { 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' }, { 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' }, { 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' }, { 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'); // Attach to the window for console manipulation window.myDiagram = myDiagram; } // end init exports.init = 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 !== null) 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 var LaneResizingTool = /** @class */ (function (_super) { __extends(LaneResizingTool, _super); function LaneResizingTool() { return _super !== null && _super.apply(this, arguments) || this; } LaneResizingTool.prototype.isLengthening = function () { return (this.handle !== null && this.handle.alignment === go.Spot.Right); }; LaneResizingTool.prototype.computeMinSize = function () { if (this.adornedObject === null) return new go.Size(MINLENGTH, MINBREADTH); var lane = this.adornedObject.part; if (!(lane instanceof go.Group) || lane.containingGroup === null) return new go.Size(MINLENGTH, MINBREADTH); // 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; }; 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; var handl = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name); if (handl === null || handl.part === null) return false; var ad = handl.part; if (ad.adornedObject === null || ad.adornedObject.part === null) return false; return (ad.adornedObject.part.category === 'Lane'); }; LaneResizingTool.prototype.resize = function (newr) { if (this.adornedObject === null) return; var lane = this.adornedObject.part; if (lane instanceof go.Group && lane.containingGroup !== null && this.isLengthening()) { // changing the length of all of the lanes lane.containingGroup.memberParts.each(function (l) { if (!(l instanceof go.Group)) return; var shape = l.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 _super.prototype.resize.call(this, newr); } relayoutDiagram(); // now that the lane has changed size, layout the pool again }; return LaneResizingTool; }(go.ResizingTool)); // 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 var PoolLayout = /** @class */ (function (_super) { __extends(PoolLayout, _super); function PoolLayout() { var _this = _super !== null && _super.apply(this, arguments) || 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; }; return _this; } 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_1 = 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_1.width : Math.max(shape.width, minsize_1.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 _super.prototype.doLayout.call(this, coll); diagram.commitTransaction('PoolLayout'); }; return PoolLayout; }(go.GridLayout)); // 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_1 = '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_1, 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'); } exports.addActivityNodeBoundaryEvent = addActivityNodeBoundaryEvent; // changes the item of the object function rename(obj) { if (obj === null || obj.part === null || obj.part.data === null) return; myDiagram.startTransaction('rename'); var newName = prompt('Rename ' + obj.part.data.item + ' to:'); myDiagram.model.setDataProperty(obj.part.data, 'item', newName); myDiagram.commitTransaction('rename'); } exports.rename = 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'); } exports.updateGridOption = updateGridOption; // 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; } } exports.updateSnapOption = updateSnapOption; // user specifies the amount of space between nodes when making rows and column function askSpace() { var space = parseFloat(prompt('Desired space between nodes (in pixels):') || '0'); return space; } exports.askSpace = askSpace; var UnsavedFileName = '(Unsaved File)'; function getCurrentFileName() { var currentFile = document.getElementById('currentFile'); var name = currentFile.textContent || ''; if (name && name[name.length - 1] === '*') return name.substr(0, name.length - 1); return name; } exports.getCurrentFileName = getCurrentFileName; function setCurrentFileName(name) { var currentFile = document.getElementById('currentFile'); if (myDiagram.isModified) { name += '*'; } currentFile.textContent = name; } exports.setCurrentFileName = setCurrentFileName; 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(); } exports.newDocument = newDocument; 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; } exports.resetModel = resetModel; function checkLocalStorage() { return (typeof (Storage) !== 'undefined') && (window.localStorage !== undefined); } exports.checkLocalStorage = checkLocalStorage; // 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; } } } exports.saveDocument = saveDocument; // 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; } } } exports.saveDocumentAs = saveDocumentAs; // 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'); } } exports.openDocument = openDocument; // shows the remove HTML element function removeDocument() { if (checkLocalStorage()) { openElement('removeDocument', 'mySavedFiles2'); } } exports.removeDocument = removeDocument; // these functions are called when panel buttons are clicked function loadFile() { var listbox = document.getElementById('mySavedFiles'); // get selected filename var fileName; 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'); } exports.loadFile = loadFile; 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; }); } exports.loadJSON = loadJSON; // 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); } exports.saveDiagramProperties = saveDiagramProperties; // 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); } exports.loadDiagramProperties = loadDiagramProperties; // deletes the selected file from local storage function removeFile() { var listbox = document.getElementById('mySavedFiles2'); if (listbox === null) return; // get selected filename var fileName; 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'); } exports.removeFile = removeFile; function updateFileList(id) { // displays cached floor plan files in the listboxes var listbox = document.getElementById(id); if (listbox === null) return; // 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); } } exports.updateFileList = updateFileList; function openElement(id, listid) { var panel = document.getElementById(id); if (panel !== null && panel.style.visibility === 'hidden') { updateFileList(listid); panel.style.visibility = 'visible'; } } exports.openElement = openElement; // hides the open/remove elements when the "cancel" button is pressed function closeElement(id) { var panel = document.getElementById(id); if (panel !== null && panel.style.visibility === 'visible') { panel.style.visibility = 'hidden'; } } exports.closeElement = closeElement; function undo() { myDiagram.commandHandler.undo(); } exports.undo = undo; function redo() { myDiagram.commandHandler.redo(); } exports.redo = redo; function cutSelection() { myDiagram.commandHandler.cutSelection(); } exports.cutSelection = cutSelection; function copySelection() { myDiagram.commandHandler.copySelection(); } exports.copySelection = copySelection; function pasteSelection() { myDiagram.commandHandler.pasteSelection(); } exports.pasteSelection = pasteSelection; function deleteSelection() { myDiagram.commandHandler.deleteSelection(); } exports.deleteSelection = deleteSelection; function selectAll() { myDiagram.commandHandler.selectAll(); } exports.selectAll = selectAll; function alignLeft() { myDiagram.commandHandler.alignLeft(); } exports.alignLeft = alignLeft; function alignRight() { myDiagram.commandHandler.alignRight(); } exports.alignRight = alignRight; function alignTop() { myDiagram.commandHandler.alignTop(); } exports.alignTop = alignTop; function alignBottom() { myDiagram.commandHandler.alignBottom(); } exports.alignBottom = alignBottom; function alignCemterX() { myDiagram.commandHandler.alignCenterX(); } exports.alignCemterX = alignCemterX; function alignCenterY() { myDiagram.commandHandler.alignCenterY(); } exports.alignCenterY = alignCenterY; function alignRows() { myDiagram.commandHandler.alignRow(askSpace()); } exports.alignRows = alignRows; function alignColumns() { myDiagram.commandHandler.alignColumn(askSpace()); } exports.alignColumns = alignColumns; function basicOrderProcess() { loadJSON('BPMNdata/BasicOrderProcess.json'); } exports.basicOrderProcess = basicOrderProcess; function BPMNdata51() { loadJSON('BPMNdata/OMG BPMN by Example Figure 5.1.json'); } exports.BPMNdata51 = BPMNdata51; function BPMNdata52() { loadJSON('BPMNdata/OMG BPMN by Example Figure 5.2.json'); } exports.BPMNdata52 = BPMNdata52; function BPMNdata53() { loadJSON('BPMNdata/OMG BPMN by Example Figure 5.3.json'); } exports.BPMNdata53 = BPMNdata53; function cancel1() { closeElement('openDocument'); } exports.cancel1 = cancel1; function cancel2() { closeElement('removeDocument'); } exports.cancel2 = cancel2; });