feat(import): allow users to hook into via events

This commit adds more life-cycle events users can
plug into during xml parsing and rendering:

* import.parse.start
* import.parse.complete
* import.render.start
* import.render.complete
* import.done

Some other events had to go because of that, namely

* import.start
* import.(success|error)

BREAKING CHANGE:

* the event import.start got renamed to import.render.start
* the events import.success and import.error got removed
in favour of import.render.complete (passing err, warnings)
This commit is contained in:
Nico Rehwaldt 2016-03-17 09:39:43 +01:00
parent a5b8f379fc
commit 46d8abdd70
5 changed files with 139 additions and 58 deletions

View File

@ -101,23 +101,52 @@ var initialDiagram =
*/
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);
}
inherits(Modeler, Viewer);
module.exports = Modeler;
/**
* Create a new diagram to start modeling.
*
* @param {Function} done
*/
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;
};
Modeler.prototype._parsedXML = function(definitions, context) {
/**
* 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,
@ -167,7 +196,4 @@ Modeler.prototype._modelingModules = [
Modeler.prototype._modules = [].concat(
Modeler.prototype._modules,
Modeler.prototype._interactionModules,
Modeler.prototype._modelingModules);
module.exports = Modeler;
Modeler.prototype._modelingModules);

View File

@ -126,11 +126,23 @@ module.exports = Viewer;
/**
* Import and render a BPMN 2.0 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 {Function} done invoked with (err, warnings=[])
*/
@ -138,21 +150,34 @@ Viewer.prototype.importXML = function(xml, done) {
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;
if (err) {
err = checkValidationError(err);
return done(err);
}
if (typeof self._parsedXML === 'function') {
self._parsedXML(definitions, context);
self._emit('import.done', { error: err });
return done(err);
}
var parseWarnings = context.warnings;
self.importDefinitions(definitions, function(err, importWarnings) {
var allWarnings = parseWarnings.concat(importWarnings || []);
var allWarnings = [].concat(parseWarnings, importWarnings || []);
self._emit('import.done', { error: err, warnings: allWarnings });
done(err, allWarnings);
});
@ -349,6 +374,18 @@ Viewer.prototype._init = function(container, moddle, options) {
Diagram.call(this, diagramOptions);
};
/**
* 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 parent = options.container,

View File

@ -21,7 +21,13 @@ function importBpmnDiagram(diagram, definitions, done) {
var error,
warnings = [];
function parse(definitions) {
/**
* Walk the diagram semantically, importing (=drawing)
* all elements you encounter.
*
* @param {ModdleElement} definitions
*/
function render(definitions) {
var visitor = {
@ -40,19 +46,24 @@ function importBpmnDiagram(diagram, definitions, done) {
var walker = new BpmnTreeWalker(visitor, translate);
// import
// traverse BPMN 2.0 document model,
// starting at definitions
walker.handleDefinitions(definitions);
}
eventBus.fire('import.start', { definitions: definitions });
eventBus.fire('import.render.start', { definitions: definitions });
try {
parse(definitions);
render(definitions);
} catch (e) {
error = e;
}
eventBus.fire(error ? 'import.error' : 'import.success', { error: error, warnings: warnings });
eventBus.fire('import.render.complete', {
error: error,
warnings: warnings
});
done(error, warnings);
}

View File

@ -91,45 +91,6 @@ describe('Viewer', function() {
});
describe('import events', function() {
it('should fire <import.*> events', function(done) {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/simple.bpmn');
var events = [];
viewer.on('import.start', function() {
events.push('import.start');
});
viewer.on('import.success', function() {
events.push('import.success');
});
viewer.on('import.error', function() {
events.push('import.error');
});
// when
viewer.importXML(xml, function(err) {
// then
expect(events).to.eql([
'import.start',
'import.success'
]);
done(err);
});
});
});
describe('overlay support', function() {
it('should allow to add overlays', function(done) {
@ -765,6 +726,52 @@ describe('Viewer', function() {
});
describe('#importXML', function() {
it('should emit <import.*> events', function(done) {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/simple.bpmn');
var events = [];
viewer.on([
'import.parse.start',
'import.parse.complete',
'import.render.start',
'import.render.complete',
'import.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
// when
viewer.importXML(xml, function(err) {
// then
expect(events).to.eql([
[ 'import.parse.start', [ 'xml' ] ],
[ 'import.parse.complete', ['error', 'definitions', 'context' ] ],
[ 'import.render.start', [ 'definitions' ] ],
[ 'import.render.complete', [ 'error', 'warnings' ] ],
[ 'import.done', [ 'error', 'warnings' ] ]
]);
done(err);
});
});
});
describe('#on', function() {
it('should fire with given three', function(done) {

View File

@ -45,7 +45,7 @@ describe('import - Importer', function() {
describe('events', function() {
it('should fire <import.start> and <import.success>', function(done) {
it('should fire <import.render.start> and <import.render.complete>', function(done) {
// given
var xml = require('../../fixtures/bpmn/import/process.bpmn');
@ -55,13 +55,13 @@ describe('import - Importer', function() {
var eventBus = diagram.get('eventBus');
// log events
eventBus.on('import.start', function(event) {
eventBus.on('import.render.start', function(event) {
expect(event.definitions).to.exist;
eventCount++;
});
eventBus.on('import.success', function(event) {
eventBus.on('import.render.complete', function(event) {
expect(event).to.have.property('error');
expect(event).to.have.property('warnings');