dmn-js
Version:
A dmn toolkit and web modeler
1,897 lines (1,772 loc) • 1.31 MB
JavaScript
/*!
* dmn-js - dmn-viewer v17.2.1
*
* Copyright (c) 2014-present, camunda Services GmbH
*
* Released under the bpmn.io license
* http://bpmn.io/license
*
* Source Code: https://github.com/bpmn-io/dmn-js
*
* Date: 2025-05-23
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DmnJS = factory());
})(this, (function () { 'use strict';
/**
* Flatten array, one level deep.
*
* @template T
*
* @param {T[][] | T[] | null} [arr]
*
* @return {T[]}
*/
const nativeToString$4 = Object.prototype.toString;
const nativeHasOwnProperty$3 = Object.prototype.hasOwnProperty;
function isUndefined$5(obj) {
return obj === undefined;
}
function isDefined(obj) {
return obj !== undefined;
}
function isNil(obj) {
return obj == null;
}
function isArray$5(obj) {
return nativeToString$4.call(obj) === '[object Array]';
}
function isObject$2(obj) {
return nativeToString$4.call(obj) === '[object Object]';
}
function isNumber$1(obj) {
return nativeToString$4.call(obj) === '[object Number]';
}
/**
* @param {any} obj
*
* @return {boolean}
*/
function isFunction$2(obj) {
const tag = nativeToString$4.call(obj);
return tag === '[object Function]' || tag === '[object AsyncFunction]' || tag === '[object GeneratorFunction]' || tag === '[object AsyncGeneratorFunction]' || tag === '[object Proxy]';
}
function isString$4(obj) {
return nativeToString$4.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$3(target, key) {
return !isNil(target) && nativeHasOwnProperty$3.call(target, key);
}
/**
* @template T
* @typedef { (
* ((e: T) => boolean) |
* ((e: T, idx: number) => boolean) |
* ((e: T, key: string) => boolean) |
* string |
* number
* ) } Matcher
*/
/**
* @template T
* @template U
*
* @typedef { (
* ((e: T) => U) | string | number
* ) } Extractor
*/
/**
* @template T
* @typedef { (val: T, key: any) => boolean } MatchFn
*/
/**
* @template T
* @typedef { T[] } ArrayCollection
*/
/**
* @template T
* @typedef { { [key: string]: T } } StringKeyValueCollection
*/
/**
* @template T
* @typedef { { [key: number]: T } } NumberKeyValueCollection
*/
/**
* @template T
* @typedef { StringKeyValueCollection<T> | NumberKeyValueCollection<T> } KeyValueCollection
*/
/**
* @template T
* @typedef { KeyValueCollection<T> | ArrayCollection<T> } Collection
*/
/**
* Find element in collection.
*
* @template T
* @param {Collection<T>} collection
* @param {Matcher<T>} matcher
*
* @return {Object}
*/
function find$2(collection, matcher) {
const matchFn = toMatcher$1(matcher);
let match;
forEach$3(collection, function (val, key) {
if (matchFn(val, key)) {
match = val;
return false;
}
});
return match;
}
/**
* Filter elements in collection.
*
* @template T
* @param {Collection<T>} collection
* @param {Matcher<T>} matcher
*
* @return {T[]} result
*/
function filter$1(collection, matcher) {
const matchFn = toMatcher$1(matcher);
let result = [];
forEach$3(collection, function (val, key) {
if (matchFn(val, key)) {
result.push(val);
}
});
return result;
}
/**
* Iterate over collection; returning something
* (non-undefined) will stop iteration.
*
* @template T
* @param {Collection<T>} collection
* @param { ((item: T, idx: number) => (boolean|void)) | ((item: T, key: string) => (boolean|void)) } iterator
*
* @return {T} return result that stopped the iteration
*/
function forEach$3(collection, iterator) {
let val, result;
if (isUndefined$5(collection)) {
return;
}
const convertKey = isArray$5(collection) ? toNum$3 : identity$3;
for (let key in collection) {
if (has$3(collection, key)) {
val = collection[key];
result = iterator(val, convertKey(key));
if (result === false) {
return val;
}
}
}
}
/**
* Reduce collection, returning a single result.
*
* @template T
* @template V
*
* @param {Collection<T>} collection
* @param {(result: V, entry: T, index: any) => V} iterator
* @param {V} result
*
* @return {V} result returned from last iterator
*/
function reduce(collection, iterator, result) {
forEach$3(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);
}
/**
* 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$1(collection, fn) {
let result = [];
forEach$3(collection, function (val, key) {
result.push(fn(val, key));
});
return result;
}
/**
* Create an object pattern matcher.
*
* @example
*
* ```javascript
* const matcher = matchPattern({ id: 1 });
*
* let element = find(elements, matcher);
* ```
*
* @template T
*
* @param {T} pattern
*
* @return { (el: any) => boolean } matcherFn
*/
function matchPattern(pattern) {
return function (el) {
return every(pattern, function (val, key) {
return el[key] === val;
});
};
}
/**
* @template T
* @param {Matcher<T>} matcher
*
* @return {MatchFn<T>}
*/
function toMatcher$1(matcher) {
return isFunction$2(matcher) ? matcher : e => {
return e === matcher;
};
}
function identity$3(arg) {
return arg;
}
function toNum$3(arg) {
return Number(arg);
}
/* global setTimeout clearTimeout */
/**
* @typedef { {
* (...args: any[]): any;
* flush: () => void;
* cancel: () => void;
* } } DebouncedFunction
*/
/**
* Debounce fn, calling it only once if the given time
* elapsed between calls.
*
* Lodash-style the function exposes methods to `#clear`
* and `#flush` to control internal behavior.
*
* @param {Function} fn
* @param {Number} timeout
*
* @return {DebouncedFunction} debounced function
*/
function debounce(fn, timeout) {
let timer;
let lastArgs;
let lastThis;
let lastNow;
function fire(force) {
let now = Date.now();
let scheduledDiff = force ? 0 : lastNow + timeout - now;
if (scheduledDiff > 0) {
return schedule(scheduledDiff);
}
fn.apply(lastThis, lastArgs);
clear();
}
function schedule(timeout) {
timer = setTimeout(fire, timeout);
}
function clear() {
if (timer) {
clearTimeout(timer);
}
timer = lastNow = lastArgs = lastThis = undefined;
}
function flush() {
if (timer) {
fire(true);
}
clear();
}
/**
* @type { DebouncedFunction }
*/
function callback(...args) {
lastNow = Date.now();
lastArgs = args;
lastThis = this;
// ensure an execution is scheduled
if (!timer) {
schedule(timeout);
}
}
callback.flush = flush;
callback.cancel = clear;
return callback;
}
/**
* Throttle fn, calling at most once
* in the given interval.
*
* @param {Function} fn
* @param {Number} interval
*
* @return {Function} throttled function
*/
function throttle(fn, interval) {
let throttling = false;
return function (...args) {
if (throttling) {
return;
}
fn(...args);
throttling = true;
setTimeout(() => {
throttling = false;
}, interval);
};
}
/**
* Bind function against target <this>.
*
* @param {Function} fn
* @param {Object} target
*
* @return {Function} bound function
*/
function bind$3(fn, target) {
return fn.bind(target);
}
/**
* Convenience wrapper for `Object.assign`.
*
* @param {Object} target
* @param {...Object} others
*
* @return {Object} the target
*/
function assign$4(target, ...others) {
return Object.assign(target, ...others);
}
var FN_REF = '__fn';
var DEFAULT_PRIORITY$2 = 1000;
var slice = Array.prototype.slice;
/**
* @typedef { {
* stopPropagation(): void;
* preventDefault(): void;
* cancelBubble: boolean;
* defaultPrevented: boolean;
* returnValue: any;
* } } Event
*/
/**
* @template E
*
* @typedef { (event: E & Event, ...any) => any } EventBusEventCallback
*/
/**
* @typedef { {
* priority: number;
* next: EventBusListener | null;
* callback: EventBusEventCallback<any>;
* } } EventBusListener
*/
/**
* 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
* ```
*
* @template [EventMap=null]
*/
function EventBus() {
/**
* @type { Record<string, EventBusListener> }
*/
this._listeners = {};
// cleanup on destroy on lowest priority to allow
// message passing until the bitter end
this.on('diagram.destroy', 1, this._destroy, this);
}
/**
* @overlord
*
* 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.
*
* @template T
*
* @param {string|string[]} events to subscribe to
* @param {number} [priority=1000] listen priority
* @param {EventBusEventCallback<T>} callback
* @param {any} [that] callback context
*/
/**
* 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.
*
* @template {keyof EventMap} EventName
*
* @param {EventName} events to subscribe to
* @param {number} [priority=1000] listen priority
* @param {EventBusEventCallback<EventMap[EventName]>} callback
* @param {any} [that] callback context
*/
EventBus.prototype.on = function (events, priority, callback, that) {
events = isArray$5(events) ? events : [events];
if (isFunction$2(priority)) {
that = callback;
callback = priority;
priority = DEFAULT_PRIORITY$2;
}
if (!isNumber$1(priority)) {
throw new Error('priority must be a number');
}
var actualCallback = callback;
if (that) {
actualCallback = bind$3(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
});
});
};
/**
* @overlord
*
* Register an event listener that is called only once.
*
* @template T
*
* @param {string|string[]} events to subscribe to
* @param {number} [priority=1000] the listen priority
* @param {EventBusEventCallback<T>} callback
* @param {any} [that] callback context
*/
/**
* Register an event listener that is called only once.
*
* @template {keyof EventMap} EventName
*
* @param {EventName} events to subscribe to
* @param {number} [priority=1000] listen priority
* @param {EventBusEventCallback<EventMap[EventName]>} callback
* @param {any} [that] callback context
*/
EventBus.prototype.once = function (events, priority, callback, that) {
var self = this;
if (isFunction$2(priority)) {
that = callback;
callback = priority;
priority = DEFAULT_PRIORITY$2;
}
if (!isNumber$1(priority)) {
throw new Error('priority must be a number');
}
function wrappedCallback() {
wrappedCallback.__isTomb = true;
var result = callback.apply(that, arguments);
self.off(events, 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(events, 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|string[]} events
* @param {EventBusEventCallback} [callback]
*/
EventBus.prototype.off = function (events, callback) {
events = isArray$5(events) ? events : [events];
var self = this;
events.forEach(function (event) {
self._removeListener(event, callback);
});
};
/**
* Create an event recognized be the event bus.
*
* @param {Object} data Event data.
*
* @return {Event} An event that will be recognized by the event bus.
*/
EventBus.prototype.createEvent = function (data) {
var event = new InternalEvent();
event.init(data);
return event;
};
/**
* Fires an event.
*
* @example
*
* ```javascript
* // 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} [type] event type
* @param {Object} [data] event or event data
* @param {...any} [args] additional arguments the callback will be called with.
*
* @return {any} The return value. Will be set to `false` if the default was prevented.
*/
EventBus.prototype.fire = function (type, data) {
var event, firstListener, returnValue, args;
args = slice.call(arguments);
if (typeof type === 'object') {
data = type;
type = data.type;
}
if (!type) {
throw new Error('no event type specified');
}
firstListener = this._listeners[type];
if (!firstListener) {
return;
}
// we make sure we fire instances of our home made
// events here. We wrap them only once, though
if (data instanceof InternalEvent) {
// we are fine, we alread have an event
event = data;
} else {
event = this.createEvent(data);
}
// ensure we pass the event as the first parameter
args[0] = event;
// original event type (in case we delegate)
var originalType = event.type;
// update event type before delegation
if (type !== originalType) {
event.type = type;
}
try {
returnValue = this._invokeListeners(event, args, firstListener);
} finally {
// reset event type after delegation
if (type !== originalType) {
event.type = originalType;
}
}
// set the return value to false if the event default
// got prevented and no other return value exists
if (returnValue === undefined && event.defaultPrevented) {
returnValue = false;
}
return returnValue;
};
/**
* Handle an error by firing an event.
*
* @param {Error} error The error to be handled.
*
* @return {boolean} Whether the error was handled.
*/
EventBus.prototype.handleError = function (error) {
return this.fire('error', {
error: error
}) === false;
};
EventBus.prototype._destroy = function () {
this._listeners = {};
};
/**
* @param {Event} event
* @param {any[]} args
* @param {EventBusListener} listener
*
* @return {any}
*/
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;
};
/**
* @param {Event} event
* @param {any[]} args
* @param {EventBusListener} listener
*
* @return {any}
*/
EventBus.prototype._invokeListener = function (event, args, listener) {
var returnValue;
if (listener.callback.__isTomb) {
return returnValue;
}
try {
// returning false prevents the default action
returnValue = invokeFunction(listener.callback, args);
// stop propagation on return value
if (returnValue !== undefined) {
event.returnValue = returnValue;
event.stopPropagation();
}
// prevent default on return false
if (returnValue === false) {
event.preventDefault();
}
} catch (error) {
if (!this.handleError(error)) {
console.error('unhandled error in event listener', error);
throw error;
}
}
return returnValue;
};
/**
* Add new listener with a certain priority to the list
* of listeners (for the given event).
*
* The semantics of listener registration / listener execution are
* first register, first serve: New listeners will always be inserted
* after existing listeners with the same priority.
*
* Example: Inserting two listeners with priority 1000 and 1300
*
* * before: [ 1500, 1500, 1000, 1000 ]
* * after: [ 1500, 1500, (new=1300), 1000, 1000, (new=1000) ]
*
* @param {string} event
* @param {EventBusListener} newListener
*/
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;
};
/**
* @param {string} name
*
* @return {EventBusListener}
*/
EventBus.prototype._getListeners = function (name) {
return this._listeners[name];
};
/**
* @param {string} name
* @param {EventBusListener} listener
*/
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$4(this, data || {});
};
/**
* Invoke function. Be fast...
*
* @param {Function} fn
* @param {any[]} args
*
* @return {any}
*/
function invokeFunction(fn, args) {
return fn.apply(null, args);
}
/**
* Flatten array, one level deep.
*
* @param {Array<?>} arr
*
* @return {Array<?>}
*/
var nativeToString$3 = Object.prototype.toString;
function isString$3(obj) {
return nativeToString$3.call(obj) === '[object String]';
}
function _extends$2() {
_extends$2 = 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$2.apply(this, arguments);
}
/**
* Convenience wrapper for `Object.assign`.
*
* @param {Object} target
* @param {...Object} others
*
* @return {Object} the target
*/
function assign$3(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$2.apply(void 0, [target].concat(others));
}
/**
* Flatten array, one level deep.
*
* @param {Array<?>} arr
*
* @return {Array<?>}
*/
var nativeToString$2 = Object.prototype.toString;
var nativeHasOwnProperty$2 = Object.prototype.hasOwnProperty;
function isUndefined$4(obj) {
return obj === undefined;
}
function isArray$4(obj) {
return nativeToString$2.call(obj) === '[object Array]';
}
function isObject$1(obj) {
return nativeToString$2.call(obj) === '[object Object]';
}
function isString$2(obj) {
return nativeToString$2.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$2(target, key) {
return nativeHasOwnProperty$2.call(target, key);
}
/**
* Iterate over collection; returning something
* (non-undefined) will stop iteration.
*
* @param {Array|Object} collection
* @param {Function} iterator
*
* @return {Object} return result that stopped the iteration
*/
function forEach$2(collection, iterator) {
var val, result;
if (isUndefined$4(collection)) {
return;
}
var convertKey = isArray$4(collection) ? toNum$2 : identity$2;
for (var key in collection) {
if (has$2(collection, key)) {
val = collection[key];
result = iterator(val, convertKey(key));
if (result === false) {
return val;
}
}
}
}
function identity$2(arg) {
return arg;
}
function toNum$2(arg) {
return Number(arg);
}
/**
* Bind function against target <this>.
*
* @param {Function} fn
* @param {Object} target
*
* @return {Function} bound function
*/
function bind$2(fn, target) {
return fn.bind(target);
}
function _extends$1() {
_extends$1 = 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$1.apply(this, arguments);
}
/**
* Convenience wrapper for `Object.assign`.
*
* @param {Object} target
* @param {...Object} others
*
* @return {Object} the target
*/
function assign$2(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$1.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$2(properties, function (prop) {
if (prop in obj) {
result[prop] = target[prop];
}
});
return result;
}
/**
* 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$2(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$2(attrs, bind$2(function (val, key) {
this.set(key, val);
}, this));
}
ModdleElement.prototype = prototype;
ModdleElement.hasType = prototype.$instanceOf = this.model.hasType;
// static links
props.defineModel(ModdleElement, model);
props.defineDescriptor(ModdleElement, descriptor);
return ModdleElement;
};
/**
* Built-in moddle types
*/
var BUILTINS = {
String: true,
Boolean: true,
Integer: true,
Real: true,
Element: true
};
/**
* Converters for built in types from string representations
*/
var TYPE_CONVERTERS = {
String: function (s) {
return s;
},
Boolean: function (s) {
return s === 'true';
},
Integer: function (s) {
return parseInt(s, 10);
},
Real: function (s) {
return parseFloat(s);
}
};
/**
* Convert a type to its real representation
*/
function coerceType(type, value) {
var converter = TYPE_CONVERTERS[type];
if (converter) {
return converter(value);
} else {
return value;
}
}
/**
* Return whether the given type is built-in
*/
function isBuiltIn(type) {
return !!BUILTINS[type];
}
/**
* Return whether the given type is simple
*/
function isSimple(type) {
return !!TYPE_CONVERTERS[type];
}
/**
* Parses a namespaced attribute name of the form (ns:)localName to an object,
* given a default prefix to assume in case no explicit namespace is given.
*
* @param {String} name
* @param {String} [defaultPrefix] the default prefix to take, if none is present.
*
* @return {Object} the parsed name
*/
function parseName(name, defaultPrefix) {
var parts = name.split(/:/),
localName,
prefix;
// no prefix (i.e. only local name)
if (parts.length === 1) {
localName = name;
prefix = defaultPrefix;
} else
// prefix + local name
if (parts.length === 2) {
localName = parts[1];
prefix = parts[0];
} else {
throw new Error('expected <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$2(t.properties, bind$2(function (p) {
// clone property to allow extensions
p = assign$2({}, 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$2(packages, bind$2(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$2({}, pkg);
var pkgMap = this.packageMap;
ensureAvailable(pkgMap, pkg, 'prefix');
ensureAvailable(pkgMap, pkg, 'uri');
// register types
forEach$2(pkg.types, bind$2(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$2({}, type, {
superClass: (type.superClass || []).slice(),
extends: (type.extends || []).slice(),
properties: (type.properties || []).slice(),
meta: assign$2(type.meta || {})
});
var ns = parseName(type.name, pkg.prefix),
name = ns.name,
propertiesByName = {};
// parse properties
forEach$2(type.properties, bind$2(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$2(p, {
ns: propertyNs,
name: propertyName
});
propertiesByName[propertyName] = p;
}, this));
// update ns + name
assign$2(type, {
ns: ns,
name: name,
propertiesByName: propertiesByName
});
forEach$2(type.extends, bind$2(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$2(type.superClass, trait ? traverseTrait : traverseSuper);
// call iterator with (type, inherited=!trait)
iterator(type, !trait);
forEach$2(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) {
if (!isString$2(name) || !name.length) {
throw new TypeError('property name must be a non-empty string');
}
var property = this.model.getPropertyDescriptor(target, name);
var propertyName = property && property.name;
if (isUndefined$3(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$2(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$2(target, property, []);
}
return target[propertyName];
};
/**
* Define a property on the target element
*
* @param {Object} target
* @param {String} name
* @param {Object} options
*/
Properties.prototype.define = function (target, name, options) {
if (!options.writable) {
var value = options.value;
// use getters for read-only variables to support ES6 proxies
// cf. https://github.com/bpmn-io/internal-docs/issues/386
options = assign$2({}, options, {
get: function () {
return value;
}
});
delete options.value;
}
Object.defineProperty(target, name, options);
};
/**
* Define the descriptor for an element
*/
Properties.prototype.defineDescriptor = function (target, descriptor) {
this.define(target, '$descriptor', {
value: descriptor
});
};
/**
* Define the model for an element
*/
Properties.prototype.defineModel = function (target, model) {
this.define(target, '$model', {
value: model
});
};
function isUndefined$3(val) {
return typeof val === 'undefined';
}
function defineProperty$2(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$2(descriptor) ? descriptor : descriptor.ns.name;
var type = cache[name];
if (!type) {
descriptor = this.registry.getEffectiveDescriptor(name);
type = cache[name] = this.factory.createType(descriptor);
}
return type;
};
/**
* Creates an any-element type to be used within model instances.
*
* This can be used to create custom elements that lie outside the meta-model.
* The created element contains all the meta-data required to serialize it
* as part of meta-model elements.
*
* @method Moddle#createAny
*
* @example
*
* var foo = moddle.createAny('vendor:Foo', 'http://vendor', {
* value: 'bar'
* });
*
* var container = moddle.create('my:Container', 'http://my', {
* any: [ foo ]
* });
*
* // go ahead and serialize the stuff
*
*
* @param {String} name the name of the element
* @param {String} nsUri the namespace uri of the element
* @param {Object} [properties] a map of properties to initialize the instance with
* @return {Object} the any type instance
*/
Moddle.prototype.createAny = function (name, nsUri, properties) {
var nameNs = parseName(name);
var element = {
$type: name,
$instanceOf: function (type) {
return type === this.$type;
}
};
var descriptor = {
name: name,
isGeneric: true,
ns: {
prefix: nameNs.prefix,
localName: nameNs.localName,
uri: nsUri
}
};
this.properties.defineDescriptor(element, descriptor);
this.properties.defineModel(element, this);
this.properties.define(element, '$parent', {
enumerable: false,
writable: true
});
this.properties.define(element, '$instanceOf', {
enumerable: false,
writable: true
});
forEach$2(properties, function (a, key) {
if (isObject$1(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];
};
/**
* Flatten array, one level deep.