19831 lines
440 KiB
JavaScript
19831 lines
440 KiB
JavaScript
/*!
|
|
* bpmn-js - bpmn-navigated-viewer v4.0.4
|
|
*
|
|
* 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: 2019-08-05
|
|
*/
|
|
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
|
typeof define === 'function' && define.amd ? define(factory) :
|
|
(global = global || self, global.BpmnJS = factory());
|
|
}(this, function () { 'use strict';
|
|
|
|
function createCommonjsModule(fn, module) {
|
|
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
|
}
|
|
|
|
var inherits_browser = createCommonjsModule(function (module) {
|
|
if (typeof Object.create === 'function') {
|
|
// implementation from standard node.js 'util' module
|
|
module.exports = function inherits(ctor, superCtor) {
|
|
ctor.super_ = superCtor;
|
|
ctor.prototype = Object.create(superCtor.prototype, {
|
|
constructor: {
|
|
value: ctor,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true
|
|
}
|
|
});
|
|
};
|
|
} else {
|
|
// old school shim for old browsers
|
|
module.exports = function inherits(ctor, superCtor) {
|
|
ctor.super_ = superCtor;
|
|
var TempCtor = function () {};
|
|
TempCtor.prototype = superCtor.prototype;
|
|
ctor.prototype = new TempCtor();
|
|
ctor.prototype.constructor = ctor;
|
|
};
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Flatten array, one level deep.
|
|
*
|
|
* @param {Array<?>} arr
|
|
*
|
|
* @return {Array<?>}
|
|
*/
|
|
|
|
var nativeToString = Object.prototype.toString;
|
|
var nativeHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
function isUndefined(obj) {
|
|
return obj === undefined;
|
|
}
|
|
function isDefined(obj) {
|
|
return obj !== undefined;
|
|
}
|
|
function isArray(obj) {
|
|
return nativeToString.call(obj) === '[object Array]';
|
|
}
|
|
function isObject(obj) {
|
|
return nativeToString.call(obj) === '[object Object]';
|
|
}
|
|
function isNumber(obj) {
|
|
return nativeToString.call(obj) === '[object Number]';
|
|
}
|
|
function isFunction(obj) {
|
|
var tag = nativeToString.call(obj);
|
|
return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]';
|
|
}
|
|
function isString(obj) {
|
|
return nativeToString.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(target, key) {
|
|
return nativeHasOwnProperty.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(collection, function (val, key) {
|
|
if (matcher(val, key)) {
|
|
match = val;
|
|
return false;
|
|
}
|
|
});
|
|
return match;
|
|
}
|
|
/**
|
|
* Find element in collection.
|
|
*
|
|
* @param {Array|Object} collection
|
|
* @param {Function} matcher
|
|
*
|
|
* @return {Array} result
|
|
*/
|
|
|
|
function filter(collection, matcher) {
|
|
var result = [];
|
|
forEach(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(collection, iterator) {
|
|
var val, result;
|
|
|
|
if (isUndefined(collection)) {
|
|
return;
|
|
}
|
|
|
|
var convertKey = isArray(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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* 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(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(collection, function (val, key) {
|
|
result.push(fn(val, key));
|
|
});
|
|
return result;
|
|
}
|
|
/**
|
|
* Create an object pattern matcher.
|
|
*
|
|
* @example
|
|
*
|
|
* const matcher = matchPattern({ id: 1 });
|
|
*
|
|
* var 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(arg) {
|
|
return arg;
|
|
}
|
|
|
|
function toNum(arg) {
|
|
return Number(arg);
|
|
}
|
|
|
|
/**
|
|
* Debounce fn, calling it only once if
|
|
* the given time elapsed between calls.
|
|
*
|
|
* @param {Function} fn
|
|
* @param {Number} timeout
|
|
*
|
|
* @return {Function} debounced function
|
|
*/
|
|
function debounce(fn, timeout) {
|
|
var timer;
|
|
var lastArgs;
|
|
var lastThis;
|
|
var lastNow;
|
|
|
|
function fire() {
|
|
var now = Date.now();
|
|
var scheduledDiff = lastNow + timeout - now;
|
|
|
|
if (scheduledDiff > 0) {
|
|
return schedule(scheduledDiff);
|
|
}
|
|
|
|
fn.apply(lastThis, lastArgs);
|
|
timer = lastNow = lastArgs = lastThis = undefined;
|
|
}
|
|
|
|
function schedule(timeout) {
|
|
timer = setTimeout(fire, timeout);
|
|
}
|
|
|
|
return function () {
|
|
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);
|
|
}
|
|
};
|
|
}
|
|
/**
|
|
* Bind function against target <this>.
|
|
*
|
|
* @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(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(obj, function (prop, key) {
|
|
if (properties.indexOf(key) === -1) {
|
|
result[key] = prop;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Element prototype.
|
|
*/
|
|
|
|
var proto = Element.prototype;
|
|
|
|
/**
|
|
* Vendor function.
|
|
*/
|
|
|
|
var vendor = proto.matchesSelector
|
|
|| proto.webkitMatchesSelector
|
|
|| proto.mozMatchesSelector
|
|
|| proto.msMatchesSelector
|
|
|| proto.oMatchesSelector;
|
|
|
|
/**
|
|
* Expose `match()`.
|
|
*/
|
|
|
|
var matchesSelector = match;
|
|
|
|
/**
|
|
* Match `el` to `selector`.
|
|
*
|
|
* @param {Element} el
|
|
* @param {String} selector
|
|
* @return {Boolean}
|
|
* @api public
|
|
*/
|
|
|
|
function match(el, selector) {
|
|
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;
|
|
}
|
|
|
|
var closest = function (element, selector, checkYoSelf) {
|
|
var parent = checkYoSelf ? element : element.parentNode;
|
|
|
|
while (parent && parent !== document) {
|
|
if (matchesSelector(parent, selector)) return parent;
|
|
parent = parent.parentNode;
|
|
}
|
|
};
|
|
|
|
var bind$1 = window.addEventListener ? 'addEventListener' : 'attachEvent',
|
|
unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
|
|
prefix = 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 + 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 + 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'];
|
|
|
|
var bind$1$1 = function(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
|
|
*/
|
|
|
|
var unbind$1 = function(el, type, fn, capture){
|
|
if (forceCaptureEvents.indexOf(type) !== -1) capture = true;
|
|
|
|
componentEvent.unbind(el, type, fn, capture);
|
|
};
|
|
|
|
var delegateEvents = {
|
|
bind: bind$1$1,
|
|
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 = ' <link/><table></table><a href="/a">a</a><input type="checkbox"/>';
|
|
// 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, '<fieldset>', '</fieldset>'],
|
|
tr: [2, '<table><tbody>', '</tbody></table>'],
|
|
col: [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
|
|
// 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<div>', '</div>'] : [0, '', '']
|
|
};
|
|
|
|
map$1.td =
|
|
map$1.th = [3, '<table><tbody><tr>', '</tr></tbody></table>'];
|
|
|
|
map$1.option =
|
|
map$1.optgroup = [1, '<select multiple="multiple">', '</select>'];
|
|
|
|
map$1.thead =
|
|
map$1.tbody =
|
|
map$1.colgroup =
|
|
map$1.caption =
|
|
map$1.tfoot = [1, '<table>', '</table>'];
|
|
|
|
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, '<svg xmlns="http://www.w3.org/2000/svg" version="1.1">','</svg>'];
|
|
|
|
/**
|
|
* Parse `html` and return a DOM Node instance, which could be a TextNode,
|
|
* HTML DOM Node of some kind (<div> 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;
|
|
}
|
|
|
|
var proto$1 = typeof Element !== 'undefined' ? Element.prototype : {};
|
|
var vendor$1 = proto$1.matches
|
|
|| proto$1.matchesSelector
|
|
|| proto$1.webkitMatchesSelector
|
|
|| proto$1.mozMatchesSelector
|
|
|| proto$1.msMatchesSelector
|
|
|| proto$1.oMatchesSelector;
|
|
|
|
var matchesSelector$1 = match$1;
|
|
|
|
/**
|
|
* Match `el` to `selector`.
|
|
*
|
|
* @param {Element} el
|
|
* @param {String} selector
|
|
* @return {Boolean}
|
|
* @api public
|
|
*/
|
|
|
|
function match$1(el, selector) {
|
|
if (!el || el.nodeType !== 1) return false;
|
|
if (vendor$1) return vendor$1.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;
|
|
}
|
|
|
|
function query(selector, el) {
|
|
el = el || document;
|
|
|
|
return el.querySelector(selector);
|
|
}
|
|
|
|
function remove(el) {
|
|
el.parentNode && el.parentNode.removeChild(el);
|
|
}
|
|
|
|
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$1(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$1(child);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
var ns = {
|
|
svg: 'http://www.w3.org/2000/svg'
|
|
};
|
|
|
|
/**
|
|
* DOM parsing utility
|
|
*/
|
|
|
|
var SVG_START = '<svg xmlns="' + ns.svg + '"';
|
|
|
|
function parse$1(svg) {
|
|
|
|
var unwrap = false;
|
|
|
|
// ensure we import a valid svg document
|
|
if (svg.substring(0, 4) === '<svg') {
|
|
if (svg.indexOf(ns.svg) === -1) {
|
|
svg = SVG_START + svg.substring(4);
|
|
}
|
|
} else {
|
|
// namespace svg
|
|
svg = SVG_START + '>' + svg + '</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(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 = create('svg');
|
|
|
|
function extend(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 = node.createSVGMatrix();
|
|
|
|
switch (arguments.length) {
|
|
case 0:
|
|
return matrix;
|
|
case 1:
|
|
return extend(matrix, a);
|
|
case 6:
|
|
return extend(matrix, {
|
|
a: a,
|
|
b: b,
|
|
c: c,
|
|
d: d,
|
|
e: e,
|
|
f: f
|
|
});
|
|
}
|
|
}
|
|
|
|
function createTransform(matrix) {
|
|
if (matrix) {
|
|
return node.createSVGTransformFromMatrix(matrix);
|
|
} else {
|
|
return node.createSVGTransform();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Serialization util
|
|
*/
|
|
|
|
var TEXT_ENTITIES = /([&<>]{1})/g;
|
|
var ATTR_ENTITIES = /([\n\r"]{1})/g;
|
|
|
|
var ENTITY_REPLACEMENT = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '\''
|
|
};
|
|
|
|
function escape(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(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(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('</', node.tagName, '>');
|
|
} else {
|
|
output.push('/>');
|
|
}
|
|
break;
|
|
|
|
// COMMENT
|
|
case 8:
|
|
output.push('<!--', escape(node.nodeValue, TEXT_ENTITIES), '-->');
|
|
break;
|
|
|
|
// CDATA
|
|
case 4:
|
|
output.push('<![CDATA[', node.nodeValue, ']]>');
|
|
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(element, svg) {
|
|
|
|
var parsed = parse$1(svg);
|
|
|
|
// clear element contents
|
|
clear$1(element);
|
|
|
|
if (!svg) {
|
|
return;
|
|
}
|
|
|
|
if (!isFragment(parsed)) {
|
|
// extract <svg> from parsed document
|
|
parsed = parsed.documentElement;
|
|
}
|
|
|
|
var nodes = slice(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(element, svg);
|
|
} catch (e) {
|
|
throw new Error('error parsing SVG: ' + e.message);
|
|
}
|
|
|
|
return element;
|
|
} else {
|
|
return get(element);
|
|
}
|
|
}
|
|
|
|
|
|
function slice(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<SVGTransform|SVGMatrix>} [transforms]
|
|
*
|
|
* @return {SVGTransform} the consolidated transform
|
|
*/
|
|
function transform(node, transforms) {
|
|
var transformList = node.transform.baseVal;
|
|
|
|
if (transforms) {
|
|
|
|
if (!Array.isArray(transforms)) {
|
|
transforms = [ transforms ];
|
|
}
|
|
|
|
setTransforms(transformList, transforms);
|
|
}
|
|
|
|
return transformList.consolidate();
|
|
}
|
|
|
|
var CLASS_PATTERN = /^class /;
|
|
|
|
function isClass(fn) {
|
|
return CLASS_PATTERN.test(fn.toString());
|
|
}
|
|
|
|
function isArray$1(obj) {
|
|
return Object.prototype.toString.call(obj) === '[object Array]';
|
|
}
|
|
|
|
function annotate() {
|
|
var args = Array.prototype.slice.call(arguments);
|
|
|
|
if (args.length === 1 && isArray$1(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 = /^function\s*[^(]*\(\s*([^)]*)\)/m;
|
|
var FN_ARG = /\/\*([^*]*)\*\//m;
|
|
|
|
function parse$2(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 [];
|
|
}
|
|
|
|
return match[1] && match[1].split(',').map(function (arg) {
|
|
match = arg.match(FN_ARG);
|
|
return match ? match[1].trim() : arg.trim();
|
|
}) || [];
|
|
}
|
|
|
|
function Module() {
|
|
var providers = [];
|
|
|
|
this.factory = function (name, factory) {
|
|
providers.push([name, 'factory', factory]);
|
|
return this;
|
|
};
|
|
|
|
this.value = function (name, value) {
|
|
providers.push([name, 'value', value]);
|
|
return this;
|
|
};
|
|
|
|
this.type = function (name, type) {
|
|
providers.push([name, 'type', type]);
|
|
return this;
|
|
};
|
|
|
|
this.forEach = function (iterator) {
|
|
providers.forEach(iterator);
|
|
};
|
|
}
|
|
|
|
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
|
|
|
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
|
|
|
|
function Injector(modules, parent) {
|
|
parent = parent || {
|
|
get: function get(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 error(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 {Object}
|
|
*/
|
|
var get = 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 (hasProp(instances, name)) {
|
|
return instances[name];
|
|
}
|
|
|
|
if (hasProp(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);
|
|
};
|
|
|
|
var fnDef = function fnDef(fn) {
|
|
var locals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
|
|
if (typeof fn !== 'function') {
|
|
if (isArray$1(fn)) {
|
|
fn = annotate(fn.slice());
|
|
} else {
|
|
throw new Error('Cannot invoke "' + fn + '". Expected a function!');
|
|
}
|
|
}
|
|
|
|
var inject = fn.$inject || parse$2(fn);
|
|
var dependencies = inject.map(function (dep) {
|
|
if (hasProp(locals, dep)) {
|
|
return locals[dep];
|
|
} else {
|
|
return get(dep);
|
|
}
|
|
});
|
|
|
|
return {
|
|
fn: fn,
|
|
dependencies: dependencies
|
|
};
|
|
};
|
|
|
|
var instantiate = function instantiate(Type) {
|
|
var _fnDef = fnDef(Type),
|
|
dependencies = _fnDef.dependencies,
|
|
fn = _fnDef.fn;
|
|
|
|
return new (Function.prototype.bind.apply(fn, [null].concat(_toConsumableArray(dependencies))))();
|
|
};
|
|
|
|
var invoke = function invoke(func, context, locals) {
|
|
var _fnDef2 = fnDef(func, locals),
|
|
dependencies = _fnDef2.dependencies,
|
|
fn = _fnDef2.fn;
|
|
|
|
return fn.call.apply(fn, [context].concat(_toConsumableArray(dependencies)));
|
|
};
|
|
|
|
var createPrivateInjectorFactory = function createPrivateInjectorFactory(privateChildInjector) {
|
|
return annotate(function (key) {
|
|
return privateChildInjector.get(key);
|
|
});
|
|
};
|
|
|
|
var createChild = 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(_value) {
|
|
return _value;
|
|
}
|
|
};
|
|
|
|
modules.forEach(function (module) {
|
|
|
|
function arrayUnwrap(type, value) {
|
|
if (type !== 'value' && isArray$1(value)) {
|
|
value = annotate(value.slice());
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// TODO(vojta): handle wrong inputs (modules)
|
|
if (module instanceof Module) {
|
|
module.forEach(function (provider) {
|
|
var name = provider[0];
|
|
var type = provider[1];
|
|
var value = provider[2];
|
|
|
|
providers[name] = [factoryMap[type], arrayUnwrap(type, value), type];
|
|
});
|
|
} else if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') {
|
|
if (module.__exports__) {
|
|
var clonedModule = Object.keys(module).reduce(function (m, key) {
|
|
if (key.substring(0, 2) !== '__') {
|
|
m[key] = module[key];
|
|
}
|
|
return m;
|
|
}, Object.create(null));
|
|
|
|
var privateInjector = new Injector((module.__modules__ || []).concat([clonedModule]), self);
|
|
var getFromPrivateInjector = annotate(function (key) {
|
|
return privateInjector.get(key);
|
|
});
|
|
module.__exports__.forEach(function (key) {
|
|
providers[key] = [getFromPrivateInjector, key, 'private', privateInjector];
|
|
});
|
|
} else {
|
|
Object.keys(module).forEach(function (name) {
|
|
if (module[name][2] === 'private') {
|
|
providers[name] = module[name];
|
|
return;
|
|
}
|
|
|
|
var type = module[name][0];
|
|
var value = module[name][1];
|
|
|
|
providers[name] = [factoryMap[type], arrayUnwrap(type, value), type];
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// public API
|
|
this.get = get;
|
|
this.invoke = invoke;
|
|
this.instantiate = instantiate;
|
|
this.createChild = createChild;
|
|
}
|
|
|
|
// helpers /////////////////
|
|
|
|
function hasProp(obj, prop) {
|
|
return Object.hasOwnProperty.call(obj, prop);
|
|
}
|
|
|
|
var DEFAULT_RENDER_PRIORITY = 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;
|
|
|
|
eventBus.on([ 'render.shape', 'render.connection' ], renderPriority, function(evt, context) {
|
|
var type = evt.type,
|
|
element = context.element,
|
|
visuals = context.gfx;
|
|
|
|
if (self.canRender(element)) {
|
|
if (type === 'render.shape') {
|
|
return self.drawShape(visuals, element);
|
|
} else {
|
|
return self.drawConnection(visuals, element);
|
|
}
|
|
}
|
|
});
|
|
|
|
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() {};
|
|
|
|
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('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;
|
|
}
|
|
|
|
/**
|
|
* Returns the surrounding bbox for all elements in
|
|
* the array or the element primitive.
|
|
*
|
|
* @param {Array<djs.model.Shape>|djs.model.Shape} elements
|
|
* @param {Boolean} stopRecursion
|
|
*/
|
|
function getBBox(elements, stopRecursion) {
|
|
|
|
stopRecursion = !!stopRecursion;
|
|
if (!isArray(elements)) {
|
|
elements = [elements];
|
|
}
|
|
|
|
var minX,
|
|
minY,
|
|
maxX,
|
|
maxY;
|
|
|
|
forEach(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);
|
|
}
|
|
|
|
// apply default renderer with lowest possible priority
|
|
// so that it only kicks in if noone else could render
|
|
var DEFAULT_RENDER_PRIORITY$1 = 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$1);
|
|
|
|
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 });
|
|
}
|
|
|
|
inherits_browser(DefaultRenderer, BaseRenderer);
|
|
|
|
|
|
DefaultRenderer.prototype.canRender = function() {
|
|
return true;
|
|
};
|
|
|
|
DefaultRenderer.prototype.drawShape = function drawShape(visuals, element) {
|
|
var rect = create('rect');
|
|
|
|
attr$1(rect, {
|
|
x: 0,
|
|
y: 0,
|
|
width: element.width || 0,
|
|
height: element.height || 0
|
|
});
|
|
|
|
if (isFrameElement(element)) {
|
|
attr$1(rect, this.FRAME_STYLE);
|
|
} else {
|
|
attr$1(rect, this.SHAPE_STYLE);
|
|
}
|
|
|
|
append(visuals, rect);
|
|
|
|
return rect;
|
|
};
|
|
|
|
DefaultRenderer.prototype.drawConnection = function drawConnection(visuals, connection) {
|
|
|
|
var line = createLine(connection.waypoints, this.CONNECTION_STYLE);
|
|
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<String>} 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<String>} traits
|
|
* @param {Object} additionalAttrs
|
|
*
|
|
* @return {Object} the style defintion
|
|
*/
|
|
this.style = function(traits, additionalAttrs) {
|
|
|
|
if (!isArray(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(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<Object>} [collection]
|
|
* @param {Object} [element]
|
|
*
|
|
* @return {Number} the previous index of the element
|
|
*/
|
|
function remove$2(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<Object>} 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;
|
|
}
|
|
|
|
/**
|
|
* 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 <div> around the svg element with the respective size
|
|
// this way we can always get the correct container size
|
|
// (this is impossible for <svg> elements at the moment)
|
|
var parent = document.createElement('div');
|
|
parent.setAttribute('class', 'djs-container');
|
|
|
|
assign(parent.style, {
|
|
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('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';
|
|
|
|
|
|
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._init(config || {});
|
|
}
|
|
|
|
Canvas.$inject = [
|
|
'config.canvas',
|
|
'eventBus',
|
|
'graphicsFactory',
|
|
'elementRegistry'
|
|
];
|
|
|
|
|
|
Canvas.prototype._init = function(config) {
|
|
|
|
var eventBus = this._eventBus;
|
|
|
|
// Creates a <svg> element that is wrapped into a <div>.
|
|
// 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)
|
|
//
|
|
// <div class="djs-container" style="width: {desired-width}, height: {desired-height}">
|
|
// <svg width="100%" height="100%">
|
|
// ...
|
|
// </svg>
|
|
// </div>
|
|
|
|
// html container
|
|
var container = this._container = createContainer(config);
|
|
|
|
var svg = this._svg = create('svg');
|
|
attr$1(svg, { width: '100%', height: '100%' });
|
|
|
|
append(container, svg);
|
|
|
|
var viewport = this._viewport = createGroup(svg, 'viewport');
|
|
|
|
this._layers = {};
|
|
|
|
// 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'
|
|
], 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._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.setRootElement(null, true);
|
|
} else {
|
|
self._removeElement(element, type);
|
|
}
|
|
});
|
|
|
|
// 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, 0);
|
|
};
|
|
|
|
/**
|
|
* 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;
|
|
};
|
|
|
|
/**
|
|
* 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 (!index) {
|
|
index = 0;
|
|
}
|
|
|
|
var childIndex = reduce(this._layers, function(childIndex, layer) {
|
|
if (index >= layer.index) {
|
|
childIndex++;
|
|
}
|
|
|
|
return childIndex;
|
|
}, 0);
|
|
|
|
return {
|
|
group: createGroup(this._viewport, 'layer-' + name, childIndex),
|
|
index: index
|
|
};
|
|
|
|
};
|
|
|
|
/**
|
|
* 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([ 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; // <g class="... some-marker"> ... </g>
|
|
*
|
|
* @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);
|
|
}
|
|
};
|
|
|
|
Canvas.prototype.getRootElement = function() {
|
|
if (!this._rootElement) {
|
|
this.setRootElement({ id: '__implicitroot', children: [] });
|
|
}
|
|
|
|
return this._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} element
|
|
* @param {Boolean} [override] whether to override the current root element, if any
|
|
*
|
|
* @return {Object|djs.model.Root} new root element
|
|
*/
|
|
Canvas.prototype.setRootElement = function(element, override) {
|
|
|
|
if (element) {
|
|
this._ensureValid('root', element);
|
|
}
|
|
|
|
var currentRoot = this._rootElement,
|
|
elementRegistry = this._elementRegistry,
|
|
eventBus = this._eventBus;
|
|
|
|
if (currentRoot) {
|
|
if (!override) {
|
|
throw new Error('rootElement already set, need to specify override');
|
|
}
|
|
|
|
// simulate element remove event sequence
|
|
eventBus.fire('root.remove', { element: currentRoot });
|
|
eventBus.fire('root.removed', { element: currentRoot });
|
|
|
|
elementRegistry.remove(currentRoot);
|
|
}
|
|
|
|
if (element) {
|
|
var gfx = this.getDefaultLayer();
|
|
|
|
// resemble element add event sequence
|
|
eventBus.fire('root.add', { element: element });
|
|
|
|
elementRegistry.add(element, gfx, this._svg);
|
|
|
|
eventBus.fire('root.added', { element: element, gfx: gfx });
|
|
}
|
|
|
|
this._rootElement = element;
|
|
|
|
return element;
|
|
};
|
|
|
|
|
|
|
|
// 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 with id ' + 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$2(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,
|
|
transform$1,
|
|
scale,
|
|
x, y;
|
|
|
|
if (!box) {
|
|
// compute the inner box based on the
|
|
// diagrams default layer. This allows us to exclude
|
|
// external components, such as overlays
|
|
innerBox = this.getDefaultLayer().getBBox();
|
|
|
|
transform$1 = transform(viewport);
|
|
matrix = transform$1 ? transform$1.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,
|
|
height: innerBox.height,
|
|
x: innerBox.x,
|
|
y: innerBox.y
|
|
},
|
|
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(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 };
|
|
};
|
|
|
|
|
|
/**
|
|
* 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);
|
|
};
|
|
|
|
/**
|
|
* 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<djs.model.Base>}
|
|
*/
|
|
ElementRegistry.prototype.filter = function(fn) {
|
|
|
|
var filtered = [];
|
|
|
|
this.forEach(function(element, gfx) {
|
|
if (fn(element, gfx)) {
|
|
filtered.push(element);
|
|
}
|
|
});
|
|
|
|
return filtered;
|
|
};
|
|
|
|
/**
|
|
* Return all rendered model elements.
|
|
*
|
|
* @return {Array<djs.model.Base>}
|
|
*/
|
|
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); // <g ...>
|
|
*
|
|
* elementRegistry.getGraphics(rootElement, true); // <svg ...>
|
|
*
|
|
*
|
|
* @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');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 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<Object>} collection
|
|
* @param {Refs} refs instance
|
|
* @param {Object} property represented by the collection
|
|
* @param {Object} target object the collection is attached to
|
|
*
|
|
* @return {RefsCollection<Object>} the extended array
|
|
*/
|
|
function extend$1(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;
|
|
}
|
|
|
|
var extend_1 = extend$1;
|
|
|
|
var isExtended_1 = isExtended;
|
|
|
|
var collection = {
|
|
extend: extend_1,
|
|
isExtended: isExtended_1
|
|
};
|
|
|
|
function hasOwnProperty(e, property) {
|
|
return Object.prototype.hasOwnProperty.call(e, property.name || property);
|
|
}
|
|
|
|
function defineCollectionProperty(ref, property, target) {
|
|
|
|
var collection$1 = collection.extend(target[property.name] || [], ref, property, target);
|
|
|
|
Object.defineProperty(target, property.name, {
|
|
enumerable: property.enumerable,
|
|
value: collection$1
|
|
});
|
|
|
|
if (collection$1.length) {
|
|
|
|
collection$1.forEach(function(o) {
|
|
ref.set(o, property.inverse, target);
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
function defineProperty(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.
|
|
*
|
|
* <p>
|
|
* When bound to an object using {@link Refs#bind} the references
|
|
* get activated and ensure that add and remove operations are applied
|
|
* reversely, too.
|
|
* </p>
|
|
*
|
|
* <p>
|
|
* 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.
|
|
* </p>
|
|
*
|
|
* @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(a, b) {
|
|
|
|
if (!(this instanceof Refs)) {
|
|
return new Refs(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.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(this, property, target);
|
|
}
|
|
};
|
|
|
|
Refs.prototype.ensureRefsCollection = function(target, property) {
|
|
|
|
var collection$1 = target[property.name];
|
|
|
|
if (!collection.isExtended(collection$1)) {
|
|
defineCollectionProperty(this, property, target);
|
|
}
|
|
|
|
return collection$1;
|
|
};
|
|
|
|
Refs.prototype.ensureBound = function(target, property) {
|
|
if (!hasOwnProperty(target, property)) {
|
|
this.bind(target, property);
|
|
}
|
|
};
|
|
|
|
Refs.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.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;
|
|
|
|
var objectRefs = refs;
|
|
|
|
var Collection = collection;
|
|
objectRefs.Collection = Collection;
|
|
|
|
var parentRefs = new objectRefs({ name: 'children', enumerable: true, collection: true }, { name: 'parent' }),
|
|
labelRefs = new objectRefs({ name: 'labels', enumerable: true, collection: true }, { name: 'labelTarget' }),
|
|
attacherRefs = new objectRefs({ name: 'attachers', collection: true }, { name: 'host' }),
|
|
outgoingRefs = new objectRefs({ name: 'outgoing', collection: true }, { name: 'source' }),
|
|
incomingRefs = new objectRefs({ name: 'incoming', collection: true }, { name: 'target' });
|
|
|
|
/**
|
|
* @namespace djs.model
|
|
*/
|
|
|
|
/**
|
|
* @memberOf djs.model
|
|
*/
|
|
|
|
/**
|
|
* The basic graphical representation
|
|
*
|
|
* @class
|
|
*
|
|
* @abstract
|
|
*/
|
|
function Base() {
|
|
|
|
/**
|
|
* 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<Connection>
|
|
*/
|
|
outgoingRefs.bind(this, 'outgoing');
|
|
|
|
/**
|
|
* The list of incoming connections
|
|
*
|
|
* @name Base#incoming
|
|
* @type Array<Connection>
|
|
*/
|
|
incomingRefs.bind(this, 'incoming');
|
|
}
|
|
|
|
|
|
/**
|
|
* A graphical object
|
|
*
|
|
* @class
|
|
* @constructor
|
|
*
|
|
* @extends Base
|
|
*/
|
|
function Shape() {
|
|
Base.call(this);
|
|
|
|
/**
|
|
* Indicates frame shapes
|
|
*
|
|
* @name Shape#isFrame
|
|
* @type Boolean
|
|
*/
|
|
|
|
/**
|
|
* The list of children
|
|
*
|
|
* @name Shape#children
|
|
* @type Array<Base>
|
|
*/
|
|
parentRefs.bind(this, 'children');
|
|
|
|
/**
|
|
* @name Shape#host
|
|
* @type Shape
|
|
*/
|
|
attacherRefs.bind(this, 'host');
|
|
|
|
/**
|
|
* @name Shape#attachers
|
|
* @type Shape
|
|
*/
|
|
attacherRefs.bind(this, 'attachers');
|
|
}
|
|
|
|
inherits_browser(Shape, Base);
|
|
|
|
|
|
/**
|
|
* A root graphical object
|
|
*
|
|
* @class
|
|
* @constructor
|
|
*
|
|
* @extends Shape
|
|
*/
|
|
function Root() {
|
|
Shape.call(this);
|
|
}
|
|
|
|
inherits_browser(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');
|
|
}
|
|
|
|
inherits_browser(Label, Shape);
|
|
|
|
|
|
/**
|
|
* A connection between two elements
|
|
*
|
|
* @class
|
|
* @constructor
|
|
*
|
|
* @extends Base
|
|
*/
|
|
function Connection() {
|
|
Base.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');
|
|
}
|
|
|
|
inherits_browser(Connection, Base);
|
|
|
|
|
|
var types = {
|
|
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$1(type, attrs) {
|
|
var Type = types[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$1(type, attrs);
|
|
};
|
|
|
|
var FN_REF = '__fn';
|
|
|
|
var DEFAULT_PRIORITY = 1000;
|
|
|
|
var slice$1 = 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<String>} 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(events) ? events : [ events ];
|
|
|
|
if (isFunction(priority)) {
|
|
that = callback;
|
|
callback = priority;
|
|
priority = DEFAULT_PRIORITY;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (!isNumber(priority)) {
|
|
throw new Error('priority must be a number');
|
|
}
|
|
|
|
function wrappedCallback() {
|
|
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<String>} events
|
|
* @param {Function} [callback]
|
|
*/
|
|
EventBus.prototype.off = function(events, callback) {
|
|
|
|
events = isArray(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$1.call(arguments);
|
|
|
|
if (typeof type === 'object') {
|
|
event = type;
|
|
type = event.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;
|
|
|
|
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 (e) {
|
|
if (!this.handleError(e)) {
|
|
console.error('unhandled error in event listener');
|
|
console.error(e.stack);
|
|
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
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<Object>} 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<SVGElement>} gfx
|
|
*
|
|
* @return {Snap<SVGElement>}
|
|
*/
|
|
function getVisual(gfx) {
|
|
return query('.djs-visual', gfx);
|
|
}
|
|
|
|
/**
|
|
* Returns the children for a given diagram element.
|
|
*
|
|
* @param {Snap<SVGElement>} gfx
|
|
* @return {Snap<SVGElement>}
|
|
*/
|
|
function getChildren(gfx) {
|
|
return gfx.parentNode.childNodes[1];
|
|
}
|
|
|
|
/**
|
|
* @param {<SVGElement>} element
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
* @param {Number} angle
|
|
* @param {Number} amount
|
|
*/
|
|
function transform$1(gfx, x, y, angle, amount) {
|
|
var translate = createTransform();
|
|
translate.setTranslate(x, y);
|
|
|
|
var rotate = createTransform();
|
|
rotate.setRotate(angle, 0, 0);
|
|
|
|
var scale = createTransform();
|
|
scale.setScale(amount || 1, amount || 1);
|
|
|
|
transform(gfx, [ translate, rotate, scale ]);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {SVGElement} element
|
|
* @param {Number} x
|
|
* @param {Number} y
|
|
*/
|
|
function translate(gfx, x, y) {
|
|
var translate = createTransform();
|
|
translate.setTranslate(x, y);
|
|
|
|
transform(gfx, translate);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param {SVGElement} element
|
|
* @param {Number} angle
|
|
*/
|
|
function rotate(gfx, angle) {
|
|
var rotate = createTransform();
|
|
rotate.setRotate(angle, 0, 0);
|
|
|
|
transform(gfx, rotate);
|
|
}
|
|
|
|
/**
|
|
* 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._getChildren = 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('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 <g class="djs-visual" /> 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:
|
|
*
|
|
* <g class="djs-group">
|
|
*
|
|
* <!-- the gfx -->
|
|
* <g class="djs-element djs-(shape|connection|frame)">
|
|
* <g class="djs-visual">
|
|
* <!-- the renderer draws in here -->
|
|
* </g>
|
|
*
|
|
* <!-- extensions (overlays, click box, ...) goes here
|
|
* </g>
|
|
*
|
|
* <!-- the gfx child nodes -->
|
|
* <g class="djs-children"></g>
|
|
* </g>
|
|
*
|
|
* @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('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('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('g');
|
|
classes$1(visual).add('djs-visual');
|
|
|
|
append(gfx, visual);
|
|
|
|
return gfx;
|
|
};
|
|
|
|
GraphicsFactory.prototype.create = function(type, element, parentIndex) {
|
|
var childrenGfx = this._getChildren(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(parents, function(parent) {
|
|
|
|
var children = parent.children;
|
|
|
|
if (!children) {
|
|
return;
|
|
}
|
|
|
|
var childGfx = self._getChildren(parent);
|
|
|
|
forEach(children.slice().reverse(), function(c) {
|
|
var gfx = elementRegistry.getGraphics(c);
|
|
|
|
prependTo(gfx.parentNode, childGfx);
|
|
});
|
|
});
|
|
};
|
|
|
|
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(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$1(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 ]
|
|
};
|
|
|
|
/**
|
|
* Bootstrap an injector from a list of modules, instantiating a number of default components
|
|
*
|
|
* @ignore
|
|
* @param {Array<didi.Module>} bootstrapModules
|
|
*
|
|
* @return {didi.Injector} a injector to use to access the components
|
|
*/
|
|
function bootstrap(bootstrapModules) {
|
|
|
|
var modules = [],
|
|
components = [];
|
|
|
|
function hasModule(m) {
|
|
return modules.indexOf(m) >= 0;
|
|
}
|
|
|
|
function addModule(m) {
|
|
modules.push(m);
|
|
}
|
|
|
|
function visit(m) {
|
|
if (hasModule(m)) {
|
|
return;
|
|
}
|
|
|
|
(m.__depends__ || []).forEach(visit);
|
|
|
|
if (hasModule(m)) {
|
|
return;
|
|
}
|
|
|
|
addModule(m);
|
|
|
|
(m.__init__ || []).forEach(function(c) {
|
|
components.push(c);
|
|
});
|
|
}
|
|
|
|
bootstrapModules.forEach(visit);
|
|
|
|
var injector = new Injector(modules);
|
|
|
|
components.forEach(function(c) {
|
|
|
|
try {
|
|
// eagerly resolve component (fn or string)
|
|
injector[typeof c === 'string' ? 'get' : 'invoke'](c);
|
|
} catch (e) {
|
|
console.error('Failed to instantiate component');
|
|
console.error(e.stack);
|
|
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
return injector;
|
|
}
|
|
|
|
/**
|
|
* Creates an injector from passed options.
|
|
*
|
|
* @ignore
|
|
* @param {Object} options
|
|
* @return {didi.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<didi.Module> to the constructor.
|
|
*
|
|
* @class djs.Diagram
|
|
* @memberOf djs
|
|
* @constructor
|
|
*
|
|
* @example
|
|
*
|
|
* <caption>Creating a plug-in that logs whenever a shape is added to the canvas.</caption>
|
|
*
|
|
* // 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<didi.Module>} [options.modules] external modules to instantiate with the diagram
|
|
* @param {didi.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$1() { }
|
|
|
|
Base$1.prototype.get = function(name) {
|
|
return this.$model.properties.get(this, name);
|
|
};
|
|
|
|
Base$1.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$1.prototype);
|
|
|
|
// initialize default values
|
|
forEach(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(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, 10); }
|
|
};
|
|
|
|
/**
|
|
* 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 <prefix:localName> or <localName>, 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(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<Package>} packages
|
|
* @param {Properties} properties
|
|
*/
|
|
function Registry(packages, properties) {
|
|
this.packageMap = {};
|
|
this.typeMap = {};
|
|
|
|
this.packages = [];
|
|
|
|
this.properties = properties;
|
|
|
|
forEach(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(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(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(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(type.superClass, trait ? traverseTrait : traverseSuper);
|
|
|
|
// call iterator with (type, inherited=!trait)
|
|
iterator(type, !trait);
|
|
|
|
forEach(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$1(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$1(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$1(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) {
|
|
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$1(val) {
|
|
return typeof val === 'undefined';
|
|
}
|
|
|
|
function defineProperty$1(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<Package>} 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 });
|
|
|
|
forEach(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$1 = 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$1.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 = 'xsi:type';
|
|
|
|
var NON_WHITESPACE_OUTSIDE_ROOT_NODE = 'non-whitespace outside of root node';
|
|
|
|
function error(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<string, ?>=} 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<string, string>}}
|
|
*/
|
|
var nsUriToPrefix;
|
|
|
|
/**
|
|
* Handle parse error.
|
|
*
|
|
* @param {string|Error} err
|
|
*/
|
|
function handleError(err) {
|
|
if (!(err instanceof Error)) {
|
|
err = error(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(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('required args <name, cb>');
|
|
}
|
|
|
|
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; // <!XXXXX zzzz="eeee">
|
|
case 'question': onQuestion = cb; break; // <? .... ?>
|
|
case 'comment': onComment = cb; break;
|
|
default:
|
|
throw error('unsupported event: ' + name);
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Set the namespace to prefix mapping.
|
|
*
|
|
* @example
|
|
*
|
|
* parser.ns({
|
|
* 'http://foo': 'foo',
|
|
* 'http://bar': 'bar'
|
|
* });
|
|
*
|
|
* @param {!Object<string, string>} nsMap
|
|
*
|
|
* @return {Parser}
|
|
*/
|
|
this['ns'] = function(nsMap) {
|
|
|
|
if (typeof nsMap === 'undefined') {
|
|
nsMap = {};
|
|
}
|
|
|
|
if (typeof nsMap !== 'object') {
|
|
throw error('required args <nsMap={}>');
|
|
}
|
|
|
|
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('required args <xml=string>');
|
|
}
|
|
|
|
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,
|
|
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) {
|
|
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) {
|
|
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) {
|
|
console.log(i - startOfLine);
|
|
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) { // "!"
|
|
w = xml.charCodeAt(i+2);
|
|
if (w === 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;
|
|
}
|
|
|
|
|
|
if (w === 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;
|
|
}
|
|
|
|
j = xml.indexOf('>', i + 1);
|
|
if (j === -1) {
|
|
return handleError('unclosed tag');
|
|
}
|
|
|
|
if (onAttention) {
|
|
onAttention(xml.substring(i, j + 1), decodeEntities, getContext);
|
|
if (parseStop) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
j += 1;
|
|
continue;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
j = xml.indexOf('>', i + 1);
|
|
|
|
if (j == -1) {
|
|
return handleError('unclosed tag');
|
|
}
|
|
|
|
// don't process attributes;
|
|
// there are none
|
|
cachedAttrs = {};
|
|
|
|
// if (xml.charCodeAt(i+1) === 47) { // </...
|
|
if (w === 47) { // </...
|
|
tagStart = false;
|
|
tagEnd = true;
|
|
|
|
if (!nodeStack.length) {
|
|
return handleError('missing open tag');
|
|
}
|
|
|
|
// verify open <-> 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'
|
|
};
|
|
|
|
var XSI_TYPE$1 = 'xsi:type';
|
|
|
|
function serializeFormat(element) {
|
|
return element.xml && element.xml.serialize;
|
|
}
|
|
|
|
function serializeAsType(element) {
|
|
return serializeFormat(element) === XSI_TYPE$1;
|
|
}
|
|
|
|
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$1(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;
|
|
}
|
|
|
|
function defer(fn) {
|
|
setTimeout(fn, 0);
|
|
}
|
|
|
|
/**
|
|
* 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$1('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$1('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$1('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$1('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(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(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) {
|
|
|
|
if (serializeAsType(property)) {
|
|
elementTypeName = node.attributes[XSI_TYPE$1];
|
|
|
|
// 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$1('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$1('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);
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse the given XML into a moddle document tree.
|
|
*
|
|
* @param {String} xml
|
|
* @param {ElementHandler|Object} options or rootHandler
|
|
* @param {Function} done
|
|
*/
|
|
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$1(message);
|
|
}
|
|
}
|
|
|
|
function handleWarning(err, getContext) {
|
|
// just like handling errors in <lax=true> 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
|
|
// <!CDATA[ ... ]> sections and in between tags
|
|
text = text.trim();
|
|
|
|
if (!text) {
|
|
return;
|
|
}
|
|
|
|
handleCData(text, getContext);
|
|
}
|
|
|
|
var uriMap = model.getPackages().reduce(function(uriMap, p) {
|
|
uriMap[p.uri] = p.prefix;
|
|
|
|
return uriMap;
|
|
}, {});
|
|
|
|
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);
|
|
|
|
// deferred parse XML to make loading really ascnchronous
|
|
// this ensures the execution environment (node or browser)
|
|
// is kept responsive and that certain optimization strategies
|
|
// can kick in
|
|
defer(function() {
|
|
var err;
|
|
|
|
try {
|
|
parser.parse(xml);
|
|
|
|
resolveReferences();
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
var element = rootHandler.element;
|
|
|
|
// handle the situation that we could not extract
|
|
// the desired root element from the document
|
|
if (!err && !element) {
|
|
err = error$1('failed to parse document as <' + rootHandler.type.$descriptor.name + '>');
|
|
}
|
|
|
|
done(err, err ? undefined : element, context);
|
|
});
|
|
};
|
|
|
|
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 = '<?xml version="1.0" encoding="UTF-8"?>\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.logUsed = function(ns) {
|
|
var uri = ns.uri;
|
|
|
|
used[uri] = this.byUri(uri);
|
|
};
|
|
|
|
this.getUsed = function(ns) {
|
|
|
|
function isUsed(ns) {
|
|
return used[ns.uri];
|
|
}
|
|
|
|
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 map(namespaces.getUsed(), 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 (!element.hasOwnProperty(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$1(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$1(str, ESCAPE_ATTR_CHARS, ESCAPE_ATTR_MAP);
|
|
}
|
|
|
|
function escapeBody(str) {
|
|
return escape$1(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 + '</' + this.tagName + '>')
|
|
.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('</' + this.tagName + '>')
|
|
.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(element, function(val, key) {
|
|
|
|
var nonNsAttr;
|
|
|
|
if (key === '$body') {
|
|
body.push(new BodySerializer().build({ type: 'String' }, val));
|
|
} else
|
|
if (key === '$children') {
|
|
forEach(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<Object>}
|
|
*/
|
|
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(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(attributes, function(attr) {
|
|
|
|
// do not serialize xsi:type attribute
|
|
// it is set manually based on the actual implementation type
|
|
if (attr.name === XSI_TYPE$1) {
|
|
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(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(value, function(v) {
|
|
body.push(new ValueSerializer(self.addTagName(self.nsPropertyTagName(p))).build(p, v));
|
|
});
|
|
} else
|
|
if (isReference) {
|
|
forEach(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(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) {
|
|
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(properties, function(p) {
|
|
|
|
var value = element.get(p.name);
|
|
|
|
if (p.isReference) {
|
|
|
|
if (!p.isMany) {
|
|
value = value.id;
|
|
}
|
|
else {
|
|
var values = [];
|
|
forEach(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);
|
|
}
|
|
|
|
attrs.push({ name: name, value: value });
|
|
};
|
|
|
|
ElementSerializer.prototype.serializeAttributes = function(writer) {
|
|
var attrs = this.attrs,
|
|
namespaces = this.namespaces;
|
|
|
|
if (namespaces) {
|
|
attrs = getNsAttrs(namespaces).concat(attrs);
|
|
}
|
|
|
|
forEach(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(this.body, function(b) {
|
|
b.serializeTo(writer);
|
|
});
|
|
|
|
if (indent) {
|
|
writer
|
|
.unindent()
|
|
.appendIndent();
|
|
}
|
|
|
|
writer.append('</' + this.tagName + '>');
|
|
}
|
|
|
|
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$1),
|
|
(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);
|
|
|
|
|
|
/**
|
|
* 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
|
|
* @param {Function} done callback that is invoked with (err, result, parseContext)
|
|
* once the import completes
|
|
*/
|
|
BpmnModdle.prototype.fromXML = function(xmlStr, typeName, options, done) {
|
|
|
|
if (!isString(typeName)) {
|
|
done = options;
|
|
options = typeName;
|
|
typeName = 'bpmn:Definitions';
|
|
}
|
|
|
|
if (isFunction(options)) {
|
|
done = options;
|
|
options = {};
|
|
}
|
|
|
|
var reader = new Reader(assign({ model: this, lax: true }, options));
|
|
var rootHandler = reader.handler(typeName);
|
|
|
|
reader.fromXML(xmlStr, rootHandler, done);
|
|
};
|
|
|
|
|
|
/**
|
|
* 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
|
|
* @param {Function} done callback invoked with (err, xmlStr) once the import completes
|
|
*/
|
|
BpmnModdle.prototype.toXML = function(element, options, done) {
|
|
|
|
if (isFunction(options)) {
|
|
done = options;
|
|
options = {};
|
|
}
|
|
|
|
var writer = new Writer(options);
|
|
|
|
var result;
|
|
var err;
|
|
|
|
try {
|
|
result = writer.toXML(element);
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
return done(err, result);
|
|
};
|
|
|
|
var name = "BPMN20";
|
|
var uri = "http://www.omg.org/spec/BPMN/20100524/MODEL";
|
|
var associations = [
|
|
];
|
|
var types$1 = [
|
|
{
|
|
name: "Interface",
|
|
superClass: [
|
|
"RootElement"
|
|
],
|
|
properties: [
|
|
{
|
|
name: "name",
|
|
isAttr: true,
|
|
type: "String"
|
|
},
|
|
{
|
|
name: "operations",
|
|
type: "Operation",
|
|
isMany: true
|
|
},
|
|
{
|
|
name: "implementationRef",
|
|
type: "String",
|
|
isAttr: true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
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",
|
|
type: "String",
|
|
isAttr: true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
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",
|
|
type: "LaneSet",
|
|
isMany: true,
|
|
replaces: "FlowElementsContainer#laneSets"
|
|
},
|
|
{
|
|
name: "flowElements",
|
|
type: "FlowElement",
|
|
isMany: true,
|
|
replaces: "FlowElementsContainer#flowElements"
|
|
},
|
|
{
|
|
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: "applyId",
|
|
isAttr: true,
|
|
type: "String"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
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: "assignment",
|
|
type: "Assignment",
|
|
isMany: true
|
|
},
|
|
{
|
|
name: "sourceRef",
|
|
type: "ItemAwareElement",
|
|
isMany: true,
|
|
isReference: true
|
|
},
|
|
{
|
|
name: "targetRef",
|
|
type: "ItemAwareElement",
|
|
isReference: true
|
|
},
|
|
{
|
|
name: "transformation",
|
|
type: "FormalExpression",
|
|
xml: {
|
|
serialize: "property"
|
|
}
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "DataInput",
|
|
superClass: [
|
|
"ItemAwareElement"
|
|
],
|
|
properties: [
|
|
{
|
|
name: "name",
|
|
isAttr: true,
|
|
type: "String"
|
|
},
|
|
{
|
|
name: "isCollection",
|
|
"default": false,
|
|
isAttr: true,
|
|
type: "Boolean"
|
|
},
|
|
{
|
|
name: "inputSetRef",
|
|
type: "InputSet",
|
|
isVirtual: true,
|
|
isMany: true,
|
|
isReference: true
|
|
},
|
|
{
|
|
name: "inputSetWithOptional",
|
|
type: "InputSet",
|
|
isVirtual: true,
|
|
isMany: true,
|
|
isReference: true
|
|
},
|
|
{
|
|
name: "inputSetWithWhileExecuting",
|
|
type: "InputSet",
|
|
isVirtual: true,
|
|
isMany: 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",
|
|
isVirtual: true,
|
|
isMany: true,
|
|
isReference: true
|
|
},
|
|
{
|
|
name: "outputSetWithOptional",
|
|
type: "OutputSet",
|
|
isVirtual: true,
|
|
isMany: true,
|
|
isReference: true
|
|
},
|
|
{
|
|
name: "outputSetWithWhileExecuting",
|
|
type: "OutputSet",
|
|
isVirtual: true,
|
|
isMany: 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",
|
|
type: "String",
|
|
isBody: true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
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",
|
|
type: "String",
|
|
isAttr: true
|
|
},
|
|
{
|
|
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",
|
|
isVirtual: true,
|
|
isMany: 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",
|
|
isVirtual: true,
|
|
isMany: true,
|
|
isReference: true
|
|
},
|
|
{
|
|
name: "outgoingConversationLinks",
|
|
type: "ConversationLink",
|
|
isVirtual: true,
|
|
isMany: 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",
|
|
isVirtual: true,
|
|
isMany: 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"
|
|
],
|
|
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 = [
|
|
{
|
|
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 prefix$1 = "bpmn";
|
|
var xml = {
|
|
tagAlias: "lowerCase",
|
|
typePrefix: "t"
|
|
};
|
|
var BpmnPackage = {
|
|
name: name,
|
|
uri: uri,
|
|
associations: associations,
|
|
types: types$1,
|
|
enumerations: enumerations,
|
|
prefix: prefix$1,
|
|
xml: xml
|
|
};
|
|
|
|
var name$1 = "BPMNDI";
|
|
var uri$1 = "http://www.omg.org/spec/BPMN/20100524/DI";
|
|
var types$2 = [
|
|
{
|
|
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$1 = [
|
|
{
|
|
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$1 = [
|
|
];
|
|
var prefix$2 = "bpmndi";
|
|
var BpmnDiPackage = {
|
|
name: name$1,
|
|
uri: uri$1,
|
|
types: types$2,
|
|
enumerations: enumerations$1,
|
|
associations: associations$1,
|
|
prefix: prefix$2
|
|
};
|
|
|
|
var name$2 = "DC";
|
|
var uri$2 = "http://www.omg.org/spec/DD/20100524/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 prefix$3 = "dc";
|
|
var associations$2 = [
|
|
];
|
|
var DcPackage = {
|
|
name: name$2,
|
|
uri: uri$2,
|
|
types: types$3,
|
|
prefix: prefix$3,
|
|
associations: associations$2
|
|
};
|
|
|
|
var name$3 = "DI";
|
|
var uri$3 = "http://www.omg.org/spec/DD/20100524/DI";
|
|
var types$4 = [
|
|
{
|
|
name: "DiagramElement",
|
|
isAbstract: true,
|
|
properties: [
|
|
{
|
|
name: "id",
|
|
type: "String",
|
|
isAttr: true,
|
|
isId: true
|
|
},
|
|
{
|
|
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,
|
|
isVirtual: true,
|
|
isMany: 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",
|
|
type: "String",
|
|
isAttr: true,
|
|
isId: true
|
|
},
|
|
{
|
|
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,
|
|
isVirtual: true,
|
|
isMany: 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",
|
|
isVirtual: true,
|
|
isMany: true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "LabeledShape",
|
|
isAbstract: true,
|
|
superClass: [
|
|
"Shape"
|
|
],
|
|
properties: [
|
|
{
|
|
name: "ownedLabel",
|
|
type: "Label",
|
|
isReadOnly: true,
|
|
subsettedProperty: "DiagramElement-ownedElement",
|
|
isVirtual: true,
|
|
isMany: true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "Label",
|
|
isAbstract: true,
|
|
superClass: [
|
|
"Node"
|
|
],
|
|
properties: [
|
|
{
|
|
name: "bounds",
|
|
type: "dc:Bounds"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "Style",
|
|
isAbstract: true,
|
|
properties: [
|
|
{
|
|
name: "id",
|
|
type: "String",
|
|
isAttr: true,
|
|
isId: true
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: "Extension",
|
|
properties: [
|
|
{
|
|
name: "values",
|
|
type: "Element",
|
|
isMany: true
|
|
}
|
|
]
|
|
}
|
|
];
|
|
var associations$3 = [
|
|
];
|
|
var prefix$4 = "di";
|
|
var xml$1 = {
|
|
tagAlias: "lowerCase"
|
|
};
|
|
var DiPackage = {
|
|
name: name$3,
|
|
uri: uri$3,
|
|
types: types$4,
|
|
associations: associations$3,
|
|
prefix: prefix$4,
|
|
xml: xml$1
|
|
};
|
|
|
|
var name$4 = "bpmn.io colors for BPMN";
|
|
var uri$4 = "http://bpmn.io/schema/bpmn/biocolor/1.0";
|
|
var prefix$5 = "bioc";
|
|
var types$5 = [
|
|
{
|
|
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$2 = [
|
|
];
|
|
var associations$4 = [
|
|
];
|
|
var BiocPackage = {
|
|
name: name$4,
|
|
uri: uri$4,
|
|
prefix: prefix$5,
|
|
types: types$5,
|
|
enumerations: enumerations$2,
|
|
associations: associations$4
|
|
};
|
|
|
|
var packages = {
|
|
bpmn: BpmnPackage,
|
|
bpmndi: BpmnDiPackage,
|
|
dc: DcPackage,
|
|
di: DiPackage,
|
|
bioc: BiocPackage
|
|
};
|
|
|
|
function BpmnModdle$1(additionalPackages, options) {
|
|
var pks = assign({}, packages, additionalPackages);
|
|
|
|
return new BpmnModdle(pks, options);
|
|
}
|
|
|
|
function elementToString(e) {
|
|
if (!e) {
|
|
return '<null>';
|
|
}
|
|
|
|
return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />';
|
|
}
|
|
|
|
var diRefs = new objectRefs(
|
|
{ name: 'bpmnElement', enumerable: true },
|
|
{ name: 'di', configurable: true }
|
|
);
|
|
|
|
/**
|
|
* 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 = [];
|
|
|
|
// 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, ctx);
|
|
}
|
|
|
|
function visitRoot(element, diagram) {
|
|
return handler.root(element, diagram);
|
|
}
|
|
|
|
function visitIfDi(element, ctx) {
|
|
|
|
try {
|
|
var gfx = element.di && 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 (bpmnElement.di) {
|
|
logError(
|
|
translate('multiple DI elements defined for {element}', {
|
|
element: elementToString(bpmnElement)
|
|
}),
|
|
{ element: bpmnElement }
|
|
);
|
|
} else {
|
|
diRefs.bind(bpmnElement, 'di');
|
|
bpmnElement.di = di;
|
|
}
|
|
} 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(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
|
|
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')) {
|
|
handleProcess(rootElement, ctx);
|
|
} else if (is(rootElement, 'bpmn:Collaboration')) {
|
|
handleCollaboration(rootElement);
|
|
|
|
// 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(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(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(ioSpecification.dataInputs, contextual(handleDataInput, context));
|
|
forEach(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(flowNode.dataInputAssociations, contextual(handleDataAssociation, context));
|
|
forEach(flowNode.dataOutputAssociations, contextual(handleDataAssociation, context));
|
|
});
|
|
}
|
|
|
|
function handleSequenceFlow(sequenceFlow, context) {
|
|
visitIfDi(sequenceFlow, context);
|
|
}
|
|
|
|
function handleDataElement(dataObject, context) {
|
|
visitIfDi(dataObject, context);
|
|
}
|
|
|
|
function handleBoundaryEvent(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(laneSet.lanes, contextual(handleLane, context));
|
|
}
|
|
|
|
function handleLaneSets(laneSets, context) {
|
|
forEach(laneSets, contextual(handleLaneSet, context));
|
|
}
|
|
|
|
function handleFlowElementsContainer(container, context) {
|
|
handleFlowElements(container.flowElements, context);
|
|
|
|
if (container.laneSets) {
|
|
handleLaneSets(container.laneSets, context);
|
|
}
|
|
}
|
|
|
|
function handleFlowElements(flowElements, context) {
|
|
forEach(flowElements, function(e) {
|
|
if (is(e, 'bpmn:SequenceFlow')) {
|
|
deferred.push(function() {
|
|
handleSequenceFlow(e, context);
|
|
});
|
|
} else if (is(e, 'bpmn:BoundaryEvent')) {
|
|
deferred.unshift(function() {
|
|
handleBoundaryEvent(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) {
|
|
|
|
forEach(collaboration.participants, contextual(handleParticipant));
|
|
|
|
handleArtifacts(collaboration.artifacts);
|
|
|
|
// handle message flows latest in the process
|
|
deferred.push(function() {
|
|
handleMessageFlows(collaboration.messageFlows);
|
|
});
|
|
}
|
|
|
|
|
|
function wireFlowNodeRefs(lane) {
|
|
// wire the virtual flowNodeRefs <-> relationship
|
|
forEach(lane.flowNodeRef, function(flowNode) {
|
|
var lanes = flowNode.get('lanes');
|
|
|
|
if (lanes) {
|
|
lanes.push(lane);
|
|
}
|
|
});
|
|
}
|
|
|
|
// API //////////////////////
|
|
|
|
return {
|
|
handleDeferred: handleDeferred,
|
|
handleDefinitions: handleDefinitions,
|
|
handleSubProcess: handleSubProcess,
|
|
registerDi: registerDi
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Import the definitions into a diagram.
|
|
*
|
|
* Errors and warnings are reported through the specified callback.
|
|
*
|
|
* @param {djs.Diagram} diagram
|
|
* @param {ModdleElement<Definitions>} definitions
|
|
* @param {ModdleElement<BPMNDiagram>} [bpmnDiagram] the diagram to be rendered
|
|
* (if not provided, the first one will be rendered)
|
|
* @param {Function} done the callback, invoked with (err, [ warning ]) once the import is done
|
|
*/
|
|
function importBpmnDiagram(diagram, definitions, bpmnDiagram, done) {
|
|
|
|
if (isFunction(bpmnDiagram)) {
|
|
done = bpmnDiagram;
|
|
bpmnDiagram = null;
|
|
}
|
|
|
|
var importer,
|
|
eventBus,
|
|
translate;
|
|
|
|
var error,
|
|
warnings = [];
|
|
|
|
/**
|
|
* Walk the diagram semantically, importing (=drawing)
|
|
* all elements you encounter.
|
|
*
|
|
* @param {ModdleElement<Definitions>} definitions
|
|
* @param {ModdleElement<BPMNDiagram>} bpmnDiagram
|
|
*/
|
|
function render(definitions, bpmnDiagram) {
|
|
|
|
var visitor = {
|
|
|
|
root: function(element) {
|
|
return importer.add(element);
|
|
},
|
|
|
|
element: function(element, parentShape) {
|
|
return importer.add(element, parentShape);
|
|
},
|
|
|
|
error: function(message, context) {
|
|
warnings.push({ message: message, context: context });
|
|
}
|
|
};
|
|
|
|
var walker = new BpmnTreeWalker(visitor, translate);
|
|
|
|
// traverse BPMN 2.0 document model,
|
|
// starting at definitions
|
|
walker.handleDefinitions(definitions, bpmnDiagram);
|
|
}
|
|
|
|
try {
|
|
importer = diagram.get('bpmnImporter');
|
|
eventBus = diagram.get('eventBus');
|
|
translate = diagram.get('translate');
|
|
|
|
eventBus.fire('import.render.start', { definitions: definitions });
|
|
|
|
render(definitions, bpmnDiagram);
|
|
|
|
eventBus.fire('import.render.complete', {
|
|
error: error,
|
|
warnings: warnings
|
|
});
|
|
} catch (e) {
|
|
error = e;
|
|
}
|
|
|
|
done(error, warnings);
|
|
}
|
|
|
|
/**
|
|
* 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 the business object for a given element.
|
|
*
|
|
* @param {djs.model.Base|ModdleElement} element
|
|
*
|
|
* @return {ModdleElement}
|
|
*/
|
|
function getBusinessObject(element) {
|
|
return (element && element.businessObject) || element;
|
|
}
|
|
|
|
function isExpanded(element) {
|
|
|
|
if (is$1(element, 'bpmn:CallActivity')) {
|
|
return false;
|
|
}
|
|
|
|
if (is$1(element, 'bpmn:SubProcess')) {
|
|
return !!getBusinessObject(element).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] || '';
|
|
}
|
|
}
|
|
|
|
// 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 getDi(element) {
|
|
return element.businessObject.di;
|
|
}
|
|
|
|
function getSemantic(element) {
|
|
return element.businessObject;
|
|
}
|
|
|
|
|
|
// color access //////////////////////
|
|
|
|
function getFillColor(element, defaultColor) {
|
|
return getDi(element).get('bioc:fill') || defaultColor || 'white';
|
|
}
|
|
|
|
function getStrokeColor(element, defaultColor) {
|
|
return getDi(element).get('bioc:stroke') || defaultColor || 'black';
|
|
}
|
|
|
|
|
|
// 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);
|
|
}
|
|
|
|
function createCommonjsModule$1(fn, module) {
|
|
return module = { exports: {} }, fn(module, module.exports), module.exports;
|
|
}
|
|
|
|
var hat_1 = createCommonjsModule$1(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;
|
|
|
|
|
|
function BpmnRenderer(
|
|
config, eventBus, styles, pathMap,
|
|
canvas, textRenderer, priority) {
|
|
|
|
BaseRenderer.call(this, eventBus, priority);
|
|
|
|
var defaultFillColor = config && config.defaultFillColor,
|
|
defaultStrokeColor = config && config.defaultStrokeColor;
|
|
|
|
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('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('defs');
|
|
|
|
append(canvas._svg, defs);
|
|
}
|
|
|
|
append(defs, marker);
|
|
|
|
markers[id] = marker;
|
|
}
|
|
|
|
function colorEscape(str) {
|
|
return str.replace(/[()\s,#]+/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('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('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('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('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('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('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('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('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('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('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('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 as(type) {
|
|
return function(parentGfx, element) {
|
|
return handlers[type](parentGfx, element);
|
|
};
|
|
}
|
|
|
|
function renderer(type) {
|
|
return handlers[type];
|
|
}
|
|
|
|
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: getStrokeColor(element, 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: getStrokeColor(element, defaultStrokeColor)
|
|
}
|
|
)
|
|
});
|
|
}
|
|
|
|
function renderLaneLabel(parentGfx, text, element) {
|
|
var textBox = renderLabel(parentGfx, text, {
|
|
box: {
|
|
height: 30,
|
|
width: element.height
|
|
},
|
|
align: 'center-middle',
|
|
style: {
|
|
fill: getStrokeColor(element, defaultStrokeColor)
|
|
}
|
|
});
|
|
|
|
var top = -1 * element.height;
|
|
|
|
transform$1(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: getStrokeColor(element, 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';
|
|
}
|
|
|
|
drawPath(parentGfx, markerPathData, messageAttrs);
|
|
}
|
|
|
|
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, {
|
|
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: getStrokeColor(element, 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: 1,
|
|
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(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(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 - 16) / element.height;
|
|
|
|
var pathData = pathMap.getScaledPath('DATA_OBJECT_COLLECTION_PATH', {
|
|
xScaleFactor: 1,
|
|
yScaleFactor: 1,
|
|
containerWidth: element.width,
|
|
containerHeight: element.height,
|
|
position: {
|
|
mx: 0.451,
|
|
my: yPosition
|
|
}
|
|
});
|
|
|
|
/* collection path */ drawPath(parentGfx, pathData, {
|
|
strokeWidth: 2
|
|
});
|
|
}
|
|
|
|
|
|
// extension API, use at your own risk
|
|
this._drawPath = drawPath;
|
|
|
|
}
|
|
|
|
|
|
inherits_browser(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.handlers[type];
|
|
|
|
/* jshint -W040 */
|
|
return h(parentGfx, element);
|
|
};
|
|
|
|
BpmnRenderer.prototype.drawConnection = function(parentGfx, element) {
|
|
var type = element.type;
|
|
var h = this.handlers[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 = {
|
|
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<String>} 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
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* 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|-)/g),
|
|
part,
|
|
shortenedParts = [],
|
|
length = 0;
|
|
|
|
// try to shorten via spaces + hyphens
|
|
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 === '-') {
|
|
shortenedParts.pop();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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('svg');
|
|
|
|
attr$1(helperSvg, {
|
|
id: 'helper-svg',
|
|
width: 0,
|
|
height: 0,
|
|
style: 'visibility: hidden; position: fixed'
|
|
});
|
|
|
|
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,
|
|
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);
|
|
|
|
var lines = text.split(/\r?\n/g),
|
|
layouted = [];
|
|
|
|
var maxWidth = box.width - padding.left - padding.right;
|
|
|
|
// ensure correct rendering by attaching helper text node to invisible SVG
|
|
var helperText = create('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('text');
|
|
|
|
attr$1(textElement, style);
|
|
|
|
// layout each line taking into account that parent
|
|
// shape might resize to fit text size
|
|
forEach(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('tspan');
|
|
attr$1(tspan, { x: x, y: y });
|
|
|
|
tspan.textContent = line.text;
|
|
|
|
append(textElement, tspan);
|
|
});
|
|
|
|
remove$1(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
|
|
*
|
|
* <h1>Path definition</h1>
|
|
* A parameterized path is defined like this:
|
|
* <pre>
|
|
* '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]
|
|
* }
|
|
* </pre>
|
|
* <p>It's important to specify a correct <b>height and width</b> 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).</p>
|
|
* <p>The '<b>heightElements</b>' and '<b>widthElements</b>' 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 <b>heightElement</b>'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.
|
|
* <ul>
|
|
* <li>The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....</li>
|
|
* <li>The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....</li>
|
|
* </ul>
|
|
* The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index.
|
|
* </p>
|
|
*/
|
|
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 0 15 l 0 -15 ' +
|
|
'm 4 15 l 0 -15 ' +
|
|
'm 4 15 l 0 -15 ',
|
|
height: 61,
|
|
width: 51,
|
|
heightElements: [12],
|
|
widthElements: [1, 6, 12, 15]
|
|
},
|
|
'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.
|
|
* <h1>Use case</h1>
|
|
* <p>Use case is to scale the content of elements (event, gateways) based
|
|
* on the element bounding box's size.
|
|
* </p>
|
|
* <h1>Why not transform</h1>
|
|
* <p>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.</p>
|
|
*
|
|
* @param {String} pathId The ID of the path.
|
|
* @param {Object} param <p>
|
|
* Example param object scales the path to 60% size of the container (data.width, data.height).
|
|
* <pre>
|
|
* {
|
|
* xScaleFactor: 0.6,
|
|
* yScaleFactor:0.6,
|
|
* containerWidth: data.width,
|
|
* containerHeight: data.height,
|
|
* position: {
|
|
* mx: 0.46,
|
|
* my: 0.2,
|
|
* }
|
|
* }
|
|
* </pre>
|
|
* <ul>
|
|
* <li>targetpathwidth = xScaleFactor * containerWidth</li>
|
|
* <li>targetpathheight = yScaleFactor * containerHeight</li>
|
|
* <li>Position is used to set the starting coordinate of the path. M is computed:
|
|
* <ul>
|
|
* <li>position.x * containerWidth</li>
|
|
* <li>position.y * containerHeight</li>
|
|
* </ul>
|
|
* Center of the container <pre> position: {
|
|
* mx: 0.5,
|
|
* my: 0.5,
|
|
* }</pre>
|
|
* Upper left corner of the container
|
|
* <pre> position: {
|
|
* mx: 0.0,
|
|
* my: 0.0,
|
|
* }</pre>
|
|
* </li>
|
|
* </ul>
|
|
* </p>
|
|
*
|
|
*/
|
|
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 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$1(template, replacements) {
|
|
|
|
replacements = replacements || {};
|
|
|
|
return template.replace(/{([^}]+)}/g, function(_, key) {
|
|
return replacements[key] || '{' + key + '}';
|
|
});
|
|
}
|
|
|
|
var TranslateModule = {
|
|
translate: [ 'value', translate$1 ]
|
|
};
|
|
|
|
var DEFAULT_LABEL_SIZE$1 = {
|
|
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<Point>} 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<Point>} 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$1.height / 2
|
|
};
|
|
} else {
|
|
return {
|
|
x: element.x + element.width / 2,
|
|
y: element.y + element.height + DEFAULT_LABEL_SIZE$1.height / 2
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the bounds of an elements label, parsed from the elements DI or
|
|
* generated from its bounds.
|
|
*
|
|
* @param {BpmnElement} semantic
|
|
* @param {djs.model.Base} element
|
|
*/
|
|
function getExternalLabelBounds(semantic, element) {
|
|
|
|
var mid,
|
|
size,
|
|
bounds,
|
|
di = semantic.di,
|
|
label = di.label;
|
|
|
|
if (label && label.bounds) {
|
|
bounds = label.bounds;
|
|
size = {
|
|
width: Math.max(DEFAULT_LABEL_SIZE$1.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$1;
|
|
}
|
|
|
|
return assign({
|
|
x: mid.x - size.width / 2,
|
|
y: mid.y - size.height / 2
|
|
}, size);
|
|
}
|
|
|
|
/**
|
|
* This file contains portions that got extraced from Snap.svg (licensed Apache-2.0).
|
|
*
|
|
* @see https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js
|
|
*/
|
|
|
|
/* eslint no-fallthrough: "off" */
|
|
|
|
var math = Math,
|
|
PI = math.PI;
|
|
|
|
function roundPoint(point) {
|
|
|
|
return {
|
|
x: Math.round(point.x),
|
|
y: Math.round(point.y)
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the mid of the given bounds or point.
|
|
*
|
|
* @param {Bounds|Point} bounds
|
|
*
|
|
* @return {Point}
|
|
*/
|
|
function getMid(bounds) {
|
|
return roundPoint({
|
|
x: bounds.x + (bounds.width || 0) / 2,
|
|
y: bounds.y + (bounds.height || 0) / 2
|
|
});
|
|
}
|
|
|
|
function elementData(semantic, attrs) {
|
|
return assign({
|
|
id: semantic.id,
|
|
type: semantic.$type,
|
|
businessObject: semantic
|
|
}, attrs);
|
|
}
|
|
|
|
function getWaypoints(bo, source, target) {
|
|
|
|
var waypoints = bo.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, parentElement) {
|
|
|
|
var di = semantic.di,
|
|
element,
|
|
translate = this._translate,
|
|
hidden;
|
|
|
|
var parentIndex;
|
|
|
|
// ROOT ELEMENT
|
|
// handle the special case that we deal with a
|
|
// invisible root element (process or collaboration)
|
|
if (is$1(di, 'bpmndi:BPMNPlane')) {
|
|
|
|
// add a virtual element (not being drawn)
|
|
element = this._elementFactory.createRoot(elementData(semantic));
|
|
|
|
this._canvas.setRootElement(element);
|
|
}
|
|
|
|
// SHAPE
|
|
else if (is$1(di, 'bpmndi:BPMNShape')) {
|
|
|
|
var collapsed = !isExpanded(semantic);
|
|
hidden = parentElement && (parentElement.hidden || parentElement.collapsed);
|
|
|
|
var bounds = semantic.di.bounds;
|
|
|
|
element = this._elementFactory.createShape(elementData(semantic, {
|
|
collapsed: collapsed,
|
|
hidden: hidden,
|
|
x: Math.round(bounds.x),
|
|
y: Math.round(bounds.y),
|
|
width: Math.round(bounds.width),
|
|
height: Math.round(bounds.height)
|
|
}));
|
|
|
|
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 wether data store is inside our outside of its semantic parent
|
|
if (!isPointInsideBBox(parentElement, getMid(bounds))) {
|
|
parentElement = this._canvas.getRootElement();
|
|
}
|
|
}
|
|
|
|
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, {
|
|
hidden: hidden,
|
|
source: source,
|
|
target: target,
|
|
waypoints: getWaypoints(semantic, 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 = null;
|
|
}
|
|
|
|
// insert sequence flows behind other flow nodes (cf. #727)
|
|
if (is$1(semantic, 'bpmn:SequenceFlow')) {
|
|
parentIndex = 0;
|
|
}
|
|
|
|
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, 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, element) {
|
|
var bounds,
|
|
text,
|
|
label;
|
|
|
|
bounds = getExternalLabelBounds(semantic, 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, {
|
|
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;
|
|
}
|
|
|
|
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 isPrimaryButton(event) {
|
|
// button === 0 -> left áka primary mouse button
|
|
return !(getOriginal(event) || event).button;
|
|
}
|
|
|
|
function hasPrimaryModifier(event) {
|
|
var originalEvent = getOriginal(event) || event;
|
|
|
|
if (!isPrimaryButton(event)) {
|
|
return false;
|
|
}
|
|
|
|
// Use alt as primary modifier key for mac OS
|
|
if (isMac()) {
|
|
return originalEvent.metaKey;
|
|
} else {
|
|
return originalEvent.ctrlKey;
|
|
}
|
|
}
|
|
|
|
function allowAll(e) { return true; }
|
|
|
|
var LOW_PRIORITY = 500;
|
|
|
|
/**
|
|
* A plugin that provides interaction events for diagram elements.
|
|
*
|
|
* It emits the following events:
|
|
*
|
|
* * element.hover
|
|
* * element.out
|
|
* * element.click
|
|
* * element.dblclick
|
|
* * element.mousedown
|
|
* * element.contextmenu
|
|
*
|
|
* 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 HIT_STYLE = styles.cls('djs-hit', [ 'no-fill', 'no-border' ], {
|
|
stroke: 'white',
|
|
strokeWidth: 15
|
|
});
|
|
|
|
/**
|
|
* 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 = {
|
|
mouseover: 'element.hover',
|
|
mouseout: 'element.out',
|
|
click: 'element.click',
|
|
dblclick: 'element.dblclick',
|
|
mousedown: 'element.mousedown',
|
|
mousemove: 'element.mousemove',
|
|
mouseup: 'element.mouseup',
|
|
contextmenu: 'element.contextmenu'
|
|
};
|
|
|
|
var ignoredFilters = {
|
|
'element.contextmenu': allowAll
|
|
};
|
|
|
|
|
|
// 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 elementSelector = 'svg, .djs-element';
|
|
|
|
// event registration
|
|
|
|
function registerEvent(node, event, localEvent, ignoredFilter) {
|
|
|
|
var handler = handlers[localEvent] = function(event) {
|
|
fire(localEvent, event);
|
|
};
|
|
|
|
if (ignoredFilter) {
|
|
ignoredFilters[localEvent] = ignoredFilter;
|
|
}
|
|
|
|
handler.$delegate = delegateEvents.bind(node, elementSelector, event, handler);
|
|
}
|
|
|
|
function unregisterEvent(node, event, localEvent) {
|
|
|
|
var handler = mouseHandler(localEvent);
|
|
|
|
if (!handler) {
|
|
return;
|
|
}
|
|
|
|
delegateEvents.unbind(node, event, handler.$delegate);
|
|
}
|
|
|
|
function registerEvents(svg) {
|
|
forEach(bindings, function(val, key) {
|
|
registerEvent(svg, key, val);
|
|
});
|
|
}
|
|
|
|
function unregisterEvents(svg) {
|
|
forEach(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);
|
|
});
|
|
|
|
|
|
eventBus.on([ 'shape.added', 'connection.added' ], function(event) {
|
|
var element = event.element,
|
|
gfx = event.gfx,
|
|
hit;
|
|
|
|
if (element.waypoints) {
|
|
hit = createLine(element.waypoints);
|
|
} else {
|
|
hit = create('rect');
|
|
attr$1(hit, {
|
|
x: 0,
|
|
y: 0,
|
|
width: element.width,
|
|
height: element.height
|
|
});
|
|
}
|
|
|
|
attr$1(hit, HIT_STYLE);
|
|
|
|
append(gfx, hit);
|
|
});
|
|
|
|
// 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', LOW_PRIORITY, function(event) {
|
|
|
|
var element = event.element,
|
|
gfx = event.gfx,
|
|
hit = query('.djs-hit', gfx);
|
|
|
|
attr$1(hit, {
|
|
width: element.width,
|
|
height: element.height
|
|
});
|
|
});
|
|
|
|
eventBus.on('connection.changed', function(event) {
|
|
|
|
var element = event.element,
|
|
gfx = event.gfx,
|
|
hit = query('.djs-hit', gfx);
|
|
|
|
updateLine(hit, element.waypoints);
|
|
});
|
|
|
|
|
|
// API
|
|
|
|
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 ]
|
|
};
|
|
|
|
var LOW_PRIORITY$1 = 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('rect');
|
|
|
|
attr$1(outline, assign({
|
|
x: 10,
|
|
y: 10,
|
|
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$1, 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) {
|
|
|
|
this._eventBus = eventBus;
|
|
|
|
this._selectedElements = [];
|
|
|
|
var self = this;
|
|
|
|
eventBus.on([ 'shape.remove', 'connection.remove' ], function(e) {
|
|
var element = e.element;
|
|
self.deselect(element);
|
|
});
|
|
|
|
eventBus.on([ 'diagram.clear' ], function(e) {
|
|
self.select(null);
|
|
});
|
|
}
|
|
|
|
Selection.$inject = [ 'eventBus' ];
|
|
|
|
|
|
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(elements)) {
|
|
elements = elements ? [ elements ] : [];
|
|
}
|
|
|
|
// selection may be cleared by passing an empty array or null
|
|
// to the method
|
|
if (add) {
|
|
forEach(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';
|
|
|
|
|
|
/**
|
|
* A plugin that adds a visible selection UI to shapes and connections
|
|
* by appending the <code>hover</code> and <code>selected</code> classes to them.
|
|
*
|
|
* @class
|
|
*
|
|
* Makes elements selectable, too.
|
|
*
|
|
* @param {EventBus} events
|
|
* @param {SelectionService} selection
|
|
* @param {Canvas} canvas
|
|
*/
|
|
function SelectionVisuals(events, canvas, selection, styles) {
|
|
|
|
this._multiSelectionBox = null;
|
|
|
|
function addMarker(e, cls) {
|
|
canvas.addMarker(e, cls);
|
|
}
|
|
|
|
function removeMarker(e, cls) {
|
|
canvas.removeMarker(e, cls);
|
|
}
|
|
|
|
events.on('element.hover', function(event) {
|
|
addMarker(event.element, MARKER_HOVER);
|
|
});
|
|
|
|
events.on('element.out', function(event) {
|
|
removeMarker(event.element, MARKER_HOVER);
|
|
});
|
|
|
|
events.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(oldSelection, function(e) {
|
|
if (newSelection.indexOf(e) === -1) {
|
|
deselect(e);
|
|
}
|
|
});
|
|
|
|
forEach(newSelection, function(e) {
|
|
if (oldSelection.indexOf(e) === -1) {
|
|
select(e);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
SelectionVisuals.$inject = [
|
|
'eventBus',
|
|
'canvas',
|
|
'selection',
|
|
'styles'
|
|
];
|
|
|
|
function SelectionBehavior(
|
|
eventBus, selection, canvas,
|
|
elementRegistry) {
|
|
|
|
eventBus.on('create.end', 500, function(e) {
|
|
|
|
// select the created shape after a
|
|
// successful create operation
|
|
if (e.context.canExecute) {
|
|
selection.select(e.context.shape);
|
|
}
|
|
});
|
|
|
|
eventBus.on('connect.end', 500, function(e) {
|
|
|
|
// select the connect end target
|
|
// after a connect operation
|
|
if (e.context.canExecute && e.context.target) {
|
|
selection.select(e.context.target);
|
|
}
|
|
});
|
|
|
|
eventBus.on('shape.move.end', 500, function(e) {
|
|
var previousSelection = e.previousSelection || [];
|
|
|
|
var shape = elementRegistry.get(e.context.shape.id);
|
|
|
|
// make sure at least the main moved element is being
|
|
// selected after a move operation
|
|
var inSelection = find(previousSelection, function(selectedShape) {
|
|
return shape.id === selectedShape.id;
|
|
});
|
|
|
|
if (!inSelection) {
|
|
selection.select(shape);
|
|
}
|
|
});
|
|
|
|
// Shift + click selection
|
|
eventBus.on('element.click', function(event) {
|
|
|
|
var element = event.element;
|
|
|
|
// do not select the root element
|
|
// or connections
|
|
if (element === canvas.getRootElement()) {
|
|
element = null;
|
|
}
|
|
|
|
var isSelected = selection.isSelected(element),
|
|
isMultiSelect = selection.get().length > 1;
|
|
|
|
// mouse-event: SELECTION_KEY
|
|
var add = hasPrimaryModifier(event);
|
|
|
|
// select OR deselect element in multi selection
|
|
if (isSelected && isMultiSelect) {
|
|
if (add) {
|
|
return selection.deselect(element);
|
|
} else {
|
|
return selection.select(element);
|
|
}
|
|
} else
|
|
if (!isSelected) {
|
|
selection.select(element, add);
|
|
} else {
|
|
selection.deselect(element);
|
|
}
|
|
});
|
|
}
|
|
|
|
SelectionBehavior.$inject = [
|
|
'eventBus',
|
|
'selection',
|
|
'canvas',
|
|
'elementRegistry'
|
|
];
|
|
|
|
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: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
|
|
* });
|
|
*
|
|
* // or add via shape id
|
|
*
|
|
* overlays.add('some-element-id', {
|
|
* position: {
|
|
* top: -5,
|
|
* left: -5
|
|
* }
|
|
* html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
|
|
* });
|
|
*
|
|
* // or add with optional type
|
|
*
|
|
* overlays.add(someShape, 'badge', {
|
|
* position: {
|
|
* top: -5,
|
|
* left: -5
|
|
* }
|
|
* html: '<div style="width: 10px; background: fuchsia; color: white;">0</div>'
|
|
* });
|
|
*
|
|
*
|
|
* // 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<Object>} 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} [id]
|
|
* @param {Object} [filter]
|
|
*/
|
|
Overlays.prototype.remove = function(filter) {
|
|
|
|
var overlays = this.get(filter) || [];
|
|
|
|
if (!isArray(overlays)) {
|
|
overlays = [ overlays ];
|
|
}
|
|
|
|
var self = this;
|
|
|
|
forEach(overlays, function(overlay) {
|
|
|
|
var container = self._getOverlayContainer(overlay.element, true);
|
|
|
|
if (overlay) {
|
|
remove(overlay.html);
|
|
remove(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);
|
|
};
|
|
|
|
|
|
Overlays.prototype._createOverlayContainer = function(element) {
|
|
var html = domify('<div class="djs-overlays" style="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('<div class="djs-overlay" data-overlay-id="' + id + '" style="position: absolute">');
|
|
|
|
htmlContainer.appendChild(html);
|
|
|
|
if (overlay.type) {
|
|
classes(htmlContainer).add('djs-overlay-' + overlay.type);
|
|
}
|
|
|
|
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,
|
|
minZoom = show && show.minZoom,
|
|
maxZoom = show && show.maxZoom,
|
|
htmlContainer = overlay.htmlContainer,
|
|
visible = true;
|
|
|
|
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(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(overlays, function(o) {
|
|
self.remove(o.id);
|
|
});
|
|
|
|
var container = self._getOverlayContainer(element);
|
|
|
|
if (container) {
|
|
remove(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(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);
|
|
}
|
|
});
|
|
|
|
|
|
// clear overlays with diagram
|
|
|
|
eventBus.on('diagram.clear', this.clear, this);
|
|
};
|
|
|
|
|
|
|
|
// helpers /////////////////////////////
|
|
|
|
function createRoot(parentNode) {
|
|
var root = domify(
|
|
'<div class="djs-overlay-container" style="position: absolute; width: 0; height: 0;" />'
|
|
);
|
|
|
|
parentNode.insertBefore(root, parentNode.firstChild);
|
|
|
|
return root;
|
|
}
|
|
|
|
function setPosition(el, x, y) {
|
|
assign(el.style, { left: x + 'px', top: y + 'px' });
|
|
}
|
|
|
|
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 ]
|
|
};
|
|
|
|
/**
|
|
* This file must not be changed or exchanged.
|
|
*
|
|
* @see http://bpmn.io/license for more information.
|
|
*/
|
|
|
|
|
|
// inlined ../../resources/logo.svg
|
|
var BPMNIO_LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 960"><path fill="#fff" d="M960 60v839c0 33-27 61-60 61H60c-33 0-60-27-60-60V60C0 27 27 0 60 0h839c34 0 61 27 61 60z"/><path fill="#52b415" d="M217 548a205 205 0 0 0-144 58 202 202 0 0 0-4 286 202 202 0 0 0 285 3 200 200 0 0 0 48-219 203 203 0 0 0-185-128zM752 6a206 206 0 0 0-192 285 206 206 0 0 0 269 111 207 207 0 0 0 111-260A204 204 0 0 0 752 6zM62 0A62 62 0 0 0 0 62v398l60 46a259 259 0 0 1 89-36c5-28 10-57 14-85l99 2 12 85a246 246 0 0 1 88 38l70-52 69 71-52 68c17 30 29 58 35 90l86 14-2 100-86 12a240 240 0 0 1-38 89l43 58h413c37 0 60-27 60-61V407a220 220 0 0 1-44 40l21 85-93 39-45-76a258 258 0 0 1-98 1l-45 76-94-39 22-85a298 298 0 0 1-70-69l-86 22-38-94 76-45a258 258 0 0 1-1-98l-76-45 40-94 85 22a271 271 0 0 1 41-47z"/></svg>';
|
|
|
|
var BPMNIO_LOGO_URL = 'data:image/svg+xml,' + encodeURIComponent(BPMNIO_LOGO_SVG);
|
|
|
|
var BPMNIO_IMG = '<img width="52" height="52" src="' + BPMNIO_LOGO_URL + '" />';
|
|
|
|
function css(attrs) {
|
|
return attrs.join(';');
|
|
}
|
|
|
|
var LIGHTBOX_STYLES = css([
|
|
'z-index: 1001',
|
|
'position: fixed',
|
|
'top: 0',
|
|
'left: 0',
|
|
'right: 0',
|
|
'bottom: 0'
|
|
]);
|
|
|
|
var BACKDROP_STYLES = css([
|
|
'width: 100%',
|
|
'height: 100%',
|
|
'background: rgba(0,0,0,0.2)'
|
|
]);
|
|
|
|
var NOTICE_STYLES = css([
|
|
'position: absolute',
|
|
'left: 50%',
|
|
'top: 40%',
|
|
'margin: 0 -130px',
|
|
'width: 260px',
|
|
'padding: 10px',
|
|
'background: white',
|
|
'border: solid 1px #AAA',
|
|
'border-radius: 3px',
|
|
'font-family: Helvetica, Arial, sans-serif',
|
|
'font-size: 14px',
|
|
'line-height: 1.2em'
|
|
]);
|
|
|
|
var LIGHTBOX_MARKUP =
|
|
'<div class="bjs-powered-by-lightbox" style="' + LIGHTBOX_STYLES + '">' +
|
|
'<div class="backdrop" style="' + BACKDROP_STYLES + '"></div>' +
|
|
'<div class="notice" style="' + NOTICE_STYLES + '">' +
|
|
'<a href="http://bpmn.io" target="_blank" style="float: left; margin-right: 10px">' +
|
|
BPMNIO_IMG +
|
|
'</a>' +
|
|
'Web-based tooling for BPMN, DMN and CMMN diagrams ' +
|
|
'powered by <a href="http://bpmn.io" target="_blank">bpmn.io</a>.' +
|
|
'</div>' +
|
|
'</div>';
|
|
|
|
|
|
var lightbox;
|
|
|
|
function open() {
|
|
|
|
if (!lightbox) {
|
|
lightbox = domify(LIGHTBOX_MARKUP);
|
|
|
|
delegateEvents.bind(lightbox, '.backdrop', 'click', function(event) {
|
|
document.body.removeChild(lightbox);
|
|
});
|
|
}
|
|
|
|
document.body.appendChild(lightbox);
|
|
}
|
|
|
|
/**
|
|
* The code in the <project-logo></project-logo> area
|
|
* must not be changed.
|
|
*
|
|
* @see http://bpmn.io/license for more information.
|
|
*/
|
|
|
|
|
|
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>} definitions
|
|
* @param {String} diagramId
|
|
*
|
|
* @return {ModdleElement<BPMNDiagram>|null}
|
|
*/
|
|
function findBPMNDiagram(definitions, diagramId) {
|
|
if (!diagramId) {
|
|
return null;
|
|
}
|
|
|
|
return find(definitions.diagrams, function(element) {
|
|
return element.id === diagramId;
|
|
}) || null;
|
|
}
|
|
|
|
/**
|
|
* 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<didi.Module>} [options.modules] a list of modules to override the default modules
|
|
* @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
|
|
*/
|
|
function Viewer(options) {
|
|
|
|
options = assign({}, DEFAULT_OPTIONS, options);
|
|
|
|
this._moddle = this._createModdle(options);
|
|
|
|
this._container = this._createContainer(options);
|
|
|
|
/* <project-logo> */
|
|
|
|
addProjectLogo(this._container);
|
|
|
|
/* </project-logo> */
|
|
|
|
this._init(this._container, this._moddle, options);
|
|
}
|
|
|
|
inherits_browser(Viewer, Diagram);
|
|
|
|
|
|
/**
|
|
* 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<BPMNDiagram>|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
|
|
* @param {Function} [done] invoked with (err, warnings=[])
|
|
*/
|
|
Viewer.prototype.importXML = function(xml, bpmnDiagram, done) {
|
|
|
|
if (isFunction(bpmnDiagram)) {
|
|
done = bpmnDiagram;
|
|
bpmnDiagram = null;
|
|
}
|
|
|
|
// done is optional
|
|
done = done || function() {};
|
|
|
|
var self = this;
|
|
|
|
// hook in pre-parse listeners +
|
|
// allow xml manipulation
|
|
xml = this._emit('import.parse.start', { xml: xml }) || xml;
|
|
|
|
this._moddle.fromXML(xml, 'bpmn:Definitions', function(err, definitions, context) {
|
|
|
|
// hook in post parse listeners +
|
|
// allow definitions manipulation
|
|
definitions = self._emit('import.parse.complete', {
|
|
error: err,
|
|
definitions: definitions,
|
|
context: context
|
|
}) || definitions;
|
|
|
|
var parseWarnings = context.warnings;
|
|
|
|
if (err) {
|
|
err = checkValidationError(err);
|
|
|
|
self._emit('import.done', { error: err, warnings: parseWarnings });
|
|
|
|
return done(err, parseWarnings);
|
|
}
|
|
|
|
self.importDefinitions(definitions, bpmnDiagram, function(err, importWarnings) {
|
|
var allWarnings = [].concat(parseWarnings, importWarnings || []);
|
|
|
|
self._emit('import.done', { error: err, warnings: allWarnings });
|
|
|
|
done(err, allWarnings);
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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>} definitions parsed BPMN 2.0 definitions
|
|
* @param {ModdleElement<BPMNDiagram>|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
|
|
* @param {Function} [done] invoked with (err, warnings=[])
|
|
*/
|
|
Viewer.prototype.importDefinitions = function(definitions, bpmnDiagram, done) {
|
|
|
|
if (isFunction(bpmnDiagram)) {
|
|
done = bpmnDiagram;
|
|
bpmnDiagram = null;
|
|
}
|
|
|
|
// done is optional
|
|
done = done || function() {};
|
|
|
|
this._setDefinitions(definitions);
|
|
|
|
return this.open(bpmnDiagram, done);
|
|
};
|
|
|
|
/**
|
|
* 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<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open
|
|
* @param {Function} [done] invoked with (err, warnings=[])
|
|
*/
|
|
Viewer.prototype.open = function(bpmnDiagramOrId, done) {
|
|
|
|
if (isFunction(bpmnDiagramOrId)) {
|
|
done = bpmnDiagramOrId;
|
|
bpmnDiagramOrId = null;
|
|
}
|
|
|
|
var definitions = this._definitions;
|
|
var bpmnDiagram = bpmnDiagramOrId;
|
|
|
|
// done is optional
|
|
done = done || function() {};
|
|
|
|
if (!definitions) {
|
|
return done(new Error('no XML imported'));
|
|
}
|
|
|
|
if (typeof bpmnDiagramOrId === 'string') {
|
|
bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
|
|
|
|
if (!bpmnDiagram) {
|
|
return done(new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found'));
|
|
}
|
|
}
|
|
|
|
// clear existing rendered diagram
|
|
// catch synchronous exceptions during #clear()
|
|
try {
|
|
this.clear();
|
|
} catch (error) {
|
|
return done(error);
|
|
}
|
|
|
|
// perform graphical import
|
|
return importBpmnDiagram(this, definitions, bpmnDiagram, done);
|
|
};
|
|
|
|
/**
|
|
* 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 formated XML
|
|
* @param {Boolean} [options.preamble=true] output preamble
|
|
*
|
|
* @param {Function} done invoked with (err, xml)
|
|
*/
|
|
Viewer.prototype.saveXML = function(options, done) {
|
|
|
|
if (!done) {
|
|
done = options;
|
|
options = {};
|
|
}
|
|
|
|
var self = this;
|
|
|
|
var definitions = this._definitions;
|
|
|
|
if (!definitions) {
|
|
return done(new Error('no definitions loaded'));
|
|
}
|
|
|
|
// allow to fiddle around with definitions
|
|
definitions = this._emit('saveXML.start', {
|
|
definitions: definitions
|
|
}) || definitions;
|
|
|
|
this._moddle.toXML(definitions, options, function(err, xml) {
|
|
|
|
try {
|
|
xml = self._emit('saveXML.serialized', {
|
|
error: err,
|
|
xml: xml
|
|
}) || xml;
|
|
|
|
self._emit('saveXML.done', {
|
|
error: err,
|
|
xml: xml
|
|
});
|
|
} catch (e) {
|
|
console.error('error in saveXML life-cycle listener', e);
|
|
}
|
|
|
|
done(err, xml);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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]
|
|
* @param {Function} done invoked with (err, svgStr)
|
|
*/
|
|
Viewer.prototype.saveSVG = function(options, done) {
|
|
|
|
if (!done) {
|
|
done = options;
|
|
options = {};
|
|
}
|
|
|
|
this._emit('saveSVG.start');
|
|
|
|
var svg, err;
|
|
|
|
try {
|
|
var canvas = this.get('canvas');
|
|
|
|
var contentNode = canvas.getDefaultLayer(),
|
|
defsNode = query('defs', canvas._svg);
|
|
|
|
var contents = innerSVG(contentNode),
|
|
defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
|
|
|
|
var bbox = contentNode.getBBox();
|
|
|
|
svg =
|
|
'<?xml version="1.0" encoding="utf-8"?>\n' +
|
|
'<!-- created with bpmn-js / http://bpmn.io -->\n' +
|
|
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
|
|
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
|
|
'width="' + bbox.width + '" height="' + bbox.height + '" ' +
|
|
'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
|
|
defs + contents +
|
|
'</svg>';
|
|
} catch (e) {
|
|
err = e;
|
|
}
|
|
|
|
this._emit('saveSVG.done', {
|
|
error: err,
|
|
svg: svg
|
|
});
|
|
|
|
done(err, svg);
|
|
};
|
|
|
|
/**
|
|
* 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 Viewer#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 Viewer#invoke
|
|
*/
|
|
|
|
|
|
Viewer.prototype._setDefinitions = function(definitions) {
|
|
this._definitions = definitions;
|
|
};
|
|
|
|
Viewer.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 Viewer#clear
|
|
*/
|
|
Viewer.prototype.clear = function() {
|
|
|
|
// remove businessObject#di binding
|
|
//
|
|
// this is necessary, as we establish the bindings
|
|
// in the BpmnTreeWalker (and assume none are given
|
|
// on reimport)
|
|
this.get('elementRegistry').forEach(function(element) {
|
|
var bo = element.businessObject;
|
|
|
|
if (bo && bo.di) {
|
|
delete bo.di;
|
|
}
|
|
});
|
|
|
|
// remove drawn elements
|
|
Diagram.prototype.clear.call(this);
|
|
};
|
|
|
|
/**
|
|
* Destroy the viewer instance and remove all its
|
|
* remainders from the document tree.
|
|
*/
|
|
Viewer.prototype.destroy = function() {
|
|
|
|
// diagram destroy
|
|
Diagram.prototype.destroy.call(this);
|
|
|
|
// dom detach
|
|
remove(this._container);
|
|
};
|
|
|
|
/**
|
|
* Register an event listener
|
|
*
|
|
* Remove a previously added listener via {@link #off(event, callback)}.
|
|
*
|
|
* @param {String} event
|
|
* @param {Number} [priority]
|
|
* @param {Function} callback
|
|
* @param {Object} [that]
|
|
*/
|
|
Viewer.prototype.on = function(event, priority, callback, target) {
|
|
return this.get('eventBus').on(event, priority, callback, target);
|
|
};
|
|
|
|
/**
|
|
* De-register an event listener
|
|
*
|
|
* @param {String} event
|
|
* @param {Function} callback
|
|
*/
|
|
Viewer.prototype.off = function(event, callback) {
|
|
this.get('eventBus').off(event, callback);
|
|
};
|
|
|
|
Viewer.prototype.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();
|
|
};
|
|
|
|
Viewer.prototype.getDefinitions = function() {
|
|
return this._definitions;
|
|
};
|
|
|
|
Viewer.prototype.detach = function() {
|
|
|
|
var container = this._container,
|
|
parentNode = container.parentNode;
|
|
|
|
if (!parentNode) {
|
|
return;
|
|
}
|
|
|
|
this._emit('detach', {});
|
|
|
|
parentNode.removeChild(container);
|
|
};
|
|
|
|
Viewer.prototype._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)
|
|
*/
|
|
Viewer.prototype._emit = function(type, event) {
|
|
return this.get('eventBus').fire(type, event);
|
|
};
|
|
|
|
Viewer.prototype._createContainer = function(options) {
|
|
|
|
var container = domify('<div class="bjs-container"></div>');
|
|
|
|
assign(container.style, {
|
|
width: ensureUnit(options.width),
|
|
height: ensureUnit(options.height),
|
|
position: options.position
|
|
});
|
|
|
|
return container;
|
|
};
|
|
|
|
Viewer.prototype._createModdle = function(options) {
|
|
var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
|
|
|
|
return new BpmnModdle$1(moddleOptions);
|
|
};
|
|
|
|
// modules the viewer is composed of
|
|
Viewer.prototype._modules = [
|
|
CoreModule$1,
|
|
TranslateModule,
|
|
SelectionModule,
|
|
OverlaysModule
|
|
];
|
|
|
|
// default moddle extensions the viewer is composed of
|
|
Viewer.prototype._moddleExtensions = {};
|
|
|
|
/**
|
|
* 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 =
|
|
'<a href="http://bpmn.io" ' +
|
|
'target="_blank" ' +
|
|
'class="bjs-powered-by" ' +
|
|
'title="Powered by bpmn.io" ' +
|
|
'style="position: absolute; bottom: 15px; right: 15px; z-index: 100">' +
|
|
img +
|
|
'</a>';
|
|
|
|
var linkElement = domify(linkMarkup);
|
|
|
|
container.appendChild(linkElement);
|
|
|
|
componentEvent.bind(linkElement, 'click', function(event) {
|
|
open();
|
|
|
|
event.preventDefault();
|
|
});
|
|
}
|
|
|
|
/* </project-logo> */
|
|
|
|
/**
|
|
* 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|String[]} keys
|
|
* @param {KeyboardEvent} event
|
|
*/
|
|
function isKey(keys, event) {
|
|
keys = isArray(keys) ? keys : [ keys ];
|
|
|
|
return keys.indexOf(event.key) > -1;
|
|
}
|
|
|
|
/**
|
|
* @param {KeyboardEvent} event
|
|
*/
|
|
function isShift(event) {
|
|
return event.shiftKey;
|
|
}
|
|
|
|
var KEYDOWN_EVENT = 'keyboard.keydown',
|
|
KEYUP_EVENT = 'keyboard.keyup';
|
|
|
|
var DEFAULT_PRIORITY$1 = 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 target = event.target,
|
|
eventBusResult;
|
|
|
|
if (isInput(target)) {
|
|
return;
|
|
}
|
|
|
|
var context = {
|
|
keyEvent: event
|
|
};
|
|
|
|
eventBusResult = this._eventBus.fire(type || KEYDOWN_EVENT, context);
|
|
|
|
if (eventBusResult) {
|
|
event.preventDefault();
|
|
}
|
|
};
|
|
|
|
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$1;
|
|
}
|
|
|
|
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$1(target, 'input, textarea') || target.contentEditable === 'true');
|
|
}
|
|
|
|
var LOW_PRIORITY$3 = 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$3, 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 (isCmd(event) && !isShift(event) && isKey(['z', 'Z'], event)) {
|
|
editorActions.trigger('undo');
|
|
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// redo
|
|
// CTRL + Y
|
|
// CMD + SHIFT + Z
|
|
addListener('redo', function(context) {
|
|
|
|
var event = context.keyEvent;
|
|
|
|
if (isCmd(event) && (isKey(['y', 'Y'], event) || (isKey(['z', 'Z'], event) && isShift(event)))) {
|
|
editorActions.trigger('redo');
|
|
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// copy
|
|
// CTRL/CMD + C
|
|
addListener('copy', function(context) {
|
|
|
|
var event = context.keyEvent;
|
|
|
|
if (isCmd(event) && isKey(['c', 'C'], event)) {
|
|
editorActions.trigger('copy');
|
|
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// paste
|
|
// CTRL/CMD + V
|
|
addListener('paste', function(context) {
|
|
|
|
var event = context.keyEvent;
|
|
|
|
if (isCmd(event) && isKey(['v', 'V'], event)) {
|
|
editorActions.trigger('paste');
|
|
|
|
return true;
|
|
}
|
|
});
|
|
|
|
// zoom in one step
|
|
// CTRL/CMD + +
|
|
addListener('stepZoom', function(context) {
|
|
|
|
var event = context.keyEvent;
|
|
|
|
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([ '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$1(mode) {
|
|
var classes$1 = classes(document.body);
|
|
|
|
classes$1.removeMatching(CURSOR_CLS_PATTERN);
|
|
|
|
if (mode) {
|
|
classes$1.add('djs-cursor-' + mode);
|
|
}
|
|
}
|
|
|
|
function unset() {
|
|
set$1(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,
|
|
position = toPoint(event),
|
|
delta$1 = delta(position, start);
|
|
|
|
if (!context.dragging && length(delta$1) > THRESHOLD) {
|
|
context.dragging = true;
|
|
|
|
install(eventBus);
|
|
|
|
set$1('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;
|
|
}
|
|
|
|
|
|
// reject non-left left mouse button or modifier key
|
|
if (event.button || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
return;
|
|
}
|
|
|
|
context = {
|
|
start: toPoint(event)
|
|
};
|
|
|
|
componentEvent.bind(document, 'mousemove', handleMove);
|
|
componentEvent.bind(document, 'mouseup', handleEnd);
|
|
|
|
// we've handled the event
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
inherits_browser(NavigatedViewer, Viewer);
|
|
|
|
NavigatedViewer.prototype._navigationModules = [
|
|
KeyboardMoveModule,
|
|
MoveCanvasModule,
|
|
ZoomScrollModule
|
|
];
|
|
|
|
NavigatedViewer.prototype._modules = [].concat(
|
|
NavigatedViewer.prototype._modules,
|
|
NavigatedViewer.prototype._navigationModules);
|
|
|
|
return NavigatedViewer;
|
|
|
|
}));
|