gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
745 lines (744 loc) • 92.5 kB
JavaScript
/*
* Copyright (C) 1998-2020 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.js", "../../extensionsTS/Figures.js", "../../release/go.js", "./BPMNClasses.js"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var DrawCommandHandler_js_1 = require("../../extensionsTS/DrawCommandHandler.js");
require("../../extensionsTS/Figures.js");
var go = require("../../release/go.js");
var BPMNClasses_js_1 = require("./BPMNClasses.js");
// 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,