123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- /**
- * The code in the <project-logo></project-logo> area
- * must not be changed.
- *
- * @see http://bpmn.io/license for more information.
- */
- import {
- assign,
- find,
- isFunction,
- isNumber,
- omit
- } from 'min-dash';
- import {
- domify,
- query as domQuery,
- remove as domRemove
- } from 'min-dom';
- import {
- innerSVG
- } from 'tiny-svg';
- import Diagram from 'diagram-js';
- import BpmnModdle from 'bpmn-moddle';
- import inherits from 'inherits';
- import {
- importBpmnDiagram
- } from './import/Importer';
- /**
- * A base viewer for BPMN 2.0 diagrams.
- *
- * Have a look at {@link Viewer}, {@link NavigatedViewer} or {@link Modeler} for
- * bundles that include actual features.
- *
- * @param {Object} [options] configuration options to pass to the viewer
- * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
- * @param {string|number} [options.width] the width of the viewer
- * @param {string|number} [options.height] the height of the viewer
- * @param {Object} [options.moddleExtensions] extension packages to provide
- * @param {Array<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
- */
- export default function BaseViewer(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(BaseViewer, 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=[])
- */
- BaseViewer.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=[])
- */
- BaseViewer.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=[])
- */
- BaseViewer.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 formatted XML
- * @param {boolean} [options.preamble=true] output preamble
- *
- * @param {Function} done invoked with (err, xml)
- */
- BaseViewer.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)
- */
- BaseViewer.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 = domQuery('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 BaseViewer#get
- */
- /**
- * Invoke a function in the context of this viewer.
- *
- * @example
- *
- * viewer.invoke(function(elementRegistry) {
- * var startEventShape = elementRegistry.get('StartEvent_1');
- * });
- *
- * @param {Function} fn to be invoked
- *
- * @return {Object} the functions return value
- *
- * @method BaseViewer#invoke
- */
- BaseViewer.prototype._setDefinitions = function(definitions) {
- this._definitions = definitions;
- };
- BaseViewer.prototype.getModules = function() {
- return this._modules;
- };
- /**
- * Remove all drawn elements from the viewer.
- *
- * After calling this method the viewer can still
- * be reused for opening another diagram.
- *
- * @method BaseViewer#clear
- */
- BaseViewer.prototype.clear = function() {
- if (!this.getDefinitions()) {
- // no diagram to clear
- return;
- }
- // remove 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.
- */
- BaseViewer.prototype.destroy = function() {
- // diagram destroy
- Diagram.prototype.destroy.call(this);
- // dom detach
- domRemove(this._container);
- };
- /**
- * Register an event listener
- *
- * Remove a previously added listener via {@link #off(event, callback)}.
- *
- * @param {string} event
- * @param {number} [priority]
- * @param {Function} callback
- * @param {Object} [that]
- */
- BaseViewer.prototype.on = function(event, priority, callback, target) {
- return this.get('eventBus').on(event, priority, callback, target);
- };
- /**
- * De-register an event listener
- *
- * @param {string} event
- * @param {Function} callback
- */
- BaseViewer.prototype.off = function(event, callback) {
- this.get('eventBus').off(event, callback);
- };
- BaseViewer.prototype.attachTo = function(parentNode) {
- if (!parentNode) {
- throw new Error('parentNode required');
- }
- // ensure we detach from the
- // previous, old parent
- this.detach();
- // unwrap jQuery if provided
- if (parentNode.get && parentNode.constructor.prototype.jquery) {
- parentNode = parentNode.get(0);
- }
- if (typeof parentNode === 'string') {
- parentNode = domQuery(parentNode);
- }
- parentNode.appendChild(this._container);
- this._emit('attach', {});
- this.get('canvas').resized();
- };
- BaseViewer.prototype.getDefinitions = function() {
- return this._definitions;
- };
- BaseViewer.prototype.detach = function() {
- var container = this._container,
- parentNode = container.parentNode;
- if (!parentNode) {
- return;
- }
- this._emit('detach', {});
- parentNode.removeChild(container);
- };
- BaseViewer.prototype._init = function(container, moddle, options) {
- var baseModules = options.modules || this.getModules(),
- additionalModules = options.additionalModules || [],
- staticModules = [
- {
- bpmnjs: [ 'value', this ],
- moddle: [ 'value', moddle ]
- }
- ];
- var diagramModules = [].concat(staticModules, baseModules, additionalModules);
- var diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
- canvas: assign({}, options.canvas, { container: container }),
- modules: diagramModules
- });
- // invoke diagram constructor
- Diagram.call(this, diagramOptions);
- if (options && options.container) {
- this.attachTo(options.container);
- }
- };
- /**
- * Emit an event on the underlying {@link EventBus}
- *
- * @param {string} type
- * @param {Object} event
- *
- * @return {Object} event processing result (if any)
- */
- BaseViewer.prototype._emit = function(type, event) {
- return this.get('eventBus').fire(type, event);
- };
- BaseViewer.prototype._createContainer = function(options) {
- var container = domify('<div class="bjs-container"></div>');
- assign(container.style, {
- width: ensureUnit(options.width),
- height: ensureUnit(options.height),
- position: options.position
- });
- return container;
- };
- BaseViewer.prototype._createModdle = function(options) {
- var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
- return new BpmnModdle(moddleOptions);
- };
- BaseViewer.prototype._modules = [];
- // helpers ///////////////
- 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;
- }
- /* <project-logo> */
- import {
- open as openPoweredBy,
- BPMNIO_IMG
- } from './util/PoweredByUtil';
- import {
- event as domEvent
- } from 'min-dom';
- /**
- * 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);
- domEvent.bind(linkElement, 'click', function(event) {
- openPoweredBy();
- event.preventDefault();
- });
- }
- /* </project-logo> */
|