cmmn-js
Version:
A cmmn 1.1 toolkit
1,236 lines (957 loc) • 30.5 kB
JavaScript
'use strict';
var inherits = require('inherits'),
isArray = require('min-dash').isArray,
isObject = require('min-dash').isObject,
assign = require('min-dash').assign;
var BaseRenderer = require('diagram-js/lib/draw/BaseRenderer').default,
TextUtil = require('diagram-js/lib/util/Text').default,
DiUtil = require('../util/DiUtil'),
ModelUtil = require('../util/ModelUtil');
var isStandardEventVisible = DiUtil.isStandardEventVisible;
var isPlanningTableCollapsed = DiUtil.isPlanningTableCollapsed;
var isCollapsed = DiUtil.isCollapsed;
var isCasePlanModel = ModelUtil.isCasePlanModel;
var getBusinessObject = ModelUtil.getBusinessObject;
var getDefinition = ModelUtil.getDefinition;
var isRequired = ModelUtil.isRequired;
var isRepeatable = ModelUtil.isRepeatable;
var isManualActivation = ModelUtil.isManualActivation;
var isAutoComplete = ModelUtil.isAutoComplete;
var hasPlanningTable = ModelUtil.hasPlanningTable;
var getName = ModelUtil.getName;
var is = ModelUtil.is;
var getStandardEvent = ModelUtil.getStandardEvent;
var domQuery = require('min-dom').query;
var svgAppend = require('tiny-svg').append,
svgAttr = require('tiny-svg').attr,
svgClasses = require('tiny-svg').classes,
svgCreate = require('tiny-svg').create;
var translate = require('diagram-js/lib/util/SvgTransformUtil').translate;
var createLine = require('diagram-js/lib/util/RenderUtil').createLine;
function CmmnRenderer(eventBus, styles, pathMap) {
BaseRenderer.call(this, eventBus);
var TASK_BORDER_RADIUS = 10;
var MILESTONE_BORDER_RADIUS = 24;
var STAGE_EDGE_OFFSET = 20;
var LABEL_STYLE = {
fontFamily: 'Arial, sans-serif',
fontSize: '12px'
};
var textUtil = new TextUtil({
style: LABEL_STYLE,
size: { width: 100 }
});
var markers = {};
function addMarker(id, element) {
markers[id] = element;
}
function marker(id) {
return markers[id];
}
function initMarkers(svg) {
function createMarker(id, options) {
var attrs = assign({
fill: 'black',
strokeWidth: 1,
strokeLinecap: 'round',
strokeDasharray: 'none'
}, options.attrs);
var ref = options.ref || { x: 0, y: 0 };
var scale = options.scale || 1;
// fix for safari / chrome / firefox bug not correctly
// resetting stroke dash array
if (attrs.strokeDasharray === 'none') {
attrs.strokeDasharray = [10000, 1];
}
var marker = svgCreate('marker');
svgAttr(options.element, attrs);
svgAppend(marker, options.element);
svgAttr(marker, {
id: id,
viewBox: '0 0 20 20',
refX: ref.x,
refY: ref.y,
markerWidth: 20 * scale,
markerHeight: 20 * scale,
orient: 'auto'
});
var defs = domQuery('defs', svg);
if (!defs) {
defs = svgCreate('defs');
svgAppend(svg, defs);
}
svgAppend(defs, marker);
return addMarker(id, marker);
}
var associationStart = svgCreate('path');
svgAttr(associationStart, { d: 'M 11 5 L 1 10 L 11 15' });
createMarker('association-start', {
element: associationStart,
attrs: {
fill: 'none',
stroke: 'black',
strokeWidth: 1.5
},
ref: { x: 1, y: 10 },
scale: 0.5
});
var associationEnd = svgCreate('path');
svgAttr(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' });
createMarker('association-end', {
element: associationEnd,
attrs: {
fill: 'none',
stroke: 'black',
strokeWidth: 1.5
},
ref: { x: 12, y: 10 },
scale: 0.5
});
}
// draw shape //////////////////////////////////////////////////////////////
function computeStyle(custom, traits, defaultStyles) {
if (!isArray(traits)) {
defaultStyles = traits;
traits = [];
}
return styles.style(traits || [], assign(defaultStyles, custom || {}));
}
function drawCircle(parentGfx, width, height, offset, attrs) {
if (isObject(offset)) {
attrs = offset;
offset = 0;
}
offset = offset || 0;
attrs = computeStyle(attrs, {
stroke: 'black',
strokeWidth: 2,
fill: 'white'
});
var cx = width / 2,
cy = height / 2;
var circle = svgCreate('circle');
svgAttr(circle, {
cx: cx,
cy: cy,
r: Math.round((width + height) / 4 - offset)
});
svgAttr(circle, attrs);
svgAppend(parentGfx, circle);
return circle;
}
function drawRect(parentGfx, width, height, r, offset, attrs) {
if (isObject(offset)) {
attrs = offset;
offset = 0;
}
offset = offset || 0;
attrs = computeStyle(attrs, {
stroke: 'black',
strokeWidth: 2,
fill: 'white'
});
var rect = svgCreate('rect');
svgAttr(rect, {
x: offset,
y: offset,
width: width - offset * 2,
height: height - offset * 2,
rx: r,
ry: r
});
svgAttr(rect, attrs);
svgAppend(parentGfx, rect);
return rect;
}
function drawDiamond(parentGfx, width, height, attrs) {
var x_2 = width / 2;
var y_2 = height / 2;
var points = [
{ x: x_2, y: 0 },
{ x: width, y: y_2 },
{ x: x_2, y: height },
{ x: 0, y: y_2 }
];
var pointsString = points.map(function(point) {
return point.x + ',' + point.y;
}).join(' ');
attrs = computeStyle(attrs, {
stroke: 'black',
strokeWidth: 2,
fill: 'white'
});
var polygon = svgCreate('polygon');
svgAttr(polygon, {
points: pointsString
});
svgAttr(polygon, attrs);
svgAppend(parentGfx, polygon);
return polygon;
}
function drawPath(parentGfx, d, attrs) {
attrs = computeStyle(attrs, [ 'no-fill' ], {
strokeWidth: 2,
stroke: 'black'
});
var path = svgCreate('path');
svgAttr(path, { d: d });
svgAttr(path, attrs);
svgAppend(parentGfx, path);
return path;
}
function drawOctagon(parentGfx, width, height, offset, attrs) {
offset = offset || 20;
var x1 = offset;
var y1 = height;
var x2 = 0;
var y2 = height - offset;
var x3 = 0;
var y3 = offset;
var x4 = offset;
var y4 = 0;
var x5 = width - offset;
var y5 = 0;
var x6 = width;
var y6 = offset;
var x7 = width;
var y7 = height - offset;
var x8 = width - offset;
var y8 = height;
var points = [
{ x: x1, y: y1 },
{ x: x2, y: y2 },
{ x: x3, y: y3 },
{ x: x4, y: y4 },
{ x: x5, y: y5 },
{ x: x6, y: y6 },
{ x: x7, y: y7 },
{ x: x8, y: y8 }
];
attrs = attrs || {};
attrs.fill = 'white';
attrs.stroke = 'black';
attrs.strokeWidth = 2;
return drawPolygon(parentGfx, points, attrs);
}
function drawPolygon(parentGfx, points, attrs) {
var pointsString = points.map(function(point) {
return point.x + ',' + point.y;
}).join(' ');
var polygon = svgCreate('polygon');
svgAttr(polygon, {
points: pointsString
});
svgAttr(polygon, attrs);
svgAppend(parentGfx, polygon);
return polygon;
}
// draw connection ////////////////////////////////////////////
function drawLine(parentGfx, waypoints, attrs) {
attrs = computeStyle(attrs, [ 'no-fill' ], {
stroke: 'black',
strokeWidth: 2,
fill: 'none'
});
var line = createLine(waypoints, attrs);
svgAppend(parentGfx, line);
return line;
}
function createPathFromConnection(connection) {
var waypoints = connection.waypoints;
var pathData = 'm ' + waypoints[0].x + ',' + waypoints[0].y;
for (var i = 1; i < waypoints.length; i++) {
pathData += 'L' + waypoints[i].x + ',' + waypoints[i].y + ' ';
}
return pathData;
}
// render label //////////////////////////////////////////////
function renderLabel(parentGfx, label, options) {
var text = textUtil.createText(label || '', options);
svgClasses(text).add('djs-label');
svgAppend(parentGfx, text);
return text;
}
function renderEmbeddedLabel(p, element, align) {
var name = getName(element);
return renderLabel(p, name, {
box: element,
align: align,
padding: 5
});
}
function renderExpandedStageLabel(p, element, align) {
var name = getName(element);
var textbox = renderLabel(p, name, { box: element, align: align, padding: 5 });
// reset the position of the text box
translate(textbox, STAGE_EDGE_OFFSET, 0);
return textbox;
}
function renderCasePlanModelLabel(p, element) {
var bo = getBusinessObject(element);
// default maximum textbox dimensions
var height = 18;
var width = (element.width / 2) - 60;
var label = bo.name;
// create text box
var textBox = renderLabel(p, label, {
box: { height: height, width: width },
align: 'left-top'
});
var minWidth = 60,
padding = 40,
textBoxWidth = textBox.getBBox().width;
// set polygon width based on actual textbox size
var polygonWidth = textBoxWidth + padding;
if (textBoxWidth < minWidth) {
polygonWidth = minWidth + padding;
}
var polygonPoints = [
{ x: 10, y: 0 },
{ x: 20, y: -height },
{ x: polygonWidth, y: -height },
{ x: polygonWidth + 10, y: 0 }
];
// The pointer-events attribute is needed to allow clicks on the polygon
// which otherwise would be prevented by the parent node ('djs-visual').
var polygon = drawPolygon(p, polygonPoints, {
fill: 'white',
stroke: 'black',
strokeWidth: 2,
fillOpacity: 0.95,
'pointer-events': 'all'
});
// make sure the textbox is visually on top of the polygon
textBox.parentNode.insertBefore(polygon, textBox);
// reset the position of the text box
translate(textBox, 25, -height + 5);
return textBox;
}
function renderExternalLabel(parentGfx, element) {
var name = getName(element),
hide = false;
var standardEvent = getStandardEvent(element);
if (standardEvent) {
var standardEventVisible = isStandardEventVisible(element);
standardEvent = '[' + standardEvent + ']';
if (!name) {
name = standardEvent;
element.hidden = hide = !standardEventVisible;
}
else {
if (standardEventVisible) {
name = name + ' ' + standardEvent;
}
}
}
var box = {
width: 90,
height: 30,
x: element.width / 2 + element.x,
y: element.height / 2 + element.y
};
element.hidden = element.labelTarget.hidden || hide || !name;
return renderLabel(parentGfx, name, { box: box, style: { fontSize: '11px' } });
}
// render elements //////////////////////////////////////////
function renderer(type) {
return handlers[type];
}
var handlers = {
'cmmn:PlanItem': function(p, element) {
var definition = getDefinition(element);
return renderer(definition.$type)(p, element);
},
'cmmn:DiscretionaryItem': function(p, element) {
var definition = getDefinition(element);
var attrs = {
strokeDasharray: '10, 12'
};
if (is(definition, 'cmmn:Task')) {
assign(attrs, {
strokeDasharray: '12, 12.4',
strokeDashoffset: 13.6
});
}
return renderer(definition.$type)(p, element, attrs);
},
// STAGE
'cmmn:Stage': function(p, element, attrs) {
attrs = assign({ fillOpacity: 0.95 }, attrs);
var rect;
if (isCasePlanModel(element)) {
return handlers['cmmn:CasePlanModel'](p, element);
}
rect = drawOctagon(p, element.width, element.height, STAGE_EDGE_OFFSET, attrs);
if (!isCollapsed(element)) {
renderExpandedStageLabel(p, element, 'left-top');
}
else {
renderEmbeddedLabel(p, element, 'center-middle');
}
attachPlanningTableMarker(p, element);
attachStageMarkers(p, element);
return rect;
},
// STAGE
'cmmn:PlanFragment': function(p, element, attrs) {
var rect = drawRect(p, element.width, element.height, TASK_BORDER_RADIUS, {
strokeDasharray: '10, 12',
fillOpacity: 0.95
});
renderEmbeddedLabel(p, element, isCollapsed(element) ? 'center-middle' : 'left-top');
attachStageMarkers(p, element);
return rect;
},
'cmmn:CasePlanModel': function(p, element) {
var rect = drawRect(p, element.width, element.height, 0, {
fillOpacity: 0.95
});
renderCasePlanModelLabel(p, element);
attachPlanningTableMarker(p, element);
attachCasePlanModelMarkers(p, element);
return rect;
},
// MILESTONE
'cmmn:Milestone': function(p, element, attrs) {
var rect = drawRect(p, element.width, element.height, MILESTONE_BORDER_RADIUS, attrs);
renderEmbeddedLabel(p, element, 'center-middle');
attachTaskMarkers(p, element);
return rect;
},
// EVENT LISTENER
'cmmn:EventListener': function(p, element, attrs) {
var outerCircle = drawCircle(p, element.width, element.height, attrs);
attrs = attrs || {};
attrs.strokeWidth = 2;
drawCircle(p, element.width, element.height, 0.1 * element.height, attrs);
return outerCircle;
},
'cmmn:TimerEventListener': function(p, element, attrs) {
var circle = renderer('cmmn:EventListener')(p, element, attrs);
var pathData = pathMap.getScaledPath('EVENT_TIMER_WH', {
xScaleFactor: 0.75,
yScaleFactor: 0.75,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.5,
my: 0.5
}
});
drawPath(p, pathData, {
strokeWidth: 2,
strokeLinecap: 'square'
});
for (var i = 0;i < 12;i++) {
var linePathData = pathMap.getScaledPath('EVENT_TIMER_LINE', {
xScaleFactor: 0.75,
yScaleFactor: 0.75,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.5,
my: 0.5
}
});
var width = element.width / 2;
var height = element.height / 2;
drawPath(p, linePathData, {
strokeWidth: 1,
strokeLinecap: 'square',
transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')'
});
}
return circle;
},
'cmmn:UserEventListener': function(p, element, attrs) {
var circle = renderer('cmmn:EventListener')(p, element, attrs);
// TODO: The user event decorator has to be
// scaled correctly!
var x = 20;
var y = 15;
var pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', {
abspos: {
x: x,
y: y
}
});
/* user path */ drawPath(p, pathData, {
strokeWidth: 0.5,
fill: 'none'
});
var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
abspos: {
x: x,
y: y
}
});
/* user2 path */ drawPath(p, pathData2, {
strokeWidth: 0.5,
fill: 'none'
});
var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
abspos: {
x: x,
y: y
}
});
/* user3 path */ drawPath(p, pathData3, {
strokeWidth: 0.5,
fill: 'black'
});
return circle;
},
// TASK
'cmmn:Task': function(p, element, attrs) {
var rect = drawRect(p, element.width, element.height, TASK_BORDER_RADIUS, attrs);
renderEmbeddedLabel(p, element, 'center-middle');
attachTaskMarkers(p, element);
return rect;
},
'cmmn:HumanTask': function(p, element, attrs) {
var task = renderer('cmmn:Task')(p, element, attrs);
var bo = element.businessObject;
var definition = bo.definitionRef;
if (definition.isBlocking) {
var x = 15;
var y = 12;
var pathData1 = pathMap.getScaledPath('TASK_TYPE_USER_1', {
abspos: {
x: x,
y: y
}
});
/* user path */ drawPath(p, pathData1, {
strokeWidth: 0.5,
fill: 'none'
});
var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', {
abspos: {
x: x,
y: y
}
});
/* user2 path */ drawPath(p, pathData2, {
strokeWidth: 0.5,
fill: 'none'
});
var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', {
abspos: {
x: x,
y: y
}
});
/* user3 path */ drawPath(p, pathData3, {
strokeWidth: 0.5,
fill: 'black'
});
}
else {
var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', {
abspos: {
x: 17,
y: 15
}
});
/* manual path */ drawPath(p, pathData, {
strokeWidth: 1.25,
fill: 'white',
stroke: 'black'
});
}
attachPlanningTableMarker(p, element);
return task;
},
'cmmn:CaseTask': function(p, element, attrs) {
var task = renderer('cmmn:Task')(p, element, attrs);
var pathData = pathMap.getScaledPath('TASK_TYPE_FOLDER', {
abspos: {
x: 7,
y: 7
}
});
/* manual path */ drawPath(p, pathData, {
strokeWidth: 1.25,
fill: 'white',
stroke: 'black'
});
return task;
},
'cmmn:ProcessTask': function(p, element, attrs) {
var task = renderer('cmmn:Task')(p, element, attrs);
var pathData = pathMap.getScaledPath('TASK_TYPE_CHEVRON', {
abspos: {
x: 5,
y: 5
}
});
/* manual path */ drawPath(p, pathData, {
strokeWidth: 1.25,
fill: 'white',
stroke: 'black'
});
return task;
},
'cmmn:DecisionTask': function(p, element, attrs) {
var task = renderer('cmmn:Task')(p, element, attrs);
var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', {
abspos: {
x: 8,
y: 8
}
});
drawPath(p, headerPathData, {
strokeWidth: 1,
fill: '000'
});
var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', {
abspos: {
x: 8,
y: 8
}
});
drawPath(p, headerData, {
strokeWidth: 1
});
return task;
},
'cmmn:CaseFileItem': function(p, element, attrs) {
var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.474,
my: 0.296
}
});
return drawPath(p, pathData, { fill: 'white' });
},
// ARTIFACTS
'cmmn:TextAnnotation': function(p, element) {
var style = {
'fill': 'none',
'stroke': 'none'
};
var textElement = drawRect(p, element.width, element.height, 0, 0, style);
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.0,
my: 0.0
}
});
drawPath(p, textPathData);
var text = getBusinessObject(element).text || '';
renderLabel(p, text, { box: element, align: 'left-middle', padding: 5 });
return textElement;
},
'cmmn:Association': function(p, element, attrs) {
var semantic = getBusinessObject(element);
attrs = assign({
strokeDasharray: '0.5, 5',
strokeLinecap: 'round',
strokeLinejoin: 'round'
}, attrs || {});
if (semantic.associationDirection === 'One' ||
semantic.associationDirection === 'Both') {
attrs.markerEnd = marker('association-end');
}
if (semantic.associationDirection === 'Both') {
attrs.markerStart = marker('association-start');
}
return drawLine(p, element.waypoints, attrs);
},
// MARKERS
'StageMarker': function(p, element) {
var markerRect = drawRect(p, 14, 14, 0, {
strokeWidth: 1,
stroke: 'black'
});
translate(markerRect, element.width / 2 - 7, element.height - 17);
var path = isCollapsed(element) ? 'MARKER_STAGE_COLLAPSED' : 'MARKER_STAGE_EXPANDED';
var stagePath = pathMap.getScaledPath(path, {
xScaleFactor: 1.5,
yScaleFactor: 1.5,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: (element.width / 2 - 7) / element.width,
my: (element.height - 17) / element.height
}
});
drawPath(p, stagePath);
},
'RequiredMarker': function(p, element, position) {
var path = pathMap.getScaledPath('MARKER_REQUIRED', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position) / element.width),
my: (element.height - 17) / element.height
}
});
drawPath(p, path, { strokeWidth: 3 });
},
'AutoCompleteMarker': function(p, element, position) {
var markerRect = drawRect(p, 11, 14, 0, {
strokeWidth: 1,
stroke: 'black',
fill: 'black'
});
translate(markerRect, element.width / 2 + position + 2, element.height - 17);
},
'ManualActivationMarker': function(p, element, position) {
var path = pathMap.getScaledPath('MARKER_MANUAL_ACTIVATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position) / element.width),
my: (element.height - 17) / element.height
}
});
drawPath(p, path, { strokeWidth: 1 });
},
'RepetitionMarker': function(p, element, position) {
var path = pathMap.getScaledPath('MARKER_REPEATABLE', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: ((element.width / 2 + position) / element.width),
my: (element.height - 17) / element.height
}
});
drawPath(p, path);
},
'PlanningTableMarker': function(p, element, position) {
var planningTableRect = drawRect(p, 30, 24, 0, {
strokeWidth: 1.5,
stroke: 'black'
});
translate(planningTableRect, element.width / 2 - 15, -17);
var isCollapsed = isPlanningTableCollapsed(element);
var marker = isCollapsed ? 'MARKER_PLANNING_TABLE_COLLAPSED' : 'MARKER_PLANNING_TABLE_EXPANDED';
var stagePath = pathMap.getScaledPath(marker, {
xScaleFactor: 1.5,
yScaleFactor: 1.5,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: (element.width / 2 - 15) / element.width,
my: (-17) / element.height
}
});
drawPath(p, stagePath, {
strokeWidth: 1.5
});
},
'cmmn:OnPart': function(p, element) {
var pathData = createPathFromConnection(element);
var path = drawPath(p, pathData, {
strokeDasharray: '10, 5, 2, 5, 2, 5',
strokeWidth: 1.5
});
return path;
},
'cmmn:PlanItemOnPart': function(p, element) {
return renderer('cmmn:OnPart')(p, element);
},
'cmmn:CaseFileItemOnPart': function(p, element) {
return renderer('cmmn:OnPart')(p, element);
},
'cmmn:EntryCriterion': function(p, element) {
return drawDiamond(p, element.width, element.height, {
fill: 'white'
});
},
'cmmn:ExitCriterion': function(p, element) {
return drawDiamond(p, element.width, element.height, {
fill: 'black'
});
},
'cmmndi:CMMNEdge': function(p, element) {
var bo = getBusinessObject(element);
if (bo.cmmnElementRef) {
return renderer(bo.cmmnElementRef.$type)(p, element);
}
var pathData = createPathFromConnection(element);
var path = drawPath(p, pathData, {
strokeDasharray: '3, 5',
strokeWidth: 1
});
return path;
},
'label': function(parentGfx, element) {
// Update external label size and bounds during rendering when
// we have the actual rendered bounds anyway.
var textElement = renderExternalLabel(parentGfx, element);
var textBBox;
try {
textBBox = textElement.getBBox();
} catch (e) {
textBBox = { width: 0, height: 0, x: 0 };
}
// update element.x so that the layouted text is still
// center alligned (newX = oldMidX - newWidth / 2)
element.x = Math.ceil(element.x + element.width / 2) - Math.ceil((textBBox.width / 2));
// take element width, height from actual bounds
element.width = Math.ceil(textBBox.width);
element.height = Math.ceil(textBBox.height);
// compensate bounding box x
svgAttr(textElement, {
transform: 'translate(' + (-1 * textBBox.x) + ',0)'
});
return textElement;
}
};
// attach markers /////////////////////////
function attachTaskMarkers(p, element) {
var obj = getBusinessObject(element);
var padding = 6;
var markers = [];
if (isRequired(obj)) {
markers.push({ marker: 'RequiredMarker', width: 1 });
}
if (isManualActivation(obj)) {
markers.push({ marker: 'ManualActivationMarker', width: 14 });
}
if (isRepeatable(obj)) {
markers.push({ marker: 'RepetitionMarker', width: 14 });
}
if (markers.length) {
if (markers.length === 1) {
// align marker in the middle of the element
drawMarker(markers[0].marker, p, element, (markers[0].width / 2) * (-1));
}
else if (markers.length === 2) {
/* align marker:
*
* | |
* +-------------+
* ^
* |
* +-+ +-+
* |0| |1| <-- markers
* +-+ +-+
* (leftMarker) (rightMarker)
*/
drawMarker(markers[0].marker, p, element, (markers[0].width * (-1)) - (padding /2));
drawMarker(markers[1].marker, p, element, padding / 2);
}
else if (markers.length === 3) {
/* align marker:
*
* | |
* +-------------+
* ^
* |
* +-+ +-+ +-+
* |0| |1| |2| <-- markers
* +-+ +-+ +-+
*/
/* 1 */ drawMarker(markers[1].marker, p, element, markers[1].width / 2 * (-1));
/* 0 */ drawMarker(markers[0].marker, p, element, (markers[1].width / 2 * (-1)) - padding - markers[0].width);
/* 2 */ drawMarker(markers[2].marker, p, element, (markers[1].width / 2) + padding);
}
}
}
function attachCasePlanModelMarkers(p, element) {
var obj = getBusinessObject(element);
if (isAutoComplete(obj)) {
drawMarker('AutoCompleteMarker', p, element, -7);
}
}
function attachStageMarkers(p, element, stage) {
var obj = getBusinessObject(element);
var padding = 6;
drawMarker('StageMarker', p, element, -7);
var leftMarkers = [];
if (isRequired(obj)) {
leftMarkers.push({ marker: 'RequiredMarker', width: 1 });
}
if (isAutoComplete(obj)) {
leftMarkers.push({ marker: 'AutoCompleteMarker', width: 14 });
}
if (leftMarkers.length) {
if (leftMarkers.length === 1) {
drawMarker(leftMarkers[0].marker, p, element, (leftMarkers[0].width * (-1) - 7 - padding));
}
else if (leftMarkers.length === 2) {
drawMarker(
leftMarkers[0].marker,
p,
element,
((leftMarkers[1].width * (-1)) - 7 - padding) - (leftMarkers[0].width * (-1)) - padding
);
drawMarker(leftMarkers[1].marker, p, element, (leftMarkers[1].width * (-1)) - 7 - padding);
}
}
var rightMarkers = [];
if (isManualActivation(obj)) {
rightMarkers.push({ marker: 'ManualActivationMarker', width: 14 });
}
if (isRepeatable(obj)) {
rightMarkers.push({ marker: 'RepetitionMarker', width: 14 });
}
if (rightMarkers.length) {
if (rightMarkers.length === 1) {
drawMarker(rightMarkers[0].marker, p, element, 7 + padding);
}
else if (rightMarkers.length === 2) {
drawMarker(rightMarkers[0].marker, p, element, 7 + padding);
drawMarker(rightMarkers[1].marker, p, element, 7 + padding + rightMarkers[0].width + padding);
}
}
}
function attachPlanningTableMarker(p, element) {
if (hasPlanningTable(element)) {
drawMarker('PlanningTableMarker', p, element);
}
}
function drawMarker(marker, parent, element, position) {
renderer(marker)(parent, element, position);
}
// draw shape and connection ////////////////////////////////////
function drawShape(parent, element) {
var h = handlers[element.type];
/* jshint -W040 */
if (!h) {
return BaseRenderer.prototype.drawShape.apply(this, [ parent, element ]);
} else {
return h(parent, element);
}
}
function drawConnection(parent, element) {
var type = element.type;
var h = handlers[type];
/* jshint -W040 */
if (!h) {
return BaseRenderer.prototype.drawConnection.apply(this, [ parent, element ]);
} else {
return h(parent, element);
}
}
this.canRender = function(element) {
return is(element, 'cmmn:CMMNElement') || is(element, 'cmmndi:CMMNEdge');
};
this.drawShape = drawShape;
this.drawConnection = drawConnection;
// hook onto canvas init event to initialize
// connection start/end markers on svg
eventBus.on('canvas.init', function(event) {
initMarkers(event.svg);
});
}
inherits(CmmnRenderer, BaseRenderer);
CmmnRenderer.$inject = [ 'eventBus', 'styles', 'pathMap' ];
module.exports = CmmnRenderer;