/**
* The code in the area
* must not be changed.
*
* @see http://bpmn.io/license for more information.
*/
'use strict';
var assign = require('lodash/object/assign'),
omit = require('lodash/object/omit'),
isString = require('lodash/lang/isString'),
isNumber = require('lodash/lang/isNumber');
var domify = require('min-dom/lib/domify'),
domQuery = require('min-dom/lib/query'),
domRemove = require('min-dom/lib/remove');
var Diagram = require('diagram-js'),
BpmnModdle = require('bpmn-moddle');
var Importer = require('./import/Importer');
function initListeners(diagram, listeners) {
var events = diagram.get('eventBus');
listeners.forEach(function(l) {
events.on(l.event, l.priority, l.handler, l.that);
});
}
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',
container: 'body'
};
/**
* Ensure the passed argument is a proper unit (defaulting to px)
*/
function ensureUnit(val) {
return val + (isNumber(val) ? 'px' : '');
}
/**
* A viewer for BPMN 2.0 diagrams.
*
* Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include
* additional features.
*
*
* ## Extending the Viewer
*
* In order to extend the viewer pass extension modules to bootstrap via the
* `additionalModules` option. An extension module is an object that exposes
* named services.
*
* The following example depicts the integration of a simple
* logging component that integrates with interaction events:
*
*
* ```javascript
*
* // logging component
* function InteractionLogger(eventBus) {
* eventBus.on('element.hover', function(event) {
* console.log()
* })
* }
*
* InteractionLogger.$inject = [ 'eventBus' ]; // minification save
*
* // extension module
* var extensionModule = {
* __init__: [ 'interactionLogger' ],
* interactionLogger: [ 'type', InteractionLogger ]
* };
*
* // extend the viewer
* var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] });
* bpmnViewer.importXML(...);
* ```
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {String|Number} [options.width] the width of the viewer
* @param {String|Number} [options.height] the height of the viewer
* @param {Object} [options.moddleExtensions] extension packages to provide
* @param {Array} [options.modules] a list of modules to override the default modules
* @param {Array} [options.additionalModules] a list of modules to use with the default modules
*/
function Viewer(options) {
this.options = options = assign({}, DEFAULT_OPTIONS, options || {});
var parent = options.container;
// support jquery element
// unwrap it if passed
if (parent.get) {
parent = parent.get(0);
}
// support selector
if (isString(parent)) {
parent = domQuery(parent);
}
var container = this.container = domify('');
parent.appendChild(container);
assign(container.style, {
width: ensureUnit(options.width),
height: ensureUnit(options.height),
position: options.position
});
/* */
addProjectLogo(container);
/* */
}
module.exports = Viewer;
Viewer.prototype.importXML = function(xml, done) {
var self = this;
this.moddle = this.createModdle();
this.moddle.fromXML(xml, 'bpmn:Definitions', function(err, definitions, context) {
if (err) {
err = checkValidationError(err);
return done(err);
}
var parseWarnings = context.warnings;
self.importDefinitions(definitions, function(err, importWarnings) {
var allWarnings = parseWarnings.concat(importWarnings || []);
done(err, allWarnings);
});
});
};
Viewer.prototype.saveXML = function(options, done) {
if (!done) {
done = options;
options = {};
}
var definitions = this.definitions;
if (!definitions) {
return done(new Error('no definitions loaded'));
}
this.moddle.toXML(definitions, options, done);
};
Viewer.prototype.createModdle = function() {
return new BpmnModdle(this.options.moddleExtensions);
};
Viewer.prototype.saveSVG = function(options, done) {
if (!done) {
done = options;
options = {};
}
var canvas = this.get('canvas');
var contentNode = canvas.getDefaultLayer(),
defsNode = canvas._svg.select('defs');
var contents = contentNode.innerSVG(),
defs = (defsNode && defsNode.outerSVG()) || '';
var bbox = contentNode.getBBox();
var svg =
'\n' +
'\n' +
'\n' +
'';
done(null, svg);
};
Viewer.prototype.get = function(name) {
if (!this.diagram) {
throw new Error('no diagram loaded');
}
return this.diagram.get(name);
};
Viewer.prototype.invoke = function(fn) {
if (!this.diagram) {
throw new Error('no diagram loaded');
}
return this.diagram.invoke(fn);
};
Viewer.prototype.importDefinitions = function(definitions, done) {
// use try/catch to not swallow synchronous exceptions
// that may be raised during model parsing
try {
if (this.diagram) {
this.clear();
}
this.definitions = definitions;
var diagram = this.diagram = this._createDiagram(this.options);
this._init(diagram);
Importer.importBpmnDiagram(diagram, definitions, done);
} catch (e) {
done(e);
}
};
Viewer.prototype._init = function(diagram) {
initListeners(diagram, this.__listeners || []);
};
Viewer.prototype._createDiagram = function(options) {
var modules = [].concat(options.modules || this.getModules(), options.additionalModules || []);
// add self as an available service
modules.unshift({
bpmnjs: [ 'value', this ],
moddle: [ 'value', this.moddle ]
});
options = omit(options, 'additionalModules');
options = assign(options, {
canvas: assign({}, options.canvas, { container: this.container }),
modules: modules
});
return new Diagram(options);
};
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.
*/
Viewer.prototype.clear = function() {
var diagram = this.diagram;
if (diagram) {
diagram.destroy();
}
};
/**
* Destroy the viewer instance and remove all its remainders
* from the document tree.
*/
Viewer.prototype.destroy = function() {
// clear underlying diagram
this.clear();
// remove container
domRemove(this.container);
};
/**
* Register an event listener on the viewer
*
* @param {String} event
* @param {Function} handler
*/
Viewer.prototype.on = function(event, priority, handler, that) {
var diagram = this.diagram,
listeners = this.__listeners = this.__listeners || [];
listeners.push({ event: event, priority: priority, handler: handler, that: that });
if (diagram) {
diagram.get('eventBus').on(event, priority, handler, that);
}
};
// modules the viewer is composed of
Viewer.prototype._modules = [
require('./core'),
require('diagram-js/lib/features/selection'),
require('diagram-js/lib/features/overlays')
];
/* */
var PoweredBy = require('./util/PoweredByUtil'),
domEvent = require('min-dom/lib/event');
/**
* Adds the project logo to the diagram container as
* required by the bpmn.io license.
*
* @see http://bpmn.io/license
*
* @param {Element} container
*/
function addProjectLogo(container) {
var logoData = PoweredBy.BPMNIO_LOGO;
var linkMarkup =
'' +
'' +
'';
var linkElement = domify(linkMarkup);
container.appendChild(linkElement);
domEvent.bind(linkElement, 'click', function(event) {
PoweredBy.open();
event.preventDefault();
});
}
/* */