diff --git a/lib/BaseModeler.js b/lib/BaseModeler.js new file mode 100644 index 00000000..efa727f6 --- /dev/null +++ b/lib/BaseModeler.js @@ -0,0 +1,74 @@ +import inherits from 'inherits'; + +import Ids from 'ids'; + +import BaseViewer from './BaseViewer'; + + +/** + * A base modeler for BPMN 2.0 diagrams. + * + * Have a look at {@link Modeler} for a bundle that includes actual features. + * + * @param {Object} [options] configuration options to pass to the viewer + * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body. + * @param {String|Number} [options.width] the width of the viewer + * @param {String|Number} [options.height] the height of the viewer + * @param {Object} [options.moddleExtensions] extension packages to provide + * @param {Array} [options.modules] a list of modules to override the default modules + * @param {Array} [options.additionalModules] a list of modules to use with the default modules + */ +export default function BaseModeler(options) { + BaseViewer.call(this, options); + + // hook ID collection into the modeler + this.on('import.parse.complete', function(event) { + if (!event.error) { + this._collectIds(event.definitions, event.context); + } + }, this); + + this.on('diagram.destroy', function() { + this.get('moddle').ids.clear(); + }, this); +} + +inherits(BaseModeler, BaseViewer); + + +/** + * Create a moddle instance, attaching ids to it. + * + * @param {Object} options + */ +BaseModeler.prototype._createModdle = function(options) { + var moddle = BaseViewer.prototype._createModdle.call(this, options); + + // attach ids to moddle to be able to track + // and validated ids in the BPMN 2.0 XML document + // tree + moddle.ids = new Ids([ 32, 36, 1 ]); + + return moddle; +}; + +/** + * Collect ids processed during parsing of the + * definitions object. + * + * @param {ModdleElement} definitions + * @param {Context} context + */ +BaseModeler.prototype._collectIds = function(definitions, context) { + + var moddle = definitions.$model, + ids = moddle.ids, + id; + + // remove references from previous import + ids.clear(); + + for (id in context.elementsById) { + ids.claim(id, context.elementsById[id]); + } +}; \ No newline at end of file diff --git a/lib/BaseViewer.js b/lib/BaseViewer.js new file mode 100644 index 00000000..cc0f3385 --- /dev/null +++ b/lib/BaseViewer.js @@ -0,0 +1,653 @@ +/** + * The code in the 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} [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 + */ +export default function BaseViewer(options) { + + options = assign({}, DEFAULT_OPTIONS, options); + + this._moddle = this._createModdle(options); + + this._container = this._createContainer(options); + + /* */ + + addProjectLogo(this._container); + + /* */ + + this._init(this._container, this._moddle, options); +} + +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|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 parsed BPMN 2.0 definitions + * @param {ModdleElement|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered) + * @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} [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 ? '' + innerSVG(defsNode) + '' : ''; + + var bbox = contentNode.getBBox(); + + svg = + '\n' + + '\n' + + '\n' + + '' + + defs + contents + + ''; + } 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('
'); + + 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 + * @param {String} diagramId + * + * @return {ModdleElement|null} + */ +function findBPMNDiagram(definitions, diagramId) { + if (!diagramId) { + return null; + } + + return find(definitions.diagrams, function(element) { + return element.id === diagramId; + }) || null; +} + + +/* */ + +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 = + '' + + img + + ''; + + var linkElement = domify(linkMarkup); + + container.appendChild(linkElement); + + domEvent.bind(linkElement, 'click', function(event) { + openPoweredBy(); + + event.preventDefault(); + }); +} + +/* */ \ No newline at end of file diff --git a/lib/Modeler.js b/lib/Modeler.js index fed04c24..c1b57bd7 100644 --- a/lib/Modeler.js +++ b/lib/Modeler.js @@ -1,9 +1,8 @@ import inherits from 'inherits'; -import Ids from 'ids'; +import BaseModeler from './BaseModeler'; import Viewer from './Viewer'; - import NavigatedViewer from './NavigatedViewer'; import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move'; @@ -131,21 +130,11 @@ var initialDiagram = * @param {Array} [options.additionalModules] a list of modules to use with the default modules */ export default function Modeler(options) { - Viewer.call(this, options); - - // hook ID collection into the modeler - this.on('import.parse.complete', function(event) { - if (!event.error) { - this._collectIds(event.definitions, event.context); - } - }, this); - - this.on('diagram.destroy', function() { - this.get('moddle').ids.clear(); - }, this); + BaseModeler.call(this, options); } -inherits(Modeler, Viewer); +inherits(Modeler, BaseModeler); + Modeler.Viewer = Viewer; Modeler.NavigatedViewer = NavigatedViewer; @@ -159,42 +148,6 @@ Modeler.prototype.createDiagram = function(done) { return this.importXML(initialDiagram, done); }; -/** - * Create a moddle instance, attaching ids to it. - * - * @param {Object} options - */ -Modeler.prototype._createModdle = function(options) { - var moddle = Viewer.prototype._createModdle.call(this, options); - - // attach ids to moddle to be able to track - // and validated ids in the BPMN 2.0 XML document - // tree - moddle.ids = new Ids([ 32, 36, 1 ]); - - return moddle; -}; - -/** - * Collect ids processed during parsing of the - * definitions object. - * - * @param {ModdleElement} definitions - * @param {Context} context - */ -Modeler.prototype._collectIds = function(definitions, context) { - - var moddle = definitions.$model, - ids = moddle.ids, - id; - - // remove references from previous import - ids.clear(); - - for (id in context.elementsById) { - ids.claim(id, context.elementsById[id]); - } -}; Modeler.prototype._interactionModules = [ @@ -242,6 +195,7 @@ Modeler.prototype._modelingModules = [ // - modeling modules Modeler.prototype._modules = [].concat( - Modeler.prototype._modules, + Viewer.prototype._modules, Modeler.prototype._interactionModules, - Modeler.prototype._modelingModules); + Modeler.prototype._modelingModules +); diff --git a/lib/NavigatedViewer.js b/lib/NavigatedViewer.js index 3e64fefc..592f59dc 100644 --- a/lib/NavigatedViewer.js +++ b/lib/NavigatedViewer.js @@ -6,6 +6,7 @@ import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move'; import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas'; import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll'; + /** * A viewer that includes mouse navigation facilities * @@ -17,6 +18,7 @@ export default function NavigatedViewer(options) { inherits(NavigatedViewer, Viewer); + NavigatedViewer.prototype._navigationModules = [ KeyboardMoveModule, MoveCanvasModule, @@ -24,5 +26,6 @@ NavigatedViewer.prototype._navigationModules = [ ]; NavigatedViewer.prototype._modules = [].concat( - NavigatedViewer.prototype._modules, - NavigatedViewer.prototype._navigationModules); \ No newline at end of file + Viewer.prototype._modules, + NavigatedViewer.prototype._navigationModules +); \ No newline at end of file diff --git a/lib/Viewer.js b/lib/Viewer.js index 25ab2f02..1f57bac3 100644 --- a/lib/Viewer.js +++ b/lib/Viewer.js @@ -1,91 +1,12 @@ -/** - * The code in the 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'; - import CoreModule from './core'; import TranslateModule from 'diagram-js/lib/i18n/translate'; import SelectionModule from 'diagram-js/lib/features/selection'; import OverlaysModule from 'diagram-js/lib/features/overlays'; +import BaseViewer from './BaseViewer'; -function checkValidationError(err) { - - // check if we can help the user by indicating wrong BPMN 2.0 xml - // (in case he or the exporting tool did not get that right) - - var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/; - var match = pattern.exec(err.message); - - if (match) { - err.message = - 'unparsable content <' + match[1] + '> detected; ' + - 'this may indicate an invalid BPMN 2.0 diagram file' + match[2]; - } - - return err; -} - -var DEFAULT_OPTIONS = { - width: '100%', - height: '100%', - position: 'relative' -}; - - -/** - * Ensure the passed argument is a proper unit (defaulting to px) - */ -function ensureUnit(val) { - return val + (isNumber(val) ? 'px' : ''); -} - - -/** - * Find BPMNDiagram in definitions by ID - * - * @param {ModdleElement} definitions - * @param {String} diagramId - * - * @return {ModdleElement|null} - */ -function findBPMNDiagram(definitions, diagramId) { - if (!diagramId) { - return null; - } - - return find(definitions.diagrams, function(element) { - return element.id === diagramId; - }) || null; -} /** * A viewer for BPMN 2.0 diagrams. @@ -135,510 +56,10 @@ function findBPMNDiagram(definitions, diagramId) { * @param {Array} [options.additionalModules] a list of modules to use with the default modules */ export default function Viewer(options) { - - options = assign({}, DEFAULT_OPTIONS, options); - - this._moddle = this._createModdle(options); - - this._container = this._createContainer(options); - - /* */ - - addProjectLogo(this._container); - - /* */ - - this._init(this._container, this._moddle, options); + BaseViewer.call(this, options); } -inherits(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|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 parsed BPMN 2.0 definitions - * @param {ModdleElement|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered) - * @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} [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 formatted 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 = domQuery('defs', canvas._svg); - - var contents = innerSVG(contentNode), - defs = defsNode ? '' + innerSVG(defsNode) + '' : ''; - - var bbox = contentNode.getBBox(); - - svg = - '\n' + - '\n' + - '\n' + - '' + - defs + contents + - ''; - } catch (e) { - err = e; - } - - 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() { - 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. - */ -Viewer.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] - */ -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 = domQuery(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('
'); - - 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(moddleOptions); -}; +inherits(Viewer, BaseViewer); // modules the viewer is composed of Viewer.prototype._modules = [ @@ -649,48 +70,4 @@ Viewer.prototype._modules = [ ]; // default moddle extensions the viewer is composed of -Viewer.prototype._moddleExtensions = {}; - -/* */ - -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 = - '' + - img + - ''; - - var linkElement = domify(linkMarkup); - - container.appendChild(linkElement); - - domEvent.bind(linkElement, 'click', function(event) { - openPoweredBy(); - - event.preventDefault(); - }); -} - -/* */ \ No newline at end of file +Viewer.prototype._moddleExtensions = {}; \ No newline at end of file diff --git a/test/spec/BaseModelerSpec.js b/test/spec/BaseModelerSpec.js new file mode 100644 index 00000000..1b67633e --- /dev/null +++ b/test/spec/BaseModelerSpec.js @@ -0,0 +1,20 @@ +import BaseModeler from 'lib/BaseModeler'; +import BaseViewer from 'lib/BaseViewer'; + + +describe('BaseModeler', function() { + + it('should instantiate', function() { + + // when + var instance = new BaseModeler(); + + // then + expect(instance.importXML).to.exist; + expect(instance.saveXML).to.exist; + + expect(instance instanceof BaseModeler).to.be.true; + expect(instance instanceof BaseViewer).to.be.true; + }); + +}); diff --git a/test/spec/BaseViewerSpec.js b/test/spec/BaseViewerSpec.js new file mode 100644 index 00000000..508c1919 --- /dev/null +++ b/test/spec/BaseViewerSpec.js @@ -0,0 +1,18 @@ +import BaseViewer from 'lib/BaseViewer'; + + +describe('BaseViewer', function() { + + it('should instantiate', function() { + + // when + var instance = new BaseViewer(); + + // then + expect(instance.importXML).to.exist; + expect(instance.saveXML).to.exist; + + expect(instance instanceof BaseViewer).to.be.true; + }); + +});