UNPKG

cmmn-js

Version:
1,933 lines (1,515 loc) 1.09 MB
/*! * cmmn-js - cmmn-modeler v0.20.0 * * Copyright (c) 2014-present, camunda Services GmbH * * Released under the bpmn.io license * http://bpmn.io/license * * Source Code: https://github.com/bpmn-io/cmmn-js * * Date: 2020-05-02 */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.CmmnJS = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(_dereq_,module,exports){ 'use strict'; var inherits = _dereq_(253); var Ids = _dereq_(252); var Viewer = _dereq_(3); var NavigatedViewer = _dereq_(2); var initialDiagram = '<?xml version="1.0" encoding="UTF-8"?>' + '<cmmn:definitions xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC" ' + 'xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" ' + 'xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" ' + 'xmlns:cmmn="http://www.omg.org/spec/CMMN/20151109/MODEL" ' + 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Test" ' + 'targetNamespace="http://bpmn.io/schema/cmmn">' + '<cmmn:case id="Case_1">' + '<cmmn:casePlanModel id="CasePlanModel_1" name="A CasePlanModel">' + '<cmmn:planItem id="PlanItem_1" definitionRef="Task_1" />' + '<cmmn:task id="Task_1" />' + '</cmmn:casePlanModel>' + '</cmmn:case>' + '<cmmndi:CMMNDI>' + '<cmmndi:CMMNDiagram id="CMMNDiagram_1">' + '<cmmndi:Size width="500" height="500" />' + '<cmmndi:CMMNShape id="DI_CasePlanModel_1" cmmnElementRef="CasePlanModel_1">' + '<dc:Bounds x="114" y="63" width="534" height="389" />' + '<cmmndi:CMMNLabel />' + '</cmmndi:CMMNShape>' + '<cmmndi:CMMNShape id="PlanItem_1_di" cmmnElementRef="PlanItem_1">' + '<dc:Bounds x="150" y="96" width="100" height="80" />' + '<cmmndi:CMMNLabel />' + '</cmmndi:CMMNShape>' + '</cmmndi:CMMNDiagram>' + '</cmmndi:CMMNDI>' + '</cmmn:definitions>'; /** * A modeler for CMMN 1.1 diagrams. * * Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include * additional features. * * ## Extending the Modeler * * In order to extend the viewer pass extension modules to bootstrap via the * `additionalModules` option. An extension module is an object that exposes * named services. * * The following example depicts the integration of a simple * logging component that integrates with interaction events: * * * ```javascript * * // logging component * function InteractionLogger(eventBus) { * eventBus.on('element.hover', function(event) { * console.log() * }) * } * * InteractionLogger.$inject = [ 'eventBus' ]; // minification save * * // extension module * var extensionModule = { * __init__: [ 'interactionLogger' ], * interactionLogger: [ 'type', InteractionLogger ] * }; * * // extend the viewer * var cmmnModeler = new Modeler({ additionalModules: [ extensionModule ] }); * cmmnModeler.importXML(...); * ``` * * * ## Customizing / Replacing Components * * You can replace individual diagram components by redefining them in override modules. * This works for all components, including those defined in the core. * * Pass in override modules via the `options.additionalModules` flag like this: * * ```javascript * function CustomContextPadProvider(contextPad) { * * contextPad.registerProvider(this); * * this.getContextPadEntries = function(element) { * // no entries, effectively disable the context pad * return {}; * }; * } * * CustomContextPadProvider.$inject = [ 'contextPad' ]; * * var overrideModule = { * contextPadProvider: [ 'type', CustomContextPadProvider ] * }; * * var cmmnModeler = new Modeler({ additionalModules: [ overrideModule ]}); * ``` * * @param {Object} [options] configuration options to pass to the viewer * @param {DOMElement} [options.container] the container to attach to * @param {String|Number} [options.width] the width of the viewer * @param {String|Number} [options.height] the height of the viewer * @param {Object} [options.moddleExtensions] extension packages to provide * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules */ function Modeler(options) { Viewer.call(this, options); // hook ID collection into the modeler this.on('import.parse.complete', function (event) { if (!event.error) { this._collectIds(event.definitions, event.context); } }, this); this.on('diagram.destroy', function () { this.get('moddle').ids.clear(); }, this); } inherits(Modeler, Viewer); module.exports = Modeler; module.exports.Viewer = Viewer; module.exports.NavigatedViewer = NavigatedViewer; /** * Create a new diagram to start modeling. * * @param {Function} [done] */ Modeler.prototype.createDiagram = function (done) { return this.importXML(initialDiagram, done); }; /** * Create a moddle instance, attaching ids to it. * * @param {Object} options */ Modeler.prototype._createModdle = function (options) { var moddle = Viewer.prototype._createModdle.call(this, options); // attach ids to moddle to be able to track // and validated ids in the CMMN 1.1 XML document // tree moddle.ids = new Ids([32, 36, 1]); return moddle; }; /** * Collect ids processed during parsing of the * definitions object. * * @param {ModdleElement} definitions * @param {Context} context */ Modeler.prototype._collectIds = function (definitions, context) { var moddle = definitions.$model, ids = moddle.ids, id; // remove references from previous import ids.clear(); for (id in context.elementsById) { ids.claim(id, context.elementsById[id]); } }; Modeler.prototype._interactionModules = [ // non-modeling components _dereq_(110).default, _dereq_(225).default, _dereq_(227).default, _dereq_(230).default, _dereq_(50)]; Modeler.prototype._modelingModules = [ // modeling components _dereq_(117).default, _dereq_(141).default, _dereq_(179).default, _dereq_(197).default, _dereq_(124).default, _dereq_(10), _dereq_(12), _dereq_(14), _dereq_(16), _dereq_(20), _dereq_(44), _dereq_(52), _dereq_(68), _dereq_(63), _dereq_(56)]; // modules the modeler is composed of // // - viewer modules // - interaction modules // - modeling modules Modeler.prototype._modules = [].concat(Modeler.prototype._modules, Modeler.prototype._interactionModules, Modeler.prototype._modelingModules); },{"10":10,"110":110,"117":117,"12":12,"124":124,"14":14,"141":141,"16":16,"179":179,"197":197,"2":2,"20":20,"225":225,"227":227,"230":230,"252":252,"253":253,"3":3,"44":44,"50":50,"52":52,"56":56,"63":63,"68":68}],2:[function(_dereq_,module,exports){ 'use strict'; var inherits = _dereq_(253); var Viewer = _dereq_(3); /** * A viewer that includes mouse navigation facilities * * @param {Object} options */ function NavigatedViewer(options) { Viewer.call(this, options); } inherits(NavigatedViewer, Viewer); module.exports = NavigatedViewer; NavigatedViewer.prototype._navigationModules = [_dereq_(230).default, _dereq_(227).default]; NavigatedViewer.prototype._modules = [].concat(NavigatedViewer.prototype._modules, NavigatedViewer.prototype._navigationModules); },{"227":227,"230":230,"253":253,"3":3}],3:[function(_dereq_,module,exports){ /** * The code in the <project-logo></project-logo> area * must not be changed. * * @see http://bpmn.io/license for more information. */ 'use strict'; var assign = _dereq_(254).assign, omit = _dereq_(254).omit, isNumber = _dereq_(254).isNumber; var inherits = _dereq_(253); var domify = _dereq_(255).domify, domQuery = _dereq_(255).query, domRemove = _dereq_(255).remove; var innerSVG = _dereq_(275).innerSVG; var Diagram = _dereq_(89).default, CmmnModdle = _dereq_(78).default; var Importer = _dereq_(71); function checkValidationError(err) { // check if we can help the user by indicating wrong CMMN 1.1 xml // (in case he or the exporting tool did not get that right) var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/; var match = pattern.exec(err.message); if (match) { err.message = 'unparsable content <' + match[1] + '> detected; ' + 'this may indicate an invalid CMMN 1.1 diagram file' + match[2]; } return err; } var DEFAULT_OPTIONS = { width: '100%', height: '100%', position: 'relative' }; /** * Ensure the passed argument is a proper unit (defaulting to px) */ function ensureUnit(val) { return val + (isNumber(val) ? 'px' : ''); } /** * A viewer for CMMN 1.1 diagrams. * * Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include * additional features. * * * ## Extending the Viewer * * In order to extend the viewer pass extension modules to bootstrap via the * `additionalModules` option. An extension module is an object that exposes * named services. * * The following example depicts the integration of a simple * logging component that integrates with interaction events: * * * ```javascript * * // logging component * function InteractionLogger(eventBus) { * eventBus.on('element.hover', function(event) { * console.log() * }) * } * * InteractionLogger.$inject = [ 'eventBus' ]; // minification save * * // extension module * var extensionModule = { * __init__: [ 'interactionLogger' ], * interactionLogger: [ 'type', InteractionLogger ] * }; * * // extend the viewer * var cmmnViewer = new Viewer({ additionalModules: [ extensionModule ] }); * cmmnViewer.importXML(...); * ``` * * @param {Object} [options] configuration options to pass to the viewer * @param {DOMElement} [options.container] the container to attach to * @param {String|Number} [options.width] the width of the viewer * @param {String|Number} [options.height] the height of the viewer * @param {Object} [options.moddleExtensions] extension packages to provide * @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules * @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules */ function Viewer(options) { options = assign({}, DEFAULT_OPTIONS, options); this._moddle = this._createModdle(options); this._container = this._createContainer(options); /* <project-logo> */ addProjectLogo(this._container); /* </project-logo> */ this._init(this._container, this._moddle, options); } inherits(Viewer, Diagram); module.exports = Viewer; /** * Parse and render a CMMN 1.1 diagram. * * Once finished the viewer reports back the result to the * provided callback function with (err, warnings). * * ## Life-Cycle Events * * During import the viewer will fire life-cycle events: * * * import.parse.start (about to read model from xml) * * import.parse.complete (model read; may have worked or not) * * import.render.start (graphical import start) * * import.render.complete (graphical import finished) * * import.done (everything done) * * You can use these events to hook into the life-cycle. * * @param {String} xml the CMMN 1.1 xml * @param {Function} [done] invoked with (err, warnings=[]) */ Viewer.prototype.importXML = function (xml, done) { // done is optional done = done || function () {}; var self = this; // hook in pre-parse listeners + // allow xml manipulation xml = this._emit('import.parse.start', { xml: xml }) || xml; this._moddle.fromXML(xml, 'cmmn:Definitions', function (err, definitions, context) { // hook in post parse listeners + // allow definitions manipulation definitions = self._emit('import.parse.complete', { error: err, definitions: definitions, context: context }) || definitions; var parseWarnings = context.warnings; if (err) { err = checkValidationError(err); self._emit('import.done', { error: err, warnings: parseWarnings }); return done(err, parseWarnings); } self.importDefinitions(definitions, function (err, importWarnings) { var allWarnings = [].concat(parseWarnings, importWarnings || []); self._emit('import.done', { error: err, warnings: allWarnings }); done(err, allWarnings); }); }); }; /** * Export the currently displayed CMMN 1.1 diagram as * a CMMN 1.1 XML document. * * ## Life-Cycle Events * * During XML saving the viewer will fire life-cycle events: * * * saveXML.start (before serialization) * * saveXML.serialized (after xml generation) * * saveXML.done (everything done) * * You can use these events to hook into the life-cycle. * * @param {Object} [options] export options * @param {Boolean} [options.format=false] output formated XML * @param {Boolean} [options.preamble=true] output preamble * * @param {Function} done invoked with (err, xml) */ Viewer.prototype.saveXML = function (options, done) { if (!done) { done = options; options = {}; } var self = this; var definitions = this._definitions; if (!definitions) { return done(new Error('no definitions loaded')); } // allow to fiddle around with definitions definitions = this._emit('saveXML.start', { definitions: definitions }) || definitions; this._moddle.toXML(definitions, options, function (err, xml) { try { xml = self._emit('saveXML.serialized', { error: err, xml: xml }) || xml; self._emit('saveXML.done', { error: err, xml: xml }); } catch (e) { console.error('error in saveXML life-cycle listener', e); } done(err, xml); }); }; Viewer.prototype.saveSVG = function (options, done) { if (!done) { done = options; options = {}; } var canvas = this.get('canvas'); var contentNode = canvas.getDefaultLayer(), defsNode = domQuery('defs', canvas._svg); var contents = innerSVG(contentNode), defs = defsNode && defsNode.outerHTML || ''; var bbox = contentNode.getBBox(); var svg = '<?xml version="1.0" encoding="utf-8"?>\n' + '<!-- created with cmmn-js / http://bpmn.io -->\n' + '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' + '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' + 'width="' + bbox.width + '" height="' + bbox.height + '" ' + 'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' + defs + contents + '</svg>'; done(null, svg); }; Viewer.prototype.importDefinitions = function (definitions, done) { // use try/catch to not swallow synchronous exceptions // that may be raised during model parsing try { if (this._definitions) { // clear existing rendered diagram this.clear(); } // update definitions this._definitions = definitions; // perform graphical import Importer.importCmmnDiagram(this, definitions, done); } catch (e) { // handle synchronous errors done(e); } }; Viewer.prototype.attachTo = function (parentNode) { if (!parentNode) { throw new Error('parentNode required'); } // ensure we detach from the // previous, old parent this.detach(); // unwrap jQuery if provided if (parentNode.get && parentNode.constructor.prototype.jquery) { parentNode = parentNode.get(0); } if (typeof parentNode === 'string') { parentNode = domQuery(parentNode); } parentNode.appendChild(this._container); this._emit('attach', {}); this.get('canvas').resized(); }; Viewer.prototype.getDefinitions = function () { return this._definitions; }; Viewer.prototype.detach = function () { var container = this._container, parentNode = container.parentNode; if (!parentNode) { return; } this._emit('detach', {}); parentNode.removeChild(container); }; Viewer.prototype.getModules = function () { return this._modules; }; /** * Destroy the viewer instance and remove all its * remainders from the document tree. */ Viewer.prototype.destroy = function () { // diagram destroy Diagram.prototype.destroy.call(this); // dom detach domRemove(this._container); }; /** * Register an event listener * * Remove a previously added listener via {@link #off(event, callback)}. * * @param {String} event * @param {Number} [priority] * @param {Function} callback * @param {Object} [that] */ Viewer.prototype.on = function (event, priority, callback, target) { return this.get('eventBus').on(event, priority, callback, target); }; /** * De-register an event listener * * @param {String} event * @param {Function} callback */ Viewer.prototype.off = function (event, callback) { this.get('eventBus').off(event, callback); }; Viewer.prototype._init = function (container, moddle, options) { var baseModules = options.modules || this.getModules(), additionalModules = options.additionalModules || [], staticModules = [{ cmmnjs: ['value', this], moddle: ['value', moddle] }]; var diagramModules = [].concat(staticModules, baseModules, additionalModules); var diagramOptions = assign(omit(options, 'additionalModules'), { canvas: assign({}, options.canvas, { container: container }), modules: diagramModules }); // invoke diagram constructor Diagram.call(this, diagramOptions); if (options && options.container) { this.attachTo(options.container); } }; /** * Emit an event on the underlying {@link EventBus} * * @param {String} type * @param {Object} event * * @return {Object} event processing result (if any) */ Viewer.prototype._emit = function (type, event) { return this.get('eventBus').fire(type, event); }; Viewer.prototype._createContainer = function (options) { var container = domify('<div class="cjs-container"></div>'); assign(container.style, { width: ensureUnit(options.width), height: ensureUnit(options.height), position: options.position }); return container; }; Viewer.prototype._createModdle = function (options) { var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions); return new CmmnModdle(moddleOptions); }; // modules the viewer is composed of Viewer.prototype._modules = [_dereq_(5), _dereq_(206).default, _dereq_(184).default]; /* <project-logo> */ var PoweredBy = _dereq_(77), domEvent = _dereq_(255).event; /** * Adds the project logo to the diagram container as * required by the bpmn.io license. * * @see http://bpmn.io/license * * @param {Element} container */ function addProjectLogo(container) { var linkMarkup = '<a href="http://bpmn.io" ' + 'target="_blank" ' + 'class="bjs-powered-by" ' + 'title="Powered by bpmn.io" ' + 'style="position: absolute; bottom: 15px; right: 15px; z-index: 100; ' + PoweredBy.LINK_STYLES + '">' + PoweredBy.BPMNIO_IMG + '</a>'; var linkElement = domify(linkMarkup); container.appendChild(linkElement); domEvent.bind(linkElement, 'click', function (event) { PoweredBy.open(); event.preventDefault(); }); } /* </project-logo> */ },{"184":184,"206":206,"253":253,"254":254,"255":255,"275":275,"5":5,"71":71,"77":77,"78":78,"89":89}],4:[function(_dereq_,module,exports){ 'use strict'; var ModelUtil = _dereq_(76), getDefinition = ModelUtil.getDefinition, getSentry = ModelUtil.getSentry; var isAny = _dereq_(45).isAny; var forEach = _dereq_(254).forEach, isArray = _dereq_(254).isArray; /** * @class * * A registry that keeps track of all items in the model. */ function ItemRegistry(elementRegistry, eventBus) { this._items = {}; this._referencedBy = {}; this._elementRegistry = elementRegistry; this._eventBus = eventBus; this._init(); } ItemRegistry.$inject = ['elementRegistry', 'eventBus']; module.exports = ItemRegistry; ItemRegistry.prototype._init = function (config) { var eventBus = this._eventBus; eventBus.on('diagram.destroy', 500, this._clear, this); eventBus.on('diagram.clear', 500, this._clear, this); }; ItemRegistry.prototype._clear = function () { this._items = {}; this._referencedBy = {}; }; /** * Register a given item. * * @param {ModdleElement} item */ ItemRegistry.prototype.add = function (item) { var items = this._items, id = item.id, definitions = this._referencedBy, definition = getReference(item), definitionId = definition && definition.id; items[id] = item; if (definition) { definitions[definitionId] = definitions[definitionId] || []; if (definitions[definitionId].indexOf(item) === -1) { definitions[definitionId].push(item); } } }; /** * Removes an item from the registry. * * @param {ModdleElement} item */ ItemRegistry.prototype.remove = function (item) { var items = this._items, id = item.id, definitions = this._referencedBy, definition = getReference(item), definitionId = definition && definition.id; delete items[id]; if (definition) { var referencingItems = definitions[definitionId] || [], idx = referencingItems.indexOf(item); if (idx !== -1) { referencingItems.splice(idx, 1); } if (!referencingItems.length) { delete definitions[definitionId]; } } }; /** * Update the registration with the new id. * * @param {ModdleElement} item * @param {String} newId */ ItemRegistry.prototype.updateId = function (element, newId) { var items, item; if (typeof element === 'string') { element = this.get(element); } if (isDefinition(element)) { items = this._referencedBy; } else { items = this._items; } if (element) { item = items[element.id]; delete items[element.id]; items[newId] = item; } }; /** * Update the registration. * * @param {ModdleElement} item * @param {ModdleElement} newReference */ ItemRegistry.prototype.updateReference = function (item, newReference) { var definitions = this._referencedBy, oldDefinition = getReference(item), oldDefinitionId = oldDefinition && oldDefinition.id; if (oldDefinition) { var referencingItems = definitions[oldDefinitionId] || [], idx = referencingItems.indexOf(item); if (idx !== -1) { referencingItems.splice(idx, 1); } if (!referencingItems.length) { delete definitions[oldDefinitionId]; } } if (newReference) { var newReferenceId = newReference.id; if (newReferenceId) { definitions[newReferenceId] = definitions[newReferenceId] || []; if (definitions[newReferenceId].indexOf(item) === -1) { definitions[newReferenceId].push(item); } } } }; /** * Return the item for a given id. * * @param {String} id for selecting the item * * @return {ModdleElement} */ ItemRegistry.prototype.get = function (id) { return this._items[id]; }; /** * Return all items that match a given filter function. * * @param {Function} fn * * @return {Array<ModdleElement>} */ ItemRegistry.prototype.filter = function (fn) { var filtered = []; this.forEach(function (element, definition) { if (fn(element, definition)) { filtered.push(element); } }); return filtered; }; /** * Return all items. * * @return {Array<ModdleElement>} */ ItemRegistry.prototype.getAll = function () { return this.filter(function (e) { return e; }); }; /** * Iterate over all items. * * @param {Function} fn */ ItemRegistry.prototype.forEach = function (fn) { var items = this._items; forEach(items, function (item) { return fn(item, getReference(item)); }); }; /** * Return for given definition all referenced items. * * @param {String|ModdleElement} filter */ ItemRegistry.prototype.getReferences = function (filter) { var id = filter.id || filter; return (this._referencedBy[id] || []).slice(); }; /** * Return for a given item id the shape element. * * @param {String|ModdleElement} filter */ ItemRegistry.prototype.getShape = function (filter) { var id = filter.id || filter; return this._elementRegistry && this._elementRegistry.get(id); }; /** * Return for a given filter all shapes. * * @param {Array<String>|String|ModdleElement} filter */ ItemRegistry.prototype.getShapes = function (filter) { var shapes = [], self = this; function add(shape) { shape && shapes.push(shape); } if (isArray(filter)) { forEach(filter, function (f) { add(self.getShape(f)); }); } else if (isDefinition(filter)) { var referencedBy = self.getReferences(filter); forEach(referencedBy, function (reference) { add(self.getShape(reference)); }); } else { add(self.getShape(filter)); } return shapes; }; function getReference(item) { return getDefinition(item) || getSentry(item); } function isDefinition(item) { return isAny(item, ['cmmn:PlanItemDefinition', 'cmmn:Sentry', 'cmmn:CaseFileItemDefinition']); } },{"254":254,"45":45,"76":76}],5:[function(_dereq_,module,exports){ 'use strict'; module.exports = { __depends__: [_dereq_(8), _dereq_(73)], itemRegistry: ['type', _dereq_(4)] }; },{"4":4,"73":73,"8":8}],6:[function(_dereq_,module,exports){ 'use strict'; var inherits = _dereq_(253), isArray = _dereq_(254).isArray, isObject = _dereq_(254).isObject, assign = _dereq_(254).assign; var BaseRenderer = _dereq_(100).default, TextUtil = _dereq_(249).default, DiUtil = _dereq_(74), ModelUtil = _dereq_(76); 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 = _dereq_(255).query; var svgAppend = _dereq_(275).append, svgAttr = _dereq_(275).attr, svgClasses = _dereq_(275).classes, svgCreate = _dereq_(275).create; var translate = _dereq_(248).translate; var createLine = _dereq_(247).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 cmmnPlanItem(p, element) { var definition = getDefinition(element); return renderer(definition.$type)(p, element); }, 'cmmn:DiscretionaryItem': function cmmnDiscretionaryItem(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 cmmnStage(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 cmmnPlanFragment(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 cmmnCasePlanModel(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 cmmnMilestone(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 cmmnEventListener(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 cmmnTimerEventListener(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 cmmnUserEventListener(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 cmmnTask(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 cmmnHumanTask(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 cmmnCaseTask(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 cmmnProcessTask(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 cmmnDecisionTask(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 cmmnCaseFileItem(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 cmmnTextAnnotation(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 cmmnAssociation(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 StageMarker(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 RequiredMarker(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 AutoCompleteMarker(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 ManualActivationMarker(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 RepetitionMarker(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 PlanningTableMarker(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 cmmnOnPart(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 cmmnPlanItemOnPart(p, element) { return renderer('cmmn:OnPart')(p, element); }, 'cmmn:CaseFileItemOnPart': function cmmnCaseFileItemOnPart(p, element) { return renderer('cmmn:OnPart')(p, element); }, 'cmmn:EntryCriterion': function cmmnEntryCriterion(p, element) { return drawDiamond(p, element.width, element.height, { fill: 'white' }); }, 'cmmn:ExitCriterion': fun