From 3c8771689590de6bc6cbb8af8e8f03845377016e Mon Sep 17 00:00:00 2001 From: Nico Rehwaldt Date: Tue, 12 Jun 2018 09:27:00 +0200 Subject: [PATCH] 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 --- lib/Viewer.js | 52 +++++- test/spec/ViewerSpec.js | 394 ++++++++++++++++++++++++---------------- 2 files changed, 289 insertions(+), 157 deletions(-) diff --git a/lib/Viewer.js b/lib/Viewer.js index 8d3c0fd4..ca7631ca 100644 --- a/lib/Viewer.js +++ b/lib/Viewer.js @@ -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); }; diff --git a/test/spec/ViewerSpec.js b/test/spec/ViewerSpec.js index 83538a46..c9d0f404 100644 --- a/test/spec/ViewerSpec.js +++ b/test/spec/ViewerSpec.js @@ -294,162 +294,6 @@ describe('Viewer', function() { }); - describe('export', function() { - - function currentTime() { - return new Date().getTime(); - } - - function validSVG(svg) { - var expectedStart = ''; - var expectedEnd = ''; - - 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('')).to.equal(-1); - expect(svg.indexOf(', 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(''); - expect(xml).to.contain(''); + expect(xml).to.contain(' 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 = ''; + var expectedEnd = ''; + + 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('')).to.equal(-1); + expect(svg.indexOf(', 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 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) {