feat(viewer): emit export events

This makes the viewer emit events during SVG and XML export.

These events allow others to hook in, i.e. to trigger additional _save_
actions.

Closes #811
This commit is contained in:
Nico Rehwaldt 2018-06-12 09:27:00 +02:00 committed by Philipp Fromme
parent e8dfccedea
commit 3c87716895
2 changed files with 289 additions and 157 deletions

View File

@ -199,6 +199,16 @@ Viewer.prototype.importXML = function(xml, 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 formated XML
* @param {Boolean} [options.preamble=true] output preamble
@ -212,19 +222,52 @@ Viewer.prototype.saveXML = function(options, done) {
options = {};
}
var self = this;
var definitions = this._definitions;
if (!definitions) {
return done(new Error('no definitions loaded'));
}
this._moddle.toXML(definitions, options, done);
// 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)
*/
@ -235,6 +278,8 @@ Viewer.prototype.saveSVG = function(options, done) {
options = {};
}
this._emit('saveSVG.start');
var svg, err;
try {
@ -261,6 +306,11 @@ Viewer.prototype.saveSVG = function(options, done) {
err = e;
}
this._emit('saveSVG.done', {
error: err,
svg: svg
});
done(err, svg);
};

View File

@ -294,162 +294,6 @@ describe('Viewer', function() {
});
describe('export', function() {
function currentTime() {
return new Date().getTime();
}
function validSVG(svg) {
var expectedStart = '<?xml version="1.0" encoding="utf-8"?>';
var expectedEnd = '</svg>';
expect(svg.indexOf(expectedStart)).to.equal(0);
expect(svg.indexOf(expectedEnd)).to.equal(svg.length - expectedEnd.length);
// ensure correct rendering of SVG contents
expect(svg.indexOf('undefined')).to.equal(-1);
// expect header to be written only once
expect(svg.indexOf('<svg width="100%" height="100%">')).to.equal(-1);
expect(svg.indexOf('<g class="viewport"')).to.equal(-1);
var parser = new DOMParser();
var svgNode = parser.parseFromString(svg, 'image/svg+xml');
// [comment, <!DOCTYPE svg>, svg]
expect(svgNode.childNodes).to.have.length(3);
// no error body
expect(svgNode.body).not.to.exist;
// FIXME(nre): make matcher
return true;
}
it('should export XML', function(done) {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
createViewer(xml, function(err, warnings, viewer) {
// when
viewer.saveXML({ format: true }, function(err, xml) {
// then
expect(xml).to.contain('<?xml version="1.0" encoding="UTF-8"?>');
expect(xml).to.contain('<bpmn2:definitions');
expect(xml).to.contain(' ');
done();
});
});
});
it('should export svg', function(done) {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
createViewer(xml, function(err, warnings, viewer) {
if (err) {
return done(err);
}
// when
viewer.saveSVG(function(err, svg) {
// then
expect(validSVG(svg)).to.be.true;
done(err);
});
});
});
it('should export huge svg', function(done) {
this.timeout(5000);
// given
var xml = require('../fixtures/bpmn/complex.bpmn');
createViewer(xml, function(err, warnings, viewer) {
if (err) {
return done(err);
}
var time = currentTime();
// when
viewer.saveSVG(function(err, svg) {
// then
expect(validSVG(svg)).to.be.true;
// no svg export should not take too long
expect(currentTime() - time).to.be.below(1000);
done(err);
});
});
});
it('should remove outer-makers on export', function(done) {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
function appendTestRect(svgDoc) {
var rect = document.createElementNS(svgDoc.namespaceURI, 'rect');
rect.setAttribute('class', 'outer-bound-marker');
rect.setAttribute('width', 500);
rect.setAttribute('height', 500);
rect.setAttribute('x', 10000);
rect.setAttribute('y', 10000);
svgDoc.appendChild(rect);
}
createViewer(xml, function(err, warnings, viewer) {
if (err) {
return done(err);
}
var svgDoc = viewer._container.childNodes[1].childNodes[1];
appendTestRect(svgDoc);
appendTestRect(svgDoc);
expect(svgDoc.querySelectorAll('.outer-bound-marker')).to.exist;
// when
viewer.saveSVG(function(err, svg) {
var svgDoc = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgDoc.innerHTML = svg;
// then
expect(validSVG(svg)).to.be.true;
expect(svgDoc.querySelector('.outer-bound-marker')).to.be.null;
done(err);
});
});
});
});
describe('creation', function() {
var testModules = [
@ -812,6 +656,244 @@ describe('Viewer', function() {
});
describe('#saveXML', function() {
it('should export XML', function(done) {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
createViewer(xml, function(err, warnings, viewer) {
// when
viewer.saveXML({ format: true }, function(err, xml) {
// then
expect(xml).to.contain('<?xml version="1.0" encoding="UTF-8"?>');
expect(xml).to.contain('<bpmn2:definitions');
expect(xml).to.contain(' ');
done();
});
});
});
it('should emit <saveXML.*> events', function(done) {
var xml = require('../fixtures/bpmn/simple.bpmn');
createViewer(xml, function(err, warnings, viewer) {
var events = [];
viewer.on([
'saveXML.start',
'saveXML.serialized',
'saveXML.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
viewer.importXML(xml, function(err) {
// when
viewer.saveXML(function(err) {
// then
expect(events).to.eql([
[ 'saveXML.start', [ 'definitions' ] ],
[ 'saveXML.serialized', ['error', 'xml' ] ],
[ 'saveXML.done', ['error', 'xml' ] ]
]);
done(err);
});
});
});
});
});
describe('#saveSVG', function() {
function currentTime() {
return new Date().getTime();
}
function validSVG(svg) {
var expectedStart = '<?xml version="1.0" encoding="utf-8"?>';
var expectedEnd = '</svg>';
expect(svg.indexOf(expectedStart)).to.equal(0);
expect(svg.indexOf(expectedEnd)).to.equal(svg.length - expectedEnd.length);
// ensure correct rendering of SVG contents
expect(svg.indexOf('undefined')).to.equal(-1);
// expect header to be written only once
expect(svg.indexOf('<svg width="100%" height="100%">')).to.equal(-1);
expect(svg.indexOf('<g class="viewport"')).to.equal(-1);
var parser = new DOMParser();
var svgNode = parser.parseFromString(svg, 'image/svg+xml');
// [comment, <!DOCTYPE svg>, svg]
expect(svgNode.childNodes).to.have.length(3);
// no error body
expect(svgNode.body).not.to.exist;
// FIXME(nre): make matcher
return true;
}
it('should export svg', function(done) {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
createViewer(xml, function(err, warnings, viewer) {
if (err) {
return done(err);
}
// when
viewer.saveSVG(function(err, svg) {
// then
expect(validSVG(svg)).to.be.true;
done(err);
});
});
});
it('should export huge svg', function(done) {
this.timeout(5000);
// given
var xml = require('../fixtures/bpmn/complex.bpmn');
createViewer(xml, function(err, warnings, viewer) {
if (err) {
return done(err);
}
var time = currentTime();
// when
viewer.saveSVG(function(err, svg) {
// then
expect(validSVG(svg)).to.be.true;
// no svg export should not take too long
expect(currentTime() - time).to.be.below(1000);
done(err);
});
});
});
it('should remove outer-makers on export', function(done) {
// given
var xml = require('../fixtures/bpmn/simple.bpmn');
function appendTestRect(svgDoc) {
var rect = document.createElementNS(svgDoc.namespaceURI, 'rect');
rect.setAttribute('class', 'outer-bound-marker');
rect.setAttribute('width', 500);
rect.setAttribute('height', 500);
rect.setAttribute('x', 10000);
rect.setAttribute('y', 10000);
svgDoc.appendChild(rect);
}
createViewer(xml, function(err, warnings, viewer) {
if (err) {
return done(err);
}
var svgDoc = viewer._container.childNodes[1].childNodes[1];
appendTestRect(svgDoc);
appendTestRect(svgDoc);
expect(svgDoc.querySelectorAll('.outer-bound-marker')).to.exist;
// when
viewer.saveSVG(function(err, svg) {
var svgDoc = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svgDoc.innerHTML = svg;
// then
expect(validSVG(svg)).to.be.true;
expect(svgDoc.querySelector('.outer-bound-marker')).to.be.null;
done(err);
});
});
});
it('should emit <saveSVG.*> events', function(done) {
var xml = require('../fixtures/bpmn/simple.bpmn');
createViewer(xml, function(err, warnings, viewer) {
var events = [];
viewer.on([
'saveSVG.start',
'saveSVG.done'
], function(e) {
// log event type + event arguments
events.push([
e.type,
Object.keys(e).filter(function(key) {
return key !== 'type';
})
]);
});
viewer.importXML(xml, function(err) {
// when
viewer.saveSVG(function() {
// then
expect(events).to.eql([
[ 'saveSVG.start', [ ] ],
[ 'saveSVG.done', ['error', 'svg' ] ]
]);
done(err);
});
});
});
});
});
describe('#on', function() {
it('should fire with given three', function(done) {