/*! * bpmn-js - bpmn-navigated-viewer v9.4.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/bpmn-js * * Date: 2022-08-22 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.BpmnJS = factory()); })(this, (function () { 'use strict'; function e(e,t){t&&(e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}));} /** * Flatten array, one level deep. * * @param {Array} arr * * @return {Array} */ var nativeToString$1 = Object.prototype.toString; var nativeHasOwnProperty$1 = Object.prototype.hasOwnProperty; function isUndefined$2(obj) { return obj === undefined; } function isDefined(obj) { return obj !== undefined; } function isArray$2(obj) { return nativeToString$1.call(obj) === '[object Array]'; } function isObject(obj) { return nativeToString$1.call(obj) === '[object Object]'; } function isNumber(obj) { return nativeToString$1.call(obj) === '[object Number]'; } function isFunction(obj) { var tag = nativeToString$1.call(obj); return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]'; } function isString(obj) { return nativeToString$1.call(obj) === '[object String]'; } /** * Return true, if target owns a property with the given key. * * @param {Object} target * @param {String} key * * @return {Boolean} */ function has$1(target, key) { return nativeHasOwnProperty$1.call(target, key); } /** * Find element in collection. * * @param {Array|Object} collection * @param {Function|Object} matcher * * @return {Object} */ function find(collection, matcher) { matcher = toMatcher(matcher); var match; forEach$1(collection, function (val, key) { if (matcher(val, key)) { match = val; return false; } }); return match; } /** * Find element index in collection. * * @param {Array|Object} collection * @param {Function} matcher * * @return {Object} */ function findIndex(collection, matcher) { matcher = toMatcher(matcher); var idx = isArray$2(collection) ? -1 : undefined; forEach$1(collection, function (val, key) { if (matcher(val, key)) { idx = key; return false; } }); return idx; } /** * Find element in collection. * * @param {Array|Object} collection * @param {Function} matcher * * @return {Array} result */ function filter(collection, matcher) { var result = []; forEach$1(collection, function (val, key) { if (matcher(val, key)) { result.push(val); } }); return result; } /** * Iterate over collection; returning something * (non-undefined) will stop iteration. * * @param {Array|Object} collection * @param {Function} iterator * * @return {Object} return result that stopped the iteration */ function forEach$1(collection, iterator) { var val, result; if (isUndefined$2(collection)) { return; } var convertKey = isArray$2(collection) ? toNum$1 : identity$1; for (var key in collection) { if (has$1(collection, key)) { val = collection[key]; result = iterator(val, convertKey(key)); if (result === false) { return val; } } } } /** * Reduce collection, returning a single result. * * @param {Object|Array} collection * @param {Function} iterator * @param {Any} result * * @return {Any} result returned from last iterator */ function reduce(collection, iterator, result) { forEach$1(collection, function (value, idx) { result = iterator(result, value, idx); }); return result; } /** * Return true if every element in the collection * matches the criteria. * * @param {Object|Array} collection * @param {Function} matcher * * @return {Boolean} */ function every(collection, matcher) { return !!reduce(collection, function (matches, val, key) { return matches && matcher(val, key); }, true); } /** * Return true if some elements in the collection * match the criteria. * * @param {Object|Array} collection * @param {Function} matcher * * @return {Boolean} */ function some(collection, matcher) { return !!find(collection, matcher); } /** * Transform a collection into another collection * by piping each member through the given fn. * * @param {Object|Array} collection * @param {Function} fn * * @return {Array} transformed collection */ function map(collection, fn) { var result = []; forEach$1(collection, function (val, key) { result.push(fn(val, key)); }); return result; } /** * Create an object pattern matcher. * * @example * * const matcher = matchPattern({ id: 1 }); * * let element = find(elements, matcher); * * @param {Object} pattern * * @return {Function} matcherFn */ function matchPattern(pattern) { return function (el) { return every(pattern, function (val, key) { return el[key] === val; }); }; } function toMatcher(matcher) { return isFunction(matcher) ? matcher : function (e) { return e === matcher; }; } function identity$1(arg) { return arg; } function toNum$1(arg) { return Number(arg); } /** * Debounce fn, calling it only once if the given time * elapsed between calls. * * Lodash-style the function exposes methods to `#clear` * and `#flush` to control internal behavior. * * @param {Function} fn * @param {Number} timeout * * @return {Function} debounced function */ function debounce(fn, timeout) { var timer; var lastArgs; var lastThis; var lastNow; function fire(force) { var now = Date.now(); var scheduledDiff = force ? 0 : lastNow + timeout - now; if (scheduledDiff > 0) { return schedule(scheduledDiff); } fn.apply(lastThis, lastArgs); clear(); } function schedule(timeout) { timer = setTimeout(fire, timeout); } function clear() { if (timer) { clearTimeout(timer); } timer = lastNow = lastArgs = lastThis = undefined; } function flush() { if (timer) { fire(true); } clear(); } function callback() { lastNow = Date.now(); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } lastArgs = args; lastThis = this; // ensure an execution is scheduled if (!timer) { schedule(timeout); } } callback.flush = flush; callback.cancel = clear; return callback; } /** * Bind function against target . * * @param {Function} fn * @param {Object} target * * @return {Function} bound function */ function bind(fn, target) { return fn.bind(target); } function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /** * Convenience wrapper for `Object.assign`. * * @param {Object} target * @param {...Object} others * * @return {Object} the target */ function assign(target) { for (var _len = arguments.length, others = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { others[_key - 1] = arguments[_key]; } return _extends.apply(void 0, [target].concat(others)); } /** * Pick given properties from the target object. * * @param {Object} target * @param {Array} properties * * @return {Object} target */ function pick(target, properties) { var result = {}; var obj = Object(target); forEach$1(properties, function (prop) { if (prop in obj) { result[prop] = target[prop]; } }); return result; } /** * Pick all target properties, excluding the given ones. * * @param {Object} target * @param {Array} properties * * @return {Object} target */ function omit(target, properties) { var result = {}; var obj = Object(target); forEach$1(obj, function (prop, key) { if (properties.indexOf(key) === -1) { result[key] = prop; } }); return result; } var DEFAULT_RENDER_PRIORITY$1 = 1000; /** * The base implementation of shape and connection renderers. * * @param {EventBus} eventBus * @param {number} [renderPriority=1000] */ function BaseRenderer(eventBus, renderPriority) { var self = this; renderPriority = renderPriority || DEFAULT_RENDER_PRIORITY$1; eventBus.on([ 'render.shape', 'render.connection' ], renderPriority, function(evt, context) { var type = evt.type, element = context.element, visuals = context.gfx, attrs = context.attrs; if (self.canRender(element)) { if (type === 'render.shape') { return self.drawShape(visuals, element, attrs); } else { return self.drawConnection(visuals, element, attrs); } } }); eventBus.on([ 'render.getShapePath', 'render.getConnectionPath' ], renderPriority, function(evt, element) { if (self.canRender(element)) { if (evt.type === 'render.getShapePath') { return self.getShapePath(element); } else { return self.getConnectionPath(element); } } }); } /** * Should check whether *this* renderer can render * the element/connection. * * @param {element} element * * @returns {boolean} */ BaseRenderer.prototype.canRender = function() {}; /** * Provides the shape's snap svg element to be drawn on the `canvas`. * * @param {djs.Graphics} visuals * @param {Shape} shape * * @returns {Snap.svg} [returns a Snap.svg paper element ] */ BaseRenderer.prototype.drawShape = function() {}; /** * Provides the shape's snap svg element to be drawn on the `canvas`. * * @param {djs.Graphics} visuals * @param {Connection} connection * * @returns {Snap.svg} [returns a Snap.svg paper element ] */ BaseRenderer.prototype.drawConnection = function() {}; /** * Gets the SVG path of a shape that represents it's visual bounds. * * @param {Shape} shape * * @return {string} svg path */ BaseRenderer.prototype.getShapePath = function() {}; /** * Gets the SVG path of a connection that represents it's visual bounds. * * @param {Connection} connection * * @return {string} svg path */ BaseRenderer.prototype.getConnectionPath = function() {}; /** * Is an element of the given BPMN type? * * @param {djs.model.Base|ModdleElement} element * @param {string} type * * @return {boolean} */ function is$1(element, type) { var bo = getBusinessObject(element); return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type); } /** * Return true if element has any of the given types. * * @param {djs.model.Base} element * @param {Array} types * * @return {boolean} */ function isAny(element, types) { return some(types, function(t) { return is$1(element, t); }); } /** * Return the business object for a given element. * * @param {djs.model.Base|ModdleElement} element * * @return {ModdleElement} */ function getBusinessObject(element) { return (element && element.businessObject) || element; } /** * Return the di object for a given element. * * @param {djs.model.Base} element * * @return {ModdleElement} */ function getDi(element) { return element && element.di; } function isExpanded(element, di) { if (is$1(element, 'bpmn:CallActivity')) { return false; } if (is$1(element, 'bpmn:SubProcess')) { di = di || getDi(element); if (di && is$1(di, 'bpmndi:BPMNPlane')) { return true; } return di && !!di.isExpanded; } if (is$1(element, 'bpmn:Participant')) { return !!getBusinessObject(element).processRef; } return true; } function isEventSubProcess(element) { return element && !!getBusinessObject(element).triggeredByEvent; } function getLabelAttr(semantic) { if ( is$1(semantic, 'bpmn:FlowElement') || is$1(semantic, 'bpmn:Participant') || is$1(semantic, 'bpmn:Lane') || is$1(semantic, 'bpmn:SequenceFlow') || is$1(semantic, 'bpmn:MessageFlow') || is$1(semantic, 'bpmn:DataInput') || is$1(semantic, 'bpmn:DataOutput') ) { return 'name'; } if (is$1(semantic, 'bpmn:TextAnnotation')) { return 'text'; } if (is$1(semantic, 'bpmn:Group')) { return 'categoryValueRef'; } } function getCategoryValue(semantic) { var categoryValueRef = semantic['categoryValueRef']; if (!categoryValueRef) { return ''; } return categoryValueRef.value || ''; } function getLabel(element) { var semantic = element.businessObject, attr = getLabelAttr(semantic); if (attr) { if (attr === 'categoryValueRef') { return getCategoryValue(semantic); } return semantic[attr] || ''; } } function ensureImported(element, target) { if (element.ownerDocument !== target.ownerDocument) { try { // may fail on webkit return target.ownerDocument.importNode(element, true); } catch (e) { // ignore } } return element; } /** * appendTo utility */ /** * Append a node to a target element and return the appended node. * * @param {SVGElement} element * @param {SVGElement} target * * @return {SVGElement} the appended node */ function appendTo(element, target) { return target.appendChild(ensureImported(element, target)); } /** * append utility */ /** * Append a node to an element * * @param {SVGElement} element * @param {SVGElement} node * * @return {SVGElement} the element */ function append(target, node) { appendTo(node, target); return target; } /** * attribute accessor utility */ var LENGTH_ATTR = 2; var CSS_PROPERTIES = { 'alignment-baseline': 1, 'baseline-shift': 1, 'clip': 1, 'clip-path': 1, 'clip-rule': 1, 'color': 1, 'color-interpolation': 1, 'color-interpolation-filters': 1, 'color-profile': 1, 'color-rendering': 1, 'cursor': 1, 'direction': 1, 'display': 1, 'dominant-baseline': 1, 'enable-background': 1, 'fill': 1, 'fill-opacity': 1, 'fill-rule': 1, 'filter': 1, 'flood-color': 1, 'flood-opacity': 1, 'font': 1, 'font-family': 1, 'font-size': LENGTH_ATTR, 'font-size-adjust': 1, 'font-stretch': 1, 'font-style': 1, 'font-variant': 1, 'font-weight': 1, 'glyph-orientation-horizontal': 1, 'glyph-orientation-vertical': 1, 'image-rendering': 1, 'kerning': 1, 'letter-spacing': 1, 'lighting-color': 1, 'marker': 1, 'marker-end': 1, 'marker-mid': 1, 'marker-start': 1, 'mask': 1, 'opacity': 1, 'overflow': 1, 'pointer-events': 1, 'shape-rendering': 1, 'stop-color': 1, 'stop-opacity': 1, 'stroke': 1, 'stroke-dasharray': 1, 'stroke-dashoffset': 1, 'stroke-linecap': 1, 'stroke-linejoin': 1, 'stroke-miterlimit': 1, 'stroke-opacity': 1, 'stroke-width': LENGTH_ATTR, 'text-anchor': 1, 'text-decoration': 1, 'text-rendering': 1, 'unicode-bidi': 1, 'visibility': 1, 'word-spacing': 1, 'writing-mode': 1 }; function getAttribute(node, name) { if (CSS_PROPERTIES[name]) { return node.style[name]; } else { return node.getAttributeNS(null, name); } } function setAttribute(node, name, value) { var hyphenated = name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); var type = CSS_PROPERTIES[hyphenated]; if (type) { // append pixel unit, unless present if (type === LENGTH_ATTR && typeof value === 'number') { value = String(value) + 'px'; } node.style[hyphenated] = value; } else { node.setAttributeNS(null, name, value); } } function setAttributes(node, attrs) { var names = Object.keys(attrs), i, name; for (i = 0, name; (name = names[i]); i++) { setAttribute(node, name, attrs[name]); } } /** * Gets or sets raw attributes on a node. * * @param {SVGElement} node * @param {Object} [attrs] * @param {String} [name] * @param {String} [value] * * @return {String} */ function attr$1(node, name, value) { if (typeof name === 'string') { if (value !== undefined) { setAttribute(node, name, value); } else { return getAttribute(node, name); } } else { setAttributes(node, name); } return node; } /** * Clear utility */ function index(arr, obj) { if (arr.indexOf) { return arr.indexOf(obj); } for (var i = 0; i < arr.length; ++i) { if (arr[i] === obj) { return i; } } return -1; } var re$1 = /\s+/; var toString$1 = Object.prototype.toString; function defined(o) { return typeof o !== 'undefined'; } /** * Wrap `el` in a `ClassList`. * * @param {Element} el * @return {ClassList} * @api public */ function classes$1(el) { return new ClassList$1(el); } function ClassList$1(el) { if (!el || !el.nodeType) { throw new Error('A DOM element reference is required'); } this.el = el; this.list = el.classList; } /** * Add class `name` if not already present. * * @param {String} name * @return {ClassList} * @api public */ ClassList$1.prototype.add = function(name) { // classList if (this.list) { this.list.add(name); return this; } // fallback var arr = this.array(); var i = index(arr, name); if (!~i) { arr.push(name); } if (defined(this.el.className.baseVal)) { this.el.className.baseVal = arr.join(' '); } else { this.el.className = arr.join(' '); } return this; }; /** * Remove class `name` when present, or * pass a regular expression to remove * any which match. * * @param {String|RegExp} name * @return {ClassList} * @api public */ ClassList$1.prototype.remove = function(name) { if ('[object RegExp]' === toString$1.call(name)) { return this.removeMatching(name); } // classList if (this.list) { this.list.remove(name); return this; } // fallback var arr = this.array(); var i = index(arr, name); if (~i) { arr.splice(i, 1); } this.el.className.baseVal = arr.join(' '); return this; }; /** * Remove all classes matching `re`. * * @param {RegExp} re * @return {ClassList} * @api private */ ClassList$1.prototype.removeMatching = function(re) { var arr = this.array(); for (var i = 0; i < arr.length; i++) { if (re.test(arr[i])) { this.remove(arr[i]); } } return this; }; /** * Toggle class `name`, can force state via `force`. * * For browsers that support classList, but do not support `force` yet, * the mistake will be detected and corrected. * * @param {String} name * @param {Boolean} force * @return {ClassList} * @api public */ ClassList$1.prototype.toggle = function(name, force) { // classList if (this.list) { if (defined(force)) { if (force !== this.list.toggle(name, force)) { this.list.toggle(name); // toggle again to correct } } else { this.list.toggle(name); } return this; } // fallback if (defined(force)) { if (!force) { this.remove(name); } else { this.add(name); } } else { if (this.has(name)) { this.remove(name); } else { this.add(name); } } return this; }; /** * Return an array of classes. * * @return {Array} * @api public */ ClassList$1.prototype.array = function() { var className = this.el.getAttribute('class') || ''; var str = className.replace(/^\s+|\s+$/g, ''); var arr = str.split(re$1); if ('' === arr[0]) { arr.shift(); } return arr; }; /** * Check if class `name` is present. * * @param {String} name * @return {ClassList} * @api public */ ClassList$1.prototype.has = ClassList$1.prototype.contains = function(name) { return ( this.list ? this.list.contains(name) : !! ~index(this.array(), name) ); }; function remove$2(element) { var parent = element.parentNode; if (parent) { parent.removeChild(element); } return element; } /** * Clear utility */ /** * Removes all children from the given element * * @param {DOMElement} element * @return {DOMElement} the element (for chaining) */ function clear$1(element) { var child; while ((child = element.firstChild)) { remove$2(child); } return element; } var ns = { svg: 'http://www.w3.org/2000/svg' }; /** * DOM parsing utility */ var SVG_START = '' + svg + ''; unwrap = true; } var parsed = parseDocument(svg); if (!unwrap) { return parsed; } var fragment = document.createDocumentFragment(); var parent = parsed.firstChild; while (parent.firstChild) { fragment.appendChild(parent.firstChild); } return fragment; } function parseDocument(svg) { var parser; // parse parser = new DOMParser(); parser.async = false; return parser.parseFromString(svg, 'text/xml'); } /** * Create utility for SVG elements */ /** * Create a specific type from name or SVG markup. * * @param {String} name the name or markup of the element * @param {Object} [attrs] attributes to set on the element * * @returns {SVGElement} */ function create$1(name, attrs) { var element; if (name.charAt(0) === '<') { element = parse$1(name).firstChild; element = document.importNode(element, true); } else { element = document.createElementNS(ns.svg, name); } if (attrs) { attr$1(element, attrs); } return element; } /** * Geometry helpers */ // fake node used to instantiate svg geometry elements var node = null; function getNode() { if (node === null) { node = create$1('svg'); } return node; } function extend$1(object, props) { var i, k, keys = Object.keys(props); for (i = 0; (k = keys[i]); i++) { object[k] = props[k]; } return object; } /** * Create matrix via args. * * @example * * createMatrix({ a: 1, b: 1 }); * createMatrix(); * createMatrix(1, 2, 0, 0, 30, 20); * * @return {SVGMatrix} */ function createMatrix(a, b, c, d, e, f) { var matrix = getNode().createSVGMatrix(); switch (arguments.length) { case 0: return matrix; case 1: return extend$1(matrix, a); case 6: return extend$1(matrix, { a: a, b: b, c: c, d: d, e: e, f: f }); } } function createTransform(matrix) { if (matrix) { return getNode().createSVGTransformFromMatrix(matrix); } else { return getNode().createSVGTransform(); } } /** * Serialization util */ var TEXT_ENTITIES = /([&<>]{1})/g; var ATTR_ENTITIES = /([\n\r"]{1})/g; var ENTITY_REPLACEMENT = { '&': '&', '<': '<', '>': '>', '"': '\'' }; function escape$1(str, pattern) { function replaceFn(match, entity) { return ENTITY_REPLACEMENT[entity] || entity; } return str.replace(pattern, replaceFn); } function serialize(node, output) { var i, len, attrMap, attrNode, childNodes; switch (node.nodeType) { // TEXT case 3: // replace special XML characters output.push(escape$1(node.textContent, TEXT_ENTITIES)); break; // ELEMENT case 1: output.push('<', node.tagName); if (node.hasAttributes()) { attrMap = node.attributes; for (i = 0, len = attrMap.length; i < len; ++i) { attrNode = attrMap.item(i); output.push(' ', attrNode.name, '="', escape$1(attrNode.value, ATTR_ENTITIES), '"'); } } if (node.hasChildNodes()) { output.push('>'); childNodes = node.childNodes; for (i = 0, len = childNodes.length; i < len; ++i) { serialize(childNodes.item(i), output); } output.push(''); } else { output.push('/>'); } break; // COMMENT case 8: output.push(''); break; // CDATA case 4: output.push(''); break; default: throw new Error('unable to handle node ' + node.nodeType); } return output; } /** * innerHTML like functionality for SVG elements. * based on innerSVG (https://code.google.com/p/innersvg) */ function set$1(element, svg) { var parsed = parse$1(svg); // clear element contents clear$1(element); if (!svg) { return; } if (!isFragment(parsed)) { // extract from parsed document parsed = parsed.documentElement; } var nodes = slice$1(parsed.childNodes); // import + append each node for (var i = 0; i < nodes.length; i++) { appendTo(nodes[i], element); } } function get(element) { var child = element.firstChild, output = []; while (child) { serialize(child, output); child = child.nextSibling; } return output.join(''); } function isFragment(node) { return node.nodeName === '#document-fragment'; } function innerSVG(element, svg) { if (svg !== undefined) { try { set$1(element, svg); } catch (e) { throw new Error('error parsing SVG: ' + e.message); } return element; } else { return get(element); } } function slice$1(arr) { return Array.prototype.slice.call(arr); } /** * transform accessor utility */ function wrapMatrix(transformList, transform) { if (transform instanceof SVGMatrix) { return transformList.createSVGTransformFromMatrix(transform); } return transform; } function setTransforms(transformList, transforms) { var i, t; transformList.clear(); for (i = 0; (t = transforms[i]); i++) { transformList.appendItem(wrapMatrix(transformList, t)); } } /** * Get or set the transforms on the given node. * * @param {SVGElement} node * @param {SVGTransform|SVGMatrix|Array} [transforms] * * @return {SVGTransform} the consolidated transform */ function transform$1(node, transforms) { var transformList = node.transform.baseVal; if (transforms) { if (!Array.isArray(transforms)) { transforms = [ transforms ]; } setTransforms(transformList, transforms); } return transformList.consolidate(); } function componentsToPath(elements) { return elements.join(',').replace(/,?([A-z]),?/g, '$1'); } function toSVGPoints(points) { var result = ''; for (var i = 0, p; (p = points[i]); i++) { result += p.x + ',' + p.y + ' '; } return result; } function createLine(points, attrs) { var line = create$1('polyline'); attr$1(line, { points: toSVGPoints(points) }); if (attrs) { attr$1(line, attrs); } return line; } function updateLine(gfx, points) { attr$1(gfx, { points: toSVGPoints(points) }); return gfx; } var black = 'hsl(225, 10%, 15%)'; // element utils ////////////////////// /** * Checks if eventDefinition of the given element matches with semantic type. * * @return {boolean} true if element is of the given semantic type */ function isTypedEvent(event, eventDefinitionType, filter) { function matches(definition, filter) { return every(filter, function(val, key) { // we want a == conversion here, to be able to catch // undefined == false and friends /* jshint -W116 */ return definition[key] == val; }); } return some(event.eventDefinitions, function(definition) { return definition.$type === eventDefinitionType && matches(event, filter); }); } function isThrowEvent(event) { return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent'); } function isCollection(element) { var dataObject = element.dataObjectRef; return element.isCollection || (dataObject && dataObject.isCollection); } function getSemantic(element) { return element.businessObject; } // color access ////////////////////// function getFillColor(element, defaultColor) { var di = getDi(element); return di.get('color:background-color') || di.get('bioc:fill') || defaultColor || 'white'; } function getStrokeColor(element, defaultColor) { var di = getDi(element); return di.get('color:border-color') || di.get('bioc:stroke') || defaultColor || black; } function getLabelColor(element, defaultColor, defaultStrokeColor) { var di = getDi(element), label = di.get('label'); return label && label.get('color:color') || defaultColor || getStrokeColor(element, defaultStrokeColor); } // cropping path customizations ////////////////////// function getCirclePath(shape) { var cx = shape.x + shape.width / 2, cy = shape.y + shape.height / 2, radius = shape.width / 2; var circlePath = [ [ 'M', cx, cy ], [ 'm', 0, -radius ], [ 'a', radius, radius, 0, 1, 1, 0, 2 * radius ], [ 'a', radius, radius, 0, 1, 1, 0, -2 * radius ], [ 'z' ] ]; return componentsToPath(circlePath); } function getRoundRectPath(shape, borderRadius) { var x = shape.x, y = shape.y, width = shape.width, height = shape.height; var roundRectPath = [ [ 'M', x + borderRadius, y ], [ 'l', width - borderRadius * 2, 0 ], [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius ], [ 'l', 0, height - borderRadius * 2 ], [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius ], [ 'l', borderRadius * 2 - width, 0 ], [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius ], [ 'l', 0, borderRadius * 2 - height ], [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius ], [ 'z' ] ]; return componentsToPath(roundRectPath); } function getDiamondPath(shape) { var width = shape.width, height = shape.height, x = shape.x, y = shape.y, halfWidth = width / 2, halfHeight = height / 2; var diamondPath = [ [ 'M', x + halfWidth, y ], [ 'l', halfWidth, halfHeight ], [ 'l', -halfWidth, halfHeight ], [ 'l', -halfWidth, -halfHeight ], [ 'z' ] ]; return componentsToPath(diamondPath); } function getRectPath(shape) { var x = shape.x, y = shape.y, width = shape.width, height = shape.height; var rectPath = [ [ 'M', x, y ], [ 'l', width, 0 ], [ 'l', 0, height ], [ 'l', -width, 0 ], [ 'z' ] ]; return componentsToPath(rectPath); } /** * Flatten array, one level deep. * * @param {Array} arr * * @return {Array} */ var nativeToString = Object.prototype.toString; var nativeHasOwnProperty = Object.prototype.hasOwnProperty; function isUndefined$1(obj) { return obj === undefined; } function isArray$1(obj) { return nativeToString.call(obj) === '[object Array]'; } /** * Return true, if target owns a property with the given key. * * @param {Object} target * @param {String} key * * @return {Boolean} */ function has(target, key) { return nativeHasOwnProperty.call(target, key); } /** * Iterate over collection; returning something * (non-undefined) will stop iteration. * * @param {Array|Object} collection * @param {Function} iterator * * @return {Object} return result that stopped the iteration */ function forEach(collection, iterator) { var val, result; if (isUndefined$1(collection)) { return; } var convertKey = isArray$1(collection) ? toNum : identity; for (var key in collection) { if (has(collection, key)) { val = collection[key]; result = iterator(val, convertKey(key)); if (result === false) { return val; } } } } function identity(arg) { return arg; } function toNum(arg) { return Number(arg); } /** * Assigns style attributes in a style-src compliant way. * * @param {Element} element * @param {...Object} styleSources * * @return {Element} the element */ function assign$1(element) { var target = element.style; for (var _len = arguments.length, styleSources = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { styleSources[_key - 1] = arguments[_key]; } forEach(styleSources, function (style) { if (!style) { return; } forEach(style, function (value, key) { target[key] = value; }); }); return element; } /** * Set attribute `name` to `val`, or get attr `name`. * * @param {Element} el * @param {String} name * @param {String} [val] * @api public */ function attr(el, name, val) { // get if (arguments.length == 2) { return el.getAttribute(name); } // remove if (val === null) { return el.removeAttribute(name); } // set el.setAttribute(name, val); return el; } var indexOf = [].indexOf; var indexof = function(arr, obj){ if (indexOf) return arr.indexOf(obj); for (var i = 0; i < arr.length; ++i) { if (arr[i] === obj) return i; } return -1; }; /** * Taken from https://github.com/component/classes * * Without the component bits. */ /** * Whitespace regexp. */ var re = /\s+/; /** * toString reference. */ var toString = Object.prototype.toString; /** * Wrap `el` in a `ClassList`. * * @param {Element} el * @return {ClassList} * @api public */ function classes(el) { return new ClassList(el); } /** * Initialize a new ClassList for `el`. * * @param {Element} el * @api private */ function ClassList(el) { if (!el || !el.nodeType) { throw new Error('A DOM element reference is required'); } this.el = el; this.list = el.classList; } /** * Add class `name` if not already present. * * @param {String} name * @return {ClassList} * @api public */ ClassList.prototype.add = function (name) { // classList if (this.list) { this.list.add(name); return this; } // fallback var arr = this.array(); var i = indexof(arr, name); if (!~i) arr.push(name); this.el.className = arr.join(' '); return this; }; /** * Remove class `name` when present, or * pass a regular expression to remove * any which match. * * @param {String|RegExp} name * @return {ClassList} * @api public */ ClassList.prototype.remove = function (name) { if ('[object RegExp]' == toString.call(name)) { return this.removeMatching(name); } // classList if (this.list) { this.list.remove(name); return this; } // fallback var arr = this.array(); var i = indexof(arr, name); if (~i) arr.splice(i, 1); this.el.className = arr.join(' '); return this; }; /** * Remove all classes matching `re`. * * @param {RegExp} re * @return {ClassList} * @api private */ ClassList.prototype.removeMatching = function (re) { var arr = this.array(); for (var i = 0; i < arr.length; i++) { if (re.test(arr[i])) { this.remove(arr[i]); } } return this; }; /** * Toggle class `name`, can force state via `force`. * * For browsers that support classList, but do not support `force` yet, * the mistake will be detected and corrected. * * @param {String} name * @param {Boolean} force * @return {ClassList} * @api public */ ClassList.prototype.toggle = function (name, force) { // classList if (this.list) { if ('undefined' !== typeof force) { if (force !== this.list.toggle(name, force)) { this.list.toggle(name); // toggle again to correct } } else { this.list.toggle(name); } return this; } // fallback if ('undefined' !== typeof force) { if (!force) { this.remove(name); } else { this.add(name); } } else { if (this.has(name)) { this.remove(name); } else { this.add(name); } } return this; }; /** * Return an array of classes. * * @return {Array} * @api public */ ClassList.prototype.array = function () { var className = this.el.getAttribute('class') || ''; var str = className.replace(/^\s+|\s+$/g, ''); var arr = str.split(re); if ('' === arr[0]) arr.shift(); return arr; }; /** * Check if class `name` is present. * * @param {String} name * @return {ClassList} * @api public */ ClassList.prototype.has = ClassList.prototype.contains = function (name) { return this.list ? this.list.contains(name) : !!~indexof(this.array(), name); }; /** * Remove all children from the given element. */ function clear(el) { var c; while (el.childNodes.length) { c = el.childNodes[0]; el.removeChild(c); } return el; } var proto = typeof Element !== 'undefined' ? Element.prototype : {}; var vendor = proto.matches || proto.matchesSelector || proto.webkitMatchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector; var matchesSelector = match; /** * Match `el` to `selector`. * * @param {Element} el * @param {String} selector * @return {Boolean} * @api public */ function match(el, selector) { if (!el || el.nodeType !== 1) return false; if (vendor) return vendor.call(el, selector); var nodes = el.parentNode.querySelectorAll(selector); for (var i = 0; i < nodes.length; i++) { if (nodes[i] == el) return true; } return false; } /** * Closest * * @param {Element} el * @param {String} selector * @param {Boolean} checkYourSelf (optional) */ function closest (element, selector, checkYourSelf) { var currentElem = checkYourSelf ? element : element.parentNode; while (currentElem && currentElem.nodeType !== document.DOCUMENT_NODE && currentElem.nodeType !== document.DOCUMENT_FRAGMENT_NODE) { if (matchesSelector(currentElem, selector)) { return currentElem; } currentElem = currentElem.parentNode; } return matchesSelector(currentElem, selector) ? currentElem : null; } var bind$1 = window.addEventListener ? 'addEventListener' : 'attachEvent', unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent', prefix$6 = bind$1 !== 'addEventListener' ? 'on' : ''; /** * Bind `el` event `type` to `fn`. * * @param {Element} el * @param {String} type * @param {Function} fn * @param {Boolean} capture * @return {Function} * @api public */ var bind_1 = function(el, type, fn, capture){ el[bind$1](prefix$6 + type, fn, capture || false); return fn; }; /** * Unbind `el` event `type`'s callback `fn`. * * @param {Element} el * @param {String} type * @param {Function} fn * @param {Boolean} capture * @return {Function} * @api public */ var unbind_1 = function(el, type, fn, capture){ el[unbind](prefix$6 + type, fn, capture || false); return fn; }; var componentEvent = { bind: bind_1, unbind: unbind_1 }; /** * Module dependencies. */ /** * Delegate event `type` to `selector` * and invoke `fn(e)`. A callback function * is returned which may be passed to `.unbind()`. * * @param {Element} el * @param {String} selector * @param {String} type * @param {Function} fn * @param {Boolean} capture * @return {Function} * @api public */ // Some events don't bubble, so we want to bind to the capture phase instead // when delegating. var forceCaptureEvents = ['focus', 'blur']; function bind$2(el, selector, type, fn, capture) { if (forceCaptureEvents.indexOf(type) !== -1) { capture = true; } return componentEvent.bind(el, type, function (e) { var target = e.target || e.srcElement; e.delegateTarget = closest(target, selector, true); if (e.delegateTarget) { fn.call(el, e); } }, capture); } /** * Unbind event `type`'s callback `fn`. * * @param {Element} el * @param {String} type * @param {Function} fn * @param {Boolean} capture * @api public */ function unbind$1(el, type, fn, capture) { if (forceCaptureEvents.indexOf(type) !== -1) { capture = true; } return componentEvent.unbind(el, type, fn, capture); } var delegate = { bind: bind$2, unbind: unbind$1 }; /** * Expose `parse`. */ var domify = parse; /** * Tests for browser support. */ var innerHTMLBug = false; var bugTestDiv; if (typeof document !== 'undefined') { bugTestDiv = document.createElement('div'); // Setup bugTestDiv.innerHTML = '
a'; // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE innerHTMLBug = !bugTestDiv.getElementsByTagName('link').length; bugTestDiv = undefined; } /** * Wrap map from jquery. */ var map$1 = { legend: [1, '
', '
'], tr: [2, '', '
'], col: [2, '', '
'], // for script/link/style tags to work in IE6-8, you have to wrap // in a div with a non-whitespace character in front, ha! _default: innerHTMLBug ? [1, 'X
', '
'] : [0, '', ''] }; map$1.td = map$1.th = [3, '', '
']; map$1.option = map$1.optgroup = [1, '']; map$1.thead = map$1.tbody = map$1.colgroup = map$1.caption = map$1.tfoot = [1, '', '
']; map$1.polyline = map$1.ellipse = map$1.polygon = map$1.circle = map$1.text = map$1.line = map$1.path = map$1.rect = map$1.g = [1, '','']; /** * Parse `html` and return a DOM Node instance, which could be a TextNode, * HTML DOM Node of some kind (
for example), or a DocumentFragment * instance, depending on the contents of the `html` string. * * @param {String} html - HTML string to "domify" * @param {Document} doc - The `document` instance to create the Node for * @return {DOMNode} the TextNode, DOM Node, or DocumentFragment instance * @api private */ function parse(html, doc) { if ('string' != typeof html) throw new TypeError('String expected'); // default to the global `document` object if (!doc) doc = document; // tag name var m = /<([\w:]+)/.exec(html); if (!m) return doc.createTextNode(html); html = html.replace(/^\s+|\s+$/g, ''); // Remove leading/trailing whitespace var tag = m[1]; // body support if (tag == 'body') { var el = doc.createElement('html'); el.innerHTML = html; return el.removeChild(el.lastChild); } // wrap map var wrap = map$1[tag] || map$1._default; var depth = wrap[0]; var prefix = wrap[1]; var suffix = wrap[2]; var el = doc.createElement('div'); el.innerHTML = prefix + html + suffix; while (depth--) el = el.lastChild; // one element if (el.firstChild == el.lastChild) { return el.removeChild(el.firstChild); } // several elements var fragment = doc.createDocumentFragment(); while (el.firstChild) { fragment.appendChild(el.removeChild(el.firstChild)); } return fragment; } function query(selector, el) { el = el || document; return el.querySelector(selector); } function all(selector, el) { el = el || document; return el.querySelectorAll(selector); } function remove$1(el) { el.parentNode && el.parentNode.removeChild(el); } /** * @param {} element * @param {number} x * @param {number} y * @param {number} angle * @param {number} amount */ function transform(gfx, x, y, angle, amount) { var translate = createTransform(); translate.setTranslate(x, y); var rotate = createTransform(); rotate.setRotate(angle || 0, 0, 0); var scale = createTransform(); scale.setScale(amount || 1, amount || 1); transform$1(gfx, [ translate, rotate, scale ]); } /** * @param {SVGElement} element * @param {number} x * @param {number} y */ function translate$1(gfx, x, y) { var translate = createTransform(); translate.setTranslate(x, y); transform$1(gfx, translate); } /** * @param {SVGElement} element * @param {number} angle */ function rotate(gfx, angle) { var rotate = createTransform(); rotate.setRotate(angle, 0, 0); transform$1(gfx, rotate); } function createCommonjsModule(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } var hat_1 = createCommonjsModule(function (module) { var hat = module.exports = function (bits, base) { if (!base) base = 16; if (bits === undefined) bits = 128; if (bits <= 0) return '0'; var digits = Math.log(Math.pow(2, bits)) / Math.log(base); for (var i = 2; digits === Infinity; i *= 2) { digits = Math.log(Math.pow(2, bits / i)) / Math.log(base) * i; } var rem = digits - Math.floor(digits); var res = ''; for (var i = 0; i < Math.floor(digits); i++) { var x = Math.floor(Math.random() * base).toString(base); res = x + res; } if (rem) { var b = Math.pow(base, rem); var x = Math.floor(Math.random() * b).toString(base); res = x + res; } var parsed = parseInt(res, base); if (parsed !== Infinity && parsed >= Math.pow(2, bits)) { return hat(bits, base) } else return res; }; hat.rack = function (bits, base, expandBy) { var fn = function (data) { var iters = 0; do { if (iters ++ > 10) { if (expandBy) bits += expandBy; else throw new Error('too many ID collisions, use more bits') } var id = hat(bits, base); } while (Object.hasOwnProperty.call(hats, id)); hats[id] = data; return id; }; var hats = fn.hats = {}; fn.get = function (id) { return fn.hats[id]; }; fn.set = function (id, value) { fn.hats[id] = value; return fn; }; fn.bits = bits || 128; fn.base = base || 16; return fn; }; }); /** * Create a new id generator / cache instance. * * You may optionally provide a seed that is used internally. * * @param {Seed} seed */ function Ids(seed) { if (!(this instanceof Ids)) { return new Ids(seed); } seed = seed || [128, 36, 1]; this._seed = seed.length ? hat_1.rack(seed[0], seed[1], seed[2]) : seed; } /** * Generate a next id. * * @param {Object} [element] element to bind the id to * * @return {String} id */ Ids.prototype.next = function (element) { return this._seed(element || true); }; /** * Generate a next id with a given prefix. * * @param {Object} [element] element to bind the id to * * @return {String} id */ Ids.prototype.nextPrefixed = function (prefix, element) { var id; do { id = prefix + this.next(true); } while (this.assigned(id)); // claim {prefix}{random} this.claim(id, element); // return return id; }; /** * Manually claim an existing id. * * @param {String} id * @param {String} [element] element the id is claimed by */ Ids.prototype.claim = function (id, element) { this._seed.set(id, element || true); }; /** * Returns true if the given id has already been assigned. * * @param {String} id * @return {Boolean} */ Ids.prototype.assigned = function (id) { return this._seed.get(id) || false; }; /** * Unclaim an id. * * @param {String} id the id to unclaim */ Ids.prototype.unclaim = function (id) { delete this._seed.hats[id]; }; /** * Clear all claimed ids. */ Ids.prototype.clear = function () { var hats = this._seed.hats, id; for (id in hats) { this.unclaim(id); } }; var RENDERER_IDS = new Ids(); var TASK_BORDER_RADIUS = 10; var INNER_OUTER_DIST = 3; var DEFAULT_FILL_OPACITY = .95, HIGH_FILL_OPACITY = .35; var ELEMENT_LABEL_DISTANCE = 10; function BpmnRenderer( config, eventBus, styles, pathMap, canvas, textRenderer, priority) { BaseRenderer.call(this, eventBus, priority); var defaultFillColor = config && config.defaultFillColor, defaultStrokeColor = config && config.defaultStrokeColor, defaultLabelColor = config && config.defaultLabelColor; var rendererId = RENDERER_IDS.next(); var markers = {}; var computeStyle = styles.computeStyle; function addMarker(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 = create$1('marker'); attr$1(options.element, attrs); append(marker, options.element); attr$1(marker, { id: id, viewBox: '0 0 20 20', refX: ref.x, refY: ref.y, markerWidth: 20 * scale, markerHeight: 20 * scale, orient: 'auto' }); var defs = query('defs', canvas._svg); if (!defs) { defs = create$1('defs'); append(canvas._svg, defs); } append(defs, marker); markers[id] = marker; } function colorEscape(str) { // only allow characters and numbers return str.replace(/[^0-9a-zA-z]+/g, '_'); } function marker(type, fill, stroke) { var id = type + '-' + colorEscape(fill) + '-' + colorEscape(stroke) + '-' + rendererId; if (!markers[id]) { createMarker(id, type, fill, stroke); } return 'url(#' + id + ')'; } function createMarker(id, type, fill, stroke) { if (type === 'sequenceflow-end') { var sequenceflowEnd = create$1('path'); attr$1(sequenceflowEnd, { d: 'M 1 5 L 11 10 L 1 15 Z' }); addMarker(id, { element: sequenceflowEnd, ref: { x: 11, y: 10 }, scale: 0.5, attrs: { fill: stroke, stroke: stroke } }); } if (type === 'messageflow-start') { var messageflowStart = create$1('circle'); attr$1(messageflowStart, { cx: 6, cy: 6, r: 3.5 }); addMarker(id, { element: messageflowStart, attrs: { fill: fill, stroke: stroke }, ref: { x: 6, y: 6 } }); } if (type === 'messageflow-end') { var messageflowEnd = create$1('path'); attr$1(messageflowEnd, { d: 'm 1 5 l 0 -3 l 7 3 l -7 3 z' }); addMarker(id, { element: messageflowEnd, attrs: { fill: fill, stroke: stroke, strokeLinecap: 'butt' }, ref: { x: 8.5, y: 5 } }); } if (type === 'association-start') { var associationStart = create$1('path'); attr$1(associationStart, { d: 'M 11 5 L 1 10 L 11 15' }); addMarker(id, { element: associationStart, attrs: { fill: 'none', stroke: stroke, strokeWidth: 1.5 }, ref: { x: 1, y: 10 }, scale: 0.5 }); } if (type === 'association-end') { var associationEnd = create$1('path'); attr$1(associationEnd, { d: 'M 1 5 L 11 10 L 1 15' }); addMarker(id, { element: associationEnd, attrs: { fill: 'none', stroke: stroke, strokeWidth: 1.5 }, ref: { x: 12, y: 10 }, scale: 0.5 }); } if (type === 'conditional-flow-marker') { var conditionalflowMarker = create$1('path'); attr$1(conditionalflowMarker, { d: 'M 0 10 L 8 6 L 16 10 L 8 14 Z' }); addMarker(id, { element: conditionalflowMarker, attrs: { fill: fill, stroke: stroke }, ref: { x: -1, y: 10 }, scale: 0.5 }); } if (type === 'conditional-default-flow-marker') { var conditionaldefaultflowMarker = create$1('path'); attr$1(conditionaldefaultflowMarker, { d: 'M 6 4 L 10 16' }); addMarker(id, { element: conditionaldefaultflowMarker, attrs: { stroke: stroke }, ref: { x: 0, y: 10 }, scale: 0.5 }); } } 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' }); if (attrs.fill === 'none') { delete attrs.fillOpacity; } var cx = width / 2, cy = height / 2; var circle = create$1('circle'); attr$1(circle, { cx: cx, cy: cy, r: Math.round((width + height) / 4 - offset) }); attr$1(circle, attrs); append(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 = create$1('rect'); attr$1(rect, { x: offset, y: offset, width: width - offset * 2, height: height - offset * 2, rx: r, ry: r }); attr$1(rect, attrs); append(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 = create$1('polygon'); attr$1(polygon, { points: pointsString }); attr$1(polygon, attrs); append(parentGfx, polygon); return polygon; } function drawLine(parentGfx, waypoints, attrs) { attrs = computeStyle(attrs, [ 'no-fill' ], { stroke: black, strokeWidth: 2, fill: 'none' }); var line = createLine(waypoints, attrs); append(parentGfx, line); return line; } function drawPath(parentGfx, d, attrs) { attrs = computeStyle(attrs, [ 'no-fill' ], { strokeWidth: 2, stroke: black }); var path = create$1('path'); attr$1(path, { d: d }); attr$1(path, attrs); append(parentGfx, path); return path; } function drawMarker(type, parentGfx, path, attrs) { return drawPath(parentGfx, path, assign({ 'data-marker': type }, attrs)); } function renderer(type) { return handlers[type]; } function as(type) { return function(parentGfx, element) { return renderer(type)(parentGfx, element); }; } function renderEventContent(element, parentGfx) { var event = getSemantic(element); var isThrowing = isThrowEvent(event); if (event.eventDefinitions && event.eventDefinitions.length > 1) { if (event.parallelMultiple) { return renderer('bpmn:ParallelMultipleEventDefinition')(parentGfx, element, isThrowing); } else { return renderer('bpmn:MultipleEventDefinition')(parentGfx, element, isThrowing); } } if (isTypedEvent(event, 'bpmn:MessageEventDefinition')) { return renderer('bpmn:MessageEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:TimerEventDefinition')) { return renderer('bpmn:TimerEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:ConditionalEventDefinition')) { return renderer('bpmn:ConditionalEventDefinition')(parentGfx, element); } if (isTypedEvent(event, 'bpmn:SignalEventDefinition')) { return renderer('bpmn:SignalEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:EscalationEventDefinition')) { return renderer('bpmn:EscalationEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:LinkEventDefinition')) { return renderer('bpmn:LinkEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:ErrorEventDefinition')) { return renderer('bpmn:ErrorEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:CancelEventDefinition')) { return renderer('bpmn:CancelEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:CompensateEventDefinition')) { return renderer('bpmn:CompensateEventDefinition')(parentGfx, element, isThrowing); } if (isTypedEvent(event, 'bpmn:TerminateEventDefinition')) { return renderer('bpmn:TerminateEventDefinition')(parentGfx, element, isThrowing); } return null; } function renderLabel(parentGfx, label, options) { options = assign({ size: { width: 100 } }, options); var text = textRenderer.createText(label || '', options); classes$1(text).add('djs-label'); append(parentGfx, text); return text; } function renderEmbeddedLabel(parentGfx, element, align) { var semantic = getSemantic(element); return renderLabel(parentGfx, semantic.name, { box: element, align: align, padding: 5, style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) } }); } function renderExternalLabel(parentGfx, element) { var box = { width: 90, height: 30, x: element.width / 2 + element.x, y: element.height / 2 + element.y }; return renderLabel(parentGfx, getLabel(element), { box: box, fitBox: true, style: assign( {}, textRenderer.getExternalStyle(), { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) } ) }); } function renderLaneLabel(parentGfx, text, element) { var textBox = renderLabel(parentGfx, text, { box: { height: 30, width: element.height }, align: 'center-middle', style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) } }); var top = -1 * element.height; transform(textBox, 0, -top, 270); } 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; } var handlers = this.handlers = { 'bpmn:Event': function(parentGfx, element, attrs) { if (!('fillOpacity' in attrs)) { attrs.fillOpacity = DEFAULT_FILL_OPACITY; } return drawCircle(parentGfx, element.width, element.height, attrs); }, 'bpmn:StartEvent': function(parentGfx, element) { var attrs = { fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }; var semantic = getSemantic(element); if (!semantic.isInterrupting) { attrs = { strokeDasharray: '6', strokeLinecap: 'round', fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }; } var circle = renderer('bpmn:Event')(parentGfx, element, attrs); renderEventContent(element, parentGfx); return circle; }, 'bpmn:MessageEventDefinition': function(parentGfx, element, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_MESSAGE', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.235, my: 0.315 } }); var fill = isThrowing ? getStrokeColor(element, defaultStrokeColor) : getFillColor(element, defaultFillColor); var stroke = isThrowing ? getFillColor(element, defaultFillColor) : getStrokeColor(element, defaultStrokeColor); var messagePath = drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill, stroke: stroke }); return messagePath; }, 'bpmn:TimerEventDefinition': function(parentGfx, element) { var circle = drawCircle(parentGfx, element.width, element.height, 0.2 * element.height, { strokeWidth: 2, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); 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(parentGfx, pathData, { strokeWidth: 2, strokeLinecap: 'square', stroke: getStrokeColor(element, defaultStrokeColor) }); 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(parentGfx, linePathData, { strokeWidth: 1, strokeLinecap: 'square', transform: 'rotate(' + (i * 30) + ',' + height + ',' + width + ')', stroke: getStrokeColor(element, defaultStrokeColor) }); } return circle; }, 'bpmn:EscalationEventDefinition': function(parentGfx, event, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_ESCALATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.5, my: 0.2 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none'; return drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill, stroke: getStrokeColor(event, defaultStrokeColor) }); }, 'bpmn:ConditionalEventDefinition': function(parentGfx, event) { var pathData = pathMap.getScaledPath('EVENT_CONDITIONAL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.5, my: 0.222 } }); return drawPath(parentGfx, pathData, { strokeWidth: 1, stroke: getStrokeColor(event, defaultStrokeColor) }); }, 'bpmn:LinkEventDefinition': function(parentGfx, event, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_LINK', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.57, my: 0.263 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none'; return drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill, stroke: getStrokeColor(event, defaultStrokeColor) }); }, 'bpmn:ErrorEventDefinition': function(parentGfx, event, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_ERROR', { xScaleFactor: 1.1, yScaleFactor: 1.1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.2, my: 0.722 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none'; return drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill, stroke: getStrokeColor(event, defaultStrokeColor) }); }, 'bpmn:CancelEventDefinition': function(parentGfx, event, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_CANCEL_45', { xScaleFactor: 1.0, yScaleFactor: 1.0, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.638, my: -0.055 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none'; var path = drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill, stroke: getStrokeColor(event, defaultStrokeColor) }); rotate(path, 45); return path; }, 'bpmn:CompensateEventDefinition': function(parentGfx, event, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_COMPENSATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.22, my: 0.5 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none'; return drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill, stroke: getStrokeColor(event, defaultStrokeColor) }); }, 'bpmn:SignalEventDefinition': function(parentGfx, event, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_SIGNAL', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.5, my: 0.2 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none'; return drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill, stroke: getStrokeColor(event, defaultStrokeColor) }); }, 'bpmn:MultipleEventDefinition': function(parentGfx, event, isThrowing) { var pathData = pathMap.getScaledPath('EVENT_MULTIPLE', { xScaleFactor: 1.1, yScaleFactor: 1.1, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.222, my: 0.36 } }); var fill = isThrowing ? getStrokeColor(event, defaultStrokeColor) : 'none'; return drawPath(parentGfx, pathData, { strokeWidth: 1, fill: fill }); }, 'bpmn:ParallelMultipleEventDefinition': function(parentGfx, event) { var pathData = pathMap.getScaledPath('EVENT_PARALLEL_MULTIPLE', { xScaleFactor: 1.2, yScaleFactor: 1.2, containerWidth: event.width, containerHeight: event.height, position: { mx: 0.458, my: 0.194 } }); return drawPath(parentGfx, pathData, { strokeWidth: 1, fill: getStrokeColor(event, defaultStrokeColor), stroke: getStrokeColor(event, defaultStrokeColor) }); }, 'bpmn:EndEvent': function(parentGfx, element) { var circle = renderer('bpmn:Event')(parentGfx, element, { strokeWidth: 4, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); renderEventContent(element, parentGfx); return circle; }, 'bpmn:TerminateEventDefinition': function(parentGfx, element) { var circle = drawCircle(parentGfx, element.width, element.height, 8, { strokeWidth: 4, fill: getStrokeColor(element, defaultStrokeColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return circle; }, 'bpmn:IntermediateEvent': function(parentGfx, element) { var outer = renderer('bpmn:Event')(parentGfx, element, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); /* inner */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, { strokeWidth: 1, fill: getFillColor(element, 'none'), stroke: getStrokeColor(element, defaultStrokeColor) }); renderEventContent(element, parentGfx); return outer; }, 'bpmn:IntermediateCatchEvent': as('bpmn:IntermediateEvent'), 'bpmn:IntermediateThrowEvent': as('bpmn:IntermediateEvent'), 'bpmn:Activity': function(parentGfx, element, attrs) { attrs = attrs || {}; if (!('fillOpacity' in attrs)) { attrs.fillOpacity = DEFAULT_FILL_OPACITY; } return drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, attrs); }, 'bpmn:Task': function(parentGfx, element) { var attrs = { fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }; var rect = renderer('bpmn:Activity')(parentGfx, element, attrs); renderEmbeddedLabel(parentGfx, element, 'center-middle'); attachTaskMarkers(parentGfx, element); return rect; }, 'bpmn:ServiceTask': function(parentGfx, element) { var task = renderer('bpmn:Task')(parentGfx, element); var pathDataBG = pathMap.getScaledPath('TASK_TYPE_SERVICE', { abspos: { x: 12, y: 18 } }); /* service bg */ drawPath(parentGfx, pathDataBG, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); var fillPathData = pathMap.getScaledPath('TASK_TYPE_SERVICE_FILL', { abspos: { x: 17.2, y: 18 } }); /* service fill */ drawPath(parentGfx, fillPathData, { strokeWidth: 0, fill: getFillColor(element, defaultFillColor) }); var pathData = pathMap.getScaledPath('TASK_TYPE_SERVICE', { abspos: { x: 17, y: 22 } }); /* service */ drawPath(parentGfx, pathData, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return task; }, 'bpmn:UserTask': function(parentGfx, element) { var task = renderer('bpmn:Task')(parentGfx, element); var x = 15; var y = 12; var pathData = pathMap.getScaledPath('TASK_TYPE_USER_1', { abspos: { x: x, y: y } }); /* user path */ drawPath(parentGfx, pathData, { strokeWidth: 0.5, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); var pathData2 = pathMap.getScaledPath('TASK_TYPE_USER_2', { abspos: { x: x, y: y } }); /* user2 path */ drawPath(parentGfx, pathData2, { strokeWidth: 0.5, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); var pathData3 = pathMap.getScaledPath('TASK_TYPE_USER_3', { abspos: { x: x, y: y } }); /* user3 path */ drawPath(parentGfx, pathData3, { strokeWidth: 0.5, fill: getStrokeColor(element, defaultStrokeColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return task; }, 'bpmn:ManualTask': function(parentGfx, element) { var task = renderer('bpmn:Task')(parentGfx, element); var pathData = pathMap.getScaledPath('TASK_TYPE_MANUAL', { abspos: { x: 17, y: 15 } }); /* manual path */ drawPath(parentGfx, pathData, { strokeWidth: 0.5, // 0.25, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return task; }, 'bpmn:SendTask': function(parentGfx, element) { var task = renderer('bpmn:Task')(parentGfx, element); var pathData = pathMap.getScaledPath('TASK_TYPE_SEND', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: 21, containerHeight: 14, position: { mx: 0.285, my: 0.357 } }); /* send path */ drawPath(parentGfx, pathData, { strokeWidth: 1, fill: getStrokeColor(element, defaultStrokeColor), stroke: getFillColor(element, defaultFillColor) }); return task; }, 'bpmn:ReceiveTask' : function(parentGfx, element) { var semantic = getSemantic(element); var task = renderer('bpmn:Task')(parentGfx, element); var pathData; if (semantic.instantiate) { drawCircle(parentGfx, 28, 28, 20 * 0.22, { strokeWidth: 1 }); pathData = pathMap.getScaledPath('TASK_TYPE_INSTANTIATING_SEND', { abspos: { x: 7.77, y: 9.52 } }); } else { pathData = pathMap.getScaledPath('TASK_TYPE_SEND', { xScaleFactor: 0.9, yScaleFactor: 0.9, containerWidth: 21, containerHeight: 14, position: { mx: 0.3, my: 0.4 } }); } /* receive path */ drawPath(parentGfx, pathData, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return task; }, 'bpmn:ScriptTask': function(parentGfx, element) { var task = renderer('bpmn:Task')(parentGfx, element); var pathData = pathMap.getScaledPath('TASK_TYPE_SCRIPT', { abspos: { x: 15, y: 20 } }); /* script path */ drawPath(parentGfx, pathData, { strokeWidth: 1, stroke: getStrokeColor(element, defaultStrokeColor) }); return task; }, 'bpmn:BusinessRuleTask': function(parentGfx, element) { var task = renderer('bpmn:Task')(parentGfx, element); var headerPathData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_HEADER', { abspos: { x: 8, y: 8 } }); var businessHeaderPath = drawPath(parentGfx, headerPathData); attr$1(businessHeaderPath, { strokeWidth: 1, fill: getFillColor(element, '#aaaaaa'), stroke: getStrokeColor(element, defaultStrokeColor) }); var headerData = pathMap.getScaledPath('TASK_TYPE_BUSINESS_RULE_MAIN', { abspos: { x: 8, y: 8 } }); var businessPath = drawPath(parentGfx, headerData); attr$1(businessPath, { strokeWidth: 1, stroke: getStrokeColor(element, defaultStrokeColor) }); return task; }, 'bpmn:SubProcess': function(parentGfx, element, attrs) { attrs = assign({ fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }, attrs); var rect = renderer('bpmn:Activity')(parentGfx, element, attrs); var expanded = isExpanded(element); if (isEventSubProcess(element)) { attr$1(rect, { strokeDasharray: '1,2' }); } renderEmbeddedLabel(parentGfx, element, expanded ? 'center-top' : 'center-middle'); if (expanded) { attachTaskMarkers(parentGfx, element); } else { attachTaskMarkers(parentGfx, element, [ 'SubProcessMarker' ]); } return rect; }, 'bpmn:AdHocSubProcess': function(parentGfx, element) { return renderer('bpmn:SubProcess')(parentGfx, element); }, 'bpmn:Transaction': function(parentGfx, element) { var outer = renderer('bpmn:SubProcess')(parentGfx, element); var innerAttrs = styles.style([ 'no-fill', 'no-events' ], { stroke: getStrokeColor(element, defaultStrokeColor) }); /* inner path */ drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS - 2, INNER_OUTER_DIST, innerAttrs); return outer; }, 'bpmn:CallActivity': function(parentGfx, element) { return renderer('bpmn:SubProcess')(parentGfx, element, { strokeWidth: 5 }); }, 'bpmn:Participant': function(parentGfx, element) { var attrs = { fillOpacity: DEFAULT_FILL_OPACITY, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }; var lane = renderer('bpmn:Lane')(parentGfx, element, attrs); var expandedPool = isExpanded(element); if (expandedPool) { drawLine(parentGfx, [ { x: 30, y: 0 }, { x: 30, y: element.height } ], { stroke: getStrokeColor(element, defaultStrokeColor) }); var text = getSemantic(element).name; renderLaneLabel(parentGfx, text, element); } else { // Collapsed pool draw text inline var text2 = getSemantic(element).name; renderLabel(parentGfx, text2, { box: element, align: 'center-middle', style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) } }); } var participantMultiplicity = !!(getSemantic(element).participantMultiplicity); if (participantMultiplicity) { renderer('ParticipantMultiplicityMarker')(parentGfx, element); } return lane; }, 'bpmn:Lane': function(parentGfx, element, attrs) { var rect = drawRect(parentGfx, element.width, element.height, 0, assign({ fill: getFillColor(element, defaultFillColor), fillOpacity: HIGH_FILL_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor) }, attrs)); var semantic = getSemantic(element); if (semantic.$type === 'bpmn:Lane') { var text = semantic.name; renderLaneLabel(parentGfx, text, element); } return rect; }, 'bpmn:InclusiveGateway': function(parentGfx, element) { var diamond = renderer('bpmn:Gateway')(parentGfx, element); /* circle path */ drawCircle(parentGfx, element.width, element.height, element.height * 0.24, { strokeWidth: 2.5, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return diamond; }, 'bpmn:ExclusiveGateway': function(parentGfx, element) { var diamond = renderer('bpmn:Gateway')(parentGfx, element); var pathData = pathMap.getScaledPath('GATEWAY_EXCLUSIVE', { xScaleFactor: 0.4, yScaleFactor: 0.4, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.32, my: 0.3 } }); if ((getDi(element).isMarkerVisible)) { drawPath(parentGfx, pathData, { strokeWidth: 1, fill: getStrokeColor(element, defaultStrokeColor), stroke: getStrokeColor(element, defaultStrokeColor) }); } return diamond; }, 'bpmn:ComplexGateway': function(parentGfx, element) { var diamond = renderer('bpmn:Gateway')(parentGfx, element); var pathData = pathMap.getScaledPath('GATEWAY_COMPLEX', { xScaleFactor: 0.5, yScaleFactor:0.5, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.46, my: 0.26 } }); /* complex path */ drawPath(parentGfx, pathData, { strokeWidth: 1, fill: getStrokeColor(element, defaultStrokeColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return diamond; }, 'bpmn:ParallelGateway': function(parentGfx, element) { var diamond = renderer('bpmn:Gateway')(parentGfx, element); var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', { xScaleFactor: 0.6, yScaleFactor:0.6, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.46, my: 0.2 } }); /* parallel path */ drawPath(parentGfx, pathData, { strokeWidth: 1, fill: getStrokeColor(element, defaultStrokeColor), stroke: getStrokeColor(element, defaultStrokeColor) }); return diamond; }, 'bpmn:EventBasedGateway': function(parentGfx, element) { var semantic = getSemantic(element); var diamond = renderer('bpmn:Gateway')(parentGfx, element); /* outer circle path */ drawCircle(parentGfx, element.width, element.height, element.height * 0.20, { strokeWidth: 1, fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor) }); var type = semantic.eventGatewayType; var instantiate = !!semantic.instantiate; function drawEvent() { var pathData = pathMap.getScaledPath('GATEWAY_EVENT_BASED', { xScaleFactor: 0.18, yScaleFactor: 0.18, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.36, my: 0.44 } }); var attrs = { strokeWidth: 2, fill: getFillColor(element, 'none'), stroke: getStrokeColor(element, defaultStrokeColor) }; /* event path */ drawPath(parentGfx, pathData, attrs); } if (type === 'Parallel') { var pathData = pathMap.getScaledPath('GATEWAY_PARALLEL', { xScaleFactor: 0.4, yScaleFactor:0.4, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.474, my: 0.296 } }); var parallelPath = drawPath(parentGfx, pathData); attr$1(parallelPath, { strokeWidth: 1, fill: 'none' }); } else if (type === 'Exclusive') { if (!instantiate) { var innerCircle = drawCircle(parentGfx, element.width, element.height, element.height * 0.26); attr$1(innerCircle, { strokeWidth: 1, fill: 'none', stroke: getStrokeColor(element, defaultStrokeColor) }); } drawEvent(); } return diamond; }, 'bpmn:Gateway': function(parentGfx, element) { var attrs = { fill: getFillColor(element, defaultFillColor), fillOpacity: DEFAULT_FILL_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor) }; return drawDiamond(parentGfx, element.width, element.height, attrs); }, 'bpmn:SequenceFlow': function(parentGfx, element) { var pathData = createPathFromConnection(element); var fill = getFillColor(element, defaultFillColor), stroke = getStrokeColor(element, defaultStrokeColor); var attrs = { strokeLinejoin: 'round', markerEnd: marker('sequenceflow-end', fill, stroke), stroke: getStrokeColor(element, defaultStrokeColor) }; var path = drawPath(parentGfx, pathData, attrs); var sequenceFlow = getSemantic(element); var source; if (element.source) { source = element.source.businessObject; // conditional flow marker if (sequenceFlow.conditionExpression && source.$instanceOf('bpmn:Activity')) { attr$1(path, { markerStart: marker('conditional-flow-marker', fill, stroke) }); } // default marker if (source.default && (source.$instanceOf('bpmn:Gateway') || source.$instanceOf('bpmn:Activity')) && source.default === sequenceFlow) { attr$1(path, { markerStart: marker('conditional-default-flow-marker', fill, stroke) }); } } return path; }, 'bpmn:Association': function(parentGfx, element, attrs) { var semantic = getSemantic(element); var fill = getFillColor(element, defaultFillColor), stroke = getStrokeColor(element, defaultStrokeColor); attrs = assign({ strokeDasharray: '0.5, 5', strokeLinecap: 'round', strokeLinejoin: 'round', stroke: getStrokeColor(element, defaultStrokeColor) }, attrs || {}); if (semantic.associationDirection === 'One' || semantic.associationDirection === 'Both') { attrs.markerEnd = marker('association-end', fill, stroke); } if (semantic.associationDirection === 'Both') { attrs.markerStart = marker('association-start', fill, stroke); } return drawLine(parentGfx, element.waypoints, attrs); }, 'bpmn:DataInputAssociation': function(parentGfx, element) { var fill = getFillColor(element, defaultFillColor), stroke = getStrokeColor(element, defaultStrokeColor); return renderer('bpmn:Association')(parentGfx, element, { markerEnd: marker('association-end', fill, stroke) }); }, 'bpmn:DataOutputAssociation': function(parentGfx, element) { var fill = getFillColor(element, defaultFillColor), stroke = getStrokeColor(element, defaultStrokeColor); return renderer('bpmn:Association')(parentGfx, element, { markerEnd: marker('association-end', fill, stroke) }); }, 'bpmn:MessageFlow': function(parentGfx, element) { var semantic = getSemantic(element), di = getDi(element); var fill = getFillColor(element, defaultFillColor), stroke = getStrokeColor(element, defaultStrokeColor); var pathData = createPathFromConnection(element); var attrs = { markerEnd: marker('messageflow-end', fill, stroke), markerStart: marker('messageflow-start', fill, stroke), strokeDasharray: '10, 12', strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: '1.5px', stroke: getStrokeColor(element, defaultStrokeColor) }; var path = drawPath(parentGfx, pathData, attrs); if (semantic.messageRef) { var midPoint = path.getPointAtLength(path.getTotalLength() / 2); var markerPathData = pathMap.getScaledPath('MESSAGE_FLOW_MARKER', { abspos: { x: midPoint.x, y: midPoint.y } }); var messageAttrs = { strokeWidth: 1 }; if (di.messageVisibleKind === 'initiating') { messageAttrs.fill = 'white'; messageAttrs.stroke = black; } else { messageAttrs.fill = '#888'; messageAttrs.stroke = 'white'; } var message = drawPath(parentGfx, markerPathData, messageAttrs); var labelText = semantic.messageRef.name; var label = renderLabel(parentGfx, labelText, { align: 'center-top', fitBox: true, style: { fill: getStrokeColor(element, defaultLabelColor) } }); var messageBounds = message.getBBox(), labelBounds = label.getBBox(); var translateX = midPoint.x - labelBounds.width / 2, translateY = midPoint.y + messageBounds.height / 2 + ELEMENT_LABEL_DISTANCE; transform(label, translateX, translateY, 0); } return path; }, 'bpmn:DataObject': function(parentGfx, element) { var pathData = pathMap.getScaledPath('DATA_OBJECT_PATH', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.474, my: 0.296 } }); var elementObject = drawPath(parentGfx, pathData, { fill: getFillColor(element, defaultFillColor), fillOpacity: DEFAULT_FILL_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor) }); var semantic = getSemantic(element); if (isCollection(semantic)) { renderDataItemCollection(parentGfx, element); } return elementObject; }, 'bpmn:DataObjectReference': as('bpmn:DataObject'), 'bpmn:DataInput': function(parentGfx, element) { var arrowPathData = pathMap.getRawPath('DATA_ARROW'); // page var elementObject = renderer('bpmn:DataObject')(parentGfx, element); /* input arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1 }); return elementObject; }, 'bpmn:DataOutput': function(parentGfx, element) { var arrowPathData = pathMap.getRawPath('DATA_ARROW'); // page var elementObject = renderer('bpmn:DataObject')(parentGfx, element); /* output arrow path */ drawPath(parentGfx, arrowPathData, { strokeWidth: 1, fill: black }); return elementObject; }, 'bpmn:DataStoreReference': function(parentGfx, element) { var DATA_STORE_PATH = pathMap.getScaledPath('DATA_STORE', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0, my: 0.133 } }); var elementStore = drawPath(parentGfx, DATA_STORE_PATH, { strokeWidth: 2, fill: getFillColor(element, defaultFillColor), fillOpacity: DEFAULT_FILL_OPACITY, stroke: getStrokeColor(element, defaultStrokeColor) }); return elementStore; }, 'bpmn:BoundaryEvent': function(parentGfx, element) { var semantic = getSemantic(element), cancel = semantic.cancelActivity; var attrs = { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }; if (!cancel) { attrs.strokeDasharray = '6'; attrs.strokeLinecap = 'round'; } // apply fillOpacity var outerAttrs = assign({}, attrs, { fillOpacity: 1 }); // apply no-fill var innerAttrs = assign({}, attrs, { fill: 'none' }); var outer = renderer('bpmn:Event')(parentGfx, element, outerAttrs); /* inner path */ drawCircle(parentGfx, element.width, element.height, INNER_OUTER_DIST, innerAttrs); renderEventContent(element, parentGfx); return outer; }, 'bpmn:Group': function(parentGfx, element) { var group = drawRect(parentGfx, element.width, element.height, TASK_BORDER_RADIUS, { stroke: getStrokeColor(element, defaultStrokeColor), strokeWidth: 1, strokeDasharray: '8,3,1,3', fill: 'none', pointerEvents: 'none' }); return group; }, 'label': function(parentGfx, element) { return renderExternalLabel(parentGfx, element); }, 'bpmn:TextAnnotation': function(parentGfx, element) { var style = { 'fill': 'none', 'stroke': 'none' }; var textElement = drawRect(parentGfx, 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(parentGfx, textPathData, { stroke: getStrokeColor(element, defaultStrokeColor) }); var text = getSemantic(element).text || ''; renderLabel(parentGfx, text, { box: element, align: 'left-top', padding: 5, style: { fill: getLabelColor(element, defaultLabelColor, defaultStrokeColor) } }); return textElement; }, 'ParticipantMultiplicityMarker': function(parentGfx, element) { var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2) / element.width), my: (element.height - 15) / element.height } }); drawMarker('participant-multiplicity', parentGfx, markerPath, { strokeWidth: 2, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); }, 'SubProcessMarker': function(parentGfx, element) { var markerRect = drawRect(parentGfx, 14, 14, 0, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); // Process marker is placed in the middle of the box // therefore fixed values can be used here translate$1(markerRect, element.width / 2 - 7.5, element.height - 20); var markerPath = pathMap.getScaledPath('MARKER_SUB_PROCESS', { xScaleFactor: 1.5, yScaleFactor: 1.5, containerWidth: element.width, containerHeight: element.height, position: { mx: (element.width / 2 - 7.5) / element.width, my: (element.height - 20) / element.height } }); drawMarker('sub-process', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); }, 'ParallelMarker': function(parentGfx, element, position) { var markerPath = pathMap.getScaledPath('MARKER_PARALLEL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + position.parallel) / element.width), my: (element.height - 20) / element.height } }); drawMarker('parallel', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); }, 'SequentialMarker': function(parentGfx, element, position) { var markerPath = pathMap.getScaledPath('MARKER_SEQUENTIAL', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + position.seq) / element.width), my: (element.height - 19) / element.height } }); drawMarker('sequential', parentGfx, markerPath, { fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); }, 'CompensationMarker': function(parentGfx, element, position) { var markerMath = pathMap.getScaledPath('MARKER_COMPENSATION', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + position.compensation) / element.width), my: (element.height - 13) / element.height } }); drawMarker('compensation', parentGfx, markerMath, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor) }); }, 'LoopMarker': function(parentGfx, element, position) { var markerPath = pathMap.getScaledPath('MARKER_LOOP', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + position.loop) / element.width), my: (element.height - 7) / element.height } }); drawMarker('loop', parentGfx, markerPath, { strokeWidth: 1, fill: getFillColor(element, defaultFillColor), stroke: getStrokeColor(element, defaultStrokeColor), strokeLinecap: 'round', strokeMiterlimit: 0.5 }); }, 'AdhocMarker': function(parentGfx, element, position) { var markerPath = pathMap.getScaledPath('MARKER_ADHOC', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: ((element.width / 2 + position.adhoc) / element.width), my: (element.height - 15) / element.height } }); drawMarker('adhoc', parentGfx, markerPath, { strokeWidth: 1, fill: getStrokeColor(element, defaultStrokeColor), stroke: getStrokeColor(element, defaultStrokeColor) }); } }; function attachTaskMarkers(parentGfx, element, taskMarkers) { var obj = getSemantic(element); var subprocess = taskMarkers && taskMarkers.indexOf('SubProcessMarker') !== -1; var position; if (subprocess) { position = { seq: -21, parallel: -22, compensation: -42, loop: -18, adhoc: 10 }; } else { position = { seq: -3, parallel: -6, compensation: -27, loop: 0, adhoc: 10 }; } forEach$1(taskMarkers, function(marker) { renderer(marker)(parentGfx, element, position); }); if (obj.isForCompensation) { renderer('CompensationMarker')(parentGfx, element, position); } if (obj.$type === 'bpmn:AdHocSubProcess') { renderer('AdhocMarker')(parentGfx, element, position); } var loopCharacteristics = obj.loopCharacteristics, isSequential = loopCharacteristics && loopCharacteristics.isSequential; if (loopCharacteristics) { if (isSequential === undefined) { renderer('LoopMarker')(parentGfx, element, position); } if (isSequential === false) { renderer('ParallelMarker')(parentGfx, element, position); } if (isSequential === true) { renderer('SequentialMarker')(parentGfx, element, position); } } } function renderDataItemCollection(parentGfx, element) { var yPosition = (element.height - 18) / element.height; var pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', { xScaleFactor: 1, yScaleFactor: 1, containerWidth: element.width, containerHeight: element.height, position: { mx: 0.33, my: yPosition } }); /* collection path */ drawPath(parentGfx, pathData, { strokeWidth: 2 }); } // extension API, use at your own risk this._drawPath = drawPath; this._renderer = renderer; } e(BpmnRenderer, BaseRenderer); BpmnRenderer.$inject = [ 'config.bpmnRenderer', 'eventBus', 'styles', 'pathMap', 'canvas', 'textRenderer' ]; BpmnRenderer.prototype.canRender = function(element) { return is$1(element, 'bpmn:BaseElement'); }; BpmnRenderer.prototype.drawShape = function(parentGfx, element) { var type = element.type; var h = this._renderer(type); /* jshint -W040 */ return h(parentGfx, element); }; BpmnRenderer.prototype.drawConnection = function(parentGfx, element) { var type = element.type; var h = this._renderer(type); /* jshint -W040 */ return h(parentGfx, element); }; BpmnRenderer.prototype.getShapePath = function(element) { if (is$1(element, 'bpmn:Event')) { return getCirclePath(element); } if (is$1(element, 'bpmn:Activity')) { return getRoundRectPath(element, TASK_BORDER_RADIUS); } if (is$1(element, 'bpmn:Gateway')) { return getDiamondPath(element); } return getRectPath(element); }; var DEFAULT_BOX_PADDING = 0; var DEFAULT_LABEL_SIZE$1 = { width: 150, height: 50 }; function parseAlign(align) { var parts = align.split('-'); return { horizontal: parts[0] || 'center', vertical: parts[1] || 'top' }; } function parsePadding(padding) { if (isObject(padding)) { return assign({ top: 0, left: 0, right: 0, bottom: 0 }, padding); } else { return { top: padding, left: padding, right: padding, bottom: padding }; } } function getTextBBox(text, fakeText) { fakeText.textContent = text; var textBBox; try { var bbox, emptyLine = text === ''; // add dummy text, when line is empty to // determine correct height fakeText.textContent = emptyLine ? 'dummy' : text; textBBox = fakeText.getBBox(); // take text rendering related horizontal // padding into account bbox = { width: textBBox.width + textBBox.x * 2, height: textBBox.height }; if (emptyLine) { // correct width bbox.width = 0; } return bbox; } catch (e) { return { width: 0, height: 0 }; } } /** * Layout the next line and return the layouted element. * * Alters the lines passed. * * @param {Array} lines * @return {Object} the line descriptor, an object { width, height, text } */ function layoutNext(lines, maxWidth, fakeText) { var originalLine = lines.shift(), fitLine = originalLine; var textBBox; for (;;) { textBBox = getTextBBox(fitLine, fakeText); textBBox.width = fitLine ? textBBox.width : 0; // try to fit if (fitLine === ' ' || fitLine === '' || textBBox.width < Math.round(maxWidth) || fitLine.length < 2) { return fit(lines, fitLine, originalLine, textBBox); } fitLine = shortenLine(fitLine, textBBox.width, maxWidth); } } function fit(lines, fitLine, originalLine, textBBox) { if (fitLine.length < originalLine.length) { var remainder = originalLine.slice(fitLine.length).trim(); lines.unshift(remainder); } return { width: textBBox.width, height: textBBox.height, text: fitLine }; } var SOFT_BREAK = '\u00AD'; /** * Shortens a line based on spacing and hyphens. * Returns the shortened result on success. * * @param {string} line * @param {number} maxLength the maximum characters of the string * @return {string} the shortened string */ function semanticShorten(line, maxLength) { var parts = line.split(/(\s|-|\u00AD)/g), part, shortenedParts = [], length = 0; // try to shorten via break chars if (parts.length > 1) { while ((part = parts.shift())) { if (part.length + length < maxLength) { shortenedParts.push(part); length += part.length; } else { // remove previous part, too if hyphen does not fit anymore if (part === '-' || part === SOFT_BREAK) { shortenedParts.pop(); } break; } } } var last = shortenedParts[shortenedParts.length - 1]; // translate trailing soft break to actual hyphen if (last && last === SOFT_BREAK) { shortenedParts[shortenedParts.length - 1] = '-'; } return shortenedParts.join(''); } function shortenLine(line, width, maxWidth) { var length = Math.max(line.length * (maxWidth / width), 1); // try to shorten semantically (i.e. based on spaces and hyphens) var shortenedLine = semanticShorten(line, length); if (!shortenedLine) { // force shorten by cutting the long word shortenedLine = line.slice(0, Math.max(Math.round(length - 1), 1)); } return shortenedLine; } function getHelperSvg() { var helperSvg = document.getElementById('helper-svg'); if (!helperSvg) { helperSvg = create$1('svg'); attr$1(helperSvg, { id: 'helper-svg' }); assign$1(helperSvg, { visibility: 'hidden', position: 'fixed', width: 0, height: 0 }); document.body.appendChild(helperSvg); } return helperSvg; } /** * Creates a new label utility * * @param {Object} config * @param {Dimensions} config.size * @param {number} config.padding * @param {Object} config.style * @param {string} config.align */ function Text(config) { this._config = assign({}, { size: DEFAULT_LABEL_SIZE$1, padding: DEFAULT_BOX_PADDING, style: {}, align: 'center-top' }, config || {}); } /** * Returns the layouted text as an SVG element. * * @param {string} text * @param {Object} options * * @return {SVGElement} */ Text.prototype.createText = function(text, options) { return this.layoutText(text, options).element; }; /** * Returns a labels layouted dimensions. * * @param {string} text to layout * @param {Object} options * * @return {Dimensions} */ Text.prototype.getDimensions = function(text, options) { return this.layoutText(text, options).dimensions; }; /** * Creates and returns a label and its bounding box. * * @method Text#createText * * @param {string} text the text to render on the label * @param {Object} options * @param {string} options.align how to align in the bounding box. * Any of { 'center-middle', 'center-top' }, * defaults to 'center-top'. * @param {string} options.style style to be applied to the text * @param {boolean} options.fitBox indicates if box will be recalculated to * fit text * * @return {Object} { element, dimensions } */ Text.prototype.layoutText = function(text, options) { var box = assign({}, this._config.size, options.box), style = assign({}, this._config.style, options.style), align = parseAlign(options.align || this._config.align), padding = parsePadding(options.padding !== undefined ? options.padding : this._config.padding), fitBox = options.fitBox || false; var lineHeight = getLineHeight(style); // we split text by lines and normalize // {soft break} + {line break} => { line break } var lines = text.split(/\u00AD?\r?\n/), layouted = []; var maxWidth = box.width - padding.left - padding.right; // ensure correct rendering by attaching helper text node to invisible SVG var helperText = create$1('text'); attr$1(helperText, { x: 0, y: 0 }); attr$1(helperText, style); var helperSvg = getHelperSvg(); append(helperSvg, helperText); while (lines.length) { layouted.push(layoutNext(lines, maxWidth, helperText)); } if (align.vertical === 'middle') { padding.top = padding.bottom = 0; } var totalHeight = reduce(layouted, function(sum, line, idx) { return sum + (lineHeight || line.height); }, 0) + padding.top + padding.bottom; var maxLineWidth = reduce(layouted, function(sum, line, idx) { return line.width > sum ? line.width : sum; }, 0); // the y position of the next line var y = padding.top; if (align.vertical === 'middle') { y += (box.height - totalHeight) / 2; } // magic number initial offset y -= (lineHeight || layouted[0].height) / 4; var textElement = create$1('text'); attr$1(textElement, style); // layout each line taking into account that parent // shape might resize to fit text size forEach$1(layouted, function(line) { var x; y += (lineHeight || line.height); switch (align.horizontal) { case 'left': x = padding.left; break; case 'right': x = ((fitBox ? maxLineWidth : maxWidth) - padding.right - line.width); break; default: // aka center x = Math.max((((fitBox ? maxLineWidth : maxWidth) - line.width) / 2 + padding.left), 0); } var tspan = create$1('tspan'); attr$1(tspan, { x: x, y: y }); tspan.textContent = line.text; append(textElement, tspan); }); remove$2(helperText); var dimensions = { width: maxLineWidth, height: totalHeight }; return { dimensions: dimensions, element: textElement }; }; function getLineHeight(style) { if ('fontSize' in style && 'lineHeight' in style) { return style.lineHeight * parseInt(style.fontSize, 10); } } var DEFAULT_FONT_SIZE = 12; var LINE_HEIGHT_RATIO = 1.2; var MIN_TEXT_ANNOTATION_HEIGHT = 30; function TextRenderer(config) { var defaultStyle = assign({ fontFamily: 'Arial, sans-serif', fontSize: DEFAULT_FONT_SIZE, fontWeight: 'normal', lineHeight: LINE_HEIGHT_RATIO }, config && config.defaultStyle || {}); var fontSize = parseInt(defaultStyle.fontSize, 10) - 1; var externalStyle = assign({}, defaultStyle, { fontSize: fontSize }, config && config.externalStyle || {}); var textUtil = new Text({ style: defaultStyle }); /** * Get the new bounds of an externally rendered, * layouted label. * * @param {Bounds} bounds * @param {string} text * * @return {Bounds} */ this.getExternalLabelBounds = function(bounds, text) { var layoutedDimensions = textUtil.getDimensions(text, { box: { width: 90, height: 30, x: bounds.width / 2 + bounds.x, y: bounds.height / 2 + bounds.y }, style: externalStyle }); // resize label shape to fit label text return { x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2), y: Math.round(bounds.y), width: Math.ceil(layoutedDimensions.width), height: Math.ceil(layoutedDimensions.height) }; }; /** * Get the new bounds of text annotation. * * @param {Bounds} bounds * @param {string} text * * @return {Bounds} */ this.getTextAnnotationBounds = function(bounds, text) { var layoutedDimensions = textUtil.getDimensions(text, { box: bounds, style: defaultStyle, align: 'left-top', padding: 5 }); return { x: bounds.x, y: bounds.y, width: bounds.width, height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height)) }; }; /** * Create a layouted text element. * * @param {string} text * @param {Object} [options] * * @return {SVGElement} rendered text */ this.createText = function(text, options) { return textUtil.createText(text, options || {}); }; /** * Get default text style. */ this.getDefaultStyle = function() { return defaultStyle; }; /** * Get the external text style. */ this.getExternalStyle = function() { return externalStyle; }; } TextRenderer.$inject = [ 'config.textRenderer' ]; /** * Map containing SVG paths needed by BpmnRenderer. */ function PathMap() { /** * Contains a map of path elements * *

Path definition

* A parameterized path is defined like this: *
     * 'GATEWAY_PARALLEL': {
     *   d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
            '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
     *   height: 17.5,
     *   width:  17.5,
     *   heightElements: [2.5, 7.5],
     *   widthElements: [2.5, 7.5]
     * }
     * 
*

It's important to specify a correct height and width for the path as the scaling * is based on the ratio between the specified height and width in this object and the * height and width that is set as scale target (Note x,y coordinates will be scaled with * individual ratios).

*

The 'heightElements' and 'widthElements' array must contain the values that will be scaled. * The scaling is based on the computed ratios. * Coordinates on the y axis should be in the heightElement's array, they will be scaled using * the computed ratio coefficient. * In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets. *

    *
  • The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....
  • *
  • The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....
  • *
* The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index. *

*/ this.pathMap = { 'EVENT_MESSAGE': { d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}', height: 36, width: 36, heightElements: [ 6, 14 ], widthElements: [ 10.5, 21 ] }, 'EVENT_SIGNAL': { d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z', height: 36, width: 36, heightElements: [ 18 ], widthElements: [ 10, 20 ] }, 'EVENT_ESCALATION': { d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z', height: 36, width: 36, heightElements: [ 20, 7 ], widthElements: [ 8 ] }, 'EVENT_CONDITIONAL': { d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' + 'M {e.x2},{e.y3} l {e.x0},0 ' + 'M {e.x2},{e.y4} l {e.x0},0 ' + 'M {e.x2},{e.y5} l {e.x0},0 ' + 'M {e.x2},{e.y6} l {e.x0},0 ' + 'M {e.x2},{e.y7} l {e.x0},0 ' + 'M {e.x2},{e.y8} l {e.x0},0 ', height: 36, width: 36, heightElements: [ 8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5 ], widthElements: [ 10.5, 14.5, 12.5 ] }, 'EVENT_LINK': { d: 'm {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z', height: 36, width: 36, heightElements: [ 4.4375, 6.75, 7.8125 ], widthElements: [ 9.84375, 13.5 ] }, 'EVENT_ERROR': { d: 'm {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z', height: 36, width: 36, heightElements: [ 0.023, 8.737, 8.151, 16.564, 10.591, 8.714 ], widthElements: [ 0.085, 6.672, 6.97, 4.273, 5.337, 6.636 ] }, 'EVENT_CANCEL_45': { d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' + '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z', height: 36, width: 36, heightElements: [ 4.75, 8.5 ], widthElements: [ 4.75, 8.5 ] }, 'EVENT_COMPENSATION': { d: 'm {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z', height: 36, width: 36, heightElements: [ 6.5, 13, 0.4, 6.1 ], widthElements: [ 9, 9.3, 8.7 ] }, 'EVENT_TIMER_WH': { d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ', height: 36, width: 36, heightElements: [ 10, 2 ], widthElements: [ 3, 7 ] }, 'EVENT_TIMER_LINE': { d: 'M {mx},{my} ' + 'm {e.x0},{e.y0} l -{e.x1},{e.y1} ', height: 36, width: 36, heightElements: [ 10, 3 ], widthElements: [ 0, 0 ] }, 'EVENT_MULTIPLE': { d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z', height: 36, width: 36, heightElements: [ 6.28099, 12.56199 ], widthElements: [ 3.1405, 9.42149, 12.56198 ] }, 'EVENT_PARALLEL_MULTIPLE': { d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' + '-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z', height: 36, width: 36, heightElements: [ 2.56228, 7.68683 ], widthElements: [ 2.56228, 7.68683 ] }, 'GATEWAY_EXCLUSIVE': { d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' + '{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' + '{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z', height: 17.5, width: 17.5, heightElements: [ 8.5, 6.5312, -6.5312, -8.5 ], widthElements: [ 6.5, -6.5, 3, -3, 5, -5 ] }, 'GATEWAY_PARALLEL': { d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' + '0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z', height: 30, width: 30, heightElements: [ 5, 12.5 ], widthElements: [ 5, 12.5 ] }, 'GATEWAY_EVENT_BASED': { d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z', height: 11, width: 11, heightElements: [ -6, 6, 12, -12 ], widthElements: [ 9, -3, -12 ] }, 'GATEWAY_COMPLEX': { d:'m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} ' + '{e.x2},0 -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} ' + '{e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} ' + '-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z', height: 17.125, width: 17.125, heightElements: [ 4.875, 3.4375, 2.125, 3 ], widthElements: [ 3.4375, 2.125, 4.875, 3 ] }, 'DATA_OBJECT_PATH': { d:'m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0', height: 61, width: 51, heightElements: [ 10, 50, 60 ], widthElements: [ 10, 40, 50, 60 ] }, 'DATA_OBJECT_COLLECTION_PATH': { d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10', height: 10, width: 10, heightElements: [], widthElements: [] }, 'DATA_ARROW': { d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z', height: 61, width: 51, heightElements: [], widthElements: [] }, 'DATA_STORE': { d:'m {mx},{my} ' + 'l 0,{e.y2} ' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' + 'l 0,-{e.y2} ' + 'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' + 'm -{e.x2},{e.y0}' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' + 'm -{e.x2},{e.y0}' + 'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0', height: 61, width: 61, heightElements: [ 7, 10, 45 ], widthElements: [ 2, 58, 60 ] }, 'TEXT_ANNOTATION': { d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0', height: 30, width: 10, heightElements: [ 30 ], widthElements: [ 10 ] }, 'MARKER_SUB_PROCESS': { d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0', height: 10, width: 10, heightElements: [], widthElements: [] }, 'MARKER_PARALLEL': { d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10', height: 10, width: 10, heightElements: [], widthElements: [] }, 'MARKER_SEQUENTIAL': { d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0', height: 10, width: 10, heightElements: [], widthElements: [] }, 'MARKER_COMPENSATION': { d: 'm {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z', height: 10, width: 21, heightElements: [], widthElements: [] }, 'MARKER_LOOP': { d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' + '-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' + '0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' + 'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902', height: 13.9, width: 13.7, heightElements: [], widthElements: [] }, 'MARKER_ADHOC': { d: 'm {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 ' + '3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' + '1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 ' + '-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' + '-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z', height: 4, width: 15, heightElements: [], widthElements: [] }, 'TASK_TYPE_SEND': { d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}', height: 14, width: 21, heightElements: [ 6, 14 ], widthElements: [ 10.5, 21 ] }, 'TASK_TYPE_SCRIPT': { d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' + 'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' + 'm -7,-12 l 5,0 ' + 'm -4.5,3 l 4.5,0 ' + 'm -3,3 l 5,0' + 'm -4,3 l 5,0', height: 15, width: 12.6, heightElements: [ 6, 14 ], widthElements: [ 10.5, 21 ] }, 'TASK_TYPE_USER_1': { d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' + '-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' + '0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' + 'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' + 'm -8,6 l 0,5.5 m 11,0 l 0,-5' }, 'TASK_TYPE_USER_2': { d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' + '-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 ' }, 'TASK_TYPE_USER_3': { d: 'm {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 ' + '4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' + '-4.20799998,3.36699999 -4.20699998,4.34799999 z' }, 'TASK_TYPE_MANUAL': { d: 'm {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 ' + '-0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 ' + '0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 ' + '-1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 ' + '0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 ' + '-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' + '2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' + '-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' + '-1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 ' + '-0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 ' + '0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 ' + '-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z' }, 'TASK_TYPE_INSTANTIATING_SEND': { d: 'm {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6' }, 'TASK_TYPE_SERVICE': { d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' + '0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' + '-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' + 'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' + '-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' + '-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' + 'h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 ' + '-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' + 'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' + 'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' + '0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' + 'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' + 'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' + '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' + '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z' }, 'TASK_TYPE_SERVICE_FILL': { d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' + '0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' + '0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z' }, 'TASK_TYPE_BUSINESS_RULE_HEADER': { d: 'm {mx},{my} 0,4 20,0 0,-4 z' }, 'TASK_TYPE_BUSINESS_RULE_MAIN': { d: 'm {mx},{my} 0,12 20,0 0,-12 z' + 'm 0,8 l 20,0 ' + 'm -13,-4 l 0,8' }, 'MESSAGE_FLOW_MARKER': { d: 'm {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6' } }; this.getRawPath = function getRawPath(pathId) { return this.pathMap[pathId].d; }; /** * Scales the path to the given height and width. *

Use case

*

Use case is to scale the content of elements (event, gateways) based * on the element bounding box's size. *

*

Why not transform

*

Scaling a path with transform() will also scale the stroke and IE does not support * the option 'non-scaling-stroke' to prevent this. * Also there are use cases where only some parts of a path should be * scaled.

* * @param {string} pathId The ID of the path. * @param {Object} param

* Example param object scales the path to 60% size of the container (data.width, data.height). *

     *   {
     *     xScaleFactor: 0.6,
     *     yScaleFactor:0.6,
     *     containerWidth: data.width,
     *     containerHeight: data.height,
     *     position: {
     *       mx: 0.46,
     *       my: 0.2,
     *     }
     *   }
     *   
*
    *
  • targetpathwidth = xScaleFactor * containerWidth
  • *
  • targetpathheight = yScaleFactor * containerHeight
  • *
  • Position is used to set the starting coordinate of the path. M is computed: *
      *
    • position.x * containerWidth
    • *
    • position.y * containerHeight
    • *
    * Center of the container
     position: {
         *       mx: 0.5,
         *       my: 0.5,
         *     }
    * Upper left corner of the container *
     position: {
         *       mx: 0.0,
         *       my: 0.0,
         *     }
    *
  • *
*

* */ this.getScaledPath = function getScaledPath(pathId, param) { var rawPath = this.pathMap[pathId]; // positioning // compute the start point of the path var mx, my; if (param.abspos) { mx = param.abspos.x; my = param.abspos.y; } else { mx = param.containerWidth * param.position.mx; my = param.containerHeight * param.position.my; } var coordinates = {}; // map for the scaled coordinates if (param.position) { // path var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor; var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor; // Apply height ratio for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) { coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio; } // Apply width ratio for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) { coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio; } } // Apply value to raw path var path = format( rawPath.d, { mx: mx, my: my, e: coordinates } ); return path; }; } // helpers ////////////////////// // copied and adjusted from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js var tokenRegex = /\{([^{}]+)\}/g, objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties function replacer(all, key, obj) { var res = obj; key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) { name = name || quotedName; if (res) { if (name in res) { res = res[name]; } typeof res == 'function' && isFunc && (res = res()); } }); res = (res == null || res == obj ? all : res) + ''; return res; } function format(str, obj) { return String(str).replace(tokenRegex, function(all, key) { return replacer(all, key, obj); }); } var DrawModule$1 = { __init__: [ 'bpmnRenderer' ], bpmnRenderer: [ 'type', BpmnRenderer ], textRenderer: [ 'type', TextRenderer ], pathMap: [ 'type', PathMap ] }; /** * A simple translation stub to be used for multi-language support * in diagrams. Can be easily replaced with a more sophisticated * solution. * * @example * * // use it inside any diagram component by injecting `translate`. * * function MyService(translate) { * alert(translate('HELLO {you}', { you: 'You!' })); * } * * @param {string} template to interpolate * @param {Object} [replacements] a map with substitutes * * @return {string} the translated string */ function translate(template, replacements) { replacements = replacements || {}; return template.replace(/{([^}]+)}/g, function(_, key) { return replacements[key] || '{' + key + '}'; }); } var TranslateModule = { translate: [ 'value', translate ] }; var DEFAULT_LABEL_SIZE = { width: 90, height: 20 }; var FLOW_LABEL_INDENT = 15; /** * Returns true if the given semantic has an external label * * @param {BpmnElement} semantic * @return {boolean} true if has label */ function isLabelExternal(semantic) { return is$1(semantic, 'bpmn:Event') || is$1(semantic, 'bpmn:Gateway') || is$1(semantic, 'bpmn:DataStoreReference') || is$1(semantic, 'bpmn:DataObjectReference') || is$1(semantic, 'bpmn:DataInput') || is$1(semantic, 'bpmn:DataOutput') || is$1(semantic, 'bpmn:SequenceFlow') || is$1(semantic, 'bpmn:MessageFlow') || is$1(semantic, 'bpmn:Group'); } /** * Get the position for sequence flow labels * * @param {Array} waypoints * @return {Point} the label position */ function getFlowLabelPosition(waypoints) { // get the waypoints mid var mid = waypoints.length / 2 - 1; var first = waypoints[Math.floor(mid)]; var second = waypoints[Math.ceil(mid + 0.01)]; // get position var position = getWaypointsMid(waypoints); // calculate angle var angle = Math.atan((second.y - first.y) / (second.x - first.x)); var x = position.x, y = position.y; if (Math.abs(angle) < Math.PI / 2) { y -= FLOW_LABEL_INDENT; } else { x += FLOW_LABEL_INDENT; } return { x: x, y: y }; } /** * Get the middle of a number of waypoints * * @param {Array} waypoints * @return {Point} the mid point */ function getWaypointsMid(waypoints) { var mid = waypoints.length / 2 - 1; var first = waypoints[Math.floor(mid)]; var second = waypoints[Math.ceil(mid + 0.01)]; return { x: first.x + (second.x - first.x) / 2, y: first.y + (second.y - first.y) / 2 }; } function getExternalLabelMid(element) { if (element.waypoints) { return getFlowLabelPosition(element.waypoints); } else if (is$1(element, 'bpmn:Group')) { return { x: element.x + element.width / 2, y: element.y + DEFAULT_LABEL_SIZE.height / 2 }; } else { return { x: element.x + element.width / 2, y: element.y + element.height + DEFAULT_LABEL_SIZE.height / 2 }; } } /** * Returns the bounds of an elements label, parsed from the elements DI or * generated from its bounds. * * @param {BpmndDi} di * @param {djs.model.Base} element */ function getExternalLabelBounds(di, element) { var mid, size, bounds, label = di.label; if (label && label.bounds) { bounds = label.bounds; size = { width: Math.max(DEFAULT_LABEL_SIZE.width, bounds.width), height: bounds.height }; mid = { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height / 2 }; } else { mid = getExternalLabelMid(element); size = DEFAULT_LABEL_SIZE; } return assign({ x: mid.x - size.width / 2, y: mid.y - size.height / 2 }, size); } var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } function roundPoint(point) { return { x: Math.round(point.x), y: Math.round(point.y) }; } /** * Convert the given bounds to a { top, left, bottom, right } descriptor. * * @param {Bounds|Point} bounds * * @return {Object} */ function asTRBL(bounds) { return { top: bounds.y, right: bounds.x + (bounds.width || 0), bottom: bounds.y + (bounds.height || 0), left: bounds.x }; } /** * Convert a { top, left, bottom, right } to an objects bounds. * * @param {Object} trbl * * @return {Bounds} */ function asBounds(trbl) { return { x: trbl.left, y: trbl.top, width: trbl.right - trbl.left, height: trbl.bottom - trbl.top }; } /** * Get the mid of the given bounds or point. * * @param {Bounds|Point} bounds * * @return {Point} */ function getBoundsMid(bounds) { return roundPoint({ x: bounds.x + (bounds.width || 0) / 2, y: bounds.y + (bounds.height || 0) / 2 }); } /** * Get the mid of the given Connection. * * @param {djs.Base.Connection} connection * * @return {Point} */ function getConnectionMid(connection) { var waypoints = connection.waypoints; // calculate total length and length of each segment var parts = waypoints.reduce(function(parts, point, index) { var lastPoint = waypoints[index - 1]; if (lastPoint) { var lastPart = parts[parts.length - 1]; var startLength = lastPart && lastPart.endLength || 0; var length = distance(lastPoint, point); parts.push({ start: lastPoint, end: point, startLength: startLength, endLength: startLength + length, length: length }); } return parts; }, []); var totalLength = parts.reduce(function(length, part) { return length + part.length; }, 0); // find which segement contains middle point var midLength = totalLength / 2; var i = 0; var midSegment = parts[i]; while (midSegment.endLength < midLength) { midSegment = parts[++i]; } // calculate relative position on mid segment var segmentProgress = (midLength - midSegment.startLength) / midSegment.length; var midPoint = { x: midSegment.start.x + (midSegment.end.x - midSegment.start.x) * segmentProgress, y: midSegment.start.y + (midSegment.end.y - midSegment.start.y) * segmentProgress }; return midPoint; } /** * Get the mid of the given Element. * * @param {djs.Base.Connection} connection * * @return {Point} */ function getMid(element) { if (isConnection(element)) { return getConnectionMid(element); } return getBoundsMid(element); } // helpers ////////////////////// function distance(a, b) { return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)); } function isConnection(element) { return !!element.waypoints; } function elementToString(e) { if (!e) { return ''; } return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />'; } /** * @param {ModdleElement} semantic * @param {ModdleElement} di * @param {Object} [attrs=null] * * @return {Object} */ function elementData(semantic, di, attrs) { return assign({ id: semantic.id, type: semantic.$type, businessObject: semantic, di: di }, attrs); } function getWaypoints(di, source, target) { var waypoints = di.waypoint; if (!waypoints || waypoints.length < 2) { return [ getMid(source), getMid(target) ]; } return waypoints.map(function(p) { return { x: p.x, y: p.y }; }); } function notYetDrawn(translate, semantic, refSemantic, property) { return new Error(translate('element {element} referenced by {referenced}#{property} not yet drawn', { element: elementToString(refSemantic), referenced: elementToString(semantic), property: property })); } /** * An importer that adds bpmn elements to the canvas * * @param {EventBus} eventBus * @param {Canvas} canvas * @param {ElementFactory} elementFactory * @param {ElementRegistry} elementRegistry * @param {Function} translate * @param {TextRenderer} textRenderer */ function BpmnImporter( eventBus, canvas, elementFactory, elementRegistry, translate, textRenderer) { this._eventBus = eventBus; this._canvas = canvas; this._elementFactory = elementFactory; this._elementRegistry = elementRegistry; this._translate = translate; this._textRenderer = textRenderer; } BpmnImporter.$inject = [ 'eventBus', 'canvas', 'elementFactory', 'elementRegistry', 'translate', 'textRenderer' ]; /** * Add bpmn element (semantic) to the canvas onto the * specified parent shape. */ BpmnImporter.prototype.add = function(semantic, di, parentElement) { var element, translate = this._translate, hidden; var parentIndex; // ROOT ELEMENT // handle the special case that we deal with a // invisible root element (process, subprocess or collaboration) if (is$1(di, 'bpmndi:BPMNPlane')) { var attrs = is$1(semantic, 'bpmn:SubProcess') ? { id: semantic.id + '_plane' } : {}; // add a virtual element (not being drawn) element = this._elementFactory.createRoot(elementData(semantic, di, attrs)); this._canvas.addRootElement(element); } // SHAPE else if (is$1(di, 'bpmndi:BPMNShape')) { var collapsed = !isExpanded(semantic, di), isFrame = isFrameElement$1(semantic); hidden = parentElement && (parentElement.hidden || parentElement.collapsed); var bounds = di.bounds; element = this._elementFactory.createShape(elementData(semantic, di, { collapsed: collapsed, hidden: hidden, x: Math.round(bounds.x), y: Math.round(bounds.y), width: Math.round(bounds.width), height: Math.round(bounds.height), isFrame: isFrame })); if (is$1(semantic, 'bpmn:BoundaryEvent')) { this._attachBoundary(semantic, element); } // insert lanes behind other flow nodes (cf. #727) if (is$1(semantic, 'bpmn:Lane')) { parentIndex = 0; } if (is$1(semantic, 'bpmn:DataStoreReference')) { // check whether data store is inside our outside of its semantic parent if (!isPointInsideBBox(parentElement, getMid(bounds))) { parentElement = this._canvas.findRoot(parentElement); } } this._canvas.addShape(element, parentElement, parentIndex); } // CONNECTION else if (is$1(di, 'bpmndi:BPMNEdge')) { var source = this._getSource(semantic), target = this._getTarget(semantic); hidden = parentElement && (parentElement.hidden || parentElement.collapsed); element = this._elementFactory.createConnection(elementData(semantic, di, { hidden: hidden, source: source, target: target, waypoints: getWaypoints(di, source, target) })); if (is$1(semantic, 'bpmn:DataAssociation')) { // render always on top; this ensures DataAssociations // are rendered correctly across different "hacks" people // love to model such as cross participant / sub process // associations parentElement = this._canvas.findRoot(parentElement); } this._canvas.addConnection(element, parentElement, parentIndex); } else { throw new Error(translate('unknown di {di} for element {semantic}', { di: elementToString(di), semantic: elementToString(semantic) })); } // (optional) LABEL if (isLabelExternal(semantic) && getLabel(element)) { this.addLabel(semantic, di, element); } this._eventBus.fire('bpmnElement.added', { element: element }); return element; }; /** * Attach the boundary element to the given host * * @param {ModdleElement} boundarySemantic * @param {djs.model.Base} boundaryElement */ BpmnImporter.prototype._attachBoundary = function(boundarySemantic, boundaryElement) { var translate = this._translate; var hostSemantic = boundarySemantic.attachedToRef; if (!hostSemantic) { throw new Error(translate('missing {semantic}#attachedToRef', { semantic: elementToString(boundarySemantic) })); } var host = this._elementRegistry.get(hostSemantic.id), attachers = host && host.attachers; if (!host) { throw notYetDrawn(translate, boundarySemantic, hostSemantic, 'attachedToRef'); } // wire element.host <> host.attachers boundaryElement.host = host; if (!attachers) { host.attachers = attachers = []; } if (attachers.indexOf(boundaryElement) === -1) { attachers.push(boundaryElement); } }; /** * add label for an element */ BpmnImporter.prototype.addLabel = function(semantic, di, element) { var bounds, text, label; bounds = getExternalLabelBounds(di, element); text = getLabel(element); if (text) { // get corrected bounds from actual layouted text bounds = this._textRenderer.getExternalLabelBounds(bounds, text); } label = this._elementFactory.createLabel(elementData(semantic, di, { id: semantic.id + '_label', labelTarget: element, type: 'label', hidden: element.hidden || !getLabel(element), x: Math.round(bounds.x), y: Math.round(bounds.y), width: Math.round(bounds.width), height: Math.round(bounds.height) })); return this._canvas.addShape(label, element.parent); }; /** * Return the drawn connection end based on the given side. * * @throws {Error} if the end is not yet drawn */ BpmnImporter.prototype._getEnd = function(semantic, side) { var element, refSemantic, type = semantic.$type, translate = this._translate; refSemantic = semantic[side + 'Ref']; // handle mysterious isMany DataAssociation#sourceRef if (side === 'source' && type === 'bpmn:DataInputAssociation') { refSemantic = refSemantic && refSemantic[0]; } // fix source / target for DataInputAssociation / DataOutputAssociation if (side === 'source' && type === 'bpmn:DataOutputAssociation' || side === 'target' && type === 'bpmn:DataInputAssociation') { refSemantic = semantic.$parent; } element = refSemantic && this._getElement(refSemantic); if (element) { return element; } if (refSemantic) { throw notYetDrawn(translate, semantic, refSemantic, side + 'Ref'); } else { throw new Error(translate('{semantic}#{side} Ref not specified', { semantic: elementToString(semantic), side: side })); } }; BpmnImporter.prototype._getSource = function(semantic) { return this._getEnd(semantic, 'source'); }; BpmnImporter.prototype._getTarget = function(semantic) { return this._getEnd(semantic, 'target'); }; BpmnImporter.prototype._getElement = function(semantic) { return this._elementRegistry.get(semantic.id); }; // helpers //////////////////// function isPointInsideBBox(bbox, point) { var x = point.x, y = point.y; return x >= bbox.x && x <= bbox.x + bbox.width && y >= bbox.y && y <= bbox.y + bbox.height; } function isFrameElement$1(semantic) { return is$1(semantic, 'bpmn:Group'); } var ImportModule = { __depends__: [ TranslateModule ], bpmnImporter: [ 'type', BpmnImporter ] }; var CoreModule$1 = { __depends__: [ DrawModule$1, ImportModule ] }; function getOriginal(event) { return event.originalEvent || event.srcEvent; } function toPoint(event) { if (event.pointers && event.pointers.length) { event = event.pointers[0]; } if (event.touches && event.touches.length) { event = event.touches[0]; } return event ? { x: event.clientX, y: event.clientY } : null; } function isMac() { return (/mac/i).test(navigator.platform); } function isButton(event, button) { return (getOriginal(event) || event).button === button; } function isPrimaryButton(event) { // button === 0 -> left áka primary mouse button return isButton(event, 0); } function isAuxiliaryButton(event) { // button === 1 -> auxiliary áka wheel button return isButton(event, 1); } function hasPrimaryModifier(event) { var originalEvent = getOriginal(event) || event; if (!isPrimaryButton(event)) { return false; } // Use cmd as primary modifier key for mac OS if (isMac()) { return originalEvent.metaKey; } else { return originalEvent.ctrlKey; } } function hasSecondaryModifier(event) { var originalEvent = getOriginal(event) || event; return isPrimaryButton(event) && originalEvent.shiftKey; } function allowAll(event) { return true; } function allowPrimaryAndAuxiliary(event) { return isPrimaryButton(event) || isAuxiliaryButton(event); } var LOW_PRIORITY$4 = 500; /** * A plugin that provides interaction events for diagram elements. * * It emits the following events: * * * element.click * * element.contextmenu * * element.dblclick * * element.hover * * element.mousedown * * element.mousemove * * element.mouseup * * element.out * * Each event is a tuple { element, gfx, originalEvent }. * * Canceling the event via Event#preventDefault() * prevents the original DOM operation. * * @param {EventBus} eventBus */ function InteractionEvents(eventBus, elementRegistry, styles) { var self = this; /** * Fire an interaction event. * * @param {string} type local event name, e.g. element.click. * @param {DOMEvent} event native event * @param {djs.model.Base} [element] the diagram element to emit the event on; * defaults to the event target */ function fire(type, event, element) { if (isIgnored(type, event)) { return; } var target, gfx, returnValue; if (!element) { target = event.delegateTarget || event.target; if (target) { gfx = target; element = elementRegistry.get(gfx); } } else { gfx = elementRegistry.getGraphics(element); } if (!gfx || !element) { return; } returnValue = eventBus.fire(type, { element: element, gfx: gfx, originalEvent: event }); if (returnValue === false) { event.stopPropagation(); event.preventDefault(); } } // TODO(nikku): document this var handlers = {}; function mouseHandler(localEventName) { return handlers[localEventName]; } function isIgnored(localEventName, event) { var filter = ignoredFilters[localEventName] || isPrimaryButton; // only react on left mouse button interactions // except for interaction events that are enabled // for secundary mouse button return !filter(event); } var bindings = { click: 'element.click', contextmenu: 'element.contextmenu', dblclick: 'element.dblclick', mousedown: 'element.mousedown', mousemove: 'element.mousemove', mouseover: 'element.hover', mouseout: 'element.out', mouseup: 'element.mouseup', }; var ignoredFilters = { 'element.contextmenu': allowAll, 'element.mousedown': allowPrimaryAndAuxiliary, 'element.mouseup': allowPrimaryAndAuxiliary, 'element.click': allowPrimaryAndAuxiliary, 'element.dblclick': allowPrimaryAndAuxiliary }; // manual event trigger ////////// /** * Trigger an interaction event (based on a native dom event) * on the target shape or connection. * * @param {string} eventName the name of the triggered DOM event * @param {MouseEvent} event * @param {djs.model.Base} targetElement */ function triggerMouseEvent(eventName, event, targetElement) { // i.e. element.mousedown... var localEventName = bindings[eventName]; if (!localEventName) { throw new Error('unmapped DOM event name <' + eventName + '>'); } return fire(localEventName, event, targetElement); } var ELEMENT_SELECTOR = 'svg, .djs-element'; // event handling /////// function registerEvent(node, event, localEvent, ignoredFilter) { var handler = handlers[localEvent] = function(event) { fire(localEvent, event); }; if (ignoredFilter) { ignoredFilters[localEvent] = ignoredFilter; } handler.$delegate = delegate.bind(node, ELEMENT_SELECTOR, event, handler); } function unregisterEvent(node, event, localEvent) { var handler = mouseHandler(localEvent); if (!handler) { return; } delegate.unbind(node, event, handler.$delegate); } function registerEvents(svg) { forEach$1(bindings, function(val, key) { registerEvent(svg, key, val); }); } function unregisterEvents(svg) { forEach$1(bindings, function(val, key) { unregisterEvent(svg, key, val); }); } eventBus.on('canvas.destroy', function(event) { unregisterEvents(event.svg); }); eventBus.on('canvas.init', function(event) { registerEvents(event.svg); }); // hit box updating //////////////// eventBus.on([ 'shape.added', 'connection.added' ], function(event) { var element = event.element, gfx = event.gfx; eventBus.fire('interactionEvents.createHit', { element: element, gfx: gfx }); }); // Update djs-hit on change. // A low priortity is necessary, because djs-hit of labels has to be updated // after the label bounds have been updated in the renderer. eventBus.on([ 'shape.changed', 'connection.changed' ], LOW_PRIORITY$4, function(event) { var element = event.element, gfx = event.gfx; eventBus.fire('interactionEvents.updateHit', { element: element, gfx: gfx }); }); eventBus.on('interactionEvents.createHit', LOW_PRIORITY$4, function(event) { var element = event.element, gfx = event.gfx; self.createDefaultHit(element, gfx); }); eventBus.on('interactionEvents.updateHit', function(event) { var element = event.element, gfx = event.gfx; self.updateDefaultHit(element, gfx); }); // hit styles //////////// var STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-stroke'); var CLICK_STROKE_HIT_STYLE = createHitStyle('djs-hit djs-hit-click-stroke'); var ALL_HIT_STYLE = createHitStyle('djs-hit djs-hit-all'); var NO_MOVE_HIT_STYLE = createHitStyle('djs-hit djs-hit-no-move'); var HIT_TYPES = { 'all': ALL_HIT_STYLE, 'click-stroke': CLICK_STROKE_HIT_STYLE, 'stroke': STROKE_HIT_STYLE, 'no-move': NO_MOVE_HIT_STYLE }; function createHitStyle(classNames, attrs) { attrs = assign({ stroke: 'white', strokeWidth: 15 }, attrs || {}); return styles.cls(classNames, [ 'no-fill', 'no-border' ], attrs); } // style helpers /////////////// function applyStyle(hit, type) { var attrs = HIT_TYPES[type]; if (!attrs) { throw new Error('invalid hit type <' + type + '>'); } attr$1(hit, attrs); return hit; } function appendHit(gfx, hit) { append(gfx, hit); } // API /** * Remove hints on the given graphics. * * @param {SVGElement} gfx */ this.removeHits = function(gfx) { var hits = all('.djs-hit', gfx); forEach$1(hits, remove$2); }; /** * Create default hit for the given element. * * @param {djs.model.Base} element * @param {SVGElement} gfx * * @return {SVGElement} created hit */ this.createDefaultHit = function(element, gfx) { var waypoints = element.waypoints, isFrame = element.isFrame, boxType; if (waypoints) { return this.createWaypointsHit(gfx, waypoints); } else { boxType = isFrame ? 'stroke' : 'all'; return this.createBoxHit(gfx, boxType, { width: element.width, height: element.height }); } }; /** * Create hits for the given waypoints. * * @param {SVGElement} gfx * @param {Array} waypoints * * @return {SVGElement} */ this.createWaypointsHit = function(gfx, waypoints) { var hit = createLine(waypoints); applyStyle(hit, 'stroke'); appendHit(gfx, hit); return hit; }; /** * Create hits for a box. * * @param {SVGElement} gfx * @param {string} hitType * @param {Object} attrs * * @return {SVGElement} */ this.createBoxHit = function(gfx, type, attrs) { attrs = assign({ x: 0, y: 0 }, attrs); var hit = create$1('rect'); applyStyle(hit, type); attr$1(hit, attrs); appendHit(gfx, hit); return hit; }; /** * Update default hit of the element. * * @param {djs.model.Base} element * @param {SVGElement} gfx * * @return {SVGElement} updated hit */ this.updateDefaultHit = function(element, gfx) { var hit = query('.djs-hit', gfx); if (!hit) { return; } if (element.waypoints) { updateLine(hit, element.waypoints); } else { attr$1(hit, { width: element.width, height: element.height }); } return hit; }; this.fire = fire; this.triggerMouseEvent = triggerMouseEvent; this.mouseHandler = mouseHandler; this.registerEvent = registerEvent; this.unregisterEvent = unregisterEvent; } InteractionEvents.$inject = [ 'eventBus', 'elementRegistry', 'styles' ]; /** * An event indicating that the mouse hovered over an element * * @event element.hover * * @type {Object} * @property {djs.model.Base} element * @property {SVGElement} gfx * @property {Event} originalEvent */ /** * An event indicating that the mouse has left an element * * @event element.out * * @type {Object} * @property {djs.model.Base} element * @property {SVGElement} gfx * @property {Event} originalEvent */ /** * An event indicating that the mouse has clicked an element * * @event element.click * * @type {Object} * @property {djs.model.Base} element * @property {SVGElement} gfx * @property {Event} originalEvent */ /** * An event indicating that the mouse has double clicked an element * * @event element.dblclick * * @type {Object} * @property {djs.model.Base} element * @property {SVGElement} gfx * @property {Event} originalEvent */ /** * An event indicating that the mouse has gone down on an element. * * @event element.mousedown * * @type {Object} * @property {djs.model.Base} element * @property {SVGElement} gfx * @property {Event} originalEvent */ /** * An event indicating that the mouse has gone up on an element. * * @event element.mouseup * * @type {Object} * @property {djs.model.Base} element * @property {SVGElement} gfx * @property {Event} originalEvent */ /** * An event indicating that the context menu action is triggered * via mouse or touch controls. * * @event element.contextmenu * * @type {Object} * @property {djs.model.Base} element * @property {SVGElement} gfx * @property {Event} originalEvent */ var InteractionEventsModule = { __init__: [ 'interactionEvents' ], interactionEvents: [ 'type', InteractionEvents ] }; /** * Returns the surrounding bbox for all elements in * the array or the element primitive. * * @param {Array|djs.model.Shape} elements * @param {boolean} [stopRecursion=false] * * @return {Bounds} */ function getBBox(elements, stopRecursion) { stopRecursion = !!stopRecursion; if (!isArray$2(elements)) { elements = [ elements ]; } var minX, minY, maxX, maxY; forEach$1(elements, function(element) { // If element is a connection the bbox must be computed first var bbox = element; if (element.waypoints && !stopRecursion) { bbox = getBBox(element.waypoints, true); } var x = bbox.x, y = bbox.y, height = bbox.height || 0, width = bbox.width || 0; if (x < minX || minX === undefined) { minX = x; } if (y < minY || minY === undefined) { minY = y; } if ((x + width) > maxX || maxX === undefined) { maxX = x + width; } if ((y + height) > maxY || maxY === undefined) { maxY = y + height; } }); return { x: minX, y: minY, height: maxY - minY, width: maxX - minX }; } function getType(element) { if ('waypoints' in element) { return 'connection'; } if ('x' in element) { return 'shape'; } return 'root'; } function isFrameElement(element) { return !!(element && element.isFrame); } var LOW_PRIORITY$3 = 500; /** * @class * * A plugin that adds an outline to shapes and connections that may be activated and styled * via CSS classes. * * @param {EventBus} eventBus * @param {Styles} styles * @param {ElementRegistry} elementRegistry */ function Outline(eventBus, styles, elementRegistry) { this.offset = 6; var OUTLINE_STYLE = styles.cls('djs-outline', [ 'no-fill' ]); var self = this; function createOutline(gfx, bounds) { var outline = create$1('rect'); attr$1(outline, assign({ x: 10, y: 10, rx: 3, width: 100, height: 100 }, OUTLINE_STYLE)); append(gfx, outline); return outline; } // A low priortity is necessary, because outlines of labels have to be updated // after the label bounds have been updated in the renderer. eventBus.on([ 'shape.added', 'shape.changed' ], LOW_PRIORITY$3, function(event) { var element = event.element, gfx = event.gfx; var outline = query('.djs-outline', gfx); if (!outline) { outline = createOutline(gfx); } self.updateShapeOutline(outline, element); }); eventBus.on([ 'connection.added', 'connection.changed' ], function(event) { var element = event.element, gfx = event.gfx; var outline = query('.djs-outline', gfx); if (!outline) { outline = createOutline(gfx); } self.updateConnectionOutline(outline, element); }); } /** * Updates the outline of a shape respecting the dimension of the * element and an outline offset. * * @param {SVGElement} outline * @param {djs.model.Base} element */ Outline.prototype.updateShapeOutline = function(outline, element) { attr$1(outline, { x: -this.offset, y: -this.offset, width: element.width + this.offset * 2, height: element.height + this.offset * 2 }); }; /** * Updates the outline of a connection respecting the bounding box of * the connection and an outline offset. * * @param {SVGElement} outline * @param {djs.model.Base} element */ Outline.prototype.updateConnectionOutline = function(outline, connection) { var bbox = getBBox(connection); attr$1(outline, { x: bbox.x - this.offset, y: bbox.y - this.offset, width: bbox.width + this.offset * 2, height: bbox.height + this.offset * 2 }); }; Outline.$inject = [ 'eventBus', 'styles', 'elementRegistry' ]; var OutlineModule = { __init__: [ 'outline' ], outline: [ 'type', Outline ] }; /** * A service that offers the current selection in a diagram. * Offers the api to control the selection, too. * * @class * * @param {EventBus} eventBus the event bus */ function Selection(eventBus, canvas) { this._eventBus = eventBus; this._canvas = canvas; this._selectedElements = []; var self = this; eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) { var element = e.element; self.deselect(element); }); eventBus.on([ 'diagram.clear', 'root.set' ], function(e) { self.select(null); }); } Selection.$inject = [ 'eventBus', 'canvas' ]; Selection.prototype.deselect = function(element) { var selectedElements = this._selectedElements; var idx = selectedElements.indexOf(element); if (idx !== -1) { var oldSelection = selectedElements.slice(); selectedElements.splice(idx, 1); this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements }); } }; Selection.prototype.get = function() { return this._selectedElements; }; Selection.prototype.isSelected = function(element) { return this._selectedElements.indexOf(element) !== -1; }; /** * This method selects one or more elements on the diagram. * * By passing an additional add parameter you can decide whether or not the element(s) * should be added to the already existing selection or not. * * @method Selection#select * * @param {Object|Object[]} elements element or array of elements to be selected * @param {boolean} [add] whether the element(s) should be appended to the current selection, defaults to false */ Selection.prototype.select = function(elements, add) { var selectedElements = this._selectedElements, oldSelection = selectedElements.slice(); if (!isArray$2(elements)) { elements = elements ? [ elements ] : []; } var canvas = this._canvas; var rootElement = canvas.getRootElement(); elements = elements.filter(function(element) { var elementRoot = canvas.findRoot(element); return rootElement === elementRoot; }); // selection may be cleared by passing an empty array or null // to the method if (add) { forEach$1(elements, function(element) { if (selectedElements.indexOf(element) !== -1) { // already selected return; } else { selectedElements.push(element); } }); } else { this._selectedElements = selectedElements = elements.slice(); } this._eventBus.fire('selection.changed', { oldSelection: oldSelection, newSelection: selectedElements }); }; var MARKER_HOVER = 'hover', MARKER_SELECTED = 'selected'; var SELECTION_OUTLINE_PADDING = 6; /** * A plugin that adds a visible selection UI to shapes and connections * by appending the hover and selected classes to them. * * @class * * Makes elements selectable, too. * * @param {Canvas} canvas * @param {EventBus} eventBus */ function SelectionVisuals(canvas, eventBus, selection) { this._canvas = canvas; var self = this; this._multiSelectionBox = null; function addMarker(e, cls) { canvas.addMarker(e, cls); } function removeMarker(e, cls) { canvas.removeMarker(e, cls); } eventBus.on('element.hover', function(event) { addMarker(event.element, MARKER_HOVER); }); eventBus.on('element.out', function(event) { removeMarker(event.element, MARKER_HOVER); }); eventBus.on('selection.changed', function(event) { function deselect(s) { removeMarker(s, MARKER_SELECTED); } function select(s) { addMarker(s, MARKER_SELECTED); } var oldSelection = event.oldSelection, newSelection = event.newSelection; forEach$1(oldSelection, function(e) { if (newSelection.indexOf(e) === -1) { deselect(e); } }); forEach$1(newSelection, function(e) { if (oldSelection.indexOf(e) === -1) { select(e); } }); self._updateSelectionOutline(newSelection); }); eventBus.on('element.changed', function(event) { if (selection.isSelected(event.element)) { self._updateSelectionOutline(selection.get()); } }); } SelectionVisuals.$inject = [ 'canvas', 'eventBus', 'selection' ]; SelectionVisuals.prototype._updateSelectionOutline = function(selection) { var layer = this._canvas.getLayer('selectionOutline'); clear$1(layer); var enabled = selection.length > 1; var container = this._canvas.getContainer(); classes$1(container)[enabled ? 'add' : 'remove']('djs-multi-select'); if (!enabled) { return; } var bBox = addSelectionOutlinePadding(getBBox(selection)); var rect = create$1('rect'); attr$1(rect, assign({ rx: 3 }, bBox)); classes$1(rect).add('djs-selection-outline'); append(layer, rect); }; // helpers ////////// function addSelectionOutlinePadding(bBox) { return { x: bBox.x - SELECTION_OUTLINE_PADDING, y: bBox.y - SELECTION_OUTLINE_PADDING, width: bBox.width + SELECTION_OUTLINE_PADDING * 2, height: bBox.height + SELECTION_OUTLINE_PADDING * 2 }; } function SelectionBehavior(eventBus, selection, canvas, elementRegistry) { // Select elements on create eventBus.on('create.end', 500, function(event) { var context = event.context, canExecute = context.canExecute, elements = context.elements, hints = context.hints || {}, autoSelect = hints.autoSelect; if (canExecute) { if (autoSelect === false) { // Select no elements return; } if (isArray$2(autoSelect)) { selection.select(autoSelect); } else { // Select all elements by default selection.select(elements.filter(isShown)); } } }); // Select connection targets on connect eventBus.on('connect.end', 500, function(event) { var context = event.context, connection = context.connection; if (connection) { selection.select(connection); } }); // Select shapes on move eventBus.on('shape.move.end', 500, function(event) { var previousSelection = event.previousSelection || []; var shape = elementRegistry.get(event.context.shape.id); // Always select main shape on move var isSelected = find(previousSelection, function(selectedShape) { return shape.id === selectedShape.id; }); if (!isSelected) { selection.select(shape); } }); // Select elements on click eventBus.on('element.click', function(event) { if (!isPrimaryButton(event)) { return; } var element = event.element; if (element === canvas.getRootElement()) { element = null; } var isSelected = selection.isSelected(element), isMultiSelect = selection.get().length > 1; // Add to selection if CTRL or SHIFT pressed var add = hasPrimaryModifier(event) || hasSecondaryModifier(event); if (isSelected && isMultiSelect) { if (add) { // Deselect element return selection.deselect(element); } else { // Select element only return selection.select(element); } } else if (!isSelected) { // Select element selection.select(element, add); } else { // Deselect element selection.deselect(element); } }); } SelectionBehavior.$inject = [ 'eventBus', 'selection', 'canvas', 'elementRegistry' ]; function isShown(element) { return !element.hidden; } var SelectionModule = { __init__: [ 'selectionVisuals', 'selectionBehavior' ], __depends__: [ InteractionEventsModule, OutlineModule ], selection: [ 'type', Selection ], selectionVisuals: [ 'type', SelectionVisuals ], selectionBehavior: [ 'type', SelectionBehavior ] }; /** * Util that provides unique IDs. * * @class djs.util.IdGenerator * @constructor * @memberOf djs.util * * The ids can be customized via a given prefix and contain a random value to avoid collisions. * * @param {string} prefix a prefix to prepend to generated ids (for better readability) */ function IdGenerator(prefix) { this._counter = 0; this._prefix = (prefix ? prefix + '-' : '') + Math.floor(Math.random() * 1000000000) + '-'; } /** * Returns a next unique ID. * * @method djs.util.IdGenerator#next * * @returns {string} the id */ IdGenerator.prototype.next = function() { return this._prefix + (++this._counter); }; // document wide unique overlay ids var ids = new IdGenerator('ov'); var LOW_PRIORITY$2 = 500; /** * A service that allows users to attach overlays to diagram elements. * * The overlay service will take care of overlay positioning during updates. * * @example * * // add a pink badge on the top left of the shape * overlays.add(someShape, { * position: { * top: -5, * left: -5 * }, * html: '
0
' * }); * * // or add via shape id * * overlays.add('some-element-id', { * position: { * top: -5, * left: -5 * } * html: '
0
' * }); * * // or add with optional type * * overlays.add(someShape, 'badge', { * position: { * top: -5, * left: -5 * } * html: '
0
' * }); * * * // remove an overlay * * var id = overlays.add(...); * overlays.remove(id); * * * You may configure overlay defaults during tool by providing a `config` module * with `overlays.defaults` as an entry: * * { * overlays: { * defaults: { * show: { * minZoom: 0.7, * maxZoom: 5.0 * }, * scale: { * min: 1 * } * } * } * * @param {Object} config * @param {EventBus} eventBus * @param {Canvas} canvas * @param {ElementRegistry} elementRegistry */ function Overlays(config, eventBus, canvas, elementRegistry) { this._eventBus = eventBus; this._canvas = canvas; this._elementRegistry = elementRegistry; this._ids = ids; this._overlayDefaults = assign({ // no show constraints show: null, // always scale scale: true }, config && config.defaults); /** * Mapping overlayId -> overlay */ this._overlays = {}; /** * Mapping elementId -> overlay container */ this._overlayContainers = []; // root html element for all overlays this._overlayRoot = createRoot(canvas.getContainer()); this._init(); } Overlays.$inject = [ 'config.overlays', 'eventBus', 'canvas', 'elementRegistry' ]; /** * Returns the overlay with the specified id or a list of overlays * for an element with a given type. * * @example * * // return the single overlay with the given id * overlays.get('some-id'); * * // return all overlays for the shape * overlays.get({ element: someShape }); * * // return all overlays on shape with type 'badge' * overlays.get({ element: someShape, type: 'badge' }); * * // shape can also be specified as id * overlays.get({ element: 'element-id', type: 'badge' }); * * * @param {Object} search * @param {string} [search.id] * @param {string|djs.model.Base} [search.element] * @param {string} [search.type] * * @return {Object|Array} the overlay(s) */ Overlays.prototype.get = function(search) { if (isString(search)) { search = { id: search }; } if (isString(search.element)) { search.element = this._elementRegistry.get(search.element); } if (search.element) { var container = this._getOverlayContainer(search.element, true); // return a list of overlays when searching by element (+type) if (container) { return search.type ? filter(container.overlays, matchPattern({ type: search.type })) : container.overlays.slice(); } else { return []; } } else if (search.type) { return filter(this._overlays, matchPattern({ type: search.type })); } else { // return single element when searching by id return search.id ? this._overlays[search.id] : null; } }; /** * Adds a HTML overlay to an element. * * @param {string|djs.model.Base} element attach overlay to this shape * @param {string} [type] optional type to assign to the overlay * @param {Object} overlay the overlay configuration * * @param {string|DOMElement} overlay.html html element to use as an overlay * @param {Object} [overlay.show] show configuration * @param {number} [overlay.show.minZoom] minimal zoom level to show the overlay * @param {number} [overlay.show.maxZoom] maximum zoom level to show the overlay * @param {Object} overlay.position where to attach the overlay * @param {number} [overlay.position.left] relative to element bbox left attachment * @param {number} [overlay.position.top] relative to element bbox top attachment * @param {number} [overlay.position.bottom] relative to element bbox bottom attachment * @param {number} [overlay.position.right] relative to element bbox right attachment * @param {boolean|Object} [overlay.scale=true] false to preserve the same size regardless of * diagram zoom * @param {number} [overlay.scale.min] * @param {number} [overlay.scale.max] * * @return {string} id that may be used to reference the overlay for update or removal */ Overlays.prototype.add = function(element, type, overlay) { if (isObject(type)) { overlay = type; type = null; } if (!element.id) { element = this._elementRegistry.get(element); } if (!overlay.position) { throw new Error('must specifiy overlay position'); } if (!overlay.html) { throw new Error('must specifiy overlay html'); } if (!element) { throw new Error('invalid element specified'); } var id = this._ids.next(); overlay = assign({}, this._overlayDefaults, overlay, { id: id, type: type, element: element, html: overlay.html }); this._addOverlay(overlay); return id; }; /** * Remove an overlay with the given id or all overlays matching the given filter. * * @see Overlays#get for filter options. * * @param {string|object} [filter] */ Overlays.prototype.remove = function(filter) { var overlays = this.get(filter) || []; if (!isArray$2(overlays)) { overlays = [ overlays ]; } var self = this; forEach$1(overlays, function(overlay) { var container = self._getOverlayContainer(overlay.element, true); if (overlay) { remove$1(overlay.html); remove$1(overlay.htmlContainer); delete overlay.htmlContainer; delete overlay.element; delete self._overlays[overlay.id]; } if (container) { var idx = container.overlays.indexOf(overlay); if (idx !== -1) { container.overlays.splice(idx, 1); } } }); }; Overlays.prototype.show = function() { setVisible(this._overlayRoot); }; Overlays.prototype.hide = function() { setVisible(this._overlayRoot, false); }; Overlays.prototype.clear = function() { this._overlays = {}; this._overlayContainers = []; clear(this._overlayRoot); }; Overlays.prototype._updateOverlayContainer = function(container) { var element = container.element, html = container.html; // update container left,top according to the elements x,y coordinates // this ensures we can attach child elements relative to this container var x = element.x, y = element.y; if (element.waypoints) { var bbox = getBBox(element); x = bbox.x; y = bbox.y; } setPosition(html, x, y); attr(container.html, 'data-container-id', element.id); }; Overlays.prototype._updateOverlay = function(overlay) { var position = overlay.position, htmlContainer = overlay.htmlContainer, element = overlay.element; // update overlay html relative to shape because // it is already positioned on the element // update relative var left = position.left, top = position.top; if (position.right !== undefined) { var width; if (element.waypoints) { width = getBBox(element).width; } else { width = element.width; } left = position.right * -1 + width; } if (position.bottom !== undefined) { var height; if (element.waypoints) { height = getBBox(element).height; } else { height = element.height; } top = position.bottom * -1 + height; } setPosition(htmlContainer, left || 0, top || 0); this._updateOverlayVisibilty(overlay, this._canvas.viewbox()); }; Overlays.prototype._createOverlayContainer = function(element) { var html = domify('
'); assign$1(html, { position: 'absolute' }); this._overlayRoot.appendChild(html); var container = { html: html, element: element, overlays: [] }; this._updateOverlayContainer(container); this._overlayContainers.push(container); return container; }; Overlays.prototype._updateRoot = function(viewbox) { var scale = viewbox.scale || 1; var matrix = 'matrix(' + [ scale, 0, 0, scale, -1 * viewbox.x * scale, -1 * viewbox.y * scale ].join(',') + ')'; setTransform(this._overlayRoot, matrix); }; Overlays.prototype._getOverlayContainer = function(element, raw) { var container = find(this._overlayContainers, function(c) { return c.element === element; }); if (!container && !raw) { return this._createOverlayContainer(element); } return container; }; Overlays.prototype._addOverlay = function(overlay) { var id = overlay.id, element = overlay.element, html = overlay.html, htmlContainer, overlayContainer; // unwrap jquery (for those who need it) if (html.get && html.constructor.prototype.jquery) { html = html.get(0); } // create proper html elements from // overlay HTML strings if (isString(html)) { html = domify(html); } overlayContainer = this._getOverlayContainer(element); htmlContainer = domify('
'); assign$1(htmlContainer, { position: 'absolute' }); htmlContainer.appendChild(html); if (overlay.type) { classes(htmlContainer).add('djs-overlay-' + overlay.type); } var elementRoot = this._canvas.findRoot(element); var activeRoot = this._canvas.getRootElement(); setVisible(htmlContainer, elementRoot === activeRoot); overlay.htmlContainer = htmlContainer; overlayContainer.overlays.push(overlay); overlayContainer.html.appendChild(htmlContainer); this._overlays[id] = overlay; this._updateOverlay(overlay); this._updateOverlayVisibilty(overlay, this._canvas.viewbox()); }; Overlays.prototype._updateOverlayVisibilty = function(overlay, viewbox) { var show = overlay.show, rootElement = this._canvas.findRoot(overlay.element), minZoom = show && show.minZoom, maxZoom = show && show.maxZoom, htmlContainer = overlay.htmlContainer, activeRootElement = this._canvas.getRootElement(), visible = true; if (rootElement !== activeRootElement) { visible = false; } else if (show) { if ( (isDefined(minZoom) && minZoom > viewbox.scale) || (isDefined(maxZoom) && maxZoom < viewbox.scale) ) { visible = false; } } setVisible(htmlContainer, visible); this._updateOverlayScale(overlay, viewbox); }; Overlays.prototype._updateOverlayScale = function(overlay, viewbox) { var shouldScale = overlay.scale, minScale, maxScale, htmlContainer = overlay.htmlContainer; var scale, transform = ''; if (shouldScale !== true) { if (shouldScale === false) { minScale = 1; maxScale = 1; } else { minScale = shouldScale.min; maxScale = shouldScale.max; } if (isDefined(minScale) && viewbox.scale < minScale) { scale = (1 / viewbox.scale || 1) * minScale; } if (isDefined(maxScale) && viewbox.scale > maxScale) { scale = (1 / viewbox.scale || 1) * maxScale; } } if (isDefined(scale)) { transform = 'scale(' + scale + ',' + scale + ')'; } setTransform(htmlContainer, transform); }; Overlays.prototype._updateOverlaysVisibilty = function(viewbox) { var self = this; forEach$1(this._overlays, function(overlay) { self._updateOverlayVisibilty(overlay, viewbox); }); }; Overlays.prototype._init = function() { var eventBus = this._eventBus; var self = this; // scroll/zoom integration function updateViewbox(viewbox) { self._updateRoot(viewbox); self._updateOverlaysVisibilty(viewbox); self.show(); } eventBus.on('canvas.viewbox.changing', function(event) { self.hide(); }); eventBus.on('canvas.viewbox.changed', function(event) { updateViewbox(event.viewbox); }); // remove integration eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) { var element = e.element; var overlays = self.get({ element: element }); forEach$1(overlays, function(o) { self.remove(o.id); }); var container = self._getOverlayContainer(element); if (container) { remove$1(container.html); var i = self._overlayContainers.indexOf(container); if (i !== -1) { self._overlayContainers.splice(i, 1); } } }); // move integration eventBus.on('element.changed', LOW_PRIORITY$2, function(e) { var element = e.element; var container = self._getOverlayContainer(element, true); if (container) { forEach$1(container.overlays, function(overlay) { self._updateOverlay(overlay); }); self._updateOverlayContainer(container); } }); // marker integration, simply add them on the overlays as classes, too. eventBus.on('element.marker.update', function(e) { var container = self._getOverlayContainer(e.element, true); if (container) { classes(container.html)[e.add ? 'add' : 'remove'](e.marker); } }); eventBus.on('root.set', function() { self._updateOverlaysVisibilty(self._canvas.viewbox()); }); // clear overlays with diagram eventBus.on('diagram.clear', this.clear, this); }; // helpers ///////////////////////////// function createRoot(parentNode) { var root = domify( '
' ); assign$1(root, { position: 'absolute', width: 0, height: 0 }); parentNode.insertBefore(root, parentNode.firstChild); return root; } function setPosition(el, x, y) { assign$1(el, { left: x + 'px', top: y + 'px' }); } /** * Set element visible * * @param {DOMElement} el * @param {boolean} [visible=true] */ function setVisible(el, visible) { el.style.display = visible === false ? 'none' : ''; } function setTransform(el, transform) { el.style['transform-origin'] = 'top left'; [ '', '-ms-', '-webkit-' ].forEach(function(prefix) { el.style[prefix + 'transform'] = transform; }); } var OverlaysModule = { __init__: [ 'overlays' ], overlays: [ 'type', Overlays ] }; /** * Adds change support to the diagram, including * *
    *
  • redrawing shapes and connections on change
  • *
* * @param {EventBus} eventBus * @param {Canvas} canvas * @param {ElementRegistry} elementRegistry * @param {GraphicsFactory} graphicsFactory */ function ChangeSupport( eventBus, canvas, elementRegistry, graphicsFactory) { // redraw shapes / connections on change eventBus.on('element.changed', function(event) { var element = event.element; // element might have been deleted and replaced by new element with same ID // thus check for parent of element except for root element if (element.parent || element === canvas.getRootElement()) { event.gfx = elementRegistry.getGraphics(element); } // shape + gfx may have been deleted if (!event.gfx) { return; } eventBus.fire(getType(element) + '.changed', event); }); eventBus.on('elements.changed', function(event) { var elements = event.elements; elements.forEach(function(e) { eventBus.fire('element.changed', { element: e }); }); graphicsFactory.updateContainments(elements); }); eventBus.on('shape.changed', function(event) { graphicsFactory.update('shape', event.element, event.gfx); }); eventBus.on('connection.changed', function(event) { graphicsFactory.update('connection', event.element, event.gfx); }); } ChangeSupport.$inject = [ 'eventBus', 'canvas', 'elementRegistry', 'graphicsFactory' ]; var ChangeSupportModule = { __init__: [ 'changeSupport' ], changeSupport: [ 'type', ChangeSupport ] }; var DEFAULT_PRIORITY$2 = 1000; /** * A utility that can be used to plug-in into the command execution for * extension and/or validation. * * @param {EventBus} eventBus * * @example * * import inherits from 'inherits-browser'; * * import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; * * function CommandLogger(eventBus) { * CommandInterceptor.call(this, eventBus); * * this.preExecute(function(event) { * console.log('command pre-execute', event); * }); * } * * inherits(CommandLogger, CommandInterceptor); * */ function CommandInterceptor(eventBus) { this._eventBus = eventBus; } CommandInterceptor.$inject = [ 'eventBus' ]; function unwrapEvent(fn, that) { return function(event) { return fn.call(that || null, event.context, event.command, event); }; } /** * Register an interceptor for a command execution * * @param {string|Array} [events] list of commands to register on * @param {string} [hook] command hook, i.e. preExecute, executed to listen on * @param {number} [priority] the priority on which to hook into the execution * @param {Function} handlerFn interceptor to be invoked with (event) * @param {boolean} unwrap if true, unwrap the event and pass (context, command, event) to the * listener instead * @param {Object} [that] Pass context (`this`) to the handler function */ CommandInterceptor.prototype.on = function(events, hook, priority, handlerFn, unwrap, that) { if (isFunction(hook) || isNumber(hook)) { that = unwrap; unwrap = handlerFn; handlerFn = priority; priority = hook; hook = null; } if (isFunction(priority)) { that = unwrap; unwrap = handlerFn; handlerFn = priority; priority = DEFAULT_PRIORITY$2; } if (isObject(unwrap)) { that = unwrap; unwrap = false; } if (!isFunction(handlerFn)) { throw new Error('handlerFn must be a function'); } if (!isArray$2(events)) { events = [ events ]; } var eventBus = this._eventBus; forEach$1(events, function(event) { // concat commandStack(.event)?(.hook)? var fullEvent = [ 'commandStack', event, hook ].filter(function(e) { return e; }).join('.'); eventBus.on(fullEvent, priority, unwrap ? unwrapEvent(handlerFn, that) : handlerFn, that); }); }; var hooks = [ 'canExecute', 'preExecute', 'preExecuted', 'execute', 'executed', 'postExecute', 'postExecuted', 'revert', 'reverted' ]; /* * Install hook shortcuts * * This will generate the CommandInterceptor#(preExecute|...|reverted) methods * which will in term forward to CommandInterceptor#on. */ forEach$1(hooks, function(hook) { /** * {canExecute|preExecute|preExecuted|execute|executed|postExecute|postExecuted|revert|reverted} * * A named hook for plugging into the command execution * * @param {string|Array} [events] list of commands to register on * @param {number} [priority] the priority on which to hook into the execution * @param {Function} handlerFn interceptor to be invoked with (event) * @param {boolean} [unwrap=false] if true, unwrap the event and pass (context, command, event) to the * listener instead * @param {Object} [that] Pass context (`this`) to the handler function */ CommandInterceptor.prototype[hook] = function(events, priority, handlerFn, unwrap, that) { if (isFunction(events) || isNumber(events)) { that = unwrap; unwrap = handlerFn; handlerFn = priority; priority = events; events = null; } this.on(events, hook, priority, handlerFn, unwrap, that); }; }); /** * A modeling behavior that ensures we set the correct root element * as we undo and redo commands. * * @param {Canvas} canvas * @param {didi.Injector} injector */ function RootElementsBehavior(canvas, injector) { injector.invoke(CommandInterceptor, this); this.executed(function(event) { var context = event.context; if (context.rootElement) { canvas.setRootElement(context.rootElement); } else { context.rootElement = canvas.getRootElement(); } }); this.revert(function(event) { var context = event.context; if (context.rootElement) { canvas.setRootElement(context.rootElement); } }); } e(RootElementsBehavior, CommandInterceptor); RootElementsBehavior.$inject = [ 'canvas', 'injector' ]; var RootElementsModule = { __init__: [ 'rootElementsBehavior' ], rootElementsBehavior: [ 'type', RootElementsBehavior ] }; var css_escape = {exports: {}}; /*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */ (function (module, exports) { (function(root, factory) { // https://github.com/umdjs/umd/blob/master/returnExports.js { // For Node.js. module.exports = factory(root); } }(typeof commonjsGlobal != 'undefined' ? commonjsGlobal : commonjsGlobal, function(root) { if (root.CSS && root.CSS.escape) { return root.CSS.escape; } // https://drafts.csswg.org/cssom/#serialize-an-identifier var cssEscape = function(value) { if (arguments.length == 0) { throw new TypeError('`CSS.escape` requires an argument.'); } var string = String(value); var length = string.length; var index = -1; var codeUnit; var result = ''; var firstCodeUnit = string.charCodeAt(0); while (++index < length) { codeUnit = string.charCodeAt(index); // Note: there’s no need to special-case astral symbols, surrogate // pairs, or lone surrogates. // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER // (U+FFFD). if (codeUnit == 0x0000) { result += '\uFFFD'; continue; } if ( // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is // U+007F, […] (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || // If the character is the first character and is in the range [0-9] // (U+0030 to U+0039), […] (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || // If the character is the second character and is in the range [0-9] // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] ( index == 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit == 0x002D ) ) { // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point result += '\\' + codeUnit.toString(16) + ' '; continue; } if ( // If the character is the first character and is a `-` (U+002D), and // there is no second character, […] index == 0 && length == 1 && codeUnit == 0x002D ) { result += '\\' + string.charAt(index); continue; } // If the character is not handled by one of the above rules and is // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to // U+005A), or [a-z] (U+0061 to U+007A), […] if ( codeUnit >= 0x0080 || codeUnit == 0x002D || codeUnit == 0x005F || codeUnit >= 0x0030 && codeUnit <= 0x0039 || codeUnit >= 0x0041 && codeUnit <= 0x005A || codeUnit >= 0x0061 && codeUnit <= 0x007A ) { // the character itself result += string.charAt(index); continue; } // Otherwise, the escaped character. // https://drafts.csswg.org/cssom/#escape-a-character result += '\\' + string.charAt(index); } return result; }; if (!root.CSS) { root.CSS = {}; } root.CSS.escape = cssEscape; return cssEscape; })); } (css_escape)); var HTML_ESCAPE_MAP = { '&': '&', '<': '<', '>': '>', '"': '"', '\'': ''' }; function escapeHTML(str) { str = '' + str; return str && str.replace(/[&<>"']/g, function(match) { return HTML_ESCAPE_MAP[match]; }); } var planeSuffix = '_plane'; /** * Get plane ID for a primary shape. * * @param {djs.model.Base|ModdleElement} element * * @returns {String} */ function getPlaneIdFromShape(element) { var id = element.id; if (is$1(element, 'bpmn:SubProcess')) { return addPlaneSuffix(id); } return id; } function addPlaneSuffix(id) { return id + planeSuffix; } var OPEN_CLASS = 'bjs-breadcrumbs-shown'; /** * Adds overlays that allow switching planes on collapsed subprocesses. * * @param {eventBus} eventBus * @param {elementRegistry} elementRegistry * @param {overlays} overlays * @param {canvas} canvas */ function DrilldownBreadcrumbs(eventBus, elementRegistry, overlays, canvas) { var breadcrumbs = domify('
    '); var container = canvas.getContainer(); var containerClasses = classes(container); container.appendChild(breadcrumbs); var boParents = []; // update breadcrumbs if name or ID of the primary shape changes eventBus.on('element.changed', function(e) { var shape = e.element, bo = getBusinessObject(shape); var isPresent = find(boParents, function(el) { return el === bo; }); if (!isPresent) { return; } updateBreadcrumbs(); }); /** * Updates the displayed breadcrumbs. If no element is provided, only the * labels are updated. * * @param {djs.model.Base} [element] */ function updateBreadcrumbs(element) { if (element) { boParents = getBoParentChain(element); } var path = boParents.map(function(parent) { var title = escapeHTML(parent.name || parent.id); var link = domify('
  • ' + title + '
  • '); var parentPlane = canvas.findRoot(getPlaneIdFromShape(parent)) || canvas.findRoot(parent.id); // when the root is a collaboration, the process does not have a corresponding // element in the elementRegisty. Instead, we search for the corresponding participant if (!parentPlane && is$1(parent, 'bpmn:Process')) { var participant = elementRegistry.find(function(element) { var bo = getBusinessObject(element); return bo && bo.processRef && bo.processRef === parent; }); parentPlane = canvas.findRoot(participant.id); } link.addEventListener('click', function() { canvas.setRootElement(parentPlane); }); return link; }); breadcrumbs.innerHTML = ''; // show breadcrumbs and expose state to .djs-container var visible = path.length > 1; containerClasses.toggle(OPEN_CLASS, visible); path.forEach(function(el) { breadcrumbs.appendChild(el); }); } eventBus.on('root.set', function(event) { updateBreadcrumbs(event.element); }); } DrilldownBreadcrumbs.$inject = [ 'eventBus', 'elementRegistry', 'overlays', 'canvas' ]; // helpers ////////// /** * Returns the parents for the element using the business object chain, * starting with the root element. * * @param {djs.model.Shape} child * * @returns {Array} parents */ function getBoParentChain(child) { var bo = getBusinessObject(child); var parents = []; for (var element = bo; element; element = element.$parent) { if (is$1(element, 'bpmn:SubProcess') || is$1(element, 'bpmn:Process')) { parents.push(element); } } return parents.reverse(); } /** * Move collapsed subprocesses into view when drilling down. * * Zoom and scroll are saved in a session. * * @param {eventBus} eventBus * @param {canvas} canvas */ function DrilldownCentering(eventBus, canvas) { var currentRoot = null; var positionMap = new Map(); eventBus.on('root.set', function(event) { var newRoot = event.element; var currentViewbox = canvas.viewbox(); var storedViewbox = positionMap.get(newRoot); positionMap.set(currentRoot, { x: currentViewbox.x, y: currentViewbox.y, zoom: currentViewbox.scale }); currentRoot = newRoot; // current root was replaced with a collaboration, we don't update the viewbox if (is$1(newRoot, 'bpmn:Collaboration') && !storedViewbox) { return; } storedViewbox = storedViewbox || { x: 0, y: 0, zoom: 1 }; var dx = (currentViewbox.x - storedViewbox.x) * currentViewbox.scale, dy = (currentViewbox.y - storedViewbox.y) * currentViewbox.scale; if (dx !== 0 || dy !== 0) { canvas.scroll({ dx: dx, dy: dy }); } if (storedViewbox.zoom !== currentViewbox.scale) { canvas.zoom(storedViewbox.zoom, { x: 0, y: 0 }); } }); eventBus.on('diagram.clear', function() { positionMap.clear(); currentRoot = null; }); } DrilldownCentering.$inject = [ 'eventBus', 'canvas' ]; /** * ES5 Map implementation. Works. */ function Map() { this._entries = []; this.set = function(key, value) { var found = false; for (var k in this._entries) { if (this._entries[k][0] === key) { this._entries[k][1] = value; found = true; break; } } if (!found) { this._entries.push([ key, value ]); } }; this.get = function(key) { for (var k in this._entries) { if (this._entries[k][0] === key) { return this._entries[k][1]; } } return null; }; this.clear = function() { this._entries.length = 0; }; this.remove = function(key) { var idx = -1; for (var k in this._entries) { if (this._entries[k][0] === key) { idx = k; break; } } if (idx !== -1) { this._entries.splice(idx, 1); } }; } var DEFAULT_POSITION = { x: 180, y: 160 }; /** * Hook into `import.render.start` and create new planes for diagrams with * collapsed subprocesses and all dis on the same plane. * * @param {eventBus} eventBus * @param {moddle} moddle */ function SubprocessCompatibility(eventBus, moddle) { this._eventBus = eventBus; this._moddle = moddle; var self = this; eventBus.on('import.render.start', 1500, function(e, context) { self.handleImport(context.definitions); }); } SubprocessCompatibility.prototype.handleImport = function(definitions) { if (!definitions.diagrams) { return; } var self = this; this._definitions = definitions; this._processToDiagramMap = {}; definitions.diagrams.forEach(function(diagram) { if (!diagram.plane || !diagram.plane.bpmnElement) { return; } self._processToDiagramMap[diagram.plane.bpmnElement.id] = diagram; }); var newDiagrams = []; definitions.diagrams.forEach(function(diagram) { var createdDiagrams = self.createNewDiagrams(diagram.plane); Array.prototype.push.apply(newDiagrams, createdDiagrams); }); newDiagrams.forEach(function(diagram) { self.movePlaneElementsToOrigin(diagram.plane); }); }; /** * Moves all DI elements from collapsed subprocesses to a new plane. * * @param {Object} plane * @return {Array} new diagrams created for the collapsed subprocesses */ SubprocessCompatibility.prototype.createNewDiagrams = function(plane) { var self = this; var collapsedElements = []; var elementsToMove = []; plane.get('planeElement').forEach(function(diElement) { var bo = diElement.bpmnElement; if (!bo) { return; } var parent = bo.$parent; if (is$1(bo, 'bpmn:SubProcess') && !diElement.isExpanded) { collapsedElements.push(bo); } if (shouldMoveToPlane(bo, plane)) { // don't change the array while we iterate over it elementsToMove.push({ diElement: diElement, parent: parent }); } }); var newDiagrams = []; // create new planes for all collapsed subprocesses, even when they are empty collapsedElements.forEach(function(element) { if (!self._processToDiagramMap[element.id]) { var diagram = self.createDiagram(element); self._processToDiagramMap[element.id] = diagram; newDiagrams.push(diagram); } }); elementsToMove.forEach(function(element) { var diElement = element.diElement; var parent = element.parent; // parent is expanded, get nearest collapsed parent while (parent && collapsedElements.indexOf(parent) === -1) { parent = parent.$parent; } // false positive, all parents are expanded if (!parent) { return; } var diagram = self._processToDiagramMap[parent.id]; self.moveToDiPlane(diElement, diagram.plane); }); return newDiagrams; }; SubprocessCompatibility.prototype.movePlaneElementsToOrigin = function(plane) { var elements = plane.get('planeElement'); // get bounding box of all elements var planeBounds = getPlaneBounds(plane); var offset = { x: planeBounds.x - DEFAULT_POSITION.x, y: planeBounds.y - DEFAULT_POSITION.y }; elements.forEach(function(diElement) { if (diElement.waypoint) { diElement.waypoint.forEach(function(waypoint) { waypoint.x = waypoint.x - offset.x; waypoint.y = waypoint.y - offset.y; }); } else if (diElement.bounds) { diElement.bounds.x = diElement.bounds.x - offset.x; diElement.bounds.y = diElement.bounds.y - offset.y; } }); }; SubprocessCompatibility.prototype.moveToDiPlane = function(diElement, newPlane) { var containingDiagram = findRootDiagram(diElement); // remove DI from old Plane and add it to the new one var parentPlaneElement = containingDiagram.plane.get('planeElement'); parentPlaneElement.splice(parentPlaneElement.indexOf(diElement), 1); newPlane.get('planeElement').push(diElement); }; SubprocessCompatibility.prototype.createDiagram = function(bo) { var plane = this._moddle.create('bpmndi:BPMNPlane', { bpmnElement: bo }); var diagram = this._moddle.create('bpmndi:BPMNDiagram', { plane: plane }); plane.$parent = diagram; plane.bpmnElement = bo; diagram.$parent = this._definitions; this._definitions.diagrams.push(diagram); return diagram; }; SubprocessCompatibility.$inject = [ 'eventBus', 'moddle' ]; // helpers ////////////////////////// function findRootDiagram(element) { if (is$1(element, 'bpmndi:BPMNDiagram')) { return element; } else { return findRootDiagram(element.$parent); } } function getPlaneBounds(plane) { var planeTrbl = { top: Infinity, right: -Infinity, bottom: -Infinity, left: Infinity }; plane.planeElement.forEach(function(element) { if (!element.bounds) { return; } var trbl = asTRBL(element.bounds); planeTrbl.top = Math.min(trbl.top, planeTrbl.top); planeTrbl.left = Math.min(trbl.left, planeTrbl.left); }); return asBounds(planeTrbl); } function shouldMoveToPlane(bo, plane) { var parent = bo.$parent; // don't move elements that are already on the plane if (!is$1(parent, 'bpmn:SubProcess') || parent === plane.bpmnElement) { return false; } // dataAssociations are children of the subprocess but rendered on process level // cf. https://github.com/bpmn-io/bpmn-js/issues/1619 if (isAny(bo, [ 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation' ])) { return false; } return true; } var LOW_PRIORITY$1 = 250; var ARROW_DOWN_SVG = ''; var EMPTY_MARKER = 'bjs-drilldown-empty'; function DrilldownOverlayBehavior( canvas, eventBus, elementRegistry, overlays ) { CommandInterceptor.call(this, eventBus); this._canvas = canvas; this._eventBus = eventBus; this._elementRegistry = elementRegistry; this._overlays = overlays; var self = this; this.executed('shape.toggleCollapse', LOW_PRIORITY$1, function(context) { var shape = context.shape; // Add overlay to the collapsed shape if (self.canDrillDown(shape)) { self.addOverlay(shape); } else { self.removeOverlay(shape); } }, true); this.reverted('shape.toggleCollapse', LOW_PRIORITY$1, function(context) { var shape = context.shape; // Add overlay to the collapsed shape if (self.canDrillDown(shape)) { self.addOverlay(shape); } else { self.removeOverlay(shape); } }, true); this.executed([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY$1, function(context) { var oldParent = context.oldParent, newParent = context.newParent || context.parent, shape = context.shape; // Add overlay to the collapsed shape if (self.canDrillDown(shape)) { self.addOverlay(shape); } self.updateDrilldownOverlay(oldParent); self.updateDrilldownOverlay(newParent); self.updateDrilldownOverlay(shape); }, true); this.reverted([ 'shape.create', 'shape.move', 'shape.delete' ], LOW_PRIORITY$1, function(context) { var oldParent = context.oldParent, newParent = context.newParent || context.parent, shape = context.shape; // Add overlay to the collapsed shape if (self.canDrillDown(shape)) { self.addOverlay(shape); } self.updateDrilldownOverlay(oldParent); self.updateDrilldownOverlay(newParent); self.updateDrilldownOverlay(shape); }, true); eventBus.on('import.render.complete', function() { elementRegistry.filter(function(e) { return self.canDrillDown(e); }).map(function(el) { self.addOverlay(el); }); }); } e(DrilldownOverlayBehavior, CommandInterceptor); DrilldownOverlayBehavior.prototype.updateDrilldownOverlay = function(shape) { var canvas = this._canvas; if (!shape) { return; } var root = canvas.findRoot(shape); if (root) { this.updateOverlayVisibility(root); } }; DrilldownOverlayBehavior.prototype.canDrillDown = function(element) { var canvas = this._canvas; return is$1(element, 'bpmn:SubProcess') && canvas.findRoot(getPlaneIdFromShape(element)); }; /** * Updates visibility of the drilldown overlay. If the plane has no elements, * the drilldown will be only shown when the element is selected. * * @param {djs.model.Shape|djs.model.Root} element collapsed shape or root element */ DrilldownOverlayBehavior.prototype.updateOverlayVisibility = function(element) { var overlays = this._overlays; var bo = element.businessObject; var overlay = overlays.get({ element: bo.id, type: 'drilldown' })[0]; if (!overlay) { return; } var hasContent = bo && bo.flowElements && bo.flowElements.length; classes(overlay.html).toggle(EMPTY_MARKER, !hasContent); }; /** * Attaches a drilldown button to the given element. We assume that the plane has * the same id as the element. * * @param {djs.model.Shape} element collapsed shape */ DrilldownOverlayBehavior.prototype.addOverlay = function(element) { var canvas = this._canvas; var overlays = this._overlays; var existingOverlays = overlays.get({ element: element, type: 'drilldown' }); if (existingOverlays.length) { this.removeOverlay(element); } var button = domify(''); button.addEventListener('click', function() { canvas.setRootElement(canvas.findRoot(getPlaneIdFromShape(element))); }); overlays.add(element, 'drilldown', { position: { bottom: -7, right: -8 }, html: button }); this.updateOverlayVisibility(element); }; DrilldownOverlayBehavior.prototype.removeOverlay = function(element) { var overlays = this._overlays; overlays.remove({ element: element, type: 'drilldown' }); }; DrilldownOverlayBehavior.$inject = [ 'canvas', 'eventBus', 'elementRegistry', 'overlays' ]; var DrilldownModdule = { __depends__: [ OverlaysModule, ChangeSupportModule, RootElementsModule ], __init__: [ 'drilldownBreadcrumbs', 'drilldownOverlayBehavior', 'drilldownCentering', 'subprocessCompatibility' ], drilldownBreadcrumbs: [ 'type', DrilldownBreadcrumbs ], drilldownCentering: [ 'type', DrilldownCentering ], drilldownOverlayBehavior: [ 'type', DrilldownOverlayBehavior ], subprocessCompatibility: [ 'type', SubprocessCompatibility ] }; var CLASS_PATTERN = /^class /; /** * @param {function} fn * * @return {boolean} */ function isClass(fn) { return CLASS_PATTERN.test(fn.toString()); } /** * @param {any} obj * * @return {boolean} */ function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; } /** * @param {any} obj * @param {string} prop * * @return {boolean} */ function hasOwnProp(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } /** * @typedef {import('./index').InjectAnnotated } InjectAnnotated */ /** * @template T * * @params {[...string[], T] | ...string[], T} args * * @return {T & InjectAnnotated} */ function annotate() { var args = Array.prototype.slice.call(arguments); if (args.length === 1 && isArray(args[0])) { args = args[0]; } var fn = args.pop(); fn.$inject = args; return fn; } // Current limitations: // - can't put into "function arg" comments // function /* (no parenthesis like this) */ (){} // function abc( /* xx (no parenthesis like this) */ a, b) {} // // Just put the comment before function or inside: // /* (((this is fine))) */ function(a, b) {} // function abc(a) { /* (((this is fine))) */} // // - can't reliably auto-annotate constructor; we'll match the // first constructor(...) pattern found which may be the one // of a nested class, too. var CONSTRUCTOR_ARGS = /constructor\s*[^(]*\(\s*([^)]*)\)/m; var FN_ARGS = /^(?:async\s+)?(?:function\s*[^(]*)?(?:\(\s*([^)]*)\)|(\w+))/m; var FN_ARG = /\/\*([^*]*)\*\//m; /** * @param {unknown} fn * * @return {string[]} */ function parseAnnotations(fn) { if (typeof fn !== 'function') { throw new Error('Cannot annotate "' + fn + '". Expected a function!'); } var match = fn.toString().match(isClass(fn) ? CONSTRUCTOR_ARGS : FN_ARGS); // may parse class without constructor if (!match) { return []; } var args = match[1] || match[2]; return args && args.split(',').map(function(arg) { var argMatch = arg.match(FN_ARG); return (argMatch && argMatch[1] || arg).trim(); }) || []; } /** * @typedef { import('./index').ModuleDeclaration } ModuleDeclaration * @typedef { import('./index').ModuleDefinition } ModuleDefinition * @typedef { import('./index').InjectorContext } InjectorContext */ /** * Create a new injector with the given modules. * * @param {ModuleDefinition[]} modules * @param {InjectorContext} [parent] */ function Injector(modules, parent) { parent = parent || { get: function(name, strict) { currentlyResolving.push(name); if (strict === false) { return null; } else { throw error('No provider for "' + name + '"!'); } } }; var currentlyResolving = []; var providers = this._providers = Object.create(parent._providers || null); var instances = this._instances = Object.create(null); var self = instances.injector = this; var error = function(msg) { var stack = currentlyResolving.join(' -> '); currentlyResolving.length = 0; return new Error(stack ? msg + ' (Resolving: ' + stack + ')' : msg); }; /** * Return a named service. * * @param {string} name * @param {boolean} [strict=true] if false, resolve missing services to null * * @return {any} */ function get(name, strict) { if (!providers[name] && name.indexOf('.') !== -1) { var parts = name.split('.'); var pivot = get(parts.shift()); while (parts.length) { pivot = pivot[parts.shift()]; } return pivot; } if (hasOwnProp(instances, name)) { return instances[name]; } if (hasOwnProp(providers, name)) { if (currentlyResolving.indexOf(name) !== -1) { currentlyResolving.push(name); throw error('Cannot resolve circular dependency!'); } currentlyResolving.push(name); instances[name] = providers[name][0](providers[name][1]); currentlyResolving.pop(); return instances[name]; } return parent.get(name, strict); } function fnDef(fn, locals) { if (typeof locals === 'undefined') { locals = {}; } if (typeof fn !== 'function') { if (isArray(fn)) { fn = annotate(fn.slice()); } else { throw new Error('Cannot invoke "' + fn + '". Expected a function!'); } } var inject = fn.$inject || parseAnnotations(fn); var dependencies = inject.map(function(dep) { if (hasOwnProp(locals, dep)) { return locals[dep]; } else { return get(dep); } }); return { fn: fn, dependencies: dependencies }; } function instantiate(Type) { var def = fnDef(Type); var fn = def.fn, dependencies = def.dependencies; // instantiate var args constructor var Constructor = Function.prototype.bind.apply(fn, [ null ].concat(dependencies)); return new Constructor(); } function invoke(func, context, locals) { var def = fnDef(func, locals); var fn = def.fn, dependencies = def.dependencies; return fn.apply(context, dependencies); } /** * @param {Injector} childInjector * * @return {Function} */ function createPrivateInjectorFactory(childInjector) { return annotate(function(key) { return childInjector.get(key); }); } /** * @param {ModuleDefinition[]} modules * @param {string[]} [forceNewInstances] * * @return {Injector} */ function createChild(modules, forceNewInstances) { if (forceNewInstances && forceNewInstances.length) { var fromParentModule = Object.create(null); var matchedScopes = Object.create(null); var privateInjectorsCache = []; var privateChildInjectors = []; var privateChildFactories = []; var provider; var cacheIdx; var privateChildInjector; var privateChildInjectorFactory; for (var name in providers) { provider = providers[name]; if (forceNewInstances.indexOf(name) !== -1) { if (provider[2] === 'private') { cacheIdx = privateInjectorsCache.indexOf(provider[3]); if (cacheIdx === -1) { privateChildInjector = provider[3].createChild([], forceNewInstances); privateChildInjectorFactory = createPrivateInjectorFactory(privateChildInjector); privateInjectorsCache.push(provider[3]); privateChildInjectors.push(privateChildInjector); privateChildFactories.push(privateChildInjectorFactory); fromParentModule[name] = [ privateChildInjectorFactory, name, 'private', privateChildInjector ]; } else { fromParentModule[name] = [ privateChildFactories[cacheIdx], name, 'private', privateChildInjectors[cacheIdx] ]; } } else { fromParentModule[name] = [ provider[2], provider[1] ]; } matchedScopes[name] = true; } if ((provider[2] === 'factory' || provider[2] === 'type') && provider[1].$scope) { /* jshint -W083 */ forceNewInstances.forEach(function(scope) { if (provider[1].$scope.indexOf(scope) !== -1) { fromParentModule[name] = [ provider[2], provider[1] ]; matchedScopes[scope] = true; } }); } } forceNewInstances.forEach(function(scope) { if (!matchedScopes[scope]) { throw new Error('No provider for "' + scope + '". Cannot use provider from the parent!'); } }); modules.unshift(fromParentModule); } return new Injector(modules, self); } var factoryMap = { factory: invoke, type: instantiate, value: function(value) { return value; } }; /** * @param {ModuleDefinition} moduleDefinition * @param {Injector} injector */ function createInitializer(moduleDefinition, injector) { var initializers = moduleDefinition.__init__ || []; return function() { initializers.forEach(function(initializer) { // eagerly resolve component (fn or string) if (typeof initializer === 'string') { injector.get(initializer); } else { injector.invoke(initializer); } }); }; } /** * @param {ModuleDefinition} moduleDefinition */ function loadModule(moduleDefinition) { var moduleExports = moduleDefinition.__exports__; // private module if (moduleExports) { var nestedModules = moduleDefinition.__modules__; var clonedModule = Object.keys(moduleDefinition).reduce(function(clonedModule, key) { if (key !== '__exports__' && key !== '__modules__' && key !== '__init__' && key !== '__depends__') { clonedModule[key] = moduleDefinition[key]; } return clonedModule; }, Object.create(null)); var childModules = (nestedModules || []).concat(clonedModule); var privateInjector = createChild(childModules); var getFromPrivateInjector = annotate(function(key) { return privateInjector.get(key); }); moduleExports.forEach(function(key) { providers[key] = [ getFromPrivateInjector, key, 'private', privateInjector ]; }); // ensure child injector initializes var initializers = (moduleDefinition.__init__ || []).slice(); initializers.unshift(function() { privateInjector.init(); }); moduleDefinition = Object.assign({}, moduleDefinition, { __init__: initializers }); return createInitializer(moduleDefinition, privateInjector); } // normal module Object.keys(moduleDefinition).forEach(function(key) { if (key === '__init__' || key === '__depends__') { return; } if (moduleDefinition[key][2] === 'private') { providers[key] = moduleDefinition[key]; return; } var type = moduleDefinition[key][0]; var value = moduleDefinition[key][1]; providers[key] = [ factoryMap[type], arrayUnwrap(type, value), type ]; }); return createInitializer(moduleDefinition, self); } /** * @param {ModuleDefinition[]} moduleDefinitions * @param {ModuleDefinition} moduleDefinition * * @return {ModuleDefinition[]} */ function resolveDependencies(moduleDefinitions, moduleDefinition) { if (moduleDefinitions.indexOf(moduleDefinition) !== -1) { return moduleDefinitions; } moduleDefinitions = (moduleDefinition.__depends__ || []).reduce(resolveDependencies, moduleDefinitions); if (moduleDefinitions.indexOf(moduleDefinition) !== -1) { return moduleDefinitions; } return moduleDefinitions.concat(moduleDefinition); } /** * @param {ModuleDefinition[]} moduleDefinitions * * @return { () => void } initializerFn */ function bootstrap(moduleDefinitions) { var initializers = moduleDefinitions .reduce(resolveDependencies, []) .map(loadModule); var initialized = false; return function() { if (initialized) { return; } initialized = true; initializers.forEach(function(initializer) { return initializer(); }); }; } // public API this.get = get; this.invoke = invoke; this.instantiate = instantiate; this.createChild = createChild; // setup this.init = bootstrap(modules); } // helpers /////////////// function arrayUnwrap(type, value) { if (type !== 'value' && isArray(value)) { value = annotate(value.slice()); } return value; } // apply default renderer with lowest possible priority // so that it only kicks in if noone else could render var DEFAULT_RENDER_PRIORITY = 1; /** * The default renderer used for shapes and connections. * * @param {EventBus} eventBus * @param {Styles} styles */ function DefaultRenderer(eventBus, styles) { // BaseRenderer.call(this, eventBus, DEFAULT_RENDER_PRIORITY); this.CONNECTION_STYLE = styles.style([ 'no-fill' ], { strokeWidth: 5, stroke: 'fuchsia' }); this.SHAPE_STYLE = styles.style({ fill: 'white', stroke: 'fuchsia', strokeWidth: 2 }); this.FRAME_STYLE = styles.style([ 'no-fill' ], { stroke: 'fuchsia', strokeDasharray: 4, strokeWidth: 2 }); } e(DefaultRenderer, BaseRenderer); DefaultRenderer.prototype.canRender = function() { return true; }; DefaultRenderer.prototype.drawShape = function drawShape(visuals, element, attrs) { var rect = create$1('rect'); attr$1(rect, { x: 0, y: 0, width: element.width || 0, height: element.height || 0 }); if (isFrameElement(element)) { attr$1(rect, assign({}, this.FRAME_STYLE, attrs || {})); } else { attr$1(rect, assign({}, this.SHAPE_STYLE, attrs || {})); } append(visuals, rect); return rect; }; DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection, attrs) { var line = createLine(connection.waypoints, assign({}, this.CONNECTION_STYLE, attrs || {})); append(visuals, line); return line; }; DefaultRenderer.prototype.getShapePath = function getShapePath(shape) { var x = shape.x, y = shape.y, width = shape.width, height = shape.height; var shapePath = [ [ 'M', x, y ], [ 'l', width, 0 ], [ 'l', 0, height ], [ 'l', -width, 0 ], [ 'z' ] ]; return componentsToPath(shapePath); }; DefaultRenderer.prototype.getConnectionPath = function getConnectionPath(connection) { var waypoints = connection.waypoints; var idx, point, connectionPath = []; for (idx = 0; (point = waypoints[idx]); idx++) { // take invisible docking into account // when creating the path point = point.original || point; connectionPath.push([ idx === 0 ? 'M' : 'L', point.x, point.y ]); } return componentsToPath(connectionPath); }; DefaultRenderer.$inject = [ 'eventBus', 'styles' ]; /** * A component that manages shape styles */ function Styles() { var defaultTraits = { 'no-fill': { fill: 'none' }, 'no-border': { strokeOpacity: 0.0 }, 'no-events': { pointerEvents: 'none' } }; var self = this; /** * Builds a style definition from a className, a list of traits and an object of additional attributes. * * @param {string} className * @param {Array} traits * @param {Object} additionalAttrs * * @return {Object} the style defintion */ this.cls = function(className, traits, additionalAttrs) { var attrs = this.style(traits, additionalAttrs); return assign(attrs, { 'class': className }); }; /** * Builds a style definition from a list of traits and an object of additional attributes. * * @param {Array} traits * @param {Object} additionalAttrs * * @return {Object} the style defintion */ this.style = function(traits, additionalAttrs) { if (!isArray$2(traits) && !additionalAttrs) { additionalAttrs = traits; traits = []; } var attrs = reduce(traits, function(attrs, t) { return assign(attrs, defaultTraits[t] || {}); }, {}); return additionalAttrs ? assign(attrs, additionalAttrs) : attrs; }; this.computeStyle = function(custom, traits, defaultStyles) { if (!isArray$2(traits)) { defaultStyles = traits; traits = []; } return self.style(traits || [], assign({}, defaultStyles, custom || {})); }; } var DrawModule = { __init__: [ 'defaultRenderer' ], defaultRenderer: [ 'type', DefaultRenderer ], styles: [ 'type', Styles ] }; /** * Failsafe remove an element from a collection * * @param {Array} [collection] * @param {Object} [element] * * @return {number} the previous index of the element */ function remove(collection, element) { if (!collection || !element) { return -1; } var idx = collection.indexOf(element); if (idx !== -1) { collection.splice(idx, 1); } return idx; } /** * Fail save add an element to the given connection, ensuring * it does not yet exist. * * @param {Array} collection * @param {Object} element * @param {number} idx */ function add(collection, element, idx) { if (!collection || !element) { return; } if (typeof idx !== 'number') { idx = -1; } var currentIdx = collection.indexOf(element); if (currentIdx !== -1) { if (currentIdx === idx) { // nothing to do, position has not changed return; } else { if (idx !== -1) { // remove from current position collection.splice(currentIdx, 1); } else { // already exists in collection return; } } } if (idx !== -1) { // insert at specified position collection.splice(idx, 0, element); } else { // push to end collection.push(element); } } function round(number, resolution) { return Math.round(number * resolution) / resolution; } function ensurePx(number) { return isNumber(number) ? number + 'px' : number; } function findRoot(element) { while (element.parent) { element = element.parent; } return element; } /** * Creates a HTML container element for a SVG element with * the given configuration * * @param {Object} options * @return {HTMLElement} the container element */ function createContainer(options) { options = assign({}, { width: '100%', height: '100%' }, options); var container = options.container || document.body; // create a
    around the svg element with the respective size // this way we can always get the correct container size // (this is impossible for elements at the moment) var parent = document.createElement('div'); parent.setAttribute('class', 'djs-container'); assign$1(parent, { position: 'relative', overflow: 'hidden', width: ensurePx(options.width), height: ensurePx(options.height) }); container.appendChild(parent); return parent; } function createGroup(parent, cls, childIndex) { var group = create$1('g'); classes$1(group).add(cls); var index = childIndex !== undefined ? childIndex : parent.childNodes.length - 1; // must ensure second argument is node or _null_ // cf. https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore parent.insertBefore(group, parent.childNodes[index] || null); return group; } var BASE_LAYER = 'base'; // render plane contents behind utility layers var PLANE_LAYER_INDEX = 0; var UTILITY_LAYER_INDEX = 1; var REQUIRED_MODEL_ATTRS = { shape: [ 'x', 'y', 'width', 'height' ], connection: [ 'waypoints' ] }; /** * The main drawing canvas. * * @class * @constructor * * @emits Canvas#canvas.init * * @param {Object} config * @param {EventBus} eventBus * @param {GraphicsFactory} graphicsFactory * @param {ElementRegistry} elementRegistry */ function Canvas(config, eventBus, graphicsFactory, elementRegistry) { this._eventBus = eventBus; this._elementRegistry = elementRegistry; this._graphicsFactory = graphicsFactory; this._rootsIdx = 0; this._layers = {}; this._planes = []; this._rootElement = null; this._init(config || {}); } Canvas.$inject = [ 'config.canvas', 'eventBus', 'graphicsFactory', 'elementRegistry' ]; /** * Creates a element that is wrapped into a
    . * This way we are always able to correctly figure out the size of the svg element * by querying the parent node. * (It is not possible to get the size of a svg element cross browser @ 2014-04-01) *
    * * ... * *
    */ Canvas.prototype._init = function(config) { var eventBus = this._eventBus; // html container var container = this._container = createContainer(config); var svg = this._svg = create$1('svg'); attr$1(svg, { width: '100%', height: '100%' }); append(container, svg); var viewport = this._viewport = createGroup(svg, 'viewport'); // debounce canvas.viewbox.changed events // for smoother diagram interaction if (config.deferUpdate !== false) { this._viewboxChanged = debounce(bind(this._viewboxChanged, this), 300); } eventBus.on('diagram.init', function() { /** * An event indicating that the canvas is ready to be drawn on. * * @memberOf Canvas * * @event canvas.init * * @type {Object} * @property {SVGElement} svg the created svg element * @property {SVGElement} viewport the direct parent of diagram elements and shapes */ eventBus.fire('canvas.init', { svg: svg, viewport: viewport }); }, this); // reset viewbox on shape changes to // recompute the viewbox eventBus.on([ 'shape.added', 'connection.added', 'shape.removed', 'connection.removed', 'elements.changed', 'root.set' ], function() { delete this._cachedViewbox; }, this); eventBus.on('diagram.destroy', 500, this._destroy, this); eventBus.on('diagram.clear', 500, this._clear, this); }; Canvas.prototype._destroy = function(emit) { this._eventBus.fire('canvas.destroy', { svg: this._svg, viewport: this._viewport }); var parent = this._container.parentNode; if (parent) { parent.removeChild(this._container); } delete this._svg; delete this._container; delete this._layers; delete this._planes; delete this._rootElement; delete this._viewport; }; Canvas.prototype._clear = function() { var self = this; var allElements = this._elementRegistry.getAll(); // remove all elements allElements.forEach(function(element) { var type = getType(element); if (type === 'root') { self.removeRootElement(element); } else { self._removeElement(element, type); } }); // remove all planes this._planes = []; this._rootElement = null; // force recomputation of view box delete this._cachedViewbox; }; /** * Returns the default layer on which * all elements are drawn. * * @returns {SVGElement} */ Canvas.prototype.getDefaultLayer = function() { return this.getLayer(BASE_LAYER, PLANE_LAYER_INDEX); }; /** * Returns a layer that is used to draw elements * or annotations on it. * * Non-existing layers retrieved through this method * will be created. During creation, the optional index * may be used to create layers below or above existing layers. * A layer with a certain index is always created above all * existing layers with the same index. * * @param {string} name * @param {number} index * * @returns {SVGElement} */ Canvas.prototype.getLayer = function(name, index) { if (!name) { throw new Error('must specify a name'); } var layer = this._layers[name]; if (!layer) { layer = this._layers[name] = this._createLayer(name, index); } // throw an error if layer creation / retrival is // requested on different index if (typeof index !== 'undefined' && layer.index !== index) { throw new Error('layer <' + name + '> already created at index <' + index + '>'); } return layer.group; }; /** * For a given index, return the number of layers that have a higher index and * are visible. * * This is used to determine the node a layer should be inserted at. * * @param {Number} index * @returns {Number} */ Canvas.prototype._getChildIndex = function(index) { return reduce(this._layers, function(childIndex, layer) { if (layer.visible && index >= layer.index) { childIndex++; } return childIndex; }, 0); }; /** * Creates a given layer and returns it. * * @param {string} name * @param {number} [index=0] * * @return {Object} layer descriptor with { index, group: SVGGroup } */ Canvas.prototype._createLayer = function(name, index) { if (typeof index === 'undefined') { index = UTILITY_LAYER_INDEX; } var childIndex = this._getChildIndex(index); return { group: createGroup(this._viewport, 'layer-' + name, childIndex), index: index, visible: true }; }; /** * Shows a given layer. * * @param {String} layer * @returns {SVGElement} */ Canvas.prototype.showLayer = function(name) { if (!name) { throw new Error('must specify a name'); } var layer = this._layers[name]; if (!layer) { throw new Error('layer <' + name + '> does not exist'); } var viewport = this._viewport; var group = layer.group; var index = layer.index; if (layer.visible) { return group; } var childIndex = this._getChildIndex(index); viewport.insertBefore(group, viewport.childNodes[childIndex] || null); layer.visible = true; return group; }; /** * Hides a given layer. * * @param {String} layer * @returns {SVGElement} */ Canvas.prototype.hideLayer = function(name) { if (!name) { throw new Error('must specify a name'); } var layer = this._layers[name]; if (!layer) { throw new Error('layer <' + name + '> does not exist'); } var group = layer.group; if (!layer.visible) { return group; } remove$2(group); layer.visible = false; return group; }; Canvas.prototype._removeLayer = function(name) { var layer = this._layers[name]; if (layer) { delete this._layers[name]; remove$2(layer.group); } }; /** * Returns the currently active layer. Can be null. * * @returns {SVGElement|null} */ Canvas.prototype.getActiveLayer = function() { var plane = this._findPlaneForRoot(this.getRootElement()); if (!plane) { return null; } return plane.layer; }; /** * Returns the plane which contains the given element. * * @param {string|djs.model.Base} element * * @return {djs.model.Base} root for element */ Canvas.prototype.findRoot = function(element) { if (typeof element === 'string') { element = this._elementRegistry.get(element); } if (!element) { return; } var plane = this._findPlaneForRoot( findRoot(element) ) || {}; return plane.rootElement; }; /** * Return a list of all root elements on the diagram. * * @return {djs.model.Root[]} */ Canvas.prototype.getRootElements = function() { return this._planes.map(function(plane) { return plane.rootElement; }); }; Canvas.prototype._findPlaneForRoot = function(rootElement) { return find(this._planes, function(plane) { return plane.rootElement === rootElement; }); }; /** * Returns the html element that encloses the * drawing canvas. * * @return {DOMNode} */ Canvas.prototype.getContainer = function() { return this._container; }; // markers ////////////////////// Canvas.prototype._updateMarker = function(element, marker, add) { var container; if (!element.id) { element = this._elementRegistry.get(element); } // we need to access all container = this._elementRegistry._elements[element.id]; if (!container) { return; } forEach$1([ container.gfx, container.secondaryGfx ], function(gfx) { if (gfx) { // invoke either addClass or removeClass based on mode if (add) { classes$1(gfx).add(marker); } else { classes$1(gfx).remove(marker); } } }); /** * An event indicating that a marker has been updated for an element * * @event element.marker.update * @type {Object} * @property {djs.model.Element} element the shape * @property {Object} gfx the graphical representation of the shape * @property {string} marker * @property {boolean} add true if the marker was added, false if it got removed */ this._eventBus.fire('element.marker.update', { element: element, gfx: container.gfx, marker: marker, add: !!add }); }; /** * Adds a marker to an element (basically a css class). * * Fires the element.marker.update event, making it possible to * integrate extension into the marker life-cycle, too. * * @example * canvas.addMarker('foo', 'some-marker'); * * var fooGfx = canvas.getGraphics('foo'); * * fooGfx; // ... * * @param {string|djs.model.Base} element * @param {string} marker */ Canvas.prototype.addMarker = function(element, marker) { this._updateMarker(element, marker, true); }; /** * Remove a marker from an element. * * Fires the element.marker.update event, making it possible to * integrate extension into the marker life-cycle, too. * * @param {string|djs.model.Base} element * @param {string} marker */ Canvas.prototype.removeMarker = function(element, marker) { this._updateMarker(element, marker, false); }; /** * Check the existence of a marker on element. * * @param {string|djs.model.Base} element * @param {string} marker */ Canvas.prototype.hasMarker = function(element, marker) { if (!element.id) { element = this._elementRegistry.get(element); } var gfx = this.getGraphics(element); return classes$1(gfx).has(marker); }; /** * Toggles a marker on an element. * * Fires the element.marker.update event, making it possible to * integrate extension into the marker life-cycle, too. * * @param {string|djs.model.Base} element * @param {string} marker */ Canvas.prototype.toggleMarker = function(element, marker) { if (this.hasMarker(element, marker)) { this.removeMarker(element, marker); } else { this.addMarker(element, marker); } }; /** * Returns the current root element. * * Supports two different modes for handling root elements: * * 1. if no root element has been added before, an implicit root will be added * and returned. This is used in applications that don't require explicit * root elements. * * 2. when root elements have been added before calling `getRootElement`, * root elements can be null. This is used for applications that want to manage * root elements themselves. * * @returns {Object|djs.model.Root|null} rootElement. */ Canvas.prototype.getRootElement = function() { var rootElement = this._rootElement; // can return null if root elements are present but none was set yet if (rootElement || this._planes.length) { return rootElement; } return this.setRootElement(this.addRootElement(null)); }; /** * Adds a given root element and returns it. * * @param {Object|djs.model.Root} rootElement * * @return {Object|djs.model.Root} rootElement */ Canvas.prototype.addRootElement = function(rootElement) { var idx = this._rootsIdx++; if (!rootElement) { rootElement = { id: '__implicitroot_' + idx, children: [], isImplicit: true }; } var layerName = rootElement.layer = 'root-' + idx; this._ensureValid('root', rootElement); var layer = this.getLayer(layerName, PLANE_LAYER_INDEX); this.hideLayer(layerName); this._addRoot(rootElement, layer); this._planes.push({ rootElement: rootElement, layer: layer }); return rootElement; }; /** * Removes a given rootElement and returns it. * * @param {djs.model.Root|String} rootElement * * @return {Object|djs.model.Root} rootElement */ Canvas.prototype.removeRootElement = function(rootElement) { if (typeof rootElement === 'string') { rootElement = this._elementRegistry.get(rootElement); } var plane = this._findPlaneForRoot(rootElement); if (!plane) { return; } // hook up life-cycle events this._removeRoot(rootElement); // clean up layer this._removeLayer(rootElement.layer); // clean up plane this._planes = this._planes.filter(function(plane) { return plane.rootElement !== rootElement; }); // clean up active root if (this._rootElement === rootElement) { this._rootElement = null; } return rootElement; }; // root element handling ////////////////////// /** * Sets a given element as the new root element for the canvas * and returns the new root element. * * @param {Object|djs.model.Root} rootElement * * @return {Object|djs.model.Root} new root element */ Canvas.prototype.setRootElement = function(rootElement, override) { if (isDefined(override)) { throw new Error('override not supported'); } if (rootElement === this._rootElement) { return; } var plane; if (!rootElement) { throw new Error('rootElement required'); } plane = this._findPlaneForRoot(rootElement); // give set add semantics for backwards compatibility if (!plane) { rootElement = this.addRootElement(rootElement); } this._setRoot(rootElement); return rootElement; }; Canvas.prototype._removeRoot = function(element) { var elementRegistry = this._elementRegistry, eventBus = this._eventBus; // simulate element remove event sequence eventBus.fire('root.remove', { element: element }); eventBus.fire('root.removed', { element: element }); elementRegistry.remove(element); }; Canvas.prototype._addRoot = function(element, gfx) { var elementRegistry = this._elementRegistry, eventBus = this._eventBus; // resemble element add event sequence eventBus.fire('root.add', { element: element }); elementRegistry.add(element, gfx); eventBus.fire('root.added', { element: element, gfx: gfx }); }; Canvas.prototype._setRoot = function(rootElement, layer) { var currentRoot = this._rootElement; if (currentRoot) { // un-associate previous root element this._elementRegistry.updateGraphics(currentRoot, null, true); // hide previous layer this.hideLayer(currentRoot.layer); } if (rootElement) { if (!layer) { layer = this._findPlaneForRoot(rootElement).layer; } // associate element with this._elementRegistry.updateGraphics(rootElement, this._svg, true); // show root layer this.showLayer(rootElement.layer); } this._rootElement = rootElement; this._eventBus.fire('root.set', { element: rootElement }); }; // add functionality ////////////////////// Canvas.prototype._ensureValid = function(type, element) { if (!element.id) { throw new Error('element must have an id'); } if (this._elementRegistry.get(element.id)) { throw new Error('element <' + element.id + '> already exists'); } var requiredAttrs = REQUIRED_MODEL_ATTRS[type]; var valid = every(requiredAttrs, function(attr) { return typeof element[attr] !== 'undefined'; }); if (!valid) { throw new Error( 'must supply { ' + requiredAttrs.join(', ') + ' } with ' + type); } }; Canvas.prototype._setParent = function(element, parent, parentIndex) { add(parent.children, element, parentIndex); element.parent = parent; }; /** * Adds an element to the canvas. * * This wires the parent <-> child relationship between the element and * a explicitly specified parent or an implicit root element. * * During add it emits the events * * * <{type}.add> (element, parent) * * <{type}.added> (element, gfx) * * Extensions may hook into these events to perform their magic. * * @param {string} type * @param {Object|djs.model.Base} element * @param {Object|djs.model.Base} [parent] * @param {number} [parentIndex] * * @return {Object|djs.model.Base} the added element */ Canvas.prototype._addElement = function(type, element, parent, parentIndex) { parent = parent || this.getRootElement(); var eventBus = this._eventBus, graphicsFactory = this._graphicsFactory; this._ensureValid(type, element); eventBus.fire(type + '.add', { element: element, parent: parent }); this._setParent(element, parent, parentIndex); // create graphics var gfx = graphicsFactory.create(type, element, parentIndex); this._elementRegistry.add(element, gfx); // update its visual graphicsFactory.update(type, element, gfx); eventBus.fire(type + '.added', { element: element, gfx: gfx }); return element; }; /** * Adds a shape to the canvas * * @param {Object|djs.model.Shape} shape to add to the diagram * @param {djs.model.Base} [parent] * @param {number} [parentIndex] * * @return {djs.model.Shape} the added shape */ Canvas.prototype.addShape = function(shape, parent, parentIndex) { return this._addElement('shape', shape, parent, parentIndex); }; /** * Adds a connection to the canvas * * @param {Object|djs.model.Connection} connection to add to the diagram * @param {djs.model.Base} [parent] * @param {number} [parentIndex] * * @return {djs.model.Connection} the added connection */ Canvas.prototype.addConnection = function(connection, parent, parentIndex) { return this._addElement('connection', connection, parent, parentIndex); }; /** * Internal remove element */ Canvas.prototype._removeElement = function(element, type) { var elementRegistry = this._elementRegistry, graphicsFactory = this._graphicsFactory, eventBus = this._eventBus; element = elementRegistry.get(element.id || element); if (!element) { // element was removed already return; } eventBus.fire(type + '.remove', { element: element }); graphicsFactory.remove(element); // unset parent <-> child relationship remove(element.parent && element.parent.children, element); element.parent = null; eventBus.fire(type + '.removed', { element: element }); elementRegistry.remove(element); return element; }; /** * Removes a shape from the canvas * * @param {string|djs.model.Shape} shape or shape id to be removed * * @return {djs.model.Shape} the removed shape */ Canvas.prototype.removeShape = function(shape) { /** * An event indicating that a shape is about to be removed from the canvas. * * @memberOf Canvas * * @event shape.remove * @type {Object} * @property {djs.model.Shape} element the shape descriptor * @property {Object} gfx the graphical representation of the shape */ /** * An event indicating that a shape has been removed from the canvas. * * @memberOf Canvas * * @event shape.removed * @type {Object} * @property {djs.model.Shape} element the shape descriptor * @property {Object} gfx the graphical representation of the shape */ return this._removeElement(shape, 'shape'); }; /** * Removes a connection from the canvas * * @param {string|djs.model.Connection} connection or connection id to be removed * * @return {djs.model.Connection} the removed connection */ Canvas.prototype.removeConnection = function(connection) { /** * An event indicating that a connection is about to be removed from the canvas. * * @memberOf Canvas * * @event connection.remove * @type {Object} * @property {djs.model.Connection} element the connection descriptor * @property {Object} gfx the graphical representation of the connection */ /** * An event indicating that a connection has been removed from the canvas. * * @memberOf Canvas * * @event connection.removed * @type {Object} * @property {djs.model.Connection} element the connection descriptor * @property {Object} gfx the graphical representation of the connection */ return this._removeElement(connection, 'connection'); }; /** * Return the graphical object underlaying a certain diagram element * * @param {string|djs.model.Base} element descriptor of the element * @param {boolean} [secondary=false] whether to return the secondary connected element * * @return {SVGElement} */ Canvas.prototype.getGraphics = function(element, secondary) { return this._elementRegistry.getGraphics(element, secondary); }; /** * Perform a viewbox update via a given change function. * * @param {Function} changeFn */ Canvas.prototype._changeViewbox = function(changeFn) { // notify others of the upcoming viewbox change this._eventBus.fire('canvas.viewbox.changing'); // perform actual change changeFn.apply(this); // reset the cached viewbox so that // a new get operation on viewbox or zoom // triggers a viewbox re-computation this._cachedViewbox = null; // notify others of the change; this step // may or may not be debounced this._viewboxChanged(); }; Canvas.prototype._viewboxChanged = function() { this._eventBus.fire('canvas.viewbox.changed', { viewbox: this.viewbox() }); }; /** * Gets or sets the view box of the canvas, i.e. the * area that is currently displayed. * * The getter may return a cached viewbox (if it is currently * changing). To force a recomputation, pass `false` as the first argument. * * @example * * canvas.viewbox({ x: 100, y: 100, width: 500, height: 500 }) * * // sets the visible area of the diagram to (100|100) -> (600|100) * // and and scales it according to the diagram width * * var viewbox = canvas.viewbox(); // pass `false` to force recomputing the box. * * console.log(viewbox); * // { * // inner: Dimensions, * // outer: Dimensions, * // scale, * // x, y, * // width, height * // } * * // if the current diagram is zoomed and scrolled, you may reset it to the * // default zoom via this method, too: * * var zoomedAndScrolledViewbox = canvas.viewbox(); * * canvas.viewbox({ * x: 0, * y: 0, * width: zoomedAndScrolledViewbox.outer.width, * height: zoomedAndScrolledViewbox.outer.height * }); * * @param {Object} [box] the new view box to set * @param {number} box.x the top left X coordinate of the canvas visible in view box * @param {number} box.y the top left Y coordinate of the canvas visible in view box * @param {number} box.width the visible width * @param {number} box.height * * @return {Object} the current view box */ Canvas.prototype.viewbox = function(box) { if (box === undefined && this._cachedViewbox) { return this._cachedViewbox; } var viewport = this._viewport, innerBox, outerBox = this.getSize(), matrix, activeLayer, transform, scale, x, y; if (!box) { // compute the inner box based on the // diagrams active layer. This allows us to exclude // external components, such as overlays activeLayer = this._rootElement ? this.getActiveLayer() : null; innerBox = activeLayer && activeLayer.getBBox() || {}; transform = transform$1(viewport); matrix = transform ? transform.matrix : createMatrix(); scale = round(matrix.a, 1000); x = round(-matrix.e || 0, 1000); y = round(-matrix.f || 0, 1000); box = this._cachedViewbox = { x: x ? x / scale : 0, y: y ? y / scale : 0, width: outerBox.width / scale, height: outerBox.height / scale, scale: scale, inner: { width: innerBox.width || 0, height: innerBox.height || 0, x: innerBox.x || 0, y: innerBox.y || 0 }, outer: outerBox }; return box; } else { this._changeViewbox(function() { scale = Math.min(outerBox.width / box.width, outerBox.height / box.height); var matrix = this._svg.createSVGMatrix() .scale(scale) .translate(-box.x, -box.y); transform$1(viewport, matrix); }); } return box; }; /** * Gets or sets the scroll of the canvas. * * @param {Object} [delta] the new scroll to apply. * * @param {number} [delta.dx] * @param {number} [delta.dy] */ Canvas.prototype.scroll = function(delta) { var node = this._viewport; var matrix = node.getCTM(); if (delta) { this._changeViewbox(function() { delta = assign({ dx: 0, dy: 0 }, delta || {}); matrix = this._svg.createSVGMatrix().translate(delta.dx, delta.dy).multiply(matrix); setCTM(node, matrix); }); } return { x: matrix.e, y: matrix.f }; }; /** * Scrolls the viewbox to contain the given element. * Optionally specify a padding to be applied to the edges. * * @param {Object|String} [element] the element to scroll to. * @param {Object|Number} [padding=100] the padding to be applied. Can also specify top, bottom, left and right. * */ Canvas.prototype.scrollToElement = function(element, padding) { var defaultPadding = 100; if (typeof element === 'string') { element = this._elementRegistry.get(element); } // set to correct rootElement var rootElement = this.findRoot(element); if (rootElement !== this.getRootElement()) { this.setRootElement(rootElement); } if (!padding) { padding = {}; } if (typeof padding === 'number') { defaultPadding = padding; } padding = { top: padding.top || defaultPadding, right: padding.right || defaultPadding, bottom: padding.bottom || defaultPadding, left: padding.left || defaultPadding }; var elementBounds = getBBox(element), elementTrbl = asTRBL(elementBounds), viewboxBounds = this.viewbox(), zoom = this.zoom(), dx, dy; // shrink viewboxBounds with padding viewboxBounds.y += padding.top / zoom; viewboxBounds.x += padding.left / zoom; viewboxBounds.width -= (padding.right + padding.left) / zoom; viewboxBounds.height -= (padding.bottom + padding.top) / zoom; var viewboxTrbl = asTRBL(viewboxBounds); var canFit = elementBounds.width < viewboxBounds.width && elementBounds.height < viewboxBounds.height; if (!canFit) { // top-left when element can't fit dx = elementBounds.x - viewboxBounds.x; dy = elementBounds.y - viewboxBounds.y; } else { var dRight = Math.max(0, elementTrbl.right - viewboxTrbl.right), dLeft = Math.min(0, elementTrbl.left - viewboxTrbl.left), dBottom = Math.max(0, elementTrbl.bottom - viewboxTrbl.bottom), dTop = Math.min(0, elementTrbl.top - viewboxTrbl.top); dx = dRight || dLeft; dy = dBottom || dTop; } this.scroll({ dx: -dx * zoom, dy: -dy * zoom }); }; /** * Gets or sets the current zoom of the canvas, optionally zooming * to the specified position. * * The getter may return a cached zoom level. Call it with `false` as * the first argument to force recomputation of the current level. * * @param {string|number} [newScale] the new zoom level, either a number, i.e. 0.9, * or `fit-viewport` to adjust the size to fit the current viewport * @param {string|Point} [center] the reference point { x: .., y: ..} to zoom to, 'auto' to zoom into mid or null * * @return {number} the current scale */ Canvas.prototype.zoom = function(newScale, center) { if (!newScale) { return this.viewbox(newScale).scale; } if (newScale === 'fit-viewport') { return this._fitViewport(center); } var outer, matrix; this._changeViewbox(function() { if (typeof center !== 'object') { outer = this.viewbox().outer; center = { x: outer.width / 2, y: outer.height / 2 }; } matrix = this._setZoom(newScale, center); }); return round(matrix.a, 1000); }; function setCTM(node, m) { var mstr = 'matrix(' + m.a + ',' + m.b + ',' + m.c + ',' + m.d + ',' + m.e + ',' + m.f + ')'; node.setAttribute('transform', mstr); } Canvas.prototype._fitViewport = function(center) { var vbox = this.viewbox(), outer = vbox.outer, inner = vbox.inner, newScale, newViewbox; // display the complete diagram without zooming in. // instead of relying on internal zoom, we perform a // hard reset on the canvas viewbox to realize this // // if diagram does not need to be zoomed in, we focus it around // the diagram origin instead if (inner.x >= 0 && inner.y >= 0 && inner.x + inner.width <= outer.width && inner.y + inner.height <= outer.height && !center) { newViewbox = { x: 0, y: 0, width: Math.max(inner.width + inner.x, outer.width), height: Math.max(inner.height + inner.y, outer.height) }; } else { newScale = Math.min(1, outer.width / inner.width, outer.height / inner.height); newViewbox = { x: inner.x + (center ? inner.width / 2 - outer.width / newScale / 2 : 0), y: inner.y + (center ? inner.height / 2 - outer.height / newScale / 2 : 0), width: outer.width / newScale, height: outer.height / newScale }; } this.viewbox(newViewbox); return this.viewbox(false).scale; }; Canvas.prototype._setZoom = function(scale, center) { var svg = this._svg, viewport = this._viewport; var matrix = svg.createSVGMatrix(); var point = svg.createSVGPoint(); var centerPoint, originalPoint, currentMatrix, scaleMatrix, newMatrix; currentMatrix = viewport.getCTM(); var currentScale = currentMatrix.a; if (center) { centerPoint = assign(point, center); // revert applied viewport transformations originalPoint = centerPoint.matrixTransform(currentMatrix.inverse()); // create scale matrix scaleMatrix = matrix .translate(originalPoint.x, originalPoint.y) .scale(1 / currentScale * scale) .translate(-originalPoint.x, -originalPoint.y); newMatrix = currentMatrix.multiply(scaleMatrix); } else { newMatrix = matrix.scale(scale); } setCTM(this._viewport, newMatrix); return newMatrix; }; /** * Returns the size of the canvas * * @return {Dimensions} */ Canvas.prototype.getSize = function() { return { width: this._container.clientWidth, height: this._container.clientHeight }; }; /** * Return the absolute bounding box for the given element * * The absolute bounding box may be used to display overlays in the * callers (browser) coordinate system rather than the zoomed in/out * canvas coordinates. * * @param {ElementDescriptor} element * @return {Bounds} the absolute bounding box */ Canvas.prototype.getAbsoluteBBox = function(element) { var vbox = this.viewbox(); var bbox; // connection // use svg bbox if (element.waypoints) { var gfx = this.getGraphics(element); bbox = gfx.getBBox(); } // shapes // use data else { bbox = element; } var x = bbox.x * vbox.scale - vbox.x * vbox.scale; var y = bbox.y * vbox.scale - vbox.y * vbox.scale; var width = bbox.width * vbox.scale; var height = bbox.height * vbox.scale; return { x: x, y: y, width: width, height: height }; }; /** * Fires an event in order other modules can react to the * canvas resizing */ Canvas.prototype.resized = function() { // force recomputation of view box delete this._cachedViewbox; this._eventBus.fire('canvas.resized'); }; var ELEMENT_ID = 'data-element-id'; /** * @class * * A registry that keeps track of all shapes in the diagram. */ function ElementRegistry(eventBus) { this._elements = {}; this._eventBus = eventBus; } ElementRegistry.$inject = [ 'eventBus' ]; /** * Register a pair of (element, gfx, (secondaryGfx)). * * @param {djs.model.Base} element * @param {SVGElement} gfx * @param {SVGElement} [secondaryGfx] optional other element to register, too */ ElementRegistry.prototype.add = function(element, gfx, secondaryGfx) { var id = element.id; this._validateId(id); // associate dom node with element attr$1(gfx, ELEMENT_ID, id); if (secondaryGfx) { attr$1(secondaryGfx, ELEMENT_ID, id); } this._elements[id] = { element: element, gfx: gfx, secondaryGfx: secondaryGfx }; }; /** * Removes an element from the registry. * * @param {djs.model.Base} element */ ElementRegistry.prototype.remove = function(element) { var elements = this._elements, id = element.id || element, container = id && elements[id]; if (container) { // unset element id on gfx attr$1(container.gfx, ELEMENT_ID, ''); if (container.secondaryGfx) { attr$1(container.secondaryGfx, ELEMENT_ID, ''); } delete elements[id]; } }; /** * Update the id of an element * * @param {djs.model.Base} element * @param {string} newId */ ElementRegistry.prototype.updateId = function(element, newId) { this._validateId(newId); if (typeof element === 'string') { element = this.get(element); } this._eventBus.fire('element.updateId', { element: element, newId: newId }); var gfx = this.getGraphics(element), secondaryGfx = this.getGraphics(element, true); this.remove(element); element.id = newId; this.add(element, gfx, secondaryGfx); }; /** * Update the graphics of an element * * @param {djs.model.Base} element * @param {SVGElement} gfx * @param {boolean} [secondary=false] whether to update the secondary connected element */ ElementRegistry.prototype.updateGraphics = function(filter, gfx, secondary) { var id = filter.id || filter; var container = this._elements[id]; if (secondary) { container.secondaryGfx = gfx; } else { container.gfx = gfx; } if (gfx) { attr$1(gfx, ELEMENT_ID, id); } return gfx; }; /** * Return the model element for a given id or graphics. * * @example * * elementRegistry.get('SomeElementId_1'); * elementRegistry.get(gfx); * * * @param {string|SVGElement} filter for selecting the element * * @return {djs.model.Base} */ ElementRegistry.prototype.get = function(filter) { var id; if (typeof filter === 'string') { id = filter; } else { id = filter && attr$1(filter, ELEMENT_ID); } var container = this._elements[id]; return container && container.element; }; /** * Return all elements that match a given filter function. * * @param {Function} fn * * @return {Array} */ ElementRegistry.prototype.filter = function(fn) { var filtered = []; this.forEach(function(element, gfx) { if (fn(element, gfx)) { filtered.push(element); } }); return filtered; }; /** * Return the first element that satisfies the provided testing function. * * @param {Function} fn * * @return {djs.model.Base} */ ElementRegistry.prototype.find = function(fn) { var map = this._elements, keys = Object.keys(map); for (var i = 0; i < keys.length; i++) { var id = keys[i], container = map[id], element = container.element, gfx = container.gfx; if (fn(element, gfx)) { return element; } } }; /** * Return all rendered model elements. * * @return {Array} */ ElementRegistry.prototype.getAll = function() { return this.filter(function(e) { return e; }); }; /** * Iterate over all diagram elements. * * @param {Function} fn */ ElementRegistry.prototype.forEach = function(fn) { var map = this._elements; Object.keys(map).forEach(function(id) { var container = map[id], element = container.element, gfx = container.gfx; return fn(element, gfx); }); }; /** * Return the graphical representation of an element or its id. * * @example * elementRegistry.getGraphics('SomeElementId_1'); * elementRegistry.getGraphics(rootElement); // * * elementRegistry.getGraphics(rootElement, true); // * * * @param {string|djs.model.Base} filter * @param {boolean} [secondary=false] whether to return the secondary connected element * * @return {SVGElement} */ ElementRegistry.prototype.getGraphics = function(filter, secondary) { var id = filter.id || filter; var container = this._elements[id]; return container && (secondary ? container.secondaryGfx : container.gfx); }; /** * Validate the suitability of the given id and signals a problem * with an exception. * * @param {string} id * * @throws {Error} if id is empty or already assigned */ ElementRegistry.prototype._validateId = function(id) { if (!id) { throw new Error('element must have an id'); } if (this._elements[id]) { throw new Error('element with id ' + id + ' already added'); } }; var objectRefs = {exports: {}}; var collection = {}; /** * An empty collection stub. Use {@link RefsCollection.extend} to extend a * collection with ref semantics. * * @class RefsCollection */ /** * Extends a collection with {@link Refs} aware methods * * @memberof RefsCollection * @static * * @param {Array} collection * @param {Refs} refs instance * @param {Object} property represented by the collection * @param {Object} target object the collection is attached to * * @return {RefsCollection} the extended array */ function extend(collection, refs, property, target) { var inverseProperty = property.inverse; /** * Removes the given element from the array and returns it. * * @method RefsCollection#remove * * @param {Object} element the element to remove */ Object.defineProperty(collection, 'remove', { value: function(element) { var idx = this.indexOf(element); if (idx !== -1) { this.splice(idx, 1); // unset inverse refs.unset(element, inverseProperty, target); } return element; } }); /** * Returns true if the collection contains the given element * * @method RefsCollection#contains * * @param {Object} element the element to check for */ Object.defineProperty(collection, 'contains', { value: function(element) { return this.indexOf(element) !== -1; } }); /** * Adds an element to the array, unless it exists already (set semantics). * * @method RefsCollection#add * * @param {Object} element the element to add * @param {Number} optional index to add element to * (possibly moving other elements around) */ Object.defineProperty(collection, 'add', { value: function(element, idx) { var currentIdx = this.indexOf(element); if (typeof idx === 'undefined') { if (currentIdx !== -1) { // element already in collection (!) return; } // add to end of array, as no idx is specified idx = this.length; } // handle already in collection if (currentIdx !== -1) { // remove element from currentIdx this.splice(currentIdx, 1); } // add element at idx this.splice(idx, 0, element); if (currentIdx === -1) { // set inverse, unless element was // in collection already refs.set(element, inverseProperty, target); } } }); // a simple marker, identifying this element // as being a refs collection Object.defineProperty(collection, '__refs_collection', { value: true }); return collection; } function isExtended(collection) { return collection.__refs_collection === true; } collection.extend = extend; collection.isExtended = isExtended; var Collection = collection; function hasOwnProperty$1(e, property) { return Object.prototype.hasOwnProperty.call(e, property.name || property); } function defineCollectionProperty(ref, property, target) { var collection = Collection.extend(target[property.name] || [], ref, property, target); Object.defineProperty(target, property.name, { enumerable: property.enumerable, value: collection }); if (collection.length) { collection.forEach(function(o) { ref.set(o, property.inverse, target); }); } } function defineProperty$1(ref, property, target) { var inverseProperty = property.inverse; var _value = target[property.name]; Object.defineProperty(target, property.name, { configurable: property.configurable, enumerable: property.enumerable, get: function() { return _value; }, set: function(value) { // return if we already performed all changes if (value === _value) { return; } var old = _value; // temporary set null _value = null; if (old) { ref.unset(old, inverseProperty, target); } // set new value _value = value; // set inverse value ref.set(_value, inverseProperty, target); } }); } /** * Creates a new references object defining two inversly related * attribute descriptors a and b. * *

    * When bound to an object using {@link Refs#bind} the references * get activated and ensure that add and remove operations are applied * reversely, too. *

    * *

    * For attributes represented as collections {@link Refs} provides the * {@link RefsCollection#add}, {@link RefsCollection#remove} and {@link RefsCollection#contains} extensions * that must be used to properly hook into the inverse change mechanism. *

    * * @class Refs * * @classdesc A bi-directional reference between two attributes. * * @param {Refs.AttributeDescriptor} a property descriptor * @param {Refs.AttributeDescriptor} b property descriptor * * @example * * var refs = Refs({ name: 'wheels', collection: true, enumerable: true }, { name: 'car' }); * * var car = { name: 'toyota' }; * var wheels = [{ pos: 'front-left' }, { pos: 'front-right' }]; * * refs.bind(car, 'wheels'); * * car.wheels // [] * car.wheels.add(wheels[0]); * car.wheels.add(wheels[1]); * * car.wheels // [{ pos: 'front-left' }, { pos: 'front-right' }] * * wheels[0].car // { name: 'toyota' }; * car.wheels.remove(wheels[0]); * * wheels[0].car // undefined */ function Refs$1(a, b) { if (!(this instanceof Refs$1)) { return new Refs$1(a, b); } // link a.inverse = b; b.inverse = a; this.props = {}; this.props[a.name] = a; this.props[b.name] = b; } /** * Binds one side of a bi-directional reference to a * target object. * * @memberOf Refs * * @param {Object} target * @param {String} property */ Refs$1.prototype.bind = function(target, property) { if (typeof property === 'string') { if (!this.props[property]) { throw new Error('no property <' + property + '> in ref'); } property = this.props[property]; } if (property.collection) { defineCollectionProperty(this, property, target); } else { defineProperty$1(this, property, target); } }; Refs$1.prototype.ensureRefsCollection = function(target, property) { var collection = target[property.name]; if (!Collection.isExtended(collection)) { defineCollectionProperty(this, property, target); } return collection; }; Refs$1.prototype.ensureBound = function(target, property) { if (!hasOwnProperty$1(target, property)) { this.bind(target, property); } }; Refs$1.prototype.unset = function(target, property, value) { if (target) { this.ensureBound(target, property); if (property.collection) { this.ensureRefsCollection(target, property).remove(value); } else { target[property.name] = undefined; } } }; Refs$1.prototype.set = function(target, property, value) { if (target) { this.ensureBound(target, property); if (property.collection) { this.ensureRefsCollection(target, property).add(value); } else { target[property.name] = value; } } }; var refs = Refs$1; (function (module) { module.exports = refs; module.exports.Collection = collection; } (objectRefs)); var Refs = /*@__PURE__*/getDefaultExportFromCjs(objectRefs.exports); var parentRefs = new Refs({ name: 'children', enumerable: true, collection: true }, { name: 'parent' }), labelRefs = new Refs({ name: 'labels', enumerable: true, collection: true }, { name: 'labelTarget' }), attacherRefs = new Refs({ name: 'attachers', collection: true }, { name: 'host' }), outgoingRefs = new Refs({ name: 'outgoing', collection: true }, { name: 'source' }), incomingRefs = new Refs({ name: 'incoming', collection: true }, { name: 'target' }); /** * @namespace djs.model */ /** * @memberOf djs.model */ /** * The basic graphical representation * * @class * * @abstract */ function Base$1() { /** * The object that backs up the shape * * @name Base#businessObject * @type Object */ Object.defineProperty(this, 'businessObject', { writable: true }); /** * Single label support, will mapped to multi label array * * @name Base#label * @type Object */ Object.defineProperty(this, 'label', { get: function() { return this.labels[0]; }, set: function(newLabel) { var label = this.label, labels = this.labels; if (!newLabel && label) { labels.remove(label); } else { labels.add(newLabel, 0); } } }); /** * The parent shape * * @name Base#parent * @type Shape */ parentRefs.bind(this, 'parent'); /** * The list of labels * * @name Base#labels * @type Label */ labelRefs.bind(this, 'labels'); /** * The list of outgoing connections * * @name Base#outgoing * @type Array */ outgoingRefs.bind(this, 'outgoing'); /** * The list of incoming connections * * @name Base#incoming * @type Array */ incomingRefs.bind(this, 'incoming'); } /** * A graphical object * * @class * @constructor * * @extends Base */ function Shape() { Base$1.call(this); /** * Indicates frame shapes * * @name Shape#isFrame * @type boolean */ /** * The list of children * * @name Shape#children * @type Array */ parentRefs.bind(this, 'children'); /** * @name Shape#host * @type Shape */ attacherRefs.bind(this, 'host'); /** * @name Shape#attachers * @type Shape */ attacherRefs.bind(this, 'attachers'); } e(Shape, Base$1); /** * A root graphical object * * @class * @constructor * * @extends Shape */ function Root() { Shape.call(this); } e(Root, Shape); /** * A label for an element * * @class * @constructor * * @extends Shape */ function Label() { Shape.call(this); /** * The labeled element * * @name Label#labelTarget * @type Base */ labelRefs.bind(this, 'labelTarget'); } e(Label, Shape); /** * A connection between two elements * * @class * @constructor * * @extends Base */ function Connection() { Base$1.call(this); /** * The element this connection originates from * * @name Connection#source * @type Base */ outgoingRefs.bind(this, 'source'); /** * The element this connection points to * * @name Connection#target * @type Base */ incomingRefs.bind(this, 'target'); } e(Connection, Base$1); var types$6 = { connection: Connection, shape: Shape, label: Label, root: Root }; /** * Creates a new model element of the specified type * * @method create * * @example * * var shape1 = Model.create('shape', { x: 10, y: 10, width: 100, height: 100 }); * var shape2 = Model.create('shape', { x: 210, y: 210, width: 100, height: 100 }); * * var connection = Model.create('connection', { waypoints: [ { x: 110, y: 55 }, {x: 210, y: 55 } ] }); * * @param {string} type lower-cased model name * @param {Object} attrs attributes to initialize the new model instance with * * @return {Base} the new model instance */ function create(type, attrs) { var Type = types$6[type]; if (!Type) { throw new Error('unknown type: <' + type + '>'); } return assign(new Type(), attrs); } /** * A factory for diagram-js shapes */ function ElementFactory() { this._uid = 12; } ElementFactory.prototype.createRoot = function(attrs) { return this.create('root', attrs); }; ElementFactory.prototype.createLabel = function(attrs) { return this.create('label', attrs); }; ElementFactory.prototype.createShape = function(attrs) { return this.create('shape', attrs); }; ElementFactory.prototype.createConnection = function(attrs) { return this.create('connection', attrs); }; /** * Create a model element with the given type and * a number of pre-set attributes. * * @param {string} type * @param {Object} attrs * @return {djs.model.Base} the newly created model instance */ ElementFactory.prototype.create = function(type, attrs) { attrs = assign({}, attrs || {}); if (!attrs.id) { attrs.id = type + '_' + (this._uid++); } return create(type, attrs); }; var FN_REF = '__fn'; var DEFAULT_PRIORITY$1 = 1000; var slice = Array.prototype.slice; /** * A general purpose event bus. * * This component is used to communicate across a diagram instance. * Other parts of a diagram can use it to listen to and broadcast events. * * * ## Registering for Events * * The event bus provides the {@link EventBus#on} and {@link EventBus#once} * methods to register for events. {@link EventBus#off} can be used to * remove event registrations. Listeners receive an instance of {@link Event} * as the first argument. It allows them to hook into the event execution. * * ```javascript * * // listen for event * eventBus.on('foo', function(event) { * * // access event type * event.type; // 'foo' * * // stop propagation to other listeners * event.stopPropagation(); * * // prevent event default * event.preventDefault(); * }); * * // listen for event with custom payload * eventBus.on('bar', function(event, payload) { * console.log(payload); * }); * * // listen for event returning value * eventBus.on('foobar', function(event) { * * // stop event propagation + prevent default * return false; * * // stop event propagation + return custom result * return { * complex: 'listening result' * }; * }); * * * // listen with custom priority (default=1000, higher is better) * eventBus.on('priorityfoo', 1500, function(event) { * console.log('invoked first!'); * }); * * * // listen for event and pass the context (`this`) * eventBus.on('foobar', function(event) { * this.foo(); * }, this); * ``` * * * ## Emitting Events * * Events can be emitted via the event bus using {@link EventBus#fire}. * * ```javascript * * // false indicates that the default action * // was prevented by listeners * if (eventBus.fire('foo') === false) { * console.log('default has been prevented!'); * }; * * * // custom args + return value listener * eventBus.on('sum', function(event, a, b) { * return a + b; * }); * * // you can pass custom arguments + retrieve result values. * var sum = eventBus.fire('sum', 1, 2); * console.log(sum); // 3 * ``` */ function EventBus() { this._listeners = {}; // cleanup on destroy on lowest priority to allow // message passing until the bitter end this.on('diagram.destroy', 1, this._destroy, this); } /** * Register an event listener for events with the given name. * * The callback will be invoked with `event, ...additionalArguments` * that have been passed to {@link EventBus#fire}. * * Returning false from a listener will prevent the events default action * (if any is specified). To stop an event from being processed further in * other listeners execute {@link Event#stopPropagation}. * * Returning anything but `undefined` from a listener will stop the listener propagation. * * @param {string|Array} events * @param {number} [priority=1000] the priority in which this listener is called, larger is higher * @param {Function} callback * @param {Object} [that] Pass context (`this`) to the callback */ EventBus.prototype.on = function(events, priority, callback, that) { events = isArray$2(events) ? events : [ events ]; if (isFunction(priority)) { that = callback; callback = priority; priority = DEFAULT_PRIORITY$1; } if (!isNumber(priority)) { throw new Error('priority must be a number'); } var actualCallback = callback; if (that) { actualCallback = bind(callback, that); // make sure we remember and are able to remove // bound callbacks via {@link #off} using the original // callback actualCallback[FN_REF] = callback[FN_REF] || callback; } var self = this; events.forEach(function(e) { self._addListener(e, { priority: priority, callback: actualCallback, next: null }); }); }; /** * Register an event listener that is executed only once. * * @param {string} event the event name to register for * @param {number} [priority=1000] the priority in which this listener is called, larger is higher * @param {Function} callback the callback to execute * @param {Object} [that] Pass context (`this`) to the callback */ EventBus.prototype.once = function(event, priority, callback, that) { var self = this; if (isFunction(priority)) { that = callback; callback = priority; priority = DEFAULT_PRIORITY$1; } if (!isNumber(priority)) { throw new Error('priority must be a number'); } function wrappedCallback() { wrappedCallback.__isTomb = true; var result = callback.apply(that, arguments); self.off(event, wrappedCallback); return result; } // make sure we remember and are able to remove // bound callbacks via {@link #off} using the original // callback wrappedCallback[FN_REF] = callback; this.on(event, priority, wrappedCallback); }; /** * Removes event listeners by event and callback. * * If no callback is given, all listeners for a given event name are being removed. * * @param {string|Array} events * @param {Function} [callback] */ EventBus.prototype.off = function(events, callback) { events = isArray$2(events) ? events : [ events ]; var self = this; events.forEach(function(event) { self._removeListener(event, callback); }); }; /** * Create an EventBus event. * * @param {Object} data * * @return {Object} event, recognized by the eventBus */ EventBus.prototype.createEvent = function(data) { var event = new InternalEvent(); event.init(data); return event; }; /** * Fires a named event. * * @example * * // fire event by name * events.fire('foo'); * * // fire event object with nested type * var event = { type: 'foo' }; * events.fire(event); * * // fire event with explicit type * var event = { x: 10, y: 20 }; * events.fire('element.moved', event); * * // pass additional arguments to the event * events.on('foo', function(event, bar) { * alert(bar); * }); * * events.fire({ type: 'foo' }, 'I am bar!'); * * @param {string} [name] the optional event name * @param {Object} [event] the event object * @param {...Object} additional arguments to be passed to the callback functions * * @return {boolean} the events return value, if specified or false if the * default action was prevented by listeners */ EventBus.prototype.fire = function(type, data) { var event, firstListener, returnValue, args; args = slice.call(arguments); if (typeof type === 'object') { data = type; type = data.type; } if (!type) { throw new Error('no event type specified'); } firstListener = this._listeners[type]; if (!firstListener) { return; } // we make sure we fire instances of our home made // events here. We wrap them only once, though if (data instanceof InternalEvent) { // we are fine, we alread have an event event = data; } else { event = this.createEvent(data); } // ensure we pass the event as the first parameter args[0] = event; // original event type (in case we delegate) var originalType = event.type; // update event type before delegation if (type !== originalType) { event.type = type; } try { returnValue = this._invokeListeners(event, args, firstListener); } finally { // reset event type after delegation if (type !== originalType) { event.type = originalType; } } // set the return value to false if the event default // got prevented and no other return value exists if (returnValue === undefined && event.defaultPrevented) { returnValue = false; } return returnValue; }; EventBus.prototype.handleError = function(error) { return this.fire('error', { error: error }) === false; }; EventBus.prototype._destroy = function() { this._listeners = {}; }; EventBus.prototype._invokeListeners = function(event, args, listener) { var returnValue; while (listener) { // handle stopped propagation if (event.cancelBubble) { break; } returnValue = this._invokeListener(event, args, listener); listener = listener.next; } return returnValue; }; EventBus.prototype._invokeListener = function(event, args, listener) { var returnValue; if (listener.callback.__isTomb) { return returnValue; } try { // returning false prevents the default action returnValue = invokeFunction(listener.callback, args); // stop propagation on return value if (returnValue !== undefined) { event.returnValue = returnValue; event.stopPropagation(); } // prevent default on return false if (returnValue === false) { event.preventDefault(); } } catch (error) { if (!this.handleError(error)) { console.error('unhandled error in event listener', error); throw error; } } return returnValue; }; /* * Add new listener with a certain priority to the list * of listeners (for the given event). * * The semantics of listener registration / listener execution are * first register, first serve: New listeners will always be inserted * after existing listeners with the same priority. * * Example: Inserting two listeners with priority 1000 and 1300 * * * before: [ 1500, 1500, 1000, 1000 ] * * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ] * * @param {string} event * @param {Object} listener { priority, callback } */ EventBus.prototype._addListener = function(event, newListener) { var listener = this._getListeners(event), previousListener; // no prior listeners if (!listener) { this._setListeners(event, newListener); return; } // ensure we order listeners by priority from // 0 (high) to n > 0 (low) while (listener) { if (listener.priority < newListener.priority) { newListener.next = listener; if (previousListener) { previousListener.next = newListener; } else { this._setListeners(event, newListener); } return; } previousListener = listener; listener = listener.next; } // add new listener to back previousListener.next = newListener; }; EventBus.prototype._getListeners = function(name) { return this._listeners[name]; }; EventBus.prototype._setListeners = function(name, listener) { this._listeners[name] = listener; }; EventBus.prototype._removeListener = function(event, callback) { var listener = this._getListeners(event), nextListener, previousListener, listenerCallback; if (!callback) { // clear listeners this._setListeners(event, null); return; } while (listener) { nextListener = listener.next; listenerCallback = listener.callback; if (listenerCallback === callback || listenerCallback[FN_REF] === callback) { if (previousListener) { previousListener.next = nextListener; } else { // new first listener this._setListeners(event, nextListener); } } previousListener = listener; listener = nextListener; } }; /** * A event that is emitted via the event bus. */ function InternalEvent() { } InternalEvent.prototype.stopPropagation = function() { this.cancelBubble = true; }; InternalEvent.prototype.preventDefault = function() { this.defaultPrevented = true; }; InternalEvent.prototype.init = function(data) { assign(this, data || {}); }; /** * Invoke function. Be fast... * * @param {Function} fn * @param {Array} args * * @return {Any} */ function invokeFunction(fn, args) { return fn.apply(null, args); } /** * SVGs for elements are generated by the {@link GraphicsFactory}. * * This utility gives quick access to the important semantic * parts of an element. */ /** * Returns the visual part of a diagram element * * @param {Snap} gfx * * @return {Snap} */ function getVisual(gfx) { return gfx.childNodes[0]; } /** * Returns the children for a given diagram element. * * @param {Snap} gfx * @return {Snap} */ function getChildren(gfx) { return gfx.parentNode.childNodes[1]; } /** * A factory that creates graphical elements * * @param {EventBus} eventBus * @param {ElementRegistry} elementRegistry */ function GraphicsFactory(eventBus, elementRegistry) { this._eventBus = eventBus; this._elementRegistry = elementRegistry; } GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ]; GraphicsFactory.prototype._getChildrenContainer = function(element) { var gfx = this._elementRegistry.getGraphics(element); var childrenGfx; // root element if (!element.parent) { childrenGfx = gfx; } else { childrenGfx = getChildren(gfx); if (!childrenGfx) { childrenGfx = create$1('g'); classes$1(childrenGfx).add('djs-children'); append(gfx.parentNode, childrenGfx); } } return childrenGfx; }; /** * Clears the graphical representation of the element and returns the * cleared visual (the element). */ GraphicsFactory.prototype._clear = function(gfx) { var visual = getVisual(gfx); clear(visual); return visual; }; /** * Creates a gfx container for shapes and connections * * The layout is as follows: * * * * * * * * * * * * * * @param {string} type the type of the element, i.e. shape | connection * @param {SVGElement} [childrenGfx] * @param {number} [parentIndex] position to create container in parent * @param {boolean} [isFrame] is frame element * * @return {SVGElement} */ GraphicsFactory.prototype._createContainer = function( type, childrenGfx, parentIndex, isFrame ) { var outerGfx = create$1('g'); classes$1(outerGfx).add('djs-group'); // insert node at position if (typeof parentIndex !== 'undefined') { prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]); } else { append(childrenGfx, outerGfx); } var gfx = create$1('g'); classes$1(gfx).add('djs-element'); classes$1(gfx).add('djs-' + type); if (isFrame) { classes$1(gfx).add('djs-frame'); } append(outerGfx, gfx); // create visual var visual = create$1('g'); classes$1(visual).add('djs-visual'); append(gfx, visual); return gfx; }; GraphicsFactory.prototype.create = function(type, element, parentIndex) { var childrenGfx = this._getChildrenContainer(element.parent); return this._createContainer(type, childrenGfx, parentIndex, isFrameElement(element)); }; GraphicsFactory.prototype.updateContainments = function(elements) { var self = this, elementRegistry = this._elementRegistry, parents; parents = reduce(elements, function(map, e) { if (e.parent) { map[e.parent.id] = e.parent; } return map; }, {}); // update all parents of changed and reorganized their children // in the correct order (as indicated in our model) forEach$1(parents, function(parent) { var children = parent.children; if (!children) { return; } var childrenGfx = self._getChildrenContainer(parent); forEach$1(children.slice().reverse(), function(child) { var childGfx = elementRegistry.getGraphics(child); prependTo(childGfx.parentNode, childrenGfx); }); }); }; GraphicsFactory.prototype.drawShape = function(visual, element) { var eventBus = this._eventBus; return eventBus.fire('render.shape', { gfx: visual, element: element }); }; GraphicsFactory.prototype.getShapePath = function(element) { var eventBus = this._eventBus; return eventBus.fire('render.getShapePath', element); }; GraphicsFactory.prototype.drawConnection = function(visual, element) { var eventBus = this._eventBus; return eventBus.fire('render.connection', { gfx: visual, element: element }); }; GraphicsFactory.prototype.getConnectionPath = function(waypoints) { var eventBus = this._eventBus; return eventBus.fire('render.getConnectionPath', waypoints); }; GraphicsFactory.prototype.update = function(type, element, gfx) { // do NOT update root element if (!element.parent) { return; } var visual = this._clear(gfx); // redraw if (type === 'shape') { this.drawShape(visual, element); // update positioning translate$1(gfx, element.x, element.y); } else if (type === 'connection') { this.drawConnection(visual, element); } else { throw new Error('unknown type: ' + type); } if (element.hidden) { attr$1(gfx, 'display', 'none'); } else { attr$1(gfx, 'display', 'block'); } }; GraphicsFactory.prototype.remove = function(element) { var gfx = this._elementRegistry.getGraphics(element); // remove remove$2(gfx.parentNode); }; // helpers ////////// function prependTo(newNode, parentNode, siblingNode) { var node = siblingNode || parentNode.firstChild; // do not prepend node to itself to prevent IE from crashing // https://github.com/bpmn-io/bpmn-js/issues/746 if (newNode === node) { return; } parentNode.insertBefore(newNode, node); } var CoreModule = { __depends__: [ DrawModule ], __init__: [ 'canvas' ], canvas: [ 'type', Canvas ], elementRegistry: [ 'type', ElementRegistry ], elementFactory: [ 'type', ElementFactory ], eventBus: [ 'type', EventBus ], graphicsFactory: [ 'type', GraphicsFactory ] }; /** * @typedef { import('didi').ModuleDeclaration } Module */ /** * Bootstrap an injector from a list of modules, instantiating a number of default components * * @param {Array} modules * * @return {Injector} a injector to use to access the components */ function bootstrap(modules) { var injector = new Injector(modules); injector.init(); return injector; } /** * Creates an injector from passed options. * * @param {Object} options * @return {Injector} */ function createInjector(options) { options = options || {}; var configModule = { 'config': [ 'value', options ] }; var modules = [ configModule, CoreModule ].concat(options.modules || []); return bootstrap(modules); } /** * The main diagram-js entry point that bootstraps the diagram with the given * configuration. * * To register extensions with the diagram, pass them as Array to the constructor. * * @class djs.Diagram * @memberOf djs * @constructor * * @example * * Creating a plug-in that logs whenever a shape is added to the canvas. * * // plug-in implemenentation * function MyLoggingPlugin(eventBus) { * eventBus.on('shape.added', function(event) { * console.log('shape ', event.shape, ' was added to the diagram'); * }); * } * * // export as module * export default { * __init__: [ 'myLoggingPlugin' ], * myLoggingPlugin: [ 'type', MyLoggingPlugin ] * }; * * * // instantiate the diagram with the new plug-in * * import MyLoggingModule from 'path-to-my-logging-plugin'; * * var diagram = new Diagram({ * modules: [ * MyLoggingModule * ] * }); * * diagram.invoke([ 'canvas', function(canvas) { * // add shape to drawing canvas * canvas.addShape({ x: 10, y: 10 }); * }); * * // 'shape ... was added to the diagram' logged to console * * @param {Object} options * @param {Array} [options.modules] external modules to instantiate with the diagram * @param {Injector} [injector] an (optional) injector to bootstrap the diagram with */ function Diagram(options, injector) { // create injector unless explicitly specified this.injector = injector = injector || createInjector(options); // API /** * Resolves a diagram service * * @method Diagram#get * * @param {string} name the name of the diagram service to be retrieved * @param {boolean} [strict=true] if false, resolve missing services to null */ this.get = injector.get; /** * Executes a function into which diagram services are injected * * @method Diagram#invoke * * @param {Function|Object[]} fn the function to resolve * @param {Object} locals a number of locals to use to resolve certain dependencies */ this.invoke = injector.invoke; // init // indicate via event /** * An event indicating that all plug-ins are loaded. * * Use this event to fire other events to interested plug-ins * * @memberOf Diagram * * @event diagram.init * * @example * * eventBus.on('diagram.init', function() { * eventBus.fire('my-custom-event', { foo: 'BAR' }); * }); * * @type {Object} */ this.get('eventBus').fire('diagram.init'); } /** * Destroys the diagram * * @method Diagram#destroy */ Diagram.prototype.destroy = function() { this.get('eventBus').fire('diagram.destroy'); }; /** * Clear the diagram, removing all contents. */ Diagram.prototype.clear = function() { this.get('eventBus').fire('diagram.clear'); }; /** * Moddle base element. */ function Base() { } Base.prototype.get = function(name) { return this.$model.properties.get(this, name); }; Base.prototype.set = function(name, value) { this.$model.properties.set(this, name, value); }; /** * A model element factory. * * @param {Moddle} model * @param {Properties} properties */ function Factory(model, properties) { this.model = model; this.properties = properties; } Factory.prototype.createType = function(descriptor) { var model = this.model; var props = this.properties, prototype = Object.create(Base.prototype); // initialize default values forEach$1(descriptor.properties, function(p) { if (!p.isMany && p.default !== undefined) { prototype[p.name] = p.default; } }); props.defineModel(prototype, model); props.defineDescriptor(prototype, descriptor); var name = descriptor.ns.name; /** * The new type constructor */ function ModdleElement(attrs) { props.define(this, '$type', { value: name, enumerable: true }); props.define(this, '$attrs', { value: {} }); props.define(this, '$parent', { writable: true }); forEach$1(attrs, bind(function(val, key) { this.set(key, val); }, this)); } ModdleElement.prototype = prototype; ModdleElement.hasType = prototype.$instanceOf = this.model.hasType; // static links props.defineModel(ModdleElement, model); props.defineDescriptor(ModdleElement, descriptor); return ModdleElement; }; /** * Built-in moddle types */ var BUILTINS = { String: true, Boolean: true, Integer: true, Real: true, Element: true }; /** * Converters for built in types from string representations */ var TYPE_CONVERTERS = { String: function(s) { return s; }, Boolean: function(s) { return s === 'true'; }, Integer: function(s) { return parseInt(s, 10); }, Real: function(s) { return parseFloat(s); } }; /** * Convert a type to its real representation */ function coerceType(type, value) { var converter = TYPE_CONVERTERS[type]; if (converter) { return converter(value); } else { return value; } } /** * Return whether the given type is built-in */ function isBuiltIn(type) { return !!BUILTINS[type]; } /** * Return whether the given type is simple */ function isSimple(type) { return !!TYPE_CONVERTERS[type]; } /** * Parses a namespaced attribute name of the form (ns:)localName to an object, * given a default prefix to assume in case no explicit namespace is given. * * @param {String} name * @param {String} [defaultPrefix] the default prefix to take, if none is present. * * @return {Object} the parsed name */ function parseName(name, defaultPrefix) { var parts = name.split(/:/), localName, prefix; // no prefix (i.e. only local name) if (parts.length === 1) { localName = name; prefix = defaultPrefix; } else // prefix + local name if (parts.length === 2) { localName = parts[1]; prefix = parts[0]; } else { throw new Error('expected or , got ' + name); } name = (prefix ? prefix + ':' : '') + localName; return { name: name, prefix: prefix, localName: localName }; } /** * A utility to build element descriptors. */ function DescriptorBuilder(nameNs) { this.ns = nameNs; this.name = nameNs.name; this.allTypes = []; this.allTypesByName = {}; this.properties = []; this.propertiesByName = {}; } DescriptorBuilder.prototype.build = function() { return pick(this, [ 'ns', 'name', 'allTypes', 'allTypesByName', 'properties', 'propertiesByName', 'bodyProperty', 'idProperty' ]); }; /** * Add property at given index. * * @param {Object} p * @param {Number} [idx] * @param {Boolean} [validate=true] */ DescriptorBuilder.prototype.addProperty = function(p, idx, validate) { if (typeof idx === 'boolean') { validate = idx; idx = undefined; } this.addNamedProperty(p, validate !== false); var properties = this.properties; if (idx !== undefined) { properties.splice(idx, 0, p); } else { properties.push(p); } }; DescriptorBuilder.prototype.replaceProperty = function(oldProperty, newProperty, replace) { var oldNameNs = oldProperty.ns; var props = this.properties, propertiesByName = this.propertiesByName, rename = oldProperty.name !== newProperty.name; if (oldProperty.isId) { if (!newProperty.isId) { throw new Error( 'property <' + newProperty.ns.name + '> must be id property ' + 'to refine <' + oldProperty.ns.name + '>'); } this.setIdProperty(newProperty, false); } if (oldProperty.isBody) { if (!newProperty.isBody) { throw new Error( 'property <' + newProperty.ns.name + '> must be body property ' + 'to refine <' + oldProperty.ns.name + '>'); } // TODO: Check compatibility this.setBodyProperty(newProperty, false); } // validate existence and get location of old property var idx = props.indexOf(oldProperty); if (idx === -1) { throw new Error('property <' + oldNameNs.name + '> not found in property list'); } // remove old property props.splice(idx, 1); // replacing the named property is intentional // // * validate only if this is a "rename" operation // * add at specific index unless we "replace" // this.addProperty(newProperty, replace ? undefined : idx, rename); // make new property available under old name propertiesByName[oldNameNs.name] = propertiesByName[oldNameNs.localName] = newProperty; }; DescriptorBuilder.prototype.redefineProperty = function(p, targetPropertyName, replace) { var nsPrefix = p.ns.prefix; var parts = targetPropertyName.split('#'); var name = parseName(parts[0], nsPrefix); var attrName = parseName(parts[1], name.prefix).name; var redefinedProperty = this.propertiesByName[attrName]; if (!redefinedProperty) { throw new Error('refined property <' + attrName + '> not found'); } else { this.replaceProperty(redefinedProperty, p, replace); } delete p.redefines; }; DescriptorBuilder.prototype.addNamedProperty = function(p, validate) { var ns = p.ns, propsByName = this.propertiesByName; if (validate) { this.assertNotDefined(p, ns.name); this.assertNotDefined(p, ns.localName); } propsByName[ns.name] = propsByName[ns.localName] = p; }; DescriptorBuilder.prototype.removeNamedProperty = function(p) { var ns = p.ns, propsByName = this.propertiesByName; delete propsByName[ns.name]; delete propsByName[ns.localName]; }; DescriptorBuilder.prototype.setBodyProperty = function(p, validate) { if (validate && this.bodyProperty) { throw new Error( 'body property defined multiple times ' + '(<' + this.bodyProperty.ns.name + '>, <' + p.ns.name + '>)'); } this.bodyProperty = p; }; DescriptorBuilder.prototype.setIdProperty = function(p, validate) { if (validate && this.idProperty) { throw new Error( 'id property defined multiple times ' + '(<' + this.idProperty.ns.name + '>, <' + p.ns.name + '>)'); } this.idProperty = p; }; DescriptorBuilder.prototype.assertNotDefined = function(p, name) { var propertyName = p.name, definedProperty = this.propertiesByName[propertyName]; if (definedProperty) { throw new Error( 'property <' + propertyName + '> already defined; ' + 'override of <' + definedProperty.definedBy.ns.name + '#' + definedProperty.ns.name + '> by ' + '<' + p.definedBy.ns.name + '#' + p.ns.name + '> not allowed without redefines'); } }; DescriptorBuilder.prototype.hasProperty = function(name) { return this.propertiesByName[name]; }; DescriptorBuilder.prototype.addTrait = function(t, inherited) { var typesByName = this.allTypesByName, types = this.allTypes; var typeName = t.name; if (typeName in typesByName) { return; } forEach$1(t.properties, bind(function(p) { // clone property to allow extensions p = assign({}, p, { name: p.ns.localName, inherited: inherited }); Object.defineProperty(p, 'definedBy', { value: t }); var replaces = p.replaces, redefines = p.redefines; // add replace/redefine support if (replaces || redefines) { this.redefineProperty(p, replaces || redefines, replaces); } else { if (p.isBody) { this.setBodyProperty(p); } if (p.isId) { this.setIdProperty(p); } this.addProperty(p); } }, this)); types.push(t); typesByName[typeName] = t; }; /** * A registry of Moddle packages. * * @param {Array} packages * @param {Properties} properties */ function Registry(packages, properties) { this.packageMap = {}; this.typeMap = {}; this.packages = []; this.properties = properties; forEach$1(packages, bind(this.registerPackage, this)); } Registry.prototype.getPackage = function(uriOrPrefix) { return this.packageMap[uriOrPrefix]; }; Registry.prototype.getPackages = function() { return this.packages; }; Registry.prototype.registerPackage = function(pkg) { // copy package pkg = assign({}, pkg); var pkgMap = this.packageMap; ensureAvailable(pkgMap, pkg, 'prefix'); ensureAvailable(pkgMap, pkg, 'uri'); // register types forEach$1(pkg.types, bind(function(descriptor) { this.registerType(descriptor, pkg); }, this)); pkgMap[pkg.uri] = pkgMap[pkg.prefix] = pkg; this.packages.push(pkg); }; /** * Register a type from a specific package with us */ Registry.prototype.registerType = function(type, pkg) { type = assign({}, type, { superClass: (type.superClass || []).slice(), extends: (type.extends || []).slice(), properties: (type.properties || []).slice(), meta: assign((type.meta || {})) }); var ns = parseName(type.name, pkg.prefix), name = ns.name, propertiesByName = {}; // parse properties forEach$1(type.properties, bind(function(p) { // namespace property names var propertyNs = parseName(p.name, ns.prefix), propertyName = propertyNs.name; // namespace property types if (!isBuiltIn(p.type)) { p.type = parseName(p.type, propertyNs.prefix).name; } assign(p, { ns: propertyNs, name: propertyName }); propertiesByName[propertyName] = p; }, this)); // update ns + name assign(type, { ns: ns, name: name, propertiesByName: propertiesByName }); forEach$1(type.extends, bind(function(extendsName) { var extended = this.typeMap[extendsName]; extended.traits = extended.traits || []; extended.traits.push(name); }, this)); // link to package this.definePackage(type, pkg); // register this.typeMap[name] = type; }; /** * Traverse the type hierarchy from bottom to top, * calling iterator with (type, inherited) for all elements in * the inheritance chain. * * @param {Object} nsName * @param {Function} iterator * @param {Boolean} [trait=false] */ Registry.prototype.mapTypes = function(nsName, iterator, trait) { var type = isBuiltIn(nsName.name) ? { name: nsName.name } : this.typeMap[nsName.name]; var self = this; /** * Traverse the selected trait. * * @param {String} cls */ function traverseTrait(cls) { return traverseSuper(cls, true); } /** * Traverse the selected super type or trait * * @param {String} cls * @param {Boolean} [trait=false] */ function traverseSuper(cls, trait) { var parentNs = parseName(cls, isBuiltIn(cls) ? '' : nsName.prefix); self.mapTypes(parentNs, iterator, trait); } if (!type) { throw new Error('unknown type <' + nsName.name + '>'); } forEach$1(type.superClass, trait ? traverseTrait : traverseSuper); // call iterator with (type, inherited=!trait) iterator(type, !trait); forEach$1(type.traits, traverseTrait); }; /** * Returns the effective descriptor for a type. * * @param {String} type the namespaced name (ns:localName) of the type * * @return {Descriptor} the resulting effective descriptor */ Registry.prototype.getEffectiveDescriptor = function(name) { var nsName = parseName(name); var builder = new DescriptorBuilder(nsName); this.mapTypes(nsName, function(type, inherited) { builder.addTrait(type, inherited); }); var descriptor = builder.build(); // define package link this.definePackage(descriptor, descriptor.allTypes[descriptor.allTypes.length - 1].$pkg); return descriptor; }; Registry.prototype.definePackage = function(target, pkg) { this.properties.define(target, '$pkg', { value: pkg }); }; ///////// helpers //////////////////////////// function ensureAvailable(packageMap, pkg, identifierKey) { var value = pkg[identifierKey]; if (value in packageMap) { throw new Error('package with ' + identifierKey + ' <' + value + '> already defined'); } } /** * A utility that gets and sets properties of model elements. * * @param {Model} model */ function Properties(model) { this.model = model; } /** * Sets a named property on the target element. * If the value is undefined, the property gets deleted. * * @param {Object} target * @param {String} name * @param {Object} value */ Properties.prototype.set = function(target, name, value) { var property = this.model.getPropertyDescriptor(target, name); var propertyName = property && property.name; if (isUndefined(value)) { // unset the property, if the specified value is undefined; // delete from $attrs (for extensions) or the target itself if (property) { delete target[propertyName]; } else { delete target.$attrs[name]; } } else { // set the property, defining well defined properties on the fly // or simply updating them in target.$attrs (for extensions) if (property) { if (propertyName in target) { target[propertyName] = value; } else { defineProperty(target, property, value); } } else { target.$attrs[name] = value; } } }; /** * Returns the named property of the given element * * @param {Object} target * @param {String} name * * @return {Object} */ Properties.prototype.get = function(target, name) { var property = this.model.getPropertyDescriptor(target, name); if (!property) { return target.$attrs[name]; } var propertyName = property.name; // check if access to collection property and lazily initialize it if (!target[propertyName] && property.isMany) { defineProperty(target, property, []); } return target[propertyName]; }; /** * Define a property on the target element * * @param {Object} target * @param {String} name * @param {Object} options */ Properties.prototype.define = function(target, name, options) { if (!options.writable) { var value = options.value; // use getters for read-only variables to support ES6 proxies // cf. https://github.com/bpmn-io/internal-docs/issues/386 options = assign({}, options, { get: function() { return value; } }); delete options.value; } Object.defineProperty(target, name, options); }; /** * Define the descriptor for an element */ Properties.prototype.defineDescriptor = function(target, descriptor) { this.define(target, '$descriptor', { value: descriptor }); }; /** * Define the model for an element */ Properties.prototype.defineModel = function(target, model) { this.define(target, '$model', { value: model }); }; function isUndefined(val) { return typeof val === 'undefined'; } function defineProperty(target, property, value) { Object.defineProperty(target, property.name, { enumerable: !property.isReference, writable: true, value: value, configurable: true }); } //// Moddle implementation ///////////////////////////////////////////////// /** * @class Moddle * * A model that can be used to create elements of a specific type. * * @example * * var Moddle = require('moddle'); * * var pkg = { * name: 'mypackage', * prefix: 'my', * types: [ * { name: 'Root' } * ] * }; * * var moddle = new Moddle([pkg]); * * @param {Array} packages the packages to contain */ function Moddle(packages) { this.properties = new Properties(this); this.factory = new Factory(this, this.properties); this.registry = new Registry(packages, this.properties); this.typeCache = {}; } /** * Create an instance of the specified type. * * @method Moddle#create * * @example * * var foo = moddle.create('my:Foo'); * var bar = moddle.create('my:Bar', { id: 'BAR_1' }); * * @param {String|Object} descriptor the type descriptor or name know to the model * @param {Object} attrs a number of attributes to initialize the model instance with * @return {Object} model instance */ Moddle.prototype.create = function(descriptor, attrs) { var Type = this.getType(descriptor); if (!Type) { throw new Error('unknown type <' + descriptor + '>'); } return new Type(attrs); }; /** * Returns the type representing a given descriptor * * @method Moddle#getType * * @example * * var Foo = moddle.getType('my:Foo'); * var foo = new Foo({ 'id' : 'FOO_1' }); * * @param {String|Object} descriptor the type descriptor or name know to the model * @return {Object} the type representing the descriptor */ Moddle.prototype.getType = function(descriptor) { var cache = this.typeCache; var name = isString(descriptor) ? descriptor : descriptor.ns.name; var type = cache[name]; if (!type) { descriptor = this.registry.getEffectiveDescriptor(name); type = cache[name] = this.factory.createType(descriptor); } return type; }; /** * Creates an any-element type to be used within model instances. * * This can be used to create custom elements that lie outside the meta-model. * The created element contains all the meta-data required to serialize it * as part of meta-model elements. * * @method Moddle#createAny * * @example * * var foo = moddle.createAny('vendor:Foo', 'http://vendor', { * value: 'bar' * }); * * var container = moddle.create('my:Container', 'http://my', { * any: [ foo ] * }); * * // go ahead and serialize the stuff * * * @param {String} name the name of the element * @param {String} nsUri the namespace uri of the element * @param {Object} [properties] a map of properties to initialize the instance with * @return {Object} the any type instance */ Moddle.prototype.createAny = function(name, nsUri, properties) { var nameNs = parseName(name); var element = { $type: name, $instanceOf: function(type) { return type === this.$type; } }; var descriptor = { name: name, isGeneric: true, ns: { prefix: nameNs.prefix, localName: nameNs.localName, uri: nsUri } }; this.properties.defineDescriptor(element, descriptor); this.properties.defineModel(element, this); this.properties.define(element, '$parent', { enumerable: false, writable: true }); this.properties.define(element, '$instanceOf', { enumerable: false, writable: true }); forEach$1(properties, function(a, key) { if (isObject(a) && a.value !== undefined) { element[a.name] = a.value; } else { element[key] = a; } }); return element; }; /** * Returns a registered package by uri or prefix * * @return {Object} the package */ Moddle.prototype.getPackage = function(uriOrPrefix) { return this.registry.getPackage(uriOrPrefix); }; /** * Returns a snapshot of all known packages * * @return {Object} the package */ Moddle.prototype.getPackages = function() { return this.registry.getPackages(); }; /** * Returns the descriptor for an element */ Moddle.prototype.getElementDescriptor = function(element) { return element.$descriptor; }; /** * Returns true if the given descriptor or instance * represents the given type. * * May be applied to this, if element is omitted. */ Moddle.prototype.hasType = function(element, type) { if (type === undefined) { type = element; element = this; } var descriptor = element.$model.getElementDescriptor(element); return (type in descriptor.allTypesByName); }; /** * Returns the descriptor of an elements named property */ Moddle.prototype.getPropertyDescriptor = function(element, property) { return this.getElementDescriptor(element).propertiesByName[property]; }; /** * Returns a mapped type's descriptor */ Moddle.prototype.getTypeDescriptor = function(type) { return this.registry.typeMap[type]; }; var fromCharCode = String.fromCharCode; var hasOwnProperty = Object.prototype.hasOwnProperty; var ENTITY_PATTERN = /&#(\d+);|&#x([0-9a-f]+);|&(\w+);/ig; var ENTITY_MAPPING = { 'amp': '&', 'apos': '\'', 'gt': '>', 'lt': '<', 'quot': '"' }; // map UPPERCASE variants of supported special chars Object.keys(ENTITY_MAPPING).forEach(function(k) { ENTITY_MAPPING[k.toUpperCase()] = ENTITY_MAPPING[k]; }); function replaceEntities(_, d, x, z) { // reserved names, i.e.   if (z) { if (hasOwnProperty.call(ENTITY_MAPPING, z)) { return ENTITY_MAPPING[z]; } else { // fall back to original value return '&' + z + ';'; } } // decimal encoded char if (d) { return fromCharCode(d); } // hex encoded char return fromCharCode(parseInt(x, 16)); } /** * A basic entity decoder that can decode a minimal * sub-set of reserved names (&) as well as * hex (ય) and decimal (ӏ) encoded characters. * * @param {string} str * * @return {string} decoded string */ function decodeEntities(s) { if (s.length > 3 && s.indexOf('&') !== -1) { return s.replace(ENTITY_PATTERN, replaceEntities); } return s; } var XSI_URI = 'http://www.w3.org/2001/XMLSchema-instance'; var XSI_PREFIX = 'xsi'; var XSI_TYPE$1 = 'xsi:type'; var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node'; function error$1(msg) { return new Error(msg); } function missingNamespaceForPrefix(prefix) { return 'missing namespace for prefix <' + prefix + '>'; } function getter(getFn) { return { 'get': getFn, 'enumerable': true }; } function cloneNsMatrix(nsMatrix) { var clone = {}, key; for (key in nsMatrix) { clone[key] = nsMatrix[key]; } return clone; } function uriPrefix(prefix) { return prefix + '$uri'; } function buildNsMatrix(nsUriToPrefix) { var nsMatrix = {}, uri, prefix; for (uri in nsUriToPrefix) { prefix = nsUriToPrefix[uri]; nsMatrix[prefix] = prefix; nsMatrix[uriPrefix(prefix)] = uri; } return nsMatrix; } function noopGetContext() { return { 'line': 0, 'column': 0 }; } function throwFunc(err) { throw err; } /** * Creates a new parser with the given options. * * @constructor * * @param {!Object=} options */ function Parser(options) { if (!this) { return new Parser(options); } var proxy = options && options['proxy']; var onText, onOpenTag, onCloseTag, onCDATA, onError = throwFunc, onWarning, onComment, onQuestion, onAttention; var getContext = noopGetContext; /** * Do we need to parse the current elements attributes for namespaces? * * @type {boolean} */ var maybeNS = false; /** * Do we process namespaces at all? * * @type {boolean} */ var isNamespace = false; /** * The caught error returned on parse end * * @type {Error} */ var returnError = null; /** * Should we stop parsing? * * @type {boolean} */ var parseStop = false; /** * A map of { uri: prefix } used by the parser. * * This map will ensure we can normalize prefixes during processing; * for each uri, only one prefix will be exposed to the handlers. * * @type {!Object}} */ var nsUriToPrefix; /** * Handle parse error. * * @param {string|Error} err */ function handleError(err) { if (!(err instanceof Error)) { err = error$1(err); } returnError = err; onError(err, getContext); } /** * Handle parse error. * * @param {string|Error} err */ function handleWarning(err) { if (!onWarning) { return; } if (!(err instanceof Error)) { err = error$1(err); } onWarning(err, getContext); } /** * Register parse listener. * * @param {string} name * @param {Function} cb * * @return {Parser} */ this['on'] = function(name, cb) { if (typeof cb !== 'function') { throw error$1('required args '); } switch (name) { case 'openTag': onOpenTag = cb; break; case 'text': onText = cb; break; case 'closeTag': onCloseTag = cb; break; case 'error': onError = cb; break; case 'warn': onWarning = cb; break; case 'cdata': onCDATA = cb; break; case 'attention': onAttention = cb; break; // case 'question': onQuestion = cb; break; // case 'comment': onComment = cb; break; default: throw error$1('unsupported event: ' + name); } return this; }; /** * Set the namespace to prefix mapping. * * @example * * parser.ns({ * 'http://foo': 'foo', * 'http://bar': 'bar' * }); * * @param {!Object} nsMap * * @return {Parser} */ this['ns'] = function(nsMap) { if (typeof nsMap === 'undefined') { nsMap = {}; } if (typeof nsMap !== 'object') { throw error$1('required args '); } var _nsUriToPrefix = {}, k; for (k in nsMap) { _nsUriToPrefix[k] = nsMap[k]; } // FORCE default mapping for schema instance _nsUriToPrefix[XSI_URI] = XSI_PREFIX; isNamespace = true; nsUriToPrefix = _nsUriToPrefix; return this; }; /** * Parse xml string. * * @param {string} xml * * @return {Error} returnError, if not thrown */ this['parse'] = function(xml) { if (typeof xml !== 'string') { throw error$1('required args '); } returnError = null; parse(xml); getContext = noopGetContext; parseStop = false; return returnError; }; /** * Stop parsing. */ this['stop'] = function() { parseStop = true; }; /** * Parse string, invoking configured listeners on element. * * @param {string} xml */ function parse(xml) { var nsMatrixStack = isNamespace ? [] : null, nsMatrix = isNamespace ? buildNsMatrix(nsUriToPrefix) : null, _nsMatrix, nodeStack = [], anonymousNsCount = 0, tagStart = false, tagEnd = false, i = 0, j = 0, x, y, q, w, v, xmlns, elementName, _elementName, elementProxy ; var attrsString = '', attrsStart = 0, cachedAttrs // false = parsed with errors, null = needs parsing ; /** * Parse attributes on demand and returns the parsed attributes. * * Return semantics: (1) `false` on attribute parse error, * (2) object hash on extracted attrs. * * @return {boolean|Object} */ function getAttrs() { if (cachedAttrs !== null) { return cachedAttrs; } var nsUri, nsUriPrefix, nsName, defaultAlias = isNamespace && nsMatrix['xmlns'], attrList = isNamespace && maybeNS ? [] : null, i = attrsStart, s = attrsString, l = s.length, hasNewMatrix, newalias, value, alias, name, attrs = {}, seenAttrs = {}, skipAttr, w, j; parseAttr: for (; i < l; i++) { skipAttr = false; w = s.charCodeAt(i); if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE={ \f\n\r\t\v} continue; } // wait for non whitespace character if (w < 65 || w > 122 || (w > 90 && w < 97)) { if (w !== 95 && w !== 58) { // char 95"_" 58":" handleWarning('illegal first char attribute name'); skipAttr = true; } } // parse attribute name for (j = i + 1; j < l; j++) { w = s.charCodeAt(j); if ( w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 46 || // '.' w === 45 || // '-' w === 95 // '_' ) { continue; } // unexpected whitespace if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE handleWarning('missing attribute value'); i = j; continue parseAttr; } // expected "=" if (w === 61) { // "=" == 61 break; } handleWarning('illegal attribute name char'); skipAttr = true; } name = s.substring(i, j); if (name === 'xmlns:xmlns') { handleWarning('illegal declaration of xmlns'); skipAttr = true; } w = s.charCodeAt(j + 1); if (w === 34) { // '"' j = s.indexOf('"', i = j + 2); if (j === -1) { j = s.indexOf('\'', i); if (j !== -1) { handleWarning('attribute value quote missmatch'); skipAttr = true; } } } else if (w === 39) { // "'" j = s.indexOf('\'', i = j + 2); if (j === -1) { j = s.indexOf('"', i); if (j !== -1) { handleWarning('attribute value quote missmatch'); skipAttr = true; } } } else { handleWarning('missing attribute value quotes'); skipAttr = true; // skip to next space for (j = j + 1; j < l; j++) { w = s.charCodeAt(j + 1); if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE break; } } } if (j === -1) { handleWarning('missing closing quotes'); j = l; skipAttr = true; } if (!skipAttr) { value = s.substring(i, j); } i = j; // ensure SPACE follows attribute // skip illegal content otherwise // example a="b"c for (; j + 1 < l; j++) { w = s.charCodeAt(j + 1); if (w === 32 || (w < 14 && w > 8)) { // WHITESPACE break; } // FIRST ILLEGAL CHAR if (i === j) { handleWarning('illegal character after attribute end'); skipAttr = true; } } // advance cursor to next attribute i = j + 1; if (skipAttr) { continue parseAttr; } // check attribute re-declaration if (name in seenAttrs) { handleWarning('attribute <' + name + '> already defined'); continue; } seenAttrs[name] = true; if (!isNamespace) { attrs[name] = value; continue; } // try to extract namespace information if (maybeNS) { newalias = ( name === 'xmlns' ? 'xmlns' : (name.charCodeAt(0) === 120 && name.substr(0, 6) === 'xmlns:') ? name.substr(6) : null ); // handle xmlns(:alias) assignment if (newalias !== null) { nsUri = decodeEntities(value); nsUriPrefix = uriPrefix(newalias); alias = nsUriToPrefix[nsUri]; if (!alias) { // no prefix defined or prefix collision if ( (newalias === 'xmlns') || (nsUriPrefix in nsMatrix && nsMatrix[nsUriPrefix] !== nsUri) ) { // alocate free ns prefix do { alias = 'ns' + (anonymousNsCount++); } while (typeof nsMatrix[alias] !== 'undefined'); } else { alias = newalias; } nsUriToPrefix[nsUri] = alias; } if (nsMatrix[newalias] !== alias) { if (!hasNewMatrix) { nsMatrix = cloneNsMatrix(nsMatrix); hasNewMatrix = true; } nsMatrix[newalias] = alias; if (newalias === 'xmlns') { nsMatrix[uriPrefix(alias)] = nsUri; defaultAlias = alias; } nsMatrix[nsUriPrefix] = nsUri; } // expose xmlns(:asd)="..." in attributes attrs[name] = value; continue; } // collect attributes until all namespace // declarations are processed attrList.push(name, value); continue; } /** end if (maybeNs) */ // handle attributes on element without // namespace declarations w = name.indexOf(':'); if (w === -1) { attrs[name] = value; continue; } // normalize ns attribute name if (!(nsName = nsMatrix[name.substring(0, w)])) { handleWarning(missingNamespaceForPrefix(name.substring(0, w))); continue; } name = defaultAlias === nsName ? name.substr(w + 1) : nsName + name.substr(w); // end: normalize ns attribute name // normalize xsi:type ns attribute value if (name === XSI_TYPE$1) { w = value.indexOf(':'); if (w !== -1) { nsName = value.substring(0, w); // handle default prefixes, i.e. xs:String gracefully nsName = nsMatrix[nsName] || nsName; value = nsName + value.substring(w); } else { value = defaultAlias + ':' + value; } } // end: normalize xsi:type ns attribute value attrs[name] = value; } // handle deferred, possibly namespaced attributes if (maybeNS) { // normalize captured attributes for (i = 0, l = attrList.length; i < l; i++) { name = attrList[i++]; value = attrList[i]; w = name.indexOf(':'); if (w !== -1) { // normalize ns attribute name if (!(nsName = nsMatrix[name.substring(0, w)])) { handleWarning(missingNamespaceForPrefix(name.substring(0, w))); continue; } name = defaultAlias === nsName ? name.substr(w + 1) : nsName + name.substr(w); // end: normalize ns attribute name // normalize xsi:type ns attribute value if (name === XSI_TYPE$1) { w = value.indexOf(':'); if (w !== -1) { nsName = value.substring(0, w); // handle default prefixes, i.e. xs:String gracefully nsName = nsMatrix[nsName] || nsName; value = nsName + value.substring(w); } else { value = defaultAlias + ':' + value; } } // end: normalize xsi:type ns attribute value } attrs[name] = value; } // end: normalize captured attributes } return cachedAttrs = attrs; } /** * Extract the parse context { line, column, part } * from the current parser position. * * @return {Object} parse context */ function getParseContext() { var splitsRe = /(\r\n|\r|\n)/g; var line = 0; var column = 0; var startOfLine = 0; var endOfLine = j; var match; var data; while (i >= startOfLine) { match = splitsRe.exec(xml); if (!match) { break; } // end of line = (break idx + break chars) endOfLine = match[0].length + match.index; if (endOfLine > i) { break; } // advance to next line line += 1; startOfLine = endOfLine; } // EOF errors if (i == -1) { column = endOfLine; data = xml.substring(j); } else // start errors if (j === 0) { data = xml.substring(j, i); } // other errors else { column = i - startOfLine; data = (j == -1 ? xml.substring(i) : xml.substring(i, j + 1)); } return { 'data': data, 'line': line, 'column': column }; } getContext = getParseContext; if (proxy) { elementProxy = Object.create({}, { 'name': getter(function() { return elementName; }), 'originalName': getter(function() { return _elementName; }), 'attrs': getter(getAttrs), 'ns': getter(function() { return nsMatrix; }) }); } // actual parse logic while (j !== -1) { if (xml.charCodeAt(j) === 60) { // "<" i = j; } else { i = xml.indexOf('<', j); } // parse end if (i === -1) { if (nodeStack.length) { return handleError('unexpected end of file'); } if (j === 0) { return handleError('missing start tag'); } if (j < xml.length) { if (xml.substring(j).trim()) { handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE); } } return; } // parse text if (j !== i) { if (nodeStack.length) { if (onText) { onText(xml.substring(j, i), decodeEntities, getContext); if (parseStop) { return; } } } else { if (xml.substring(j, i).trim()) { handleWarning(NON_WHITESPACE_OUTSIDE_ROOT_NODE); if (parseStop) { return; } } } } w = xml.charCodeAt(i+1); // parse comments + CDATA if (w === 33) { // "!" q = xml.charCodeAt(i+2); // CDATA section if (q === 91 && xml.substr(i + 3, 6) === 'CDATA[') { // 91 == "[" j = xml.indexOf(']]>', i); if (j === -1) { return handleError('unclosed cdata'); } if (onCDATA) { onCDATA(xml.substring(i + 9, j), getContext); if (parseStop) { return; } } j += 3; continue; } // comment if (q === 45 && xml.charCodeAt(i + 3) === 45) { // 45 == "-" j = xml.indexOf('-->', i); if (j === -1) { return handleError('unclosed comment'); } if (onComment) { onComment(xml.substring(i + 4, j), decodeEntities, getContext); if (parseStop) { return; } } j += 3; continue; } } // parse question if (w === 63) { // "?" j = xml.indexOf('?>', i); if (j === -1) { return handleError('unclosed question'); } if (onQuestion) { onQuestion(xml.substring(i, j + 2), getContext); if (parseStop) { return; } } j += 2; continue; } // find matching closing tag for attention or standard tags // for that we must skip through attribute values // (enclosed in single or double quotes) for (x = i + 1; ; x++) { v = xml.charCodeAt(x); if (isNaN(v)) { j = -1; return handleError('unclosed tag'); } // [10] AttValue ::= '"' ([^<&"] | Reference)* '"' | "'" ([^<&'] | Reference)* "'" // skips the quoted string // (double quotes) does not appear in a literal enclosed by (double quotes) // (single quote) does not appear in a literal enclosed by (single quote) if (v === 34) { // '"' q = xml.indexOf('"', x + 1); x = q !== -1 ? q : x; } else if (v === 39) { // "'" q = xml.indexOf("'", x + 1); x = q !== -1 ? q : x; } else if (v === 62) { // '>' j = x; break; } } // parse attention // previously comment and CDATA have already been parsed if (w === 33) { // "!" if (onAttention) { onAttention(xml.substring(i, j + 1), decodeEntities, getContext); if (parseStop) { return; } } j += 1; continue; } // don't process attributes; // there are none cachedAttrs = {}; // if (xml.charCodeAt(i+1) === 47) { // close tag match x = elementName = nodeStack.pop(); q = i + 2 + x.length; if (xml.substring(i + 2, q) !== x) { return handleError('closing tag mismatch'); } // verify chars in close tag for (; q < j; q++) { w = xml.charCodeAt(q); if (w === 32 || (w > 8 && w < 14)) { // \f\n\r\t\v space continue; } return handleError('close tag'); } } else { if (xml.charCodeAt(j - 1) === 47) { // .../> x = elementName = xml.substring(i + 1, j - 1); tagStart = true; tagEnd = true; } else { x = elementName = xml.substring(i + 1, j); tagStart = true; tagEnd = false; } if (!(w > 96 && w < 123 || w > 64 && w < 91 || w === 95 || w === 58)) { // char 95"_" 58":" return handleError('illegal first char nodeName'); } for (q = 1, y = x.length; q < y; q++) { w = x.charCodeAt(q); if (w > 96 && w < 123 || w > 64 && w < 91 || w > 47 && w < 59 || w === 45 || w === 95 || w == 46) { continue; } if (w === 32 || (w < 14 && w > 8)) { // \f\n\r\t\v space elementName = x.substring(0, q); // maybe there are attributes cachedAttrs = null; break; } return handleError('invalid nodeName'); } if (!tagEnd) { nodeStack.push(elementName); } } if (isNamespace) { _nsMatrix = nsMatrix; if (tagStart) { // remember old namespace // unless we're self-closing if (!tagEnd) { nsMatrixStack.push(_nsMatrix); } if (cachedAttrs === null) { // quick check, whether there may be namespace // declarations on the node; if that is the case // we need to eagerly parse the node attributes if ((maybeNS = x.indexOf('xmlns', q) !== -1)) { attrsStart = q; attrsString = x; getAttrs(); maybeNS = false; } } } _elementName = elementName; w = elementName.indexOf(':'); if (w !== -1) { xmlns = nsMatrix[elementName.substring(0, w)]; // prefix given; namespace must exist if (!xmlns) { return handleError('missing namespace on <' + _elementName + '>'); } elementName = elementName.substr(w + 1); } else { xmlns = nsMatrix['xmlns']; // if no default namespace is defined, // we'll import the element as anonymous. // // it is up to users to correct that to the document defined // targetNamespace, or whatever their undersanding of the // XML spec mandates. } // adjust namespace prefixs as configured if (xmlns) { elementName = xmlns + ':' + elementName; } } if (tagStart) { attrsStart = q; attrsString = x; if (onOpenTag) { if (proxy) { onOpenTag(elementProxy, decodeEntities, tagEnd, getContext); } else { onOpenTag(elementName, getAttrs, decodeEntities, tagEnd, getContext); } if (parseStop) { return; } } } if (tagEnd) { if (onCloseTag) { onCloseTag(proxy ? elementProxy : elementName, decodeEntities, tagStart, getContext); if (parseStop) { return; } } // restore old namespace if (isNamespace) { if (!tagStart) { nsMatrix = nsMatrixStack.pop(); } else { nsMatrix = _nsMatrix; } } } j += 1; } } /** end parse */ } function hasLowerCaseAlias(pkg) { return pkg.xml && pkg.xml.tagAlias === 'lowerCase'; } var DEFAULT_NS_MAP = { 'xsi': 'http://www.w3.org/2001/XMLSchema-instance', 'xml': 'http://www.w3.org/XML/1998/namespace' }; var XSI_TYPE = 'xsi:type'; function serializeFormat(element) { return element.xml && element.xml.serialize; } function serializeAsType(element) { return serializeFormat(element) === XSI_TYPE; } function serializeAsProperty(element) { return serializeFormat(element) === 'property'; } function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function aliasToName(aliasNs, pkg) { if (!hasLowerCaseAlias(pkg)) { return aliasNs.name; } return aliasNs.prefix + ':' + capitalize(aliasNs.localName); } function prefixedToName(nameNs, pkg) { var name = nameNs.name, localName = nameNs.localName; var typePrefix = pkg.xml && pkg.xml.typePrefix; if (typePrefix && localName.indexOf(typePrefix) === 0) { return nameNs.prefix + ':' + localName.slice(typePrefix.length); } else { return name; } } function normalizeXsiTypeName(name, model) { var nameNs = parseName(name); var pkg = model.getPackage(nameNs.prefix); return prefixedToName(nameNs, pkg); } function error(message) { return new Error(message); } /** * Get the moddle descriptor for a given instance or type. * * @param {ModdleElement|Function} element * * @return {Object} the moddle descriptor */ function getModdleDescriptor(element) { return element.$descriptor; } /** * A parse context. * * @class * * @param {Object} options * @param {ElementHandler} options.rootHandler the root handler for parsing a document * @param {boolean} [options.lax=false] whether or not to ignore invalid elements */ function Context(options) { /** * @property {ElementHandler} rootHandler */ /** * @property {Boolean} lax */ assign(this, options); this.elementsById = {}; this.references = []; this.warnings = []; /** * Add an unresolved reference. * * @param {Object} reference */ this.addReference = function(reference) { this.references.push(reference); }; /** * Add a processed element. * * @param {ModdleElement} element */ this.addElement = function(element) { if (!element) { throw error('expected element'); } var elementsById = this.elementsById; var descriptor = getModdleDescriptor(element); var idProperty = descriptor.idProperty, id; if (idProperty) { id = element.get(idProperty.name); if (id) { // for QName validation as per http://www.w3.org/TR/REC-xml/#NT-NameChar if (!/^([a-z][\w-.]*:)?[a-z_][\w-.]*$/i.test(id)) { throw new Error('illegal ID <' + id + '>'); } if (elementsById[id]) { throw error('duplicate ID <' + id + '>'); } elementsById[id] = element; } } }; /** * Add an import warning. * * @param {Object} warning * @param {String} warning.message * @param {Error} [warning.error] */ this.addWarning = function(warning) { this.warnings.push(warning); }; } function BaseHandler() {} BaseHandler.prototype.handleEnd = function() {}; BaseHandler.prototype.handleText = function() {}; BaseHandler.prototype.handleNode = function() {}; /** * A simple pass through handler that does nothing except for * ignoring all input it receives. * * This is used to ignore unknown elements and * attributes. */ function NoopHandler() { } NoopHandler.prototype = Object.create(BaseHandler.prototype); NoopHandler.prototype.handleNode = function() { return this; }; function BodyHandler() {} BodyHandler.prototype = Object.create(BaseHandler.prototype); BodyHandler.prototype.handleText = function(text) { this.body = (this.body || '') + text; }; function ReferenceHandler(property, context) { this.property = property; this.context = context; } ReferenceHandler.prototype = Object.create(BodyHandler.prototype); ReferenceHandler.prototype.handleNode = function(node) { if (this.element) { throw error('expected no sub nodes'); } else { this.element = this.createReference(node); } return this; }; ReferenceHandler.prototype.handleEnd = function() { this.element.id = this.body; }; ReferenceHandler.prototype.createReference = function(node) { return { property: this.property.ns.name, id: '' }; }; function ValueHandler(propertyDesc, element) { this.element = element; this.propertyDesc = propertyDesc; } ValueHandler.prototype = Object.create(BodyHandler.prototype); ValueHandler.prototype.handleEnd = function() { var value = this.body || '', element = this.element, propertyDesc = this.propertyDesc; value = coerceType(propertyDesc.type, value); if (propertyDesc.isMany) { element.get(propertyDesc.name).push(value); } else { element.set(propertyDesc.name, value); } }; function BaseElementHandler() {} BaseElementHandler.prototype = Object.create(BodyHandler.prototype); BaseElementHandler.prototype.handleNode = function(node) { var parser = this, element = this.element; if (!element) { element = this.element = this.createElement(node); this.context.addElement(element); } else { parser = this.handleChild(node); } return parser; }; /** * @class Reader.ElementHandler * */ function ElementHandler(model, typeName, context) { this.model = model; this.type = model.getType(typeName); this.context = context; } ElementHandler.prototype = Object.create(BaseElementHandler.prototype); ElementHandler.prototype.addReference = function(reference) { this.context.addReference(reference); }; ElementHandler.prototype.handleText = function(text) { var element = this.element, descriptor = getModdleDescriptor(element), bodyProperty = descriptor.bodyProperty; if (!bodyProperty) { throw error('unexpected body text <' + text + '>'); } BodyHandler.prototype.handleText.call(this, text); }; ElementHandler.prototype.handleEnd = function() { var value = this.body, element = this.element, descriptor = getModdleDescriptor(element), bodyProperty = descriptor.bodyProperty; if (bodyProperty && value !== undefined) { value = coerceType(bodyProperty.type, value); element.set(bodyProperty.name, value); } }; /** * Create an instance of the model from the given node. * * @param {Element} node the xml node */ ElementHandler.prototype.createElement = function(node) { var attributes = node.attributes, Type = this.type, descriptor = getModdleDescriptor(Type), context = this.context, instance = new Type({}), model = this.model, propNameNs; forEach$1(attributes, function(value, name) { var prop = descriptor.propertiesByName[name], values; if (prop && prop.isReference) { if (!prop.isMany) { context.addReference({ element: instance, property: prop.ns.name, id: value }); } else { // IDREFS: parse references as whitespace-separated list values = value.split(' '); forEach$1(values, function(v) { context.addReference({ element: instance, property: prop.ns.name, id: v }); }); } } else { if (prop) { value = coerceType(prop.type, value); } else if (name !== 'xmlns') { propNameNs = parseName(name, descriptor.ns.prefix); // check whether attribute is defined in a well-known namespace // if that is the case we emit a warning to indicate potential misuse if (model.getPackage(propNameNs.prefix)) { context.addWarning({ message: 'unknown attribute <' + name + '>', element: instance, property: name, value: value }); } } instance.set(name, value); } }); return instance; }; ElementHandler.prototype.getPropertyForNode = function(node) { var name = node.name; var nameNs = parseName(name); var type = this.type, model = this.model, descriptor = getModdleDescriptor(type); var propertyName = nameNs.name, property = descriptor.propertiesByName[propertyName], elementTypeName, elementType; // search for properties by name first if (property && !property.isAttr) { if (serializeAsType(property)) { elementTypeName = node.attributes[XSI_TYPE]; // xsi type is optional, if it does not exists the // default type is assumed if (elementTypeName) { // take possible type prefixes from XML // into account, i.e.: xsi:type="t{ActualType}" elementTypeName = normalizeXsiTypeName(elementTypeName, model); elementType = model.getType(elementTypeName); return assign({}, property, { effectiveType: getModdleDescriptor(elementType).name }); } } // search for properties by name first return property; } var pkg = model.getPackage(nameNs.prefix); if (pkg) { elementTypeName = aliasToName(nameNs, pkg); elementType = model.getType(elementTypeName); // search for collection members later property = find(descriptor.properties, function(p) { return !p.isVirtual && !p.isReference && !p.isAttribute && elementType.hasType(p.type); }); if (property) { return assign({}, property, { effectiveType: getModdleDescriptor(elementType).name }); } } else { // parse unknown element (maybe extension) property = find(descriptor.properties, function(p) { return !p.isReference && !p.isAttribute && p.type === 'Element'; }); if (property) { return property; } } throw error('unrecognized element <' + nameNs.name + '>'); }; ElementHandler.prototype.toString = function() { return 'ElementDescriptor[' + getModdleDescriptor(this.type).name + ']'; }; ElementHandler.prototype.valueHandler = function(propertyDesc, element) { return new ValueHandler(propertyDesc, element); }; ElementHandler.prototype.referenceHandler = function(propertyDesc) { return new ReferenceHandler(propertyDesc, this.context); }; ElementHandler.prototype.handler = function(type) { if (type === 'Element') { return new GenericElementHandler(this.model, type, this.context); } else { return new ElementHandler(this.model, type, this.context); } }; /** * Handle the child element parsing * * @param {Element} node the xml node */ ElementHandler.prototype.handleChild = function(node) { var propertyDesc, type, element, childHandler; propertyDesc = this.getPropertyForNode(node); element = this.element; type = propertyDesc.effectiveType || propertyDesc.type; if (isSimple(type)) { return this.valueHandler(propertyDesc, element); } if (propertyDesc.isReference) { childHandler = this.referenceHandler(propertyDesc).handleNode(node); } else { childHandler = this.handler(type).handleNode(node); } var newElement = childHandler.element; // child handles may decide to skip elements // by not returning anything if (newElement !== undefined) { if (propertyDesc.isMany) { element.get(propertyDesc.name).push(newElement); } else { element.set(propertyDesc.name, newElement); } if (propertyDesc.isReference) { assign(newElement, { element: element }); this.context.addReference(newElement); } else { // establish child -> parent relationship newElement.$parent = element; } } return childHandler; }; /** * An element handler that performs special validation * to ensure the node it gets initialized with matches * the handlers type (namespace wise). * * @param {Moddle} model * @param {String} typeName * @param {Context} context */ function RootElementHandler(model, typeName, context) { ElementHandler.call(this, model, typeName, context); } RootElementHandler.prototype = Object.create(ElementHandler.prototype); RootElementHandler.prototype.createElement = function(node) { var name = node.name, nameNs = parseName(name), model = this.model, type = this.type, pkg = model.getPackage(nameNs.prefix), typeName = pkg && aliasToName(nameNs, pkg) || name; // verify the correct namespace if we parse // the first element in the handler tree // // this ensures we don't mistakenly import wrong namespace elements if (!type.hasType(typeName)) { throw error('unexpected element <' + node.originalName + '>'); } return ElementHandler.prototype.createElement.call(this, node); }; function GenericElementHandler(model, typeName, context) { this.model = model; this.context = context; } GenericElementHandler.prototype = Object.create(BaseElementHandler.prototype); GenericElementHandler.prototype.createElement = function(node) { var name = node.name, ns = parseName(name), prefix = ns.prefix, uri = node.ns[prefix + '$uri'], attributes = node.attributes; return this.model.createAny(name, uri, attributes); }; GenericElementHandler.prototype.handleChild = function(node) { var handler = new GenericElementHandler(this.model, 'Element', this.context).handleNode(node), element = this.element; var newElement = handler.element, children; if (newElement !== undefined) { children = element.$children = element.$children || []; children.push(newElement); // establish child -> parent relationship newElement.$parent = element; } return handler; }; GenericElementHandler.prototype.handleEnd = function() { if (this.body) { this.element.$body = this.body; } }; /** * A reader for a meta-model * * @param {Object} options * @param {Model} options.model used to read xml files * @param {Boolean} options.lax whether to make parse errors warnings */ function Reader(options) { if (options instanceof Moddle) { options = { model: options }; } assign(this, { lax: false }, options); } /** * The fromXML result. * * @typedef {Object} ParseResult * * @property {ModdleElement} rootElement * @property {Array} references * @property {Array} warnings * @property {Object} elementsById - a mapping containing each ID -> ModdleElement */ /** * The fromXML result. * * @typedef {Error} ParseError * * @property {Array} warnings */ /** * Parse the given XML into a moddle document tree. * * @param {String} xml * @param {ElementHandler|Object} options or rootHandler * * @returns {Promise} */ Reader.prototype.fromXML = function(xml, options, done) { var rootHandler = options.rootHandler; if (options instanceof ElementHandler) { // root handler passed via (xml, { rootHandler: ElementHandler }, ...) rootHandler = options; options = {}; } else { if (typeof options === 'string') { // rootHandler passed via (xml, 'someString', ...) rootHandler = this.handler(options); options = {}; } else if (typeof rootHandler === 'string') { // rootHandler passed via (xml, { rootHandler: 'someString' }, ...) rootHandler = this.handler(rootHandler); } } var model = this.model, lax = this.lax; var context = new Context(assign({}, options, { rootHandler: rootHandler })), parser = new Parser({ proxy: true }), stack = createStack(); rootHandler.context = context; // push root handler stack.push(rootHandler); /** * Handle error. * * @param {Error} err * @param {Function} getContext * @param {boolean} lax * * @return {boolean} true if handled */ function handleError(err, getContext, lax) { var ctx = getContext(); var line = ctx.line, column = ctx.column, data = ctx.data; // we receive the full context data here, // for elements trim down the information // to the tag name, only if (data.charAt(0) === '<' && data.indexOf(' ') !== -1) { data = data.slice(0, data.indexOf(' ')) + '>'; } var message = 'unparsable content ' + (data ? data + ' ' : '') + 'detected\n\t' + 'line: ' + line + '\n\t' + 'column: ' + column + '\n\t' + 'nested error: ' + err.message; if (lax) { context.addWarning({ message: message, error: err }); return true; } else { throw error(message); } } function handleWarning(err, getContext) { // just like handling errors in mode return handleError(err, getContext, true); } /** * Resolve collected references on parse end. */ function resolveReferences() { var elementsById = context.elementsById; var references = context.references; var i, r; for (i = 0; (r = references[i]); i++) { var element = r.element; var reference = elementsById[r.id]; var property = getModdleDescriptor(element).propertiesByName[r.property]; if (!reference) { context.addWarning({ message: 'unresolved reference <' + r.id + '>', element: r.element, property: r.property, value: r.id }); } if (property.isMany) { var collection = element.get(property.name), idx = collection.indexOf(r); // we replace an existing place holder (idx != -1) or // append to the collection instead if (idx === -1) { idx = collection.length; } if (!reference) { // remove unresolvable reference collection.splice(idx, 1); } else { // add or update reference in collection collection[idx] = reference; } } else { element.set(property.name, reference); } } } function handleClose() { stack.pop().handleEnd(); } var PREAMBLE_START_PATTERN = /^<\?xml /i; var ENCODING_PATTERN = / encoding="([^"]+)"/i; var UTF_8_PATTERN = /^utf-8$/i; function handleQuestion(question) { if (!PREAMBLE_START_PATTERN.test(question)) { return; } var match = ENCODING_PATTERN.exec(question); var encoding = match && match[1]; if (!encoding || UTF_8_PATTERN.test(encoding)) { return; } context.addWarning({ message: 'unsupported document encoding <' + encoding + '>, ' + 'falling back to UTF-8' }); } function handleOpen(node, getContext) { var handler = stack.peek(); try { stack.push(handler.handleNode(node)); } catch (err) { if (handleError(err, getContext, lax)) { stack.push(new NoopHandler()); } } } function handleCData(text, getContext) { try { stack.peek().handleText(text); } catch (err) { handleWarning(err, getContext); } } function handleText(text, getContext) { // strip whitespace only nodes, i.e. before // sections and in between tags if (!text.trim()) { return; } handleCData(text, getContext); } var uriMap = model.getPackages().reduce(function(uriMap, p) { uriMap[p.uri] = p.prefix; return uriMap; }, { 'http://www.w3.org/XML/1998/namespace': 'xml' // add default xml ns }); parser .ns(uriMap) .on('openTag', function(obj, decodeStr, selfClosing, getContext) { // gracefully handle unparsable attributes (attrs=false) var attrs = obj.attrs || {}; var decodedAttrs = Object.keys(attrs).reduce(function(d, key) { var value = decodeStr(attrs[key]); d[key] = value; return d; }, {}); var node = { name: obj.name, originalName: obj.originalName, attributes: decodedAttrs, ns: obj.ns }; handleOpen(node, getContext); }) .on('question', handleQuestion) .on('closeTag', handleClose) .on('cdata', handleCData) .on('text', function(text, decodeEntities, getContext) { handleText(decodeEntities(text), getContext); }) .on('error', handleError) .on('warn', handleWarning); // async XML parsing to make sure the execution environment // (node or brower) is kept responsive and that certain optimization // strategies can kick in. return new Promise(function(resolve, reject) { var err; try { parser.parse(xml); resolveReferences(); } catch (e) { err = e; } var rootElement = rootHandler.element; if (!err && !rootElement) { err = error('failed to parse document as <' + rootHandler.type.$descriptor.name + '>'); } var warnings = context.warnings; var references = context.references; var elementsById = context.elementsById; if (err) { err.warnings = warnings; return reject(err); } else { return resolve({ rootElement: rootElement, elementsById: elementsById, references: references, warnings: warnings }); } }); }; Reader.prototype.handler = function(name) { return new RootElementHandler(this.model, name); }; // helpers ////////////////////////// function createStack() { var stack = []; Object.defineProperty(stack, 'peek', { value: function() { return this[this.length - 1]; } }); return stack; } var XML_PREAMBLE = '\n'; var ESCAPE_ATTR_CHARS = /<|>|'|"|&|\n\r|\n/g; var ESCAPE_CHARS = /<|>|&/g; function Namespaces(parent) { var prefixMap = {}; var uriMap = {}; var used = {}; var wellknown = []; var custom = []; // API this.byUri = function(uri) { return uriMap[uri] || ( parent && parent.byUri(uri) ); }; this.add = function(ns, isWellknown) { uriMap[ns.uri] = ns; if (isWellknown) { wellknown.push(ns); } else { custom.push(ns); } this.mapPrefix(ns.prefix, ns.uri); }; this.uriByPrefix = function(prefix) { return prefixMap[prefix || 'xmlns']; }; this.mapPrefix = function(prefix, uri) { prefixMap[prefix || 'xmlns'] = uri; }; this.getNSKey = function(ns) { return (ns.prefix !== undefined) ? (ns.uri + '|' + ns.prefix) : ns.uri; }; this.logUsed = function(ns) { var uri = ns.uri; var nsKey = this.getNSKey(ns); used[nsKey] = this.byUri(uri); // Inform parent recursively about the usage of this NS if (parent) { parent.logUsed(ns); } }; this.getUsed = function(ns) { function isUsed(ns) { var nsKey = self.getNSKey(ns); return used[nsKey]; } var self = this; var allNs = [].concat(wellknown, custom); return allNs.filter(isUsed); }; } function lower(string) { return string.charAt(0).toLowerCase() + string.slice(1); } function nameToAlias(name, pkg) { if (hasLowerCaseAlias(pkg)) { return lower(name); } else { return name; } } function inherits(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); } function nsName(ns) { if (isString(ns)) { return ns; } else { return (ns.prefix ? ns.prefix + ':' : '') + ns.localName; } } function getNsAttrs(namespaces) { return namespaces.getUsed().filter(function(ns) { // do not serialize built in namespace return ns.prefix !== 'xml'; }).map(function(ns) { var name = 'xmlns' + (ns.prefix ? ':' + ns.prefix : ''); return { name: name, value: ns.uri }; }); } function getElementNs(ns, descriptor) { if (descriptor.isGeneric) { return assign({ localName: descriptor.ns.localName }, ns); } else { return assign({ localName: nameToAlias(descriptor.ns.localName, descriptor.$pkg) }, ns); } } function getPropertyNs(ns, descriptor) { return assign({ localName: descriptor.ns.localName }, ns); } function getSerializableProperties(element) { var descriptor = element.$descriptor; return filter(descriptor.properties, function(p) { var name = p.name; if (p.isVirtual) { return false; } // do not serialize defaults if (!has$1(element, name)) { return false; } var value = element[name]; // do not serialize default equals if (value === p.default) { return false; } // do not serialize null properties if (value === null) { return false; } return p.isMany ? value.length : true; }); } var ESCAPE_ATTR_MAP = { '\n': '#10', '\n\r': '#10', '"': '#34', '\'': '#39', '<': '#60', '>': '#62', '&': '#38' }; var ESCAPE_MAP = { '<': 'lt', '>': 'gt', '&': 'amp' }; function escape(str, charPattern, replaceMap) { // ensure we are handling strings here str = isString(str) ? str : '' + str; return str.replace(charPattern, function(s) { return '&' + replaceMap[s] + ';'; }); } /** * Escape a string attribute to not contain any bad values (line breaks, '"', ...) * * @param {String} str the string to escape * @return {String} the escaped string */ function escapeAttr(str) { return escape(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP); } function escapeBody(str) { return escape(str, ESCAPE_CHARS, ESCAPE_MAP); } function filterAttributes(props) { return filter(props, function(p) { return p.isAttr; }); } function filterContained(props) { return filter(props, function(p) { return !p.isAttr; }); } function ReferenceSerializer(tagName) { this.tagName = tagName; } ReferenceSerializer.prototype.build = function(element) { this.element = element; return this; }; ReferenceSerializer.prototype.serializeTo = function(writer) { writer .appendIndent() .append('<' + this.tagName + '>' + this.element.id + '') .appendNewLine(); }; function BodySerializer() {} BodySerializer.prototype.serializeValue = BodySerializer.prototype.serializeTo = function(writer) { writer.append( this.escape ? escapeBody(this.value) : this.value ); }; BodySerializer.prototype.build = function(prop, value) { this.value = value; if (prop.type === 'String' && value.search(ESCAPE_CHARS) !== -1) { this.escape = true; } return this; }; function ValueSerializer(tagName) { this.tagName = tagName; } inherits(ValueSerializer, BodySerializer); ValueSerializer.prototype.serializeTo = function(writer) { writer .appendIndent() .append('<' + this.tagName + '>'); this.serializeValue(writer); writer .append('') .appendNewLine(); }; function ElementSerializer(parent, propertyDescriptor) { this.body = []; this.attrs = []; this.parent = parent; this.propertyDescriptor = propertyDescriptor; } ElementSerializer.prototype.build = function(element) { this.element = element; var elementDescriptor = element.$descriptor, propertyDescriptor = this.propertyDescriptor; var otherAttrs, properties; var isGeneric = elementDescriptor.isGeneric; if (isGeneric) { otherAttrs = this.parseGeneric(element); } else { otherAttrs = this.parseNsAttributes(element); } if (propertyDescriptor) { this.ns = this.nsPropertyTagName(propertyDescriptor); } else { this.ns = this.nsTagName(elementDescriptor); } // compute tag name this.tagName = this.addTagName(this.ns); if (!isGeneric) { properties = getSerializableProperties(element); this.parseAttributes(filterAttributes(properties)); this.parseContainments(filterContained(properties)); } this.parseGenericAttributes(element, otherAttrs); return this; }; ElementSerializer.prototype.nsTagName = function(descriptor) { var effectiveNs = this.logNamespaceUsed(descriptor.ns); return getElementNs(effectiveNs, descriptor); }; ElementSerializer.prototype.nsPropertyTagName = function(descriptor) { var effectiveNs = this.logNamespaceUsed(descriptor.ns); return getPropertyNs(effectiveNs, descriptor); }; ElementSerializer.prototype.isLocalNs = function(ns) { return ns.uri === this.ns.uri; }; /** * Get the actual ns attribute name for the given element. * * @param {Object} element * @param {Boolean} [element.inherited=false] * * @return {Object} nsName */ ElementSerializer.prototype.nsAttributeName = function(element) { var ns; if (isString(element)) { ns = parseName(element); } else { ns = element.ns; } // return just local name for inherited attributes if (element.inherited) { return { localName: ns.localName }; } // parse + log effective ns var effectiveNs = this.logNamespaceUsed(ns); // LOG ACTUAL namespace use this.getNamespaces().logUsed(effectiveNs); // strip prefix if same namespace like parent if (this.isLocalNs(effectiveNs)) { return { localName: ns.localName }; } else { return assign({ localName: ns.localName }, effectiveNs); } }; ElementSerializer.prototype.parseGeneric = function(element) { var self = this, body = this.body; var attributes = []; forEach$1(element, function(val, key) { var nonNsAttr; if (key === '$body') { body.push(new BodySerializer().build({ type: 'String' }, val)); } else if (key === '$children') { forEach$1(val, function(child) { body.push(new ElementSerializer(self).build(child)); }); } else if (key.indexOf('$') !== 0) { nonNsAttr = self.parseNsAttribute(element, key, val); if (nonNsAttr) { attributes.push({ name: key, value: val }); } } }); return attributes; }; ElementSerializer.prototype.parseNsAttribute = function(element, name, value) { var model = element.$model; var nameNs = parseName(name); var ns; // parse xmlns:foo="http://foo.bar" if (nameNs.prefix === 'xmlns') { ns = { prefix: nameNs.localName, uri: value }; } // parse xmlns="http://foo.bar" if (!nameNs.prefix && nameNs.localName === 'xmlns') { ns = { uri: value }; } if (!ns) { return { name: name, value: value }; } if (model && model.getPackage(value)) { // register well known namespace this.logNamespace(ns, true, true); } else { // log custom namespace directly as used var actualNs = this.logNamespaceUsed(ns, true); this.getNamespaces().logUsed(actualNs); } }; /** * Parse namespaces and return a list of left over generic attributes * * @param {Object} element * @return {Array} */ ElementSerializer.prototype.parseNsAttributes = function(element, attrs) { var self = this; var genericAttrs = element.$attrs; var attributes = []; // parse namespace attributes first // and log them. push non namespace attributes to a list // and process them later forEach$1(genericAttrs, function(value, name) { var nonNsAttr = self.parseNsAttribute(element, name, value); if (nonNsAttr) { attributes.push(nonNsAttr); } }); return attributes; }; ElementSerializer.prototype.parseGenericAttributes = function(element, attributes) { var self = this; forEach$1(attributes, function(attr) { // do not serialize xsi:type attribute // it is set manually based on the actual implementation type if (attr.name === XSI_TYPE) { return; } try { self.addAttribute(self.nsAttributeName(attr.name), attr.value); } catch (e) { console.warn( 'missing namespace information for ', attr.name, '=', attr.value, 'on', element, e); } }); }; ElementSerializer.prototype.parseContainments = function(properties) { var self = this, body = this.body, element = this.element; forEach$1(properties, function(p) { var value = element.get(p.name), isReference = p.isReference, isMany = p.isMany; if (!isMany) { value = [ value ]; } if (p.isBody) { body.push(new BodySerializer().build(p, value[0])); } else if (isSimple(p.type)) { forEach$1(value, function(v) { body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v)); }); } else if (isReference) { forEach$1(value, function(v) { body.push(new ReferenceSerializer(self.addTagName(self.nsPropertyTagName(p))).build(v)); }); } else { // allow serialization via type // rather than element name var asType = serializeAsType(p), asProperty = serializeAsProperty(p); forEach$1(value, function(v) { var serializer; if (asType) { serializer = new TypeSerializer(self, p); } else if (asProperty) { serializer = new ElementSerializer(self, p); } else { serializer = new ElementSerializer(self); } body.push(serializer.build(v)); }); } }); }; ElementSerializer.prototype.getNamespaces = function(local) { var namespaces = this.namespaces, parent = this.parent, parentNamespaces; if (!namespaces) { parentNamespaces = parent && parent.getNamespaces(); if (local || !parentNamespaces) { this.namespaces = namespaces = new Namespaces(parentNamespaces); } else { namespaces = parentNamespaces; } } return namespaces; }; ElementSerializer.prototype.logNamespace = function(ns, wellknown, local) { var namespaces = this.getNamespaces(local); var nsUri = ns.uri, nsPrefix = ns.prefix; var existing = namespaces.byUri(nsUri); if (!existing || local) { namespaces.add(ns, wellknown); } namespaces.mapPrefix(nsPrefix, nsUri); return ns; }; ElementSerializer.prototype.logNamespaceUsed = function(ns, local) { var element = this.element, model = element.$model, namespaces = this.getNamespaces(local); // ns may be // // * prefix only // * prefix:uri // * localName only var prefix = ns.prefix, uri = ns.uri, newPrefix, idx, wellknownUri; // handle anonymous namespaces (elementForm=unqualified), cf. #23 if (!prefix && !uri) { return { localName: ns.localName }; } wellknownUri = DEFAULT_NS_MAP[prefix] || model && (model.getPackage(prefix) || {}).uri; uri = uri || wellknownUri || namespaces.uriByPrefix(prefix); if (!uri) { throw new Error('no namespace uri given for prefix <' + prefix + '>'); } ns = namespaces.byUri(uri); if (!ns) { newPrefix = prefix; idx = 1; // find a prefix that is not mapped yet while (namespaces.uriByPrefix(newPrefix)) { newPrefix = prefix + '_' + idx++; } ns = this.logNamespace({ prefix: newPrefix, uri: uri }, wellknownUri === uri); } if (prefix) { namespaces.mapPrefix(prefix, uri); } return ns; }; ElementSerializer.prototype.parseAttributes = function(properties) { var self = this, element = this.element; forEach$1(properties, function(p) { var value = element.get(p.name); if (p.isReference) { if (!p.isMany) { value = value.id; } else { var values = []; forEach$1(value, function(v) { values.push(v.id); }); // IDREFS is a whitespace-separated list of references. value = values.join(' '); } } self.addAttribute(self.nsAttributeName(p), value); }); }; ElementSerializer.prototype.addTagName = function(nsTagName) { var actualNs = this.logNamespaceUsed(nsTagName); this.getNamespaces().logUsed(actualNs); return nsName(nsTagName); }; ElementSerializer.prototype.addAttribute = function(name, value) { var attrs = this.attrs; if (isString(value)) { value = escapeAttr(value); } // de-duplicate attributes // https://github.com/bpmn-io/moddle-xml/issues/66 var idx = findIndex(attrs, function(element) { return ( element.name.localName === name.localName && element.name.uri === name.uri && element.name.prefix === name.prefix ); }); var attr = { name: name, value: value }; if (idx !== -1) { attrs.splice(idx, 1, attr); } else { attrs.push(attr); } }; ElementSerializer.prototype.serializeAttributes = function(writer) { var attrs = this.attrs, namespaces = this.namespaces; if (namespaces) { attrs = getNsAttrs(namespaces).concat(attrs); } forEach$1(attrs, function(a) { writer .append(' ') .append(nsName(a.name)).append('="').append(a.value).append('"'); }); }; ElementSerializer.prototype.serializeTo = function(writer) { var firstBody = this.body[0], indent = firstBody && firstBody.constructor !== BodySerializer; writer .appendIndent() .append('<' + this.tagName); this.serializeAttributes(writer); writer.append(firstBody ? '>' : ' />'); if (firstBody) { if (indent) { writer .appendNewLine() .indent(); } forEach$1(this.body, function(b) { b.serializeTo(writer); }); if (indent) { writer .unindent() .appendIndent(); } writer.append(''); } writer.appendNewLine(); }; /** * A serializer for types that handles serialization of data types */ function TypeSerializer(parent, propertyDescriptor) { ElementSerializer.call(this, parent, propertyDescriptor); } inherits(TypeSerializer, ElementSerializer); TypeSerializer.prototype.parseNsAttributes = function(element) { // extracted attributes var attributes = ElementSerializer.prototype.parseNsAttributes.call(this, element); var descriptor = element.$descriptor; // only serialize xsi:type if necessary if (descriptor.name === this.propertyDescriptor.type) { return attributes; } var typeNs = this.typeNs = this.nsTagName(descriptor); this.getNamespaces().logUsed(this.typeNs); // add xsi:type attribute to represent the elements // actual type var pkg = element.$model.getPackage(typeNs.uri), typePrefix = (pkg.xml && pkg.xml.typePrefix) || ''; this.addAttribute( this.nsAttributeName(XSI_TYPE), (typeNs.prefix ? typeNs.prefix + ':' : '') + typePrefix + descriptor.ns.localName ); return attributes; }; TypeSerializer.prototype.isLocalNs = function(ns) { return ns.uri === (this.typeNs || this.ns).uri; }; function SavingWriter() { this.value = ''; this.write = function(str) { this.value += str; }; } function FormatingWriter(out, format) { var indent = ['']; this.append = function(str) { out.write(str); return this; }; this.appendNewLine = function() { if (format) { out.write('\n'); } return this; }; this.appendIndent = function() { if (format) { out.write(indent.join(' ')); } return this; }; this.indent = function() { indent.push(''); return this; }; this.unindent = function() { indent.pop(); return this; }; } /** * A writer for meta-model backed document trees * * @param {Object} options output options to pass into the writer */ function Writer(options) { options = assign({ format: false, preamble: true }, options || {}); function toXML(tree, writer) { var internalWriter = writer || new SavingWriter(); var formatingWriter = new FormatingWriter(internalWriter, options.format); if (options.preamble) { formatingWriter.append(XML_PREAMBLE); } new ElementSerializer().build(tree).serializeTo(formatingWriter); if (!writer) { return internalWriter.value; } } return { toXML: toXML }; } /** * A sub class of {@link Moddle} with support for import and export of BPMN 2.0 xml files. * * @class BpmnModdle * @extends Moddle * * @param {Object|Array} packages to use for instantiating the model * @param {Object} [options] additional options to pass over */ function BpmnModdle(packages, options) { Moddle.call(this, packages, options); } BpmnModdle.prototype = Object.create(Moddle.prototype); /** * The fromXML result. * * @typedef {Object} ParseResult * * @property {ModdleElement} rootElement * @property {Array} references * @property {Array} warnings * @property {Object} elementsById - a mapping containing each ID -> ModdleElement */ /** * The fromXML error. * * @typedef {Error} ParseError * * @property {Array} warnings */ /** * Instantiates a BPMN model tree from a given xml string. * * @param {String} xmlStr * @param {String} [typeName='bpmn:Definitions'] name of the root element * @param {Object} [options] options to pass to the underlying reader * * @returns {Promise} */ BpmnModdle.prototype.fromXML = function(xmlStr, typeName, options) { if (!isString(typeName)) { options = typeName; typeName = 'bpmn:Definitions'; } var reader = new Reader(assign({ model: this, lax: true }, options)); var rootHandler = reader.handler(typeName); return reader.fromXML(xmlStr, rootHandler); }; /** * The toXML result. * * @typedef {Object} SerializationResult * * @property {String} xml */ /** * Serializes a BPMN 2.0 object tree to XML. * * @param {String} element the root element, typically an instance of `bpmn:Definitions` * @param {Object} [options] to pass to the underlying writer * * @returns {Promise} */ BpmnModdle.prototype.toXML = function(element, options) { var writer = new Writer(options); return new Promise(function(resolve, reject) { try { var result = writer.toXML(element); return resolve({ xml: result }); } catch (err) { return reject(err); } }); }; var name$5 = "BPMN20"; var uri$5 = "http://www.omg.org/spec/BPMN/20100524/MODEL"; var prefix$5 = "bpmn"; var associations$5 = [ ]; var types$5 = [ { name: "Interface", superClass: [ "RootElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "operations", type: "Operation", isMany: true }, { name: "implementationRef", isAttr: true, type: "String" } ] }, { name: "Operation", superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "inMessageRef", type: "Message", isReference: true }, { name: "outMessageRef", type: "Message", isReference: true }, { name: "errorRef", type: "Error", isMany: true, isReference: true }, { name: "implementationRef", isAttr: true, type: "String" } ] }, { name: "EndPoint", superClass: [ "RootElement" ] }, { name: "Auditing", superClass: [ "BaseElement" ] }, { name: "GlobalTask", superClass: [ "CallableElement" ], properties: [ { name: "resources", type: "ResourceRole", isMany: true } ] }, { name: "Monitoring", superClass: [ "BaseElement" ] }, { name: "Performer", superClass: [ "ResourceRole" ] }, { name: "Process", superClass: [ "FlowElementsContainer", "CallableElement" ], properties: [ { name: "processType", type: "ProcessType", isAttr: true }, { name: "isClosed", isAttr: true, type: "Boolean" }, { name: "auditing", type: "Auditing" }, { name: "monitoring", type: "Monitoring" }, { name: "properties", type: "Property", isMany: true }, { name: "laneSets", isMany: true, replaces: "FlowElementsContainer#laneSets", type: "LaneSet" }, { name: "flowElements", isMany: true, replaces: "FlowElementsContainer#flowElements", type: "FlowElement" }, { name: "artifacts", type: "Artifact", isMany: true }, { name: "resources", type: "ResourceRole", isMany: true }, { name: "correlationSubscriptions", type: "CorrelationSubscription", isMany: true }, { name: "supports", type: "Process", isMany: true, isReference: true }, { name: "definitionalCollaborationRef", type: "Collaboration", isAttr: true, isReference: true }, { name: "isExecutable", isAttr: true, type: "Boolean" } ] }, { name: "LaneSet", superClass: [ "BaseElement" ], properties: [ { name: "lanes", type: "Lane", isMany: true }, { name: "name", isAttr: true, type: "String" } ] }, { name: "Lane", superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "partitionElementRef", type: "BaseElement", isAttr: true, isReference: true }, { name: "partitionElement", type: "BaseElement" }, { name: "flowNodeRef", type: "FlowNode", isMany: true, isReference: true }, { name: "childLaneSet", type: "LaneSet", xml: { serialize: "xsi:type" } } ] }, { name: "GlobalManualTask", superClass: [ "GlobalTask" ] }, { name: "ManualTask", superClass: [ "Task" ] }, { name: "UserTask", superClass: [ "Task" ], properties: [ { name: "renderings", type: "Rendering", isMany: true }, { name: "implementation", isAttr: true, type: "String" } ] }, { name: "Rendering", superClass: [ "BaseElement" ] }, { name: "HumanPerformer", superClass: [ "Performer" ] }, { name: "PotentialOwner", superClass: [ "HumanPerformer" ] }, { name: "GlobalUserTask", superClass: [ "GlobalTask" ], properties: [ { name: "implementation", isAttr: true, type: "String" }, { name: "renderings", type: "Rendering", isMany: true } ] }, { name: "Gateway", isAbstract: true, superClass: [ "FlowNode" ], properties: [ { name: "gatewayDirection", type: "GatewayDirection", "default": "Unspecified", isAttr: true } ] }, { name: "EventBasedGateway", superClass: [ "Gateway" ], properties: [ { name: "instantiate", "default": false, isAttr: true, type: "Boolean" }, { name: "eventGatewayType", type: "EventBasedGatewayType", isAttr: true, "default": "Exclusive" } ] }, { name: "ComplexGateway", superClass: [ "Gateway" ], properties: [ { name: "activationCondition", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "default", type: "SequenceFlow", isAttr: true, isReference: true } ] }, { name: "ExclusiveGateway", superClass: [ "Gateway" ], properties: [ { name: "default", type: "SequenceFlow", isAttr: true, isReference: true } ] }, { name: "InclusiveGateway", superClass: [ "Gateway" ], properties: [ { name: "default", type: "SequenceFlow", isAttr: true, isReference: true } ] }, { name: "ParallelGateway", superClass: [ "Gateway" ] }, { name: "RootElement", isAbstract: true, superClass: [ "BaseElement" ] }, { name: "Relationship", superClass: [ "BaseElement" ], properties: [ { name: "type", isAttr: true, type: "String" }, { name: "direction", type: "RelationshipDirection", isAttr: true }, { name: "source", isMany: true, isReference: true, type: "Element" }, { name: "target", isMany: true, isReference: true, type: "Element" } ] }, { name: "BaseElement", isAbstract: true, properties: [ { name: "id", isAttr: true, type: "String", isId: true }, { name: "documentation", type: "Documentation", isMany: true }, { name: "extensionDefinitions", type: "ExtensionDefinition", isMany: true, isReference: true }, { name: "extensionElements", type: "ExtensionElements" } ] }, { name: "Extension", properties: [ { name: "mustUnderstand", "default": false, isAttr: true, type: "Boolean" }, { name: "definition", type: "ExtensionDefinition", isAttr: true, isReference: true } ] }, { name: "ExtensionDefinition", properties: [ { name: "name", isAttr: true, type: "String" }, { name: "extensionAttributeDefinitions", type: "ExtensionAttributeDefinition", isMany: true } ] }, { name: "ExtensionAttributeDefinition", properties: [ { name: "name", isAttr: true, type: "String" }, { name: "type", isAttr: true, type: "String" }, { name: "isReference", "default": false, isAttr: true, type: "Boolean" }, { name: "extensionDefinition", type: "ExtensionDefinition", isAttr: true, isReference: true } ] }, { name: "ExtensionElements", properties: [ { name: "valueRef", isAttr: true, isReference: true, type: "Element" }, { name: "values", type: "Element", isMany: true }, { name: "extensionAttributeDefinition", type: "ExtensionAttributeDefinition", isAttr: true, isReference: true } ] }, { name: "Documentation", superClass: [ "BaseElement" ], properties: [ { name: "text", type: "String", isBody: true }, { name: "textFormat", "default": "text/plain", isAttr: true, type: "String" } ] }, { name: "Event", isAbstract: true, superClass: [ "FlowNode", "InteractionNode" ], properties: [ { name: "properties", type: "Property", isMany: true } ] }, { name: "IntermediateCatchEvent", superClass: [ "CatchEvent" ] }, { name: "IntermediateThrowEvent", superClass: [ "ThrowEvent" ] }, { name: "EndEvent", superClass: [ "ThrowEvent" ] }, { name: "StartEvent", superClass: [ "CatchEvent" ], properties: [ { name: "isInterrupting", "default": true, isAttr: true, type: "Boolean" } ] }, { name: "ThrowEvent", isAbstract: true, superClass: [ "Event" ], properties: [ { name: "dataInputs", type: "DataInput", isMany: true }, { name: "dataInputAssociations", type: "DataInputAssociation", isMany: true }, { name: "inputSet", type: "InputSet" }, { name: "eventDefinitions", type: "EventDefinition", isMany: true }, { name: "eventDefinitionRef", type: "EventDefinition", isMany: true, isReference: true } ] }, { name: "CatchEvent", isAbstract: true, superClass: [ "Event" ], properties: [ { name: "parallelMultiple", isAttr: true, type: "Boolean", "default": false }, { name: "dataOutputs", type: "DataOutput", isMany: true }, { name: "dataOutputAssociations", type: "DataOutputAssociation", isMany: true }, { name: "outputSet", type: "OutputSet" }, { name: "eventDefinitions", type: "EventDefinition", isMany: true }, { name: "eventDefinitionRef", type: "EventDefinition", isMany: true, isReference: true } ] }, { name: "BoundaryEvent", superClass: [ "CatchEvent" ], properties: [ { name: "cancelActivity", "default": true, isAttr: true, type: "Boolean" }, { name: "attachedToRef", type: "Activity", isAttr: true, isReference: true } ] }, { name: "EventDefinition", isAbstract: true, superClass: [ "RootElement" ] }, { name: "CancelEventDefinition", superClass: [ "EventDefinition" ] }, { name: "ErrorEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "errorRef", type: "Error", isAttr: true, isReference: true } ] }, { name: "TerminateEventDefinition", superClass: [ "EventDefinition" ] }, { name: "EscalationEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "escalationRef", type: "Escalation", isAttr: true, isReference: true } ] }, { name: "Escalation", properties: [ { name: "structureRef", type: "ItemDefinition", isAttr: true, isReference: true }, { name: "name", isAttr: true, type: "String" }, { name: "escalationCode", isAttr: true, type: "String" } ], superClass: [ "RootElement" ] }, { name: "CompensateEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "waitForCompletion", isAttr: true, type: "Boolean", "default": true }, { name: "activityRef", type: "Activity", isAttr: true, isReference: true } ] }, { name: "TimerEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "timeDate", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "timeCycle", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "timeDuration", type: "Expression", xml: { serialize: "xsi:type" } } ] }, { name: "LinkEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "target", type: "LinkEventDefinition", isAttr: true, isReference: true }, { name: "source", type: "LinkEventDefinition", isMany: true, isReference: true } ] }, { name: "MessageEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "messageRef", type: "Message", isAttr: true, isReference: true }, { name: "operationRef", type: "Operation", isAttr: true, isReference: true } ] }, { name: "ConditionalEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "condition", type: "Expression", xml: { serialize: "xsi:type" } } ] }, { name: "SignalEventDefinition", superClass: [ "EventDefinition" ], properties: [ { name: "signalRef", type: "Signal", isAttr: true, isReference: true } ] }, { name: "Signal", superClass: [ "RootElement" ], properties: [ { name: "structureRef", type: "ItemDefinition", isAttr: true, isReference: true }, { name: "name", isAttr: true, type: "String" } ] }, { name: "ImplicitThrowEvent", superClass: [ "ThrowEvent" ] }, { name: "DataState", superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" } ] }, { name: "ItemAwareElement", superClass: [ "BaseElement" ], properties: [ { name: "itemSubjectRef", type: "ItemDefinition", isAttr: true, isReference: true }, { name: "dataState", type: "DataState" } ] }, { name: "DataAssociation", superClass: [ "BaseElement" ], properties: [ { name: "sourceRef", type: "ItemAwareElement", isMany: true, isReference: true }, { name: "targetRef", type: "ItemAwareElement", isReference: true }, { name: "transformation", type: "FormalExpression", xml: { serialize: "property" } }, { name: "assignment", type: "Assignment", isMany: true } ] }, { name: "DataInput", superClass: [ "ItemAwareElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "isCollection", "default": false, isAttr: true, type: "Boolean" }, { name: "inputSetRef", type: "InputSet", isMany: true, isVirtual: true, isReference: true }, { name: "inputSetWithOptional", type: "InputSet", isMany: true, isVirtual: true, isReference: true }, { name: "inputSetWithWhileExecuting", type: "InputSet", isMany: true, isVirtual: true, isReference: true } ] }, { name: "DataOutput", superClass: [ "ItemAwareElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "isCollection", "default": false, isAttr: true, type: "Boolean" }, { name: "outputSetRef", type: "OutputSet", isMany: true, isVirtual: true, isReference: true }, { name: "outputSetWithOptional", type: "OutputSet", isMany: true, isVirtual: true, isReference: true }, { name: "outputSetWithWhileExecuting", type: "OutputSet", isMany: true, isVirtual: true, isReference: true } ] }, { name: "InputSet", superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "dataInputRefs", type: "DataInput", isMany: true, isReference: true }, { name: "optionalInputRefs", type: "DataInput", isMany: true, isReference: true }, { name: "whileExecutingInputRefs", type: "DataInput", isMany: true, isReference: true }, { name: "outputSetRefs", type: "OutputSet", isMany: true, isReference: true } ] }, { name: "OutputSet", superClass: [ "BaseElement" ], properties: [ { name: "dataOutputRefs", type: "DataOutput", isMany: true, isReference: true }, { name: "name", isAttr: true, type: "String" }, { name: "inputSetRefs", type: "InputSet", isMany: true, isReference: true }, { name: "optionalOutputRefs", type: "DataOutput", isMany: true, isReference: true }, { name: "whileExecutingOutputRefs", type: "DataOutput", isMany: true, isReference: true } ] }, { name: "Property", superClass: [ "ItemAwareElement" ], properties: [ { name: "name", isAttr: true, type: "String" } ] }, { name: "DataInputAssociation", superClass: [ "DataAssociation" ] }, { name: "DataOutputAssociation", superClass: [ "DataAssociation" ] }, { name: "InputOutputSpecification", superClass: [ "BaseElement" ], properties: [ { name: "dataInputs", type: "DataInput", isMany: true }, { name: "dataOutputs", type: "DataOutput", isMany: true }, { name: "inputSets", type: "InputSet", isMany: true }, { name: "outputSets", type: "OutputSet", isMany: true } ] }, { name: "DataObject", superClass: [ "FlowElement", "ItemAwareElement" ], properties: [ { name: "isCollection", "default": false, isAttr: true, type: "Boolean" } ] }, { name: "InputOutputBinding", properties: [ { name: "inputDataRef", type: "InputSet", isAttr: true, isReference: true }, { name: "outputDataRef", type: "OutputSet", isAttr: true, isReference: true }, { name: "operationRef", type: "Operation", isAttr: true, isReference: true } ] }, { name: "Assignment", superClass: [ "BaseElement" ], properties: [ { name: "from", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "to", type: "Expression", xml: { serialize: "xsi:type" } } ] }, { name: "DataStore", superClass: [ "RootElement", "ItemAwareElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "capacity", isAttr: true, type: "Integer" }, { name: "isUnlimited", "default": true, isAttr: true, type: "Boolean" } ] }, { name: "DataStoreReference", superClass: [ "ItemAwareElement", "FlowElement" ], properties: [ { name: "dataStoreRef", type: "DataStore", isAttr: true, isReference: true } ] }, { name: "DataObjectReference", superClass: [ "ItemAwareElement", "FlowElement" ], properties: [ { name: "dataObjectRef", type: "DataObject", isAttr: true, isReference: true } ] }, { name: "ConversationLink", superClass: [ "BaseElement" ], properties: [ { name: "sourceRef", type: "InteractionNode", isAttr: true, isReference: true }, { name: "targetRef", type: "InteractionNode", isAttr: true, isReference: true }, { name: "name", isAttr: true, type: "String" } ] }, { name: "ConversationAssociation", superClass: [ "BaseElement" ], properties: [ { name: "innerConversationNodeRef", type: "ConversationNode", isAttr: true, isReference: true }, { name: "outerConversationNodeRef", type: "ConversationNode", isAttr: true, isReference: true } ] }, { name: "CallConversation", superClass: [ "ConversationNode" ], properties: [ { name: "calledCollaborationRef", type: "Collaboration", isAttr: true, isReference: true }, { name: "participantAssociations", type: "ParticipantAssociation", isMany: true } ] }, { name: "Conversation", superClass: [ "ConversationNode" ] }, { name: "SubConversation", superClass: [ "ConversationNode" ], properties: [ { name: "conversationNodes", type: "ConversationNode", isMany: true } ] }, { name: "ConversationNode", isAbstract: true, superClass: [ "InteractionNode", "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "participantRef", type: "Participant", isMany: true, isReference: true }, { name: "messageFlowRefs", type: "MessageFlow", isMany: true, isReference: true }, { name: "correlationKeys", type: "CorrelationKey", isMany: true } ] }, { name: "GlobalConversation", superClass: [ "Collaboration" ] }, { name: "PartnerEntity", superClass: [ "RootElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "participantRef", type: "Participant", isMany: true, isReference: true } ] }, { name: "PartnerRole", superClass: [ "RootElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "participantRef", type: "Participant", isMany: true, isReference: true } ] }, { name: "CorrelationProperty", superClass: [ "RootElement" ], properties: [ { name: "correlationPropertyRetrievalExpression", type: "CorrelationPropertyRetrievalExpression", isMany: true }, { name: "name", isAttr: true, type: "String" }, { name: "type", type: "ItemDefinition", isAttr: true, isReference: true } ] }, { name: "Error", superClass: [ "RootElement" ], properties: [ { name: "structureRef", type: "ItemDefinition", isAttr: true, isReference: true }, { name: "name", isAttr: true, type: "String" }, { name: "errorCode", isAttr: true, type: "String" } ] }, { name: "CorrelationKey", superClass: [ "BaseElement" ], properties: [ { name: "correlationPropertyRef", type: "CorrelationProperty", isMany: true, isReference: true }, { name: "name", isAttr: true, type: "String" } ] }, { name: "Expression", superClass: [ "BaseElement" ], isAbstract: false, properties: [ { name: "body", isBody: true, type: "String" } ] }, { name: "FormalExpression", superClass: [ "Expression" ], properties: [ { name: "language", isAttr: true, type: "String" }, { name: "evaluatesToTypeRef", type: "ItemDefinition", isAttr: true, isReference: true } ] }, { name: "Message", superClass: [ "RootElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "itemRef", type: "ItemDefinition", isAttr: true, isReference: true } ] }, { name: "ItemDefinition", superClass: [ "RootElement" ], properties: [ { name: "itemKind", type: "ItemKind", isAttr: true }, { name: "structureRef", isAttr: true, type: "String" }, { name: "isCollection", "default": false, isAttr: true, type: "Boolean" }, { name: "import", type: "Import", isAttr: true, isReference: true } ] }, { name: "FlowElement", isAbstract: true, superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "auditing", type: "Auditing" }, { name: "monitoring", type: "Monitoring" }, { name: "categoryValueRef", type: "CategoryValue", isMany: true, isReference: true } ] }, { name: "SequenceFlow", superClass: [ "FlowElement" ], properties: [ { name: "isImmediate", isAttr: true, type: "Boolean" }, { name: "conditionExpression", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "sourceRef", type: "FlowNode", isAttr: true, isReference: true }, { name: "targetRef", type: "FlowNode", isAttr: true, isReference: true } ] }, { name: "FlowElementsContainer", isAbstract: true, superClass: [ "BaseElement" ], properties: [ { name: "laneSets", type: "LaneSet", isMany: true }, { name: "flowElements", type: "FlowElement", isMany: true } ] }, { name: "CallableElement", isAbstract: true, superClass: [ "RootElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "ioSpecification", type: "InputOutputSpecification", xml: { serialize: "property" } }, { name: "supportedInterfaceRef", type: "Interface", isMany: true, isReference: true }, { name: "ioBinding", type: "InputOutputBinding", isMany: true, xml: { serialize: "property" } } ] }, { name: "FlowNode", isAbstract: true, superClass: [ "FlowElement" ], properties: [ { name: "incoming", type: "SequenceFlow", isMany: true, isReference: true }, { name: "outgoing", type: "SequenceFlow", isMany: true, isReference: true }, { name: "lanes", type: "Lane", isMany: true, isVirtual: true, isReference: true } ] }, { name: "CorrelationPropertyRetrievalExpression", superClass: [ "BaseElement" ], properties: [ { name: "messagePath", type: "FormalExpression" }, { name: "messageRef", type: "Message", isAttr: true, isReference: true } ] }, { name: "CorrelationPropertyBinding", superClass: [ "BaseElement" ], properties: [ { name: "dataPath", type: "FormalExpression" }, { name: "correlationPropertyRef", type: "CorrelationProperty", isAttr: true, isReference: true } ] }, { name: "Resource", superClass: [ "RootElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "resourceParameters", type: "ResourceParameter", isMany: true } ] }, { name: "ResourceParameter", superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "isRequired", isAttr: true, type: "Boolean" }, { name: "type", type: "ItemDefinition", isAttr: true, isReference: true } ] }, { name: "CorrelationSubscription", superClass: [ "BaseElement" ], properties: [ { name: "correlationKeyRef", type: "CorrelationKey", isAttr: true, isReference: true }, { name: "correlationPropertyBinding", type: "CorrelationPropertyBinding", isMany: true } ] }, { name: "MessageFlow", superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "sourceRef", type: "InteractionNode", isAttr: true, isReference: true }, { name: "targetRef", type: "InteractionNode", isAttr: true, isReference: true }, { name: "messageRef", type: "Message", isAttr: true, isReference: true } ] }, { name: "MessageFlowAssociation", superClass: [ "BaseElement" ], properties: [ { name: "innerMessageFlowRef", type: "MessageFlow", isAttr: true, isReference: true }, { name: "outerMessageFlowRef", type: "MessageFlow", isAttr: true, isReference: true } ] }, { name: "InteractionNode", isAbstract: true, properties: [ { name: "incomingConversationLinks", type: "ConversationLink", isMany: true, isVirtual: true, isReference: true }, { name: "outgoingConversationLinks", type: "ConversationLink", isMany: true, isVirtual: true, isReference: true } ] }, { name: "Participant", superClass: [ "InteractionNode", "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "interfaceRef", type: "Interface", isMany: true, isReference: true }, { name: "participantMultiplicity", type: "ParticipantMultiplicity" }, { name: "endPointRefs", type: "EndPoint", isMany: true, isReference: true }, { name: "processRef", type: "Process", isAttr: true, isReference: true } ] }, { name: "ParticipantAssociation", superClass: [ "BaseElement" ], properties: [ { name: "innerParticipantRef", type: "Participant", isAttr: true, isReference: true }, { name: "outerParticipantRef", type: "Participant", isAttr: true, isReference: true } ] }, { name: "ParticipantMultiplicity", properties: [ { name: "minimum", "default": 0, isAttr: true, type: "Integer" }, { name: "maximum", "default": 1, isAttr: true, type: "Integer" } ], superClass: [ "BaseElement" ] }, { name: "Collaboration", superClass: [ "RootElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "isClosed", isAttr: true, type: "Boolean" }, { name: "participants", type: "Participant", isMany: true }, { name: "messageFlows", type: "MessageFlow", isMany: true }, { name: "artifacts", type: "Artifact", isMany: true }, { name: "conversations", type: "ConversationNode", isMany: true }, { name: "conversationAssociations", type: "ConversationAssociation" }, { name: "participantAssociations", type: "ParticipantAssociation", isMany: true }, { name: "messageFlowAssociations", type: "MessageFlowAssociation", isMany: true }, { name: "correlationKeys", type: "CorrelationKey", isMany: true }, { name: "choreographyRef", type: "Choreography", isMany: true, isReference: true }, { name: "conversationLinks", type: "ConversationLink", isMany: true } ] }, { name: "ChoreographyActivity", isAbstract: true, superClass: [ "FlowNode" ], properties: [ { name: "participantRef", type: "Participant", isMany: true, isReference: true }, { name: "initiatingParticipantRef", type: "Participant", isAttr: true, isReference: true }, { name: "correlationKeys", type: "CorrelationKey", isMany: true }, { name: "loopType", type: "ChoreographyLoopType", "default": "None", isAttr: true } ] }, { name: "CallChoreography", superClass: [ "ChoreographyActivity" ], properties: [ { name: "calledChoreographyRef", type: "Choreography", isAttr: true, isReference: true }, { name: "participantAssociations", type: "ParticipantAssociation", isMany: true } ] }, { name: "SubChoreography", superClass: [ "ChoreographyActivity", "FlowElementsContainer" ], properties: [ { name: "artifacts", type: "Artifact", isMany: true } ] }, { name: "ChoreographyTask", superClass: [ "ChoreographyActivity" ], properties: [ { name: "messageFlowRef", type: "MessageFlow", isMany: true, isReference: true } ] }, { name: "Choreography", superClass: [ "Collaboration", "FlowElementsContainer" ] }, { name: "GlobalChoreographyTask", superClass: [ "Choreography" ], properties: [ { name: "initiatingParticipantRef", type: "Participant", isAttr: true, isReference: true } ] }, { name: "TextAnnotation", superClass: [ "Artifact" ], properties: [ { name: "text", type: "String" }, { name: "textFormat", "default": "text/plain", isAttr: true, type: "String" } ] }, { name: "Group", superClass: [ "Artifact" ], properties: [ { name: "categoryValueRef", type: "CategoryValue", isAttr: true, isReference: true } ] }, { name: "Association", superClass: [ "Artifact" ], properties: [ { name: "associationDirection", type: "AssociationDirection", isAttr: true }, { name: "sourceRef", type: "BaseElement", isAttr: true, isReference: true }, { name: "targetRef", type: "BaseElement", isAttr: true, isReference: true } ] }, { name: "Category", superClass: [ "RootElement" ], properties: [ { name: "categoryValue", type: "CategoryValue", isMany: true }, { name: "name", isAttr: true, type: "String" } ] }, { name: "Artifact", isAbstract: true, superClass: [ "BaseElement" ] }, { name: "CategoryValue", superClass: [ "BaseElement" ], properties: [ { name: "categorizedFlowElements", type: "FlowElement", isMany: true, isVirtual: true, isReference: true }, { name: "value", isAttr: true, type: "String" } ] }, { name: "Activity", isAbstract: true, superClass: [ "FlowNode" ], properties: [ { name: "isForCompensation", "default": false, isAttr: true, type: "Boolean" }, { name: "default", type: "SequenceFlow", isAttr: true, isReference: true }, { name: "ioSpecification", type: "InputOutputSpecification", xml: { serialize: "property" } }, { name: "boundaryEventRefs", type: "BoundaryEvent", isMany: true, isReference: true }, { name: "properties", type: "Property", isMany: true }, { name: "dataInputAssociations", type: "DataInputAssociation", isMany: true }, { name: "dataOutputAssociations", type: "DataOutputAssociation", isMany: true }, { name: "startQuantity", "default": 1, isAttr: true, type: "Integer" }, { name: "resources", type: "ResourceRole", isMany: true }, { name: "completionQuantity", "default": 1, isAttr: true, type: "Integer" }, { name: "loopCharacteristics", type: "LoopCharacteristics" } ] }, { name: "ServiceTask", superClass: [ "Task" ], properties: [ { name: "implementation", isAttr: true, type: "String" }, { name: "operationRef", type: "Operation", isAttr: true, isReference: true } ] }, { name: "SubProcess", superClass: [ "Activity", "FlowElementsContainer", "InteractionNode" ], properties: [ { name: "triggeredByEvent", "default": false, isAttr: true, type: "Boolean" }, { name: "artifacts", type: "Artifact", isMany: true } ] }, { name: "LoopCharacteristics", isAbstract: true, superClass: [ "BaseElement" ] }, { name: "MultiInstanceLoopCharacteristics", superClass: [ "LoopCharacteristics" ], properties: [ { name: "isSequential", "default": false, isAttr: true, type: "Boolean" }, { name: "behavior", type: "MultiInstanceBehavior", "default": "All", isAttr: true }, { name: "loopCardinality", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "loopDataInputRef", type: "ItemAwareElement", isReference: true }, { name: "loopDataOutputRef", type: "ItemAwareElement", isReference: true }, { name: "inputDataItem", type: "DataInput", xml: { serialize: "property" } }, { name: "outputDataItem", type: "DataOutput", xml: { serialize: "property" } }, { name: "complexBehaviorDefinition", type: "ComplexBehaviorDefinition", isMany: true }, { name: "completionCondition", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "oneBehaviorEventRef", type: "EventDefinition", isAttr: true, isReference: true }, { name: "noneBehaviorEventRef", type: "EventDefinition", isAttr: true, isReference: true } ] }, { name: "StandardLoopCharacteristics", superClass: [ "LoopCharacteristics" ], properties: [ { name: "testBefore", "default": false, isAttr: true, type: "Boolean" }, { name: "loopCondition", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "loopMaximum", type: "Integer", isAttr: true } ] }, { name: "CallActivity", superClass: [ "Activity", "InteractionNode" ], properties: [ { name: "calledElement", type: "String", isAttr: true } ] }, { name: "Task", superClass: [ "Activity", "InteractionNode" ] }, { name: "SendTask", superClass: [ "Task" ], properties: [ { name: "implementation", isAttr: true, type: "String" }, { name: "operationRef", type: "Operation", isAttr: true, isReference: true }, { name: "messageRef", type: "Message", isAttr: true, isReference: true } ] }, { name: "ReceiveTask", superClass: [ "Task" ], properties: [ { name: "implementation", isAttr: true, type: "String" }, { name: "instantiate", "default": false, isAttr: true, type: "Boolean" }, { name: "operationRef", type: "Operation", isAttr: true, isReference: true }, { name: "messageRef", type: "Message", isAttr: true, isReference: true } ] }, { name: "ScriptTask", superClass: [ "Task" ], properties: [ { name: "scriptFormat", isAttr: true, type: "String" }, { name: "script", type: "String" } ] }, { name: "BusinessRuleTask", superClass: [ "Task" ], properties: [ { name: "implementation", isAttr: true, type: "String" } ] }, { name: "AdHocSubProcess", superClass: [ "SubProcess" ], properties: [ { name: "completionCondition", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "ordering", type: "AdHocOrdering", isAttr: true }, { name: "cancelRemainingInstances", "default": true, isAttr: true, type: "Boolean" } ] }, { name: "Transaction", superClass: [ "SubProcess" ], properties: [ { name: "protocol", isAttr: true, type: "String" }, { name: "method", isAttr: true, type: "String" } ] }, { name: "GlobalScriptTask", superClass: [ "GlobalTask" ], properties: [ { name: "scriptLanguage", isAttr: true, type: "String" }, { name: "script", isAttr: true, type: "String" } ] }, { name: "GlobalBusinessRuleTask", superClass: [ "GlobalTask" ], properties: [ { name: "implementation", isAttr: true, type: "String" } ] }, { name: "ComplexBehaviorDefinition", superClass: [ "BaseElement" ], properties: [ { name: "condition", type: "FormalExpression" }, { name: "event", type: "ImplicitThrowEvent" } ] }, { name: "ResourceRole", superClass: [ "BaseElement" ], properties: [ { name: "resourceRef", type: "Resource", isReference: true }, { name: "resourceParameterBindings", type: "ResourceParameterBinding", isMany: true }, { name: "resourceAssignmentExpression", type: "ResourceAssignmentExpression" }, { name: "name", isAttr: true, type: "String" } ] }, { name: "ResourceParameterBinding", properties: [ { name: "expression", type: "Expression", xml: { serialize: "xsi:type" } }, { name: "parameterRef", type: "ResourceParameter", isAttr: true, isReference: true } ], superClass: [ "BaseElement" ] }, { name: "ResourceAssignmentExpression", properties: [ { name: "expression", type: "Expression", xml: { serialize: "xsi:type" } } ], superClass: [ "BaseElement" ] }, { name: "Import", properties: [ { name: "importType", isAttr: true, type: "String" }, { name: "location", isAttr: true, type: "String" }, { name: "namespace", isAttr: true, type: "String" } ] }, { name: "Definitions", superClass: [ "BaseElement" ], properties: [ { name: "name", isAttr: true, type: "String" }, { name: "targetNamespace", isAttr: true, type: "String" }, { name: "expressionLanguage", "default": "http://www.w3.org/1999/XPath", isAttr: true, type: "String" }, { name: "typeLanguage", "default": "http://www.w3.org/2001/XMLSchema", isAttr: true, type: "String" }, { name: "imports", type: "Import", isMany: true }, { name: "extensions", type: "Extension", isMany: true }, { name: "rootElements", type: "RootElement", isMany: true }, { name: "diagrams", isMany: true, type: "bpmndi:BPMNDiagram" }, { name: "exporter", isAttr: true, type: "String" }, { name: "relationships", type: "Relationship", isMany: true }, { name: "exporterVersion", isAttr: true, type: "String" } ] } ]; var enumerations$3 = [ { name: "ProcessType", literalValues: [ { name: "None" }, { name: "Public" }, { name: "Private" } ] }, { name: "GatewayDirection", literalValues: [ { name: "Unspecified" }, { name: "Converging" }, { name: "Diverging" }, { name: "Mixed" } ] }, { name: "EventBasedGatewayType", literalValues: [ { name: "Parallel" }, { name: "Exclusive" } ] }, { name: "RelationshipDirection", literalValues: [ { name: "None" }, { name: "Forward" }, { name: "Backward" }, { name: "Both" } ] }, { name: "ItemKind", literalValues: [ { name: "Physical" }, { name: "Information" } ] }, { name: "ChoreographyLoopType", literalValues: [ { name: "None" }, { name: "Standard" }, { name: "MultiInstanceSequential" }, { name: "MultiInstanceParallel" } ] }, { name: "AssociationDirection", literalValues: [ { name: "None" }, { name: "One" }, { name: "Both" } ] }, { name: "MultiInstanceBehavior", literalValues: [ { name: "None" }, { name: "One" }, { name: "All" }, { name: "Complex" } ] }, { name: "AdHocOrdering", literalValues: [ { name: "Parallel" }, { name: "Sequential" } ] } ]; var xml$1 = { tagAlias: "lowerCase", typePrefix: "t" }; var BpmnPackage = { name: name$5, uri: uri$5, prefix: prefix$5, associations: associations$5, types: types$5, enumerations: enumerations$3, xml: xml$1 }; var name$4 = "BPMNDI"; var uri$4 = "http://www.omg.org/spec/BPMN/20100524/DI"; var prefix$4 = "bpmndi"; var types$4 = [ { name: "BPMNDiagram", properties: [ { name: "plane", type: "BPMNPlane", redefines: "di:Diagram#rootElement" }, { name: "labelStyle", type: "BPMNLabelStyle", isMany: true } ], superClass: [ "di:Diagram" ] }, { name: "BPMNPlane", properties: [ { name: "bpmnElement", isAttr: true, isReference: true, type: "bpmn:BaseElement", redefines: "di:DiagramElement#modelElement" } ], superClass: [ "di:Plane" ] }, { name: "BPMNShape", properties: [ { name: "bpmnElement", isAttr: true, isReference: true, type: "bpmn:BaseElement", redefines: "di:DiagramElement#modelElement" }, { name: "isHorizontal", isAttr: true, type: "Boolean" }, { name: "isExpanded", isAttr: true, type: "Boolean" }, { name: "isMarkerVisible", isAttr: true, type: "Boolean" }, { name: "label", type: "BPMNLabel" }, { name: "isMessageVisible", isAttr: true, type: "Boolean" }, { name: "participantBandKind", type: "ParticipantBandKind", isAttr: true }, { name: "choreographyActivityShape", type: "BPMNShape", isAttr: true, isReference: true } ], superClass: [ "di:LabeledShape" ] }, { name: "BPMNEdge", properties: [ { name: "label", type: "BPMNLabel" }, { name: "bpmnElement", isAttr: true, isReference: true, type: "bpmn:BaseElement", redefines: "di:DiagramElement#modelElement" }, { name: "sourceElement", isAttr: true, isReference: true, type: "di:DiagramElement", redefines: "di:Edge#source" }, { name: "targetElement", isAttr: true, isReference: true, type: "di:DiagramElement", redefines: "di:Edge#target" }, { name: "messageVisibleKind", type: "MessageVisibleKind", isAttr: true, "default": "initiating" } ], superClass: [ "di:LabeledEdge" ] }, { name: "BPMNLabel", properties: [ { name: "labelStyle", type: "BPMNLabelStyle", isAttr: true, isReference: true, redefines: "di:DiagramElement#style" } ], superClass: [ "di:Label" ] }, { name: "BPMNLabelStyle", properties: [ { name: "font", type: "dc:Font" } ], superClass: [ "di:Style" ] } ]; var enumerations$2 = [ { name: "ParticipantBandKind", literalValues: [ { name: "top_initiating" }, { name: "middle_initiating" }, { name: "bottom_initiating" }, { name: "top_non_initiating" }, { name: "middle_non_initiating" }, { name: "bottom_non_initiating" } ] }, { name: "MessageVisibleKind", literalValues: [ { name: "initiating" }, { name: "non_initiating" } ] } ]; var associations$4 = [ ]; var BpmnDiPackage = { name: name$4, uri: uri$4, prefix: prefix$4, types: types$4, enumerations: enumerations$2, associations: associations$4 }; var name$3 = "DC"; var uri$3 = "http://www.omg.org/spec/DD/20100524/DC"; var prefix$3 = "dc"; var types$3 = [ { name: "Boolean" }, { name: "Integer" }, { name: "Real" }, { name: "String" }, { name: "Font", properties: [ { name: "name", type: "String", isAttr: true }, { name: "size", type: "Real", isAttr: true }, { name: "isBold", type: "Boolean", isAttr: true }, { name: "isItalic", type: "Boolean", isAttr: true }, { name: "isUnderline", type: "Boolean", isAttr: true }, { name: "isStrikeThrough", type: "Boolean", isAttr: true } ] }, { name: "Point", properties: [ { name: "x", type: "Real", "default": "0", isAttr: true }, { name: "y", type: "Real", "default": "0", isAttr: true } ] }, { name: "Bounds", properties: [ { name: "x", type: "Real", "default": "0", isAttr: true }, { name: "y", type: "Real", "default": "0", isAttr: true }, { name: "width", type: "Real", isAttr: true }, { name: "height", type: "Real", isAttr: true } ] } ]; var associations$3 = [ ]; var DcPackage = { name: name$3, uri: uri$3, prefix: prefix$3, types: types$3, associations: associations$3 }; var name$2 = "DI"; var uri$2 = "http://www.omg.org/spec/DD/20100524/DI"; var prefix$2 = "di"; var types$2 = [ { name: "DiagramElement", isAbstract: true, properties: [ { name: "id", isAttr: true, isId: true, type: "String" }, { name: "extension", type: "Extension" }, { name: "owningDiagram", type: "Diagram", isReadOnly: true, isVirtual: true, isReference: true }, { name: "owningElement", type: "DiagramElement", isReadOnly: true, isVirtual: true, isReference: true }, { name: "modelElement", isReadOnly: true, isVirtual: true, isReference: true, type: "Element" }, { name: "style", type: "Style", isReadOnly: true, isVirtual: true, isReference: true }, { name: "ownedElement", type: "DiagramElement", isReadOnly: true, isMany: true, isVirtual: true } ] }, { name: "Node", isAbstract: true, superClass: [ "DiagramElement" ] }, { name: "Edge", isAbstract: true, superClass: [ "DiagramElement" ], properties: [ { name: "source", type: "DiagramElement", isReadOnly: true, isVirtual: true, isReference: true }, { name: "target", type: "DiagramElement", isReadOnly: true, isVirtual: true, isReference: true }, { name: "waypoint", isUnique: false, isMany: true, type: "dc:Point", xml: { serialize: "xsi:type" } } ] }, { name: "Diagram", isAbstract: true, properties: [ { name: "id", isAttr: true, isId: true, type: "String" }, { name: "rootElement", type: "DiagramElement", isReadOnly: true, isVirtual: true }, { name: "name", isAttr: true, type: "String" }, { name: "documentation", isAttr: true, type: "String" }, { name: "resolution", isAttr: true, type: "Real" }, { name: "ownedStyle", type: "Style", isReadOnly: true, isMany: true, isVirtual: true } ] }, { name: "Shape", isAbstract: true, superClass: [ "Node" ], properties: [ { name: "bounds", type: "dc:Bounds" } ] }, { name: "Plane", isAbstract: true, superClass: [ "Node" ], properties: [ { name: "planeElement", type: "DiagramElement", subsettedProperty: "DiagramElement-ownedElement", isMany: true } ] }, { name: "LabeledEdge", isAbstract: true, superClass: [ "Edge" ], properties: [ { name: "ownedLabel", type: "Label", isReadOnly: true, subsettedProperty: "DiagramElement-ownedElement", isMany: true, isVirtual: true } ] }, { name: "LabeledShape", isAbstract: true, superClass: [ "Shape" ], properties: [ { name: "ownedLabel", type: "Label", isReadOnly: true, subsettedProperty: "DiagramElement-ownedElement", isMany: true, isVirtual: true } ] }, { name: "Label", isAbstract: true, superClass: [ "Node" ], properties: [ { name: "bounds", type: "dc:Bounds" } ] }, { name: "Style", isAbstract: true, properties: [ { name: "id", isAttr: true, isId: true, type: "String" } ] }, { name: "Extension", properties: [ { name: "values", isMany: true, type: "Element" } ] } ]; var associations$2 = [ ]; var xml = { tagAlias: "lowerCase" }; var DiPackage = { name: name$2, uri: uri$2, prefix: prefix$2, types: types$2, associations: associations$2, xml: xml }; var name$1 = "bpmn.io colors for BPMN"; var uri$1 = "http://bpmn.io/schema/bpmn/biocolor/1.0"; var prefix$1 = "bioc"; var types$1 = [ { name: "ColoredShape", "extends": [ "bpmndi:BPMNShape" ], properties: [ { name: "stroke", isAttr: true, type: "String" }, { name: "fill", isAttr: true, type: "String" } ] }, { name: "ColoredEdge", "extends": [ "bpmndi:BPMNEdge" ], properties: [ { name: "stroke", isAttr: true, type: "String" }, { name: "fill", isAttr: true, type: "String" } ] } ]; var enumerations$1 = [ ]; var associations$1 = [ ]; var BiocPackage = { name: name$1, uri: uri$1, prefix: prefix$1, types: types$1, enumerations: enumerations$1, associations: associations$1 }; var name = "BPMN in Color"; var uri = "http://www.omg.org/spec/BPMN/non-normative/color/1.0"; var prefix = "color"; var types = [ { name: "ColoredLabel", "extends": [ "bpmndi:BPMNLabel" ], properties: [ { name: "color", isAttr: true, type: "String" } ] }, { name: "ColoredShape", "extends": [ "bpmndi:BPMNShape" ], properties: [ { name: "background-color", isAttr: true, type: "String" }, { name: "border-color", isAttr: true, type: "String" } ] }, { name: "ColoredEdge", "extends": [ "bpmndi:BPMNEdge" ], properties: [ { name: "border-color", isAttr: true, type: "String" } ] } ]; var enumerations = [ ]; var associations = [ ]; var BpmnInColorPackage = { name: name, uri: uri, prefix: prefix, types: types, enumerations: enumerations, associations: associations }; var packages = { bpmn: BpmnPackage, bpmndi: BpmnDiPackage, dc: DcPackage, di: DiPackage, bioc: BiocPackage, color: BpmnInColorPackage }; function simple(additionalPackages, options) { var pks = assign({}, packages, additionalPackages); return new BpmnModdle(pks, options); } // TODO(nikku): remove with future bpmn-js version /** * Wraps APIs to check: * * 1) If a callback is passed -> Warn users about callback deprecation. * 2) If Promise class is implemented in current environment. * * @private */ function wrapForCompatibility(api) { return function() { if (!window.Promise) { throw new Error('Promises is not supported in this environment. Please polyfill Promise.'); } var argLen = arguments.length; if (argLen >= 1 && isFunction(arguments[argLen - 1])) { var callback = arguments[argLen - 1]; console.warn(new Error( 'Passing callbacks to ' + api.name + ' is deprecated and will be removed in a future major release. ' + 'Please switch to promises: https://bpmn.io/l/moving-to-promises.html' )); var argsWithoutCallback = Array.prototype.slice.call(arguments, 0, -1); api.apply(this, argsWithoutCallback).then(function(result) { var firstKey = Object.keys(result)[0]; // The APIs we are wrapping all resolve a single item depending on the API. // For instance, importXML resolves { warnings } and saveXML returns { xml }. // That's why we can call the callback with the first item of result. return callback(null, result[firstKey]); // Passing a second paramter instead of catch because we don't want to // catch errors thrown by callback(). }, function(err) { return callback(err, err.warnings); }); } else { return api.apply(this, arguments); } }; } // TODO(nikku): remove with future bpmn-js version var DI_ERROR_MESSAGE = 'Tried to access di from the businessObject. The di is available through the diagram element only. For more information, see https://github.com/bpmn-io/bpmn-js/issues/1472'; function ensureCompatDiRef(businessObject) { // bpmnElement can have multiple independent DIs if (!has$1(businessObject, 'di')) { Object.defineProperty(businessObject, 'di', { enumerable: false, get: function() { throw new Error(DI_ERROR_MESSAGE); } }); } } /** * Returns true if an element has the given meta-model type * * @param {ModdleElement} element * @param {string} type * * @return {boolean} */ function is(element, type) { return element.$instanceOf(type); } /** * Find a suitable display candidate for definitions where the DI does not * correctly specify one. */ function findDisplayCandidate(definitions) { return find(definitions.rootElements, function(e) { return is(e, 'bpmn:Process') || is(e, 'bpmn:Collaboration'); }); } function BpmnTreeWalker(handler, translate) { // list of containers already walked var handledElements = {}; // list of elements to handle deferred to ensure // prerequisites are drawn var deferred = []; var diMap = {}; // Helpers ////////////////////// function contextual(fn, ctx) { return function(e) { fn(e, ctx); }; } function handled(element) { handledElements[element.id] = element; } function isHandled(element) { return handledElements[element.id]; } function visit(element, ctx) { var gfx = element.gfx; // avoid multiple rendering of elements if (gfx) { throw new Error( translate('already rendered {element}', { element: elementToString(element) }) ); } // call handler return handler.element(element, diMap[element.id], ctx); } function visitRoot(element, diagram) { return handler.root(element, diMap[element.id], diagram); } function visitIfDi(element, ctx) { try { var gfx = diMap[element.id] && visit(element, ctx); handled(element); return gfx; } catch (e) { logError(e.message, { element: element, error: e }); console.error(translate('failed to import {element}', { element: elementToString(element) })); console.error(e); } } function logError(message, context) { handler.error(message, context); } // DI handling ////////////////////// function registerDi(di) { var bpmnElement = di.bpmnElement; if (bpmnElement) { if (diMap[bpmnElement.id]) { logError( translate('multiple DI elements defined for {element}', { element: elementToString(bpmnElement) }), { element: bpmnElement } ); } else { diMap[bpmnElement.id] = di; ensureCompatDiRef(bpmnElement); } } else { logError( translate('no bpmnElement referenced in {element}', { element: elementToString(di) }), { element: di } ); } } function handleDiagram(diagram) { handlePlane(diagram.plane); } function handlePlane(plane) { registerDi(plane); forEach$1(plane.planeElement, handlePlaneElement); } function handlePlaneElement(planeElement) { registerDi(planeElement); } // Semantic handling ////////////////////// /** * Handle definitions and return the rendered diagram (if any) * * @param {ModdleElement} definitions to walk and import * @param {ModdleElement} [diagram] specific diagram to import and display * * @throws {Error} if no diagram to display could be found */ function handleDefinitions(definitions, diagram) { // make sure we walk the correct bpmnElement var diagrams = definitions.diagrams; if (diagram && diagrams.indexOf(diagram) === -1) { throw new Error(translate('diagram not part of bpmn:Definitions')); } if (!diagram && diagrams && diagrams.length) { diagram = diagrams[0]; } // no diagram -> nothing to import if (!diagram) { throw new Error(translate('no diagram to display')); } // load DI from selected diagram only diMap = {}; handleDiagram(diagram); var plane = diagram.plane; if (!plane) { throw new Error(translate( 'no plane for {element}', { element: elementToString(diagram) } )); } var rootElement = plane.bpmnElement; // ensure we default to a suitable display candidate (process or collaboration), // even if non is specified in DI if (!rootElement) { rootElement = findDisplayCandidate(definitions); if (!rootElement) { throw new Error(translate('no process or collaboration to display')); } else { logError( translate('correcting missing bpmnElement on {plane} to {rootElement}', { plane: elementToString(plane), rootElement: elementToString(rootElement) }) ); // correct DI on the fly plane.bpmnElement = rootElement; registerDi(plane); } } var ctx = visitRoot(rootElement, plane); if (is(rootElement, 'bpmn:Process') || is(rootElement, 'bpmn:SubProcess')) { handleProcess(rootElement, ctx); } else if (is(rootElement, 'bpmn:Collaboration')) { handleCollaboration(rootElement, ctx); // force drawing of everything not yet drawn that is part of the target DI handleUnhandledProcesses(definitions.rootElements, ctx); } else { throw new Error( translate('unsupported bpmnElement for {plane}: {rootElement}', { plane: elementToString(plane), rootElement: elementToString(rootElement) }) ); } // handle all deferred elements handleDeferred(); } function handleDeferred() { var fn; // drain deferred until empty while (deferred.length) { fn = deferred.shift(); fn(); } } function handleProcess(process, context) { handleFlowElementsContainer(process, context); handleIoSpecification(process.ioSpecification, context); handleArtifacts(process.artifacts, context); // log process handled handled(process); } function handleUnhandledProcesses(rootElements, ctx) { // walk through all processes that have not yet been drawn and draw them // if they contain lanes with DI information. // we do this to pass the free-floating lane test cases in the MIWG test suite var processes = filter(rootElements, function(e) { return !isHandled(e) && is(e, 'bpmn:Process') && e.laneSets; }); processes.forEach(contextual(handleProcess, ctx)); } function handleMessageFlow(messageFlow, context) { visitIfDi(messageFlow, context); } function handleMessageFlows(messageFlows, context) { forEach$1(messageFlows, contextual(handleMessageFlow, context)); } function handleDataAssociation(association, context) { visitIfDi(association, context); } function handleDataInput(dataInput, context) { visitIfDi(dataInput, context); } function handleDataOutput(dataOutput, context) { visitIfDi(dataOutput, context); } function handleArtifact(artifact, context) { // bpmn:TextAnnotation // bpmn:Group // bpmn:Association visitIfDi(artifact, context); } function handleArtifacts(artifacts, context) { forEach$1(artifacts, function(e) { if (is(e, 'bpmn:Association')) { deferred.push(function() { handleArtifact(e, context); }); } else { handleArtifact(e, context); } }); } function handleIoSpecification(ioSpecification, context) { if (!ioSpecification) { return; } forEach$1(ioSpecification.dataInputs, contextual(handleDataInput, context)); forEach$1(ioSpecification.dataOutputs, contextual(handleDataOutput, context)); } function handleSubProcess(subProcess, context) { handleFlowElementsContainer(subProcess, context); handleArtifacts(subProcess.artifacts, context); } function handleFlowNode(flowNode, context) { var childCtx = visitIfDi(flowNode, context); if (is(flowNode, 'bpmn:SubProcess')) { handleSubProcess(flowNode, childCtx || context); } if (is(flowNode, 'bpmn:Activity')) { handleIoSpecification(flowNode.ioSpecification, context); } // defer handling of associations // affected types: // // * bpmn:Activity // * bpmn:ThrowEvent // * bpmn:CatchEvent // deferred.push(function() { forEach$1(flowNode.dataInputAssociations, contextual(handleDataAssociation, context)); forEach$1(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context)); }); } function handleSequenceFlow(sequenceFlow, context) { visitIfDi(sequenceFlow, context); } function handleDataElement(dataObject, context) { visitIfDi(dataObject, context); } function handleLane(lane, context) { deferred.push(function() { var newContext = visitIfDi(lane, context); if (lane.childLaneSet) { handleLaneSet(lane.childLaneSet, newContext || context); } wireFlowNodeRefs(lane); }); } function handleLaneSet(laneSet, context) { forEach$1(laneSet.lanes, contextual(handleLane, context)); } function handleLaneSets(laneSets, context) { forEach$1(laneSets, contextual(handleLaneSet, context)); } function handleFlowElementsContainer(container, context) { handleFlowElements(container.flowElements, context); if (container.laneSets) { handleLaneSets(container.laneSets, context); } } function handleFlowElements(flowElements, context) { forEach$1(flowElements, function(e) { if (is(e, 'bpmn:SequenceFlow')) { deferred.push(function() { handleSequenceFlow(e, context); }); } else if (is(e, 'bpmn:BoundaryEvent')) { deferred.unshift(function() { handleFlowNode(e, context); }); } else if (is(e, 'bpmn:FlowNode')) { handleFlowNode(e, context); } else if (is(e, 'bpmn:DataObject')) ; else if (is(e, 'bpmn:DataStoreReference')) { handleDataElement(e, context); } else if (is(e, 'bpmn:DataObjectReference')) { handleDataElement(e, context); } else { logError( translate('unrecognized flowElement {element} in context {context}', { element: elementToString(e), context: (context ? elementToString(context.businessObject) : 'null') }), { element: e, context: context } ); } }); } function handleParticipant(participant, context) { var newCtx = visitIfDi(participant, context); var process = participant.processRef; if (process) { handleProcess(process, newCtx || context); } } function handleCollaboration(collaboration, context) { forEach$1(collaboration.participants, contextual(handleParticipant, context)); handleArtifacts(collaboration.artifacts, context); // handle message flows latest in the process deferred.push(function() { handleMessageFlows(collaboration.messageFlows, context); }); } function wireFlowNodeRefs(lane) { // wire the virtual flowNodeRefs <-> relationship forEach$1(lane.flowNodeRef, function(flowNode) { var lanes = flowNode.get('lanes'); if (lanes) { lanes.push(lane); } }); } // API ////////////////////// return { handleDeferred: handleDeferred, handleDefinitions: handleDefinitions, handleSubProcess: handleSubProcess, registerDi: registerDi }; } /** * The importBpmnDiagram result. * * @typedef {Object} ImportBPMNDiagramResult * * @property {Array} warnings */ /** * The importBpmnDiagram error. * * @typedef {Error} ImportBPMNDiagramError * * @property {Array} warnings */ /** * Import the definitions into a diagram. * * Errors and warnings are reported through the specified callback. * * @param {djs.Diagram} diagram * @param {ModdleElement} definitions * @param {ModdleElement} [bpmnDiagram] the diagram to be rendered * (if not provided, the first one will be rendered) * * Returns {Promise} */ function importBpmnDiagram(diagram, definitions, bpmnDiagram) { var importer, eventBus, translate, canvas; var error, warnings = []; /** * Walk the diagram semantically, importing (=drawing) * all elements you encounter. * * @param {ModdleElement} definitions * @param {ModdleElement} bpmnDiagram */ function render(definitions, bpmnDiagram) { var visitor = { root: function(element, di) { return importer.add(element, di); }, element: function(element, di, parentShape) { return importer.add(element, di, parentShape); }, error: function(message, context) { warnings.push({ message: message, context: context }); } }; var walker = new BpmnTreeWalker(visitor, translate); bpmnDiagram = bpmnDiagram || (definitions.diagrams && definitions.diagrams[0]); var diagramsToImport = getDiagramsToImport(definitions, bpmnDiagram); if (!diagramsToImport) { throw new Error(translate('no diagram to display')); } // traverse BPMN 2.0 document model, // starting at definitions forEach$1(diagramsToImport, function(diagram) { walker.handleDefinitions(definitions, diagram); }); var rootId = bpmnDiagram.plane.bpmnElement.id; // we do need to account for different ways we create root elements // each nested imported do have the `_plane` suffix, while // the root is found under the business object ID canvas.setRootElement( canvas.findRoot(rootId + '_plane') || canvas.findRoot(rootId) ); } return new Promise(function(resolve, reject) { try { importer = diagram.get('bpmnImporter'); eventBus = diagram.get('eventBus'); translate = diagram.get('translate'); canvas = diagram.get('canvas'); eventBus.fire('import.render.start', { definitions: definitions }); render(definitions, bpmnDiagram); eventBus.fire('import.render.complete', { error: error, warnings: warnings }); return resolve({ warnings: warnings }); } catch (e) { e.warnings = warnings; return reject(e); } }); } /** * Returns all diagrams in the same hierarchy as the requested diagram. * Includes all parent and sub process diagrams. * * @param {Array} definitions * @param {Object} bpmnDiagram * * @returns {Array} */ function getDiagramsToImport(definitions, bpmnDiagram) { if (!bpmnDiagram) { return; } var bpmnElement = bpmnDiagram.plane.bpmnElement, rootElement = bpmnElement; if (!is$1(bpmnElement, 'bpmn:Process') && !is$1(bpmnElement, 'bpmn:Collaboration')) { rootElement = findRootProcess(bpmnElement); } // in case the process is part of a collaboration, the plane references the // collaboration, not the process var collaboration; if (is$1(rootElement, 'bpmn:Collaboration')) { collaboration = rootElement; } else { collaboration = find(definitions.rootElements, function(element) { if (!is$1(element, 'bpmn:Collaboration')) { return; } return find(element.participants, function(participant) { return participant.processRef === rootElement; }); }); } var rootElements = [ rootElement ]; // all collaboration processes can contain sub-diagrams if (collaboration) { rootElements = map(collaboration.participants, function(participant) { return participant.processRef; }); rootElements.push(collaboration); } var allChildren = selfAndAllFlowElements(rootElements); // if we have multiple diagrams referencing the same element, we // use the first in the file var diagramsToImport = [ bpmnDiagram ]; var handledElements = [ bpmnElement ]; forEach$1(definitions.diagrams, function(diagram) { var businessObject = diagram.plane.bpmnElement; if ( allChildren.indexOf(businessObject) !== -1 && handledElements.indexOf(businessObject) === -1 ) { diagramsToImport.push(diagram); handledElements.push(businessObject); } }); return diagramsToImport; } function selfAndAllFlowElements(elements) { var result = []; forEach$1(elements, function(element) { if (!element) { return; } result.push(element); result = result.concat(selfAndAllFlowElements(element.flowElements)); }); return result; } function findRootProcess(element) { var parent = element; while (parent) { if (is$1(parent, 'bpmn:Process')) { return parent; } parent = parent.$parent; } } /** * This file must not be changed or exchanged. * * @see http://bpmn.io/license for more information. */ // inlined ../../resources/logo.svg var BPMNIO_LOGO_SVG = ''; var BPMNIO_IMG = BPMNIO_LOGO_SVG; var LOGO_STYLES = { verticalAlign: 'middle' }; var LINK_STYLES = { 'color': '#404040' }; var LIGHTBOX_STYLES = { 'zIndex': '1001', 'position': 'fixed', 'top': '0', 'left': '0', 'right': '0', 'bottom': '0' }; var BACKDROP_STYLES = { 'width': '100%', 'height': '100%', 'background': 'rgba(40,40,40,0.2)' }; var NOTICE_STYLES = { 'position': 'absolute', 'left': '50%', 'top': '40%', 'transform': 'translate(-50%)', 'width': '260px', 'padding': '10px', 'background': 'white', 'boxShadow': '0 1px 4px rgba(0,0,0,0.3)', 'fontFamily': 'Helvetica, Arial, sans-serif', 'fontSize': '14px', 'display': 'flex', 'lineHeight': '1.3' }; var LIGHTBOX_MARKUP = '
    ' + '
    ' + '
    ' + '' + BPMNIO_IMG + '' + '' + 'Web-based tooling for BPMN, DMN and CMMN diagrams ' + 'powered by bpmn.io.' + '' + '
    ' + '
    '; var lightbox; function createLightbox() { lightbox = domify(LIGHTBOX_MARKUP); assign$1(lightbox, LIGHTBOX_STYLES); assign$1(query('svg', lightbox), LOGO_STYLES); assign$1(query('.backdrop', lightbox), BACKDROP_STYLES); assign$1(query('.notice', lightbox), NOTICE_STYLES); assign$1(query('.link', lightbox), LINK_STYLES, { 'margin': '15px 20px 15px 10px', 'alignSelf': 'center' }); } function open() { if (!lightbox) { createLightbox(); delegate.bind(lightbox, '.backdrop', 'click', function(event) { document.body.removeChild(lightbox); }); } document.body.appendChild(lightbox); } /** * The code in the area * must not be changed. * * @see http://bpmn.io/license for more information. */ /** * A base viewer for BPMN 2.0 diagrams. * * Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for * bundles that include actual features. * * @param {Object} [options] configuration options to pass to the viewer * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body. * @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} [options.modules] a list of modules to override the default modules * @param {Array} [options.additionalModules] a list of modules to use with the default modules */ function BaseViewer(options) { options = assign({}, DEFAULT_OPTIONS, options); this._moddle = this._createModdle(options); this._container = this._createContainer(options); /* */ addProjectLogo(this._container); /* */ this._init(this._container, this._moddle, options); } e(BaseViewer, Diagram); /** * The importXML result. * * @typedef {Object} ImportXMLResult * * @property {Array} warnings */ /** * The importXML error. * * @typedef {Error} ImportXMLError * * @property {Array} warnings */ /** * Parse and render a BPMN 2.0 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 BPMN 2.0 xml * @param {ModdleElement|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered) * * Returns {Promise} */ BaseViewer.prototype.importXML = wrapForCompatibility(function importXML(xml, bpmnDiagram) { var self = this; function ParseCompleteEvent(data) { var event = self.get('eventBus').createEvent(data); // TODO(nikku): remove with future bpmn-js version Object.defineProperty(event, 'context', { enumerable: true, get: function() { console.warn(new Error( 'import.parse.complete is deprecated ' + 'and will be removed in future library versions' )); return { warnings: data.warnings, references: data.references, elementsById: data.elementsById }; } }); return event; } return new Promise(function(resolve, reject) { // hook in pre-parse listeners + // allow xml manipulation xml = self._emit('import.parse.start', { xml: xml }) || xml; self._moddle.fromXML(xml, 'bpmn:Definitions').then(function(result) { var definitions = result.rootElement; var references = result.references; var parseWarnings = result.warnings; var elementsById = result.elementsById; // hook in post parse listeners + // allow definitions manipulation definitions = self._emit('import.parse.complete', ParseCompleteEvent({ error: null, definitions: definitions, elementsById: elementsById, references: references, warnings: parseWarnings })) || definitions; self.importDefinitions(definitions, bpmnDiagram).then(function(result) { var allWarnings = [].concat(parseWarnings, result.warnings || []); self._emit('import.done', { error: null, warnings: allWarnings }); return resolve({ warnings: allWarnings }); }).catch(function(err) { var allWarnings = [].concat(parseWarnings, err.warnings || []); self._emit('import.done', { error: err, warnings: allWarnings }); return reject(addWarningsToError(err, allWarnings)); }); }).catch(function(err) { self._emit('import.parse.complete', { error: err }); err = checkValidationError(err); self._emit('import.done', { error: err, warnings: err.warnings }); return reject(err); }); }); }); /** * The importDefinitions result. * * @typedef {Object} ImportDefinitionsResult * * @property {Array} warnings */ /** * The importDefinitions error. * * @typedef {Error} ImportDefinitionsError * * @property {Array} warnings */ /** * Import parsed definitions and render a BPMN 2.0 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.render.start (graphical import start) * * import.render.complete (graphical import finished) * * You can use these events to hook into the life-cycle. * * @param {ModdleElement} definitions parsed BPMN 2.0 definitions * @param {ModdleElement|string} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered) * * Returns {Promise} */ BaseViewer.prototype.importDefinitions = wrapForCompatibility(function importDefinitions(definitions, bpmnDiagram) { var self = this; return new Promise(function(resolve, reject) { self._setDefinitions(definitions); self.open(bpmnDiagram).then(function(result) { var warnings = result.warnings; return resolve({ warnings: warnings }); }).catch(function(err) { return reject(err); }); }); }); /** * The open result. * * @typedef {Object} OpenResult * * @property {Array} warnings */ /** * The open error. * * @typedef {Error} OpenError * * @property {Array} warnings */ /** * Open diagram of previously imported XML. * * Once finished the viewer reports back the result to the * provided callback function with (err, warnings). * * ## Life-Cycle Events * * During switch the viewer will fire life-cycle events: * * * import.render.start (graphical import start) * * import.render.complete (graphical import finished) * * You can use these events to hook into the life-cycle. * * @param {string|ModdleElement} [bpmnDiagramOrId] id or the diagram to open * * Returns {Promise} */ BaseViewer.prototype.open = wrapForCompatibility(function open(bpmnDiagramOrId) { var definitions = this._definitions; var bpmnDiagram = bpmnDiagramOrId; var self = this; return new Promise(function(resolve, reject) { if (!definitions) { var err1 = new Error('no XML imported'); return reject(addWarningsToError(err1, [])); } if (typeof bpmnDiagramOrId === 'string') { bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId); if (!bpmnDiagram) { var err2 = new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found'); return reject(addWarningsToError(err2, [])); } } // clear existing rendered diagram // catch synchronous exceptions during #clear() try { self.clear(); } catch (error) { return reject(addWarningsToError(error, [])); } // perform graphical import importBpmnDiagram(self, definitions, bpmnDiagram).then(function(result) { var warnings = result.warnings; return resolve({ warnings: warnings }); }).catch(function(err) { return reject(err); }); }); }); /** * The saveXML result. * * @typedef {Object} SaveXMLResult * * @property {string} xml */ /** * Export the currently displayed BPMN 2.0 diagram as * a BPMN 2.0 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 formatted XML * @param {boolean} [options.preamble=true] output preamble * * Returns {Promise} */ BaseViewer.prototype.saveXML = wrapForCompatibility(function saveXML(options) { options = options || {}; var self = this; var definitions = this._definitions; return new Promise(function(resolve) { if (!definitions) { return resolve({ error: new Error('no definitions loaded') }); } // allow to fiddle around with definitions definitions = self._emit('saveXML.start', { definitions: definitions }) || definitions; self._moddle.toXML(definitions, options).then(function(result) { var xml = result.xml; xml = self._emit('saveXML.serialized', { xml: xml }) || xml; return resolve({ xml: xml }); }); }).catch(function(error) { return { error: error }; }).then(function(result) { self._emit('saveXML.done', result); var error = result.error; if (error) { return Promise.reject(error); } return result; }); }); /** * The saveSVG result. * * @typedef {Object} SaveSVGResult * * @property {string} svg */ /** * Export the currently displayed BPMN 2.0 diagram as * an SVG image. * * ## Life-Cycle Events * * During SVG saving the viewer will fire life-cycle events: * * * saveSVG.start (before serialization) * * saveSVG.done (everything done) * * You can use these events to hook into the life-cycle. * * @param {Object} [options] * * Returns {Promise} */ BaseViewer.prototype.saveSVG = wrapForCompatibility(function saveSVG(options) { var self = this; return new Promise(function(resolve, reject) { self._emit('saveSVG.start'); var svg, err; try { var canvas = self.get('canvas'); var contentNode = canvas.getActiveLayer(), defsNode = query('defs', canvas._svg); var contents = innerSVG(contentNode), defs = defsNode ? '' + innerSVG(defsNode) + '' : ''; var bbox = contentNode.getBBox(); svg = '\n' + '\n' + '\n' + '' + defs + contents + ''; } catch (e) { err = e; } self._emit('saveSVG.done', { error: err, svg: svg }); if (!err) { return resolve({ svg: svg }); } return reject(err); }); }); /** * Get a named diagram service. * * @example * * var elementRegistry = viewer.get('elementRegistry'); * var startEventShape = elementRegistry.get('StartEvent_1'); * * @param {string} name * * @return {Object} diagram service instance * * @method BaseViewer#get */ /** * Invoke a function in the context of this viewer. * * @example * * viewer.invoke(function(elementRegistry) { * var startEventShape = elementRegistry.get('StartEvent_1'); * }); * * @param {Function} fn to be invoked * * @return {Object} the functions return value * * @method BaseViewer#invoke */ BaseViewer.prototype._setDefinitions = function(definitions) { this._definitions = definitions; }; BaseViewer.prototype.getModules = function() { return this._modules; }; /** * Remove all drawn elements from the viewer. * * After calling this method the viewer can still * be reused for opening another diagram. * * @method BaseViewer#clear */ BaseViewer.prototype.clear = function() { if (!this.getDefinitions()) { // no diagram to clear return; } // remove drawn elements Diagram.prototype.clear.call(this); }; /** * Destroy the viewer instance and remove all its * remainders from the document tree. */ BaseViewer.prototype.destroy = function() { // diagram destroy Diagram.prototype.destroy.call(this); // dom detach remove$1(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] */ BaseViewer.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 */ BaseViewer.prototype.off = function(event, callback) { this.get('eventBus').off(event, callback); }; BaseViewer.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 = query(parentNode); } parentNode.appendChild(this._container); this._emit('attach', {}); this.get('canvas').resized(); }; BaseViewer.prototype.getDefinitions = function() { return this._definitions; }; BaseViewer.prototype.detach = function() { var container = this._container, parentNode = container.parentNode; if (!parentNode) { return; } this._emit('detach', {}); parentNode.removeChild(container); }; BaseViewer.prototype._init = function(container, moddle, options) { var baseModules = options.modules || this.getModules(), additionalModules = options.additionalModules || [], staticModules = [ { bpmnjs: [ '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) */ BaseViewer.prototype._emit = function(type, event) { return this.get('eventBus').fire(type, event); }; BaseViewer.prototype._createContainer = function(options) { var container = domify('
    '); assign$1(container, { width: ensureUnit(options.width), height: ensureUnit(options.height), position: options.position }); return container; }; BaseViewer.prototype._createModdle = function(options) { var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions); return new simple(moddleOptions); }; BaseViewer.prototype._modules = []; // helpers /////////////// function addWarningsToError(err, warningsAry) { err.warnings = warningsAry; return err; } function checkValidationError(err) { // check if we can help the user by indicating wrong BPMN 2.0 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 BPMN 2.0 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' : ''); } /** * Find BPMNDiagram in definitions by ID * * @param {ModdleElement} definitions * @param {string} diagramId * * @return {ModdleElement|null} */ function findBPMNDiagram(definitions, diagramId) { if (!diagramId) { return null; } return find(definitions.diagrams, function(element) { return element.id === diagramId; }) || null; } /** * 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 img = BPMNIO_IMG; var linkMarkup = '' + img + ''; var linkElement = domify(linkMarkup); assign$1(query('svg', linkElement), LOGO_STYLES); assign$1(linkElement, LINK_STYLES, { position: 'absolute', bottom: '15px', right: '15px', zIndex: '100' }); container.appendChild(linkElement); componentEvent.bind(linkElement, 'click', function(event) { open(); event.preventDefault(); }); } /* */ /** * A viewer for BPMN 2.0 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 bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] }); * bpmnViewer.importXML(...); * ``` * * @param {Object} [options] configuration options to pass to the viewer * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body. * @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} [options.modules] a list of modules to override the default modules * @param {Array} [options.additionalModules] a list of modules to use with the default modules */ function Viewer(options) { BaseViewer.call(this, options); } e(Viewer, BaseViewer); // modules the viewer is composed of Viewer.prototype._modules = [ CoreModule$1, TranslateModule, SelectionModule, OverlaysModule, DrilldownModdule ]; // default moddle extensions the viewer is composed of Viewer.prototype._moddleExtensions = {}; var KEYCODE_C = 67; var KEYCODE_V = 86; var KEYCODE_Y = 89; var KEYCODE_Z = 90; var KEYS_COPY = [ 'c', 'C', KEYCODE_C ]; var KEYS_PASTE = [ 'v', 'V', KEYCODE_V ]; var KEYS_REDO = [ 'y', 'Y', KEYCODE_Y ]; var KEYS_UNDO = [ 'z', 'Z', KEYCODE_Z ]; /** * Returns true if event was triggered with any modifier * @param {KeyboardEvent} event */ function hasModifier(event) { return (event.ctrlKey || event.metaKey || event.shiftKey || event.altKey); } /** * @param {KeyboardEvent} event */ function isCmd(event) { // ensure we don't react to AltGr // (mapped to CTRL + ALT) if (event.altKey) { return false; } return event.ctrlKey || event.metaKey; } /** * Checks if key pressed is one of provided keys. * * @param {string|Array} keys * @param {KeyboardEvent} event */ function isKey(keys, event) { keys = isArray$2(keys) ? keys : [ keys ]; return keys.indexOf(event.key) !== -1 || keys.indexOf(event.keyCode) !== -1; } /** * @param {KeyboardEvent} event */ function isShift(event) { return event.shiftKey; } function isCopy(event) { return isCmd(event) && isKey(KEYS_COPY, event); } function isPaste(event) { return isCmd(event) && isKey(KEYS_PASTE, event); } function isUndo(event) { return isCmd(event) && !isShift(event) && isKey(KEYS_UNDO, event); } function isRedo(event) { return isCmd(event) && ( isKey(KEYS_REDO, event) || ( isKey(KEYS_UNDO, event) && isShift(event) ) ); } var KEYDOWN_EVENT = 'keyboard.keydown', KEYUP_EVENT = 'keyboard.keyup'; var HANDLE_MODIFIER_ATTRIBUTE = 'input-handle-modified-keys'; var DEFAULT_PRIORITY = 1000; /** * A keyboard abstraction that may be activated and * deactivated by users at will, consuming key events * and triggering diagram actions. * * For keys pressed down, keyboard fires `keyboard.keydown` event. * The event context contains one field which is `KeyboardEvent` event. * * The implementation fires the following key events that allow * other components to hook into key handling: * * - keyboard.bind * - keyboard.unbind * - keyboard.init * - keyboard.destroy * * All events contain one field which is node. * * A default binding for the keyboard may be specified via the * `keyboard.bindTo` configuration option. * * @param {Config} config * @param {EventBus} eventBus */ function Keyboard(config, eventBus) { var self = this; this._config = config || {}; this._eventBus = eventBus; this._keydownHandler = this._keydownHandler.bind(this); this._keyupHandler = this._keyupHandler.bind(this); // properly clean dom registrations eventBus.on('diagram.destroy', function() { self._fire('destroy'); self.unbind(); }); eventBus.on('diagram.init', function() { self._fire('init'); }); eventBus.on('attach', function() { if (config && config.bindTo) { self.bind(config.bindTo); } }); eventBus.on('detach', function() { self.unbind(); }); } Keyboard.$inject = [ 'config.keyboard', 'eventBus' ]; Keyboard.prototype._keydownHandler = function(event) { this._keyHandler(event, KEYDOWN_EVENT); }; Keyboard.prototype._keyupHandler = function(event) { this._keyHandler(event, KEYUP_EVENT); }; Keyboard.prototype._keyHandler = function(event, type) { var eventBusResult; if (this._isEventIgnored(event)) { return; } var context = { keyEvent: event }; eventBusResult = this._eventBus.fire(type || KEYDOWN_EVENT, context); if (eventBusResult) { event.preventDefault(); } }; Keyboard.prototype._isEventIgnored = function(event) { return isInput(event.target) && this._isModifiedKeyIgnored(event); }; Keyboard.prototype._isModifiedKeyIgnored = function(event) { if (!isCmd(event)) { return true; } var allowedModifiers = this._getAllowedModifiers(event.target); return allowedModifiers.indexOf(event.key) === -1; }; Keyboard.prototype._getAllowedModifiers = function(element) { var modifierContainer = closest(element, '[' + HANDLE_MODIFIER_ATTRIBUTE + ']', true); if (!modifierContainer || (this._node && !this._node.contains(modifierContainer))) { return []; } return modifierContainer.getAttribute(HANDLE_MODIFIER_ATTRIBUTE).split(','); }; Keyboard.prototype.bind = function(node) { // make sure that the keyboard is only bound once to the DOM this.unbind(); this._node = node; // bind key events componentEvent.bind(node, 'keydown', this._keydownHandler, true); componentEvent.bind(node, 'keyup', this._keyupHandler, true); this._fire('bind'); }; Keyboard.prototype.getBinding = function() { return this._node; }; Keyboard.prototype.unbind = function() { var node = this._node; if (node) { this._fire('unbind'); // unbind key events componentEvent.unbind(node, 'keydown', this._keydownHandler, true); componentEvent.unbind(node, 'keyup', this._keyupHandler, true); } this._node = null; }; Keyboard.prototype._fire = function(event) { this._eventBus.fire('keyboard.' + event, { node: this._node }); }; /** * Add a listener function that is notified with `KeyboardEvent` whenever * the keyboard is bound and the user presses a key. If no priority is * provided, the default value of 1000 is used. * * @param {number} [priority] * @param {Function} listener * @param {string} type */ Keyboard.prototype.addListener = function(priority, listener, type) { if (isFunction(priority)) { type = listener; listener = priority; priority = DEFAULT_PRIORITY; } this._eventBus.on(type || KEYDOWN_EVENT, priority, listener); }; Keyboard.prototype.removeListener = function(listener, type) { this._eventBus.off(type || KEYDOWN_EVENT, listener); }; Keyboard.prototype.hasModifier = hasModifier; Keyboard.prototype.isCmd = isCmd; Keyboard.prototype.isShift = isShift; Keyboard.prototype.isKey = isKey; // helpers /////// function isInput(target) { return target && (matchesSelector(target, 'input, textarea') || target.contentEditable === 'true'); } var LOW_PRIORITY = 500; /** * Adds default keyboard bindings. * * This does not pull in any features will bind only actions that * have previously been registered against the editorActions component. * * @param {EventBus} eventBus * @param {Keyboard} keyboard */ function KeyboardBindings(eventBus, keyboard) { var self = this; eventBus.on('editorActions.init', LOW_PRIORITY, function(event) { var editorActions = event.editorActions; self.registerBindings(keyboard, editorActions); }); } KeyboardBindings.$inject = [ 'eventBus', 'keyboard' ]; /** * Register available keyboard bindings. * * @param {Keyboard} keyboard * @param {EditorActions} editorActions */ KeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) { /** * Add keyboard binding if respective editor action * is registered. * * @param {string} action name * @param {Function} fn that implements the key binding */ function addListener(action, fn) { if (editorActions.isRegistered(action)) { keyboard.addListener(fn); } } // undo // (CTRL|CMD) + Z addListener('undo', function(context) { var event = context.keyEvent; if (isUndo(event)) { editorActions.trigger('undo'); return true; } }); // redo // CTRL + Y // CMD + SHIFT + Z addListener('redo', function(context) { var event = context.keyEvent; if (isRedo(event)) { editorActions.trigger('redo'); return true; } }); // copy // CTRL/CMD + C addListener('copy', function(context) { var event = context.keyEvent; if (isCopy(event)) { editorActions.trigger('copy'); return true; } }); // paste // CTRL/CMD + V addListener('paste', function(context) { var event = context.keyEvent; if (isPaste(event)) { editorActions.trigger('paste'); return true; } }); // zoom in one step // CTRL/CMD + + addListener('stepZoom', function(context) { var event = context.keyEvent; // quirk: it has to be triggered by `=` as well to work on international keyboard layout // cf: https://github.com/bpmn-io/bpmn-js/issues/1362#issuecomment-722989754 if (isKey([ '+', 'Add', '=' ], event) && isCmd(event)) { editorActions.trigger('stepZoom', { value: 1 }); return true; } }); // zoom out one step // CTRL + - addListener('stepZoom', function(context) { var event = context.keyEvent; if (isKey([ '-', 'Subtract' ], event) && isCmd(event)) { editorActions.trigger('stepZoom', { value: -1 }); return true; } }); // zoom to the default level // CTRL + 0 addListener('zoom', function(context) { var event = context.keyEvent; if (isKey('0', event) && isCmd(event)) { editorActions.trigger('zoom', { value: 1 }); return true; } }); // delete selected element // DEL addListener('removeSelection', function(context) { var event = context.keyEvent; if (isKey([ 'Backspace', 'Delete', 'Del' ], event)) { editorActions.trigger('removeSelection'); return true; } }); }; var KeyboardModule = { __init__: [ 'keyboard', 'keyboardBindings' ], keyboard: [ 'type', Keyboard ], keyboardBindings: [ 'type', KeyboardBindings ] }; var DEFAULT_CONFIG = { moveSpeed: 50, moveSpeedAccelerated: 200 }; /** * A feature that allows users to move the canvas using the keyboard. * * @param {Object} config * @param {number} [config.moveSpeed=50] * @param {number} [config.moveSpeedAccelerated=200] * @param {Keyboard} keyboard * @param {Canvas} canvas */ function KeyboardMove( config, keyboard, canvas ) { var self = this; this._config = assign({}, DEFAULT_CONFIG, config || {}); keyboard.addListener(arrowsListener); function arrowsListener(context) { var event = context.keyEvent, config = self._config; if (!keyboard.isCmd(event)) { return; } if (keyboard.isKey([ 'ArrowLeft', 'Left', 'ArrowUp', 'Up', 'ArrowDown', 'Down', 'ArrowRight', 'Right' ], event)) { var speed = ( keyboard.isShift(event) ? config.moveSpeedAccelerated : config.moveSpeed ); var direction; switch (event.key) { case 'ArrowLeft': case 'Left': direction = 'left'; break; case 'ArrowUp': case 'Up': direction = 'up'; break; case 'ArrowRight': case 'Right': direction = 'right'; break; case 'ArrowDown': case 'Down': direction = 'down'; break; } self.moveCanvas({ speed: speed, direction: direction }); return true; } } this.moveCanvas = function(opts) { var dx = 0, dy = 0, speed = opts.speed; var actualSpeed = speed / Math.min(Math.sqrt(canvas.viewbox().scale), 1); switch (opts.direction) { case 'left': // Left dx = actualSpeed; break; case 'up': // Up dy = actualSpeed; break; case 'right': // Right dx = -actualSpeed; break; case 'down': // Down dy = -actualSpeed; break; } canvas.scroll({ dx: dx, dy: dy }); }; } KeyboardMove.$inject = [ 'config.keyboardMove', 'keyboard', 'canvas' ]; var KeyboardMoveModule = { __depends__: [ KeyboardModule ], __init__: [ 'keyboardMove' ], keyboardMove: [ 'type', KeyboardMove ] }; var CURSOR_CLS_PATTERN = /^djs-cursor-.*$/; function set(mode) { var classes$1 = classes(document.body); classes$1.removeMatching(CURSOR_CLS_PATTERN); if (mode) { classes$1.add('djs-cursor-' + mode); } } function unset() { set(null); } var TRAP_PRIORITY = 5000; /** * Installs a click trap that prevents a ghost click following a dragging operation. * * @return {Function} a function to immediately remove the installed trap. */ function install(eventBus, eventName) { eventName = eventName || 'element.click'; function trap() { return false; } eventBus.once(eventName, TRAP_PRIORITY, trap); return function() { eventBus.off(eventName, trap); }; } function delta(a, b) { return { x: a.x - b.x, y: a.y - b.y }; } var THRESHOLD = 15; /** * Move the canvas via mouse. * * @param {EventBus} eventBus * @param {Canvas} canvas */ function MoveCanvas(eventBus, canvas) { var context; // listen for move on element mouse down; // allow others to hook into the event before us though // (dragging / element moving will do this) eventBus.on('element.mousedown', 500, function(e) { return handleStart(e.originalEvent); }); function handleMove(event) { var start = context.start, button = context.button, position = toPoint(event), delta$1 = delta(position, start); if (!context.dragging && length(delta$1) > THRESHOLD) { context.dragging = true; if (button === 0) { install(eventBus); } set('grab'); } if (context.dragging) { var lastPosition = context.last || context.start; delta$1 = delta(position, lastPosition); canvas.scroll({ dx: delta$1.x, dy: delta$1.y }); context.last = position; } // prevent select event.preventDefault(); } function handleEnd(event) { componentEvent.unbind(document, 'mousemove', handleMove); componentEvent.unbind(document, 'mouseup', handleEnd); context = null; unset(); } function handleStart(event) { // event is already handled by '.djs-draggable' if (closest(event.target, '.djs-draggable')) { return; } var button = event.button; // reject right mouse button or modifier key if (button >= 2 || event.ctrlKey || event.shiftKey || event.altKey) { return; } context = { button: button, start: toPoint(event) }; componentEvent.bind(document, 'mousemove', handleMove); componentEvent.bind(document, 'mouseup', handleEnd); // we've handled the event return true; } this.isActive = function() { return !!context; }; } MoveCanvas.$inject = [ 'eventBus', 'canvas' ]; // helpers /////// function length(point) { return Math.sqrt(Math.pow(point.x, 2) + Math.pow(point.y, 2)); } var MoveCanvasModule = { __init__: [ 'moveCanvas' ], moveCanvas: [ 'type', MoveCanvas ] }; /** * Get the logarithm of x with base 10 * @param {Integer} value */ function log10(x) { return Math.log(x) / Math.log(10); } /** * Get step size for given range and number of steps. * * @param {Object} range * @param {number} range.min * @param {number} range.max */ function getStepSize(range, steps) { var minLinearRange = log10(range.min), maxLinearRange = log10(range.max); var absoluteLinearRange = Math.abs(minLinearRange) + Math.abs(maxLinearRange); return absoluteLinearRange / steps; } function cap(range, scale) { return Math.max(range.min, Math.min(range.max, scale)); } var sign = Math.sign || function(n) { return n >= 0 ? 1 : -1; }; var RANGE = { min: 0.2, max: 4 }, NUM_STEPS = 10; var DELTA_THRESHOLD = 0.1; var DEFAULT_SCALE = 0.75; /** * An implementation of zooming and scrolling within the * {@link Canvas} via the mouse wheel. * * Mouse wheel zooming / scrolling may be disabled using * the {@link toggle(enabled)} method. * * @param {Object} [config] * @param {boolean} [config.enabled=true] default enabled state * @param {number} [config.scale=.75] scroll sensivity * @param {EventBus} eventBus * @param {Canvas} canvas */ function ZoomScroll(config, eventBus, canvas) { config = config || {}; this._enabled = false; this._canvas = canvas; this._container = canvas._container; this._handleWheel = bind(this._handleWheel, this); this._totalDelta = 0; this._scale = config.scale || DEFAULT_SCALE; var self = this; eventBus.on('canvas.init', function(e) { self._init(config.enabled !== false); }); } ZoomScroll.$inject = [ 'config.zoomScroll', 'eventBus', 'canvas' ]; ZoomScroll.prototype.scroll = function scroll(delta) { this._canvas.scroll(delta); }; ZoomScroll.prototype.reset = function reset() { this._canvas.zoom('fit-viewport'); }; /** * Zoom depending on delta. * * @param {number} delta * @param {Object} position */ ZoomScroll.prototype.zoom = function zoom(delta, position) { // zoom with half the step size of stepZoom var stepSize = getStepSize(RANGE, NUM_STEPS * 2); // add until threshold reached this._totalDelta += delta; if (Math.abs(this._totalDelta) > DELTA_THRESHOLD) { this._zoom(delta, position, stepSize); // reset this._totalDelta = 0; } }; ZoomScroll.prototype._handleWheel = function handleWheel(event) { // event is already handled by '.djs-scrollable' if (closest(event.target, '.djs-scrollable', true)) { return; } var element = this._container; event.preventDefault(); // pinch to zoom is mapped to wheel + ctrlKey = true // in modern browsers (!) var isZoom = event.ctrlKey; var isHorizontalScroll = event.shiftKey; var factor = -1 * this._scale, delta; if (isZoom) { factor *= event.deltaMode === 0 ? 0.020 : 0.32; } else { factor *= event.deltaMode === 0 ? 1.0 : 16.0; } if (isZoom) { var elementRect = element.getBoundingClientRect(); var offset = { x: event.clientX - elementRect.left, y: event.clientY - elementRect.top }; delta = ( Math.sqrt( Math.pow(event.deltaY, 2) + Math.pow(event.deltaX, 2) ) * sign(event.deltaY) * factor ); // zoom in relative to diagram {x,y} coordinates this.zoom(delta, offset); } else { if (isHorizontalScroll) { delta = { dx: factor * event.deltaY, dy: 0 }; } else { delta = { dx: factor * event.deltaX, dy: factor * event.deltaY }; } this.scroll(delta); } }; /** * Zoom with fixed step size. * * @param {number} delta - Zoom delta (1 for zooming in, -1 for out). * @param {Object} position */ ZoomScroll.prototype.stepZoom = function stepZoom(delta, position) { var stepSize = getStepSize(RANGE, NUM_STEPS); this._zoom(delta, position, stepSize); }; /** * Zoom in/out given a step size. * * @param {number} delta * @param {Object} position * @param {number} stepSize */ ZoomScroll.prototype._zoom = function(delta, position, stepSize) { var canvas = this._canvas; var direction = delta > 0 ? 1 : -1; var currentLinearZoomLevel = log10(canvas.zoom()); // snap to a proximate zoom step var newLinearZoomLevel = Math.round(currentLinearZoomLevel / stepSize) * stepSize; // increase or decrease one zoom step in the given direction newLinearZoomLevel += stepSize * direction; // calculate the absolute logarithmic zoom level based on the linear zoom level // (e.g. 2 for an absolute x2 zoom) var newLogZoomLevel = Math.pow(10, newLinearZoomLevel); canvas.zoom(cap(RANGE, newLogZoomLevel), position); }; /** * Toggle the zoom scroll ability via mouse wheel. * * @param {boolean} [newEnabled] new enabled state */ ZoomScroll.prototype.toggle = function toggle(newEnabled) { var element = this._container; var handleWheel = this._handleWheel; var oldEnabled = this._enabled; if (typeof newEnabled === 'undefined') { newEnabled = !oldEnabled; } // only react on actual changes if (oldEnabled !== newEnabled) { // add or remove wheel listener based on // changed enabled state componentEvent[newEnabled ? 'bind' : 'unbind'](element, 'wheel', handleWheel, false); } this._enabled = newEnabled; return newEnabled; }; ZoomScroll.prototype._init = function(newEnabled) { this.toggle(newEnabled); }; var ZoomScrollModule = { __init__: [ 'zoomScroll' ], zoomScroll: [ 'type', ZoomScroll ] }; /** * A viewer that includes mouse navigation facilities * * @param {Object} options */ function NavigatedViewer(options) { Viewer.call(this, options); } e(NavigatedViewer, Viewer); NavigatedViewer.prototype._navigationModules = [ KeyboardMoveModule, MoveCanvasModule, ZoomScrollModule ]; NavigatedViewer.prototype._modules = [].concat( Viewer.prototype._modules, NavigatedViewer.prototype._navigationModules ); return NavigatedViewer; }));