feat(Viewer): allow to select diagram to view

This introduces a new parameter to `Viewer#importXML`
which enables to choose the diagram to display.

```
var viewer = new Viewer({ container: container });
viewer.importXML(xml, diagramId, done);
```

Closes #87
This commit is contained in:
Valerio Spadaro 2019-04-04 16:17:43 +02:00 committed by Nico Rehwaldt
parent 0c71ad30a0
commit 1c0585aaaf
6 changed files with 6277 additions and 14 deletions

View File

@ -6,6 +6,8 @@
*/
import {
assign,
find,
isFunction,
isNumber,
omit
} from 'min-dash';
@ -66,6 +68,26 @@ function ensureUnit(val) {
return val + (isNumber(val) ? 'px' : '');
}
/**
*
* Find BPMNDiagram in definitions by ID
*
* @param {ModdleElement} definitions
* @param {String} diagramId
*
* @return {Diagram|undefined}
*/
function findDiagram(definitions, diagramId) {
if (!diagramId) {
return;
}
return find(definitions.diagrams, function(element) {
return element.id === diagramId;
});
}
/**
* A viewer for BPMN 2.0 diagrams.
*
@ -152,9 +174,15 @@ inherits(Viewer, Diagram);
* You can use these events to hook into the life-cycle.
*
* @param {String} xml the BPMN 2.0 xml
* @param {String} [diagramId] id of the diagram to render (if not provided, the first one will be rendered)
* @param {Function} [done] invoked with (err, warnings=[])
*/
Viewer.prototype.importXML = function(xml, done) {
Viewer.prototype.importXML = function(xml, diagramId, done) {
if (isFunction(diagramId)) {
done = diagramId;
diagramId = null;
}
// done is optional
done = done || function() {};
@ -175,7 +203,8 @@ Viewer.prototype.importXML = function(xml, done) {
context: context
}) || definitions;
var parseWarnings = context.warnings;
var parseWarnings = context.warnings,
diagram;
if (err) {
err = checkValidationError(err);
@ -185,7 +214,17 @@ Viewer.prototype.importXML = function(xml, done) {
return done(err, parseWarnings);
}
self.importDefinitions(definitions, function(err, importWarnings) {
diagram = findDiagram(definitions, diagramId);
if (diagramId && !diagram) {
err = new Error('BPMNDiagram not found');
self._emit('import.done', { error: err, warnings: parseWarnings || [] });
return done(err);
}
self.importDefinitions(definitions, diagram, function(err, importWarnings) {
var allWarnings = [].concat(parseWarnings, importWarnings || []);
self._emit('import.done', { error: err, warnings: allWarnings });
@ -346,7 +385,12 @@ Viewer.prototype.saveSVG = function(options, done) {
*/
Viewer.prototype.importDefinitions = function(definitions, done) {
Viewer.prototype.importDefinitions = function(definitions, diagram, done) {
if (isFunction(diagram)) {
done = diagram;
diagram = null;
}
// catch synchronous exceptions during #clear()
try {
@ -362,7 +406,7 @@ Viewer.prototype.importDefinitions = function(definitions, done) {
}
// perform graphical import
return importBpmnDiagram(this, definitions, done);
return importBpmnDiagram(this, definitions, diagram, done);
};
Viewer.prototype.getModules = function() {

View File

@ -1,5 +1,8 @@
import BpmnTreeWalker from './BpmnTreeWalker';
import {
isFunction
} from 'min-dash';
/**
* Import the definitions into a diagram.
@ -8,9 +11,16 @@ import BpmnTreeWalker from './BpmnTreeWalker';
*
* @param {Diagram} diagram
* @param {ModdleElement} definitions
* @param {ModdleElement} [selectedDiagram] the BPMNDiagram element selected to be rendered
* (if not provided, the first one will be rendered)
* @param {Function} done the callback, invoked with (err, [ warning ]) once the import is done
*/
export function importBpmnDiagram(diagram, definitions, done) {
export function importBpmnDiagram(diagram, definitions, selectedDiagram, done) {
if (isFunction(selectedDiagram)) {
done = selectedDiagram;
selectedDiagram = null;
}
var importer,
eventBus,
@ -24,8 +34,9 @@ export function importBpmnDiagram(diagram, definitions, done) {
* all elements you encounter.
*
* @param {ModdleElement} definitions
* @param {ModdleElement} selectedDiagram
*/
function render(definitions) {
function render(definitions, selectedDiagram) {
var visitor = {
@ -46,7 +57,7 @@ export function importBpmnDiagram(diagram, definitions, done) {
// traverse BPMN 2.0 document model,
// starting at definitions
walker.handleDefinitions(definitions);
walker.handleDefinitions(definitions, selectedDiagram);
}
try {
@ -56,7 +67,7 @@ export function importBpmnDiagram(diagram, definitions, done) {
eventBus.fire('import.render.start', { definitions: definitions });
render(definitions);
render(definitions, selectedDiagram);
eventBus.fire('import.render.complete', {
error: error,

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_0j4810n" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.0.1">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_1" sourceRef="StartEvent_1" targetRef="Task_1" />
<bpmn:userTask id="Task_1">
<bpmn:incoming>SequenceFlow_1</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_2</bpmn:outgoing>
</bpmn:userTask>
<bpmn:sequenceFlow id="SequenceFlow_2" sourceRef="Task_1" targetRef="Task_2" />
<bpmn:userTask id="Task_2">
<bpmn:incoming>SequenceFlow_2</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_3</bpmn:outgoing>
</bpmn:userTask>
<bpmn:endEvent id="EndEvent_1">
<bpmn:incoming>SequenceFlow_3</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_3" sourceRef="Task_2" targetRef="EndEvent_1" />
</bpmn:process>
<bpmn:process id="Process_2" isExecutable="true">
<bpmn:startEvent id="StartEvent_2" />
<bpmn:intermediateThrowEvent id="IntermediateThrowEvent_1" />
<bpmn:sequenceFlow id="SequenceFlow_4" sourceRef="StartEvent_2" targetRef="IntermediateThrowEvent_1" />
<bpmn:endEvent id="EndEvent_2" />
<bpmn:sequenceFlow id="SequenceFlow_5" sourceRef="IntermediateThrowEvent_1" targetRef="EndEvent_2" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_2">
<bpmndi:BPMNPlane id="BPMNPlane_2" bpmnElement="Process_2">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_2">
<dc:Bounds x="156" y="81" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_IntermediateThrowEvent_1" bpmnElement="IntermediateThrowEvent_1">
<dc:Bounds x="234" y="81" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_EndEvent_2" bpmnElement="EndEvent_2">
<dc:Bounds x="328" y="81" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_4" bpmnElement="SequenceFlow_4">
<di:waypoint x="192" y="99" />
<di:waypoint x="234" y="99" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_5" bpmnElement="SequenceFlow_5">
<di:waypoint x="270" y="99" />
<di:waypoint x="328" y="99" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_1" bpmnElement="StartEvent_1">
<dc:Bounds x="156" y="103" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_1" bpmnElement="Task_1">
<dc:Bounds x="242" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_Task_2" bpmnElement="Task_2">
<dc:Bounds x="392" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_EndEvent_1" bpmnElement="EndEvent_1">
<dc:Bounds x="542" y="103" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_1" bpmnElement="SequenceFlow_1">
<di:waypoint x="192" y="121" />
<di:waypoint x="242" y="121" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_2" bpmnElement="SequenceFlow_2">
<di:waypoint x="342" y="121" />
<di:waypoint x="392" y="121" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_3" bpmnElement="SequenceFlow_3">
<di:waypoint x="492" y="121" />
<di:waypoint x="542" y="121" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

5976
test/fixtures/bpmn/multiple-diagrams.bpmn vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,10 @@ import Viewer from 'lib/Viewer';
import inherits from 'inherits';
import {
isFunction
} from 'min-dash';
describe('Viewer', function() {
@ -18,10 +22,15 @@ describe('Viewer', function() {
});
function createViewer(xml, done) {
function createViewer(xml, diagramId, done) {
if (isFunction(diagramId)) {
done = diagramId;
diagramId = null;
}
var viewer = new Viewer({ container: container });
viewer.importXML(xml, function(err, warnings) {
viewer.importXML(xml, diagramId, function(err, warnings) {
done(err, warnings, viewer);
});
}
@ -670,6 +679,68 @@ describe('Viewer', function() {
});
});
it('should import BPMN with multiple diagrams without diagram id specified', function(done) {
// given
var xml = require('../fixtures/bpmn/multiple-diagrams.bpmn');
// when
createViewer(xml, function(err) {
// then
done(err);
});
});
it('should import BPMN with multiple diagrams with diagram id specified', function(done) {
// given
var xml = require('../fixtures/bpmn/multiple-diagrams.bpmn');
// when
createViewer(xml, 'Diagram_80fecfcd-0165-4c36-90b6-3ea384265fe7', function(err) {
// then
done(err);
});
});
it('should complete with error if diagram of provided ID does not exist', function(done) {
// given
var xml = require('../fixtures/bpmn/multiple-diagrams.bpmn');
// when
createViewer(xml, 'Diagram_IDontExist', function(err) {
// then
expect(err).to.exist;
expect(err.message).to.eql('BPMNDiagram not found');
done();
});
});
it('should import BPMN with multiple diagrams when only xml is provided', function(done) {
// given
var viewer = new Viewer({ container: container });
var xml = require('../fixtures/bpmn/multiple-diagrams.bpmn');
// when
viewer.importXML(xml);
// then
viewer.on('import.done', function(event) {
done();
});
});
});

View File

@ -18,7 +18,8 @@ import {
} from 'diagram-js/lib/util/GraphicsUtil';
import {
find
find,
isFunction
} from 'min-dash';
import { is } from 'lib/util/ModelUtil';
@ -40,12 +41,22 @@ describe('import - Importer', function() {
});
function runImport(diagram, xml, done) {
function runImport(diagram, xml, diagramId, done) {
if (isFunction(diagramId)) {
done = diagramId;
diagramId = null;
}
var moddle = new BpmnModdle();
moddle.fromXML(xml, function(err, definitions) {
importBpmnDiagram(diagram, definitions, done);
var selectedDiagram = find(definitions.diagrams, function(element) {
return element.id === diagramId;
});
importBpmnDiagram(diagram, definitions, selectedDiagram, done);
});
}
@ -394,7 +405,79 @@ describe('import - Importer', function() {
done(err);
});
});
it('should import single diagram from multiple diagrams 2', function(done) {
// given
var xml = require('../../fixtures/bpmn/import/multiple-diagrams.bpmn');
var events = [];
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events.push({
type: 'add',
semantic: e.element.businessObject.id,
di: e.element.businessObject.di.id,
diagramElement: e.element && e.element.id
});
});
// when
runImport(diagram, xml, 'BPMNDiagram_2', function(err, warnings) {
// then
expect(events).to.eql([
{ type: 'add', semantic: 'Process_2', di: 'BPMNPlane_2', diagramElement: 'Process_2' },
{ type: 'add', semantic: 'StartEvent_2', di: '_BPMNShape_StartEvent_2', diagramElement: 'StartEvent_2' },
{ type: 'add', semantic: 'IntermediateThrowEvent_1', di: '_BPMNShape_IntermediateThrowEvent_1', diagramElement: 'IntermediateThrowEvent_1' },
{ type: 'add', semantic: 'EndEvent_2', di: '_BPMNShape_EndEvent_2', diagramElement: 'EndEvent_2' },
{ type: 'add', semantic: 'SequenceFlow_4', di: 'BPMNEdge_SequenceFlow_4', diagramElement: 'SequenceFlow_4' },
{ type: 'add', semantic: 'SequenceFlow_5', di: 'BPMNEdge_SequenceFlow_5', diagramElement: 'SequenceFlow_5' }
]);
done(err);
});
});
it('should import single diagram from multiple diagrams 1', function(done) {
// given
var xml = require('../../fixtures/bpmn/import/multiple-diagrams.bpmn');
var events = [];
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events.push({
type: 'add',
semantic: e.element.businessObject.id,
di: e.element.businessObject.di.id,
diagramElement: e.element && e.element.id
});
});
// when
runImport(diagram, xml, 'BPMNDiagram_1', function(err, warnings) {
// then
expect(events).to.eql([
{ type: 'add', semantic: 'Process_1', di: 'BPMNPlane_1', diagramElement: 'Process_1' },
{ type: 'add', semantic: 'StartEvent_1', di: '_BPMNShape_StartEvent_1', diagramElement: 'StartEvent_1' },
{ type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_1', diagramElement: 'Task_1' },
{ type: 'add', semantic: 'Task_2', di: '_BPMNShape_Task_2', diagramElement: 'Task_2' },
{ type: 'add', semantic: 'EndEvent_1', di: '_BPMNShape_EndEvent_1', diagramElement: 'EndEvent_1' },
{ type: 'add', semantic: 'SequenceFlow_1', di: 'BPMNEdge_SequenceFlow_1', diagramElement: 'SequenceFlow_1' },
{ type: 'add', semantic: 'SequenceFlow_2', di: 'BPMNEdge_SequenceFlow_2', diagramElement: 'SequenceFlow_2' },
{ type: 'add', semantic: 'SequenceFlow_3', di: 'BPMNEdge_SequenceFlow_3', diagramElement: 'SequenceFlow_3' }
]);
done(err);
});
});
});