feat(import): import bpmn shapes
We are able to import shapes based on BPMNDI. Related to #1
This commit is contained in:
parent
284657490e
commit
5a4d0b566a
|
@ -57,7 +57,8 @@ module.exports = function(grunt) {
|
|||
options: {
|
||||
alias: [
|
||||
'<%= config.sources %>/main.js:bpmn',
|
||||
'<%= config.sources %>/Model.js:bpmn/Model'
|
||||
'<%= config.sources %>/Model.js:bpmn/Model',
|
||||
'<%= config.sources %>/Diagram.js:bpmn/Diagram'
|
||||
]
|
||||
},
|
||||
sources: {
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>bpmn-js demo</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="drop-zone">Drop files here</div>
|
||||
<output id="list"></output>
|
||||
|
||||
<div class="canvas"></div>
|
||||
<div id="canvas"></div>
|
||||
|
||||
<script src="../bpmn.js"></script>
|
||||
|
||||
<script>
|
||||
var bpmn = require('bpmn');
|
||||
var Model = require('bpmn/Model');
|
||||
var Diagram = require('bpmn/Diagram');
|
||||
</script>
|
||||
|
||||
|
||||
|
@ -36,6 +38,11 @@
|
|||
if (err) {
|
||||
console.error(err);
|
||||
} else {
|
||||
var diagram = new Diagram(document.getElementById('canvas'));
|
||||
diagram.importDefinitions(definitions, function() {
|
||||
console.log("import done!!");
|
||||
});
|
||||
|
||||
console.log(definitions);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,160 @@
|
|||
/**
|
||||
* outline styles
|
||||
*/
|
||||
.djs-outline {
|
||||
fill: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.djs-group.hover .djs-outline,
|
||||
.djs-group.selected .djs-outline {
|
||||
visibility: visible;
|
||||
stroke-dasharray: 2,4;
|
||||
}
|
||||
|
||||
.djs-group.selected .djs-outline {
|
||||
stroke: blue;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.djs-group.hover .djs-outline {
|
||||
stroke: red;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.djs-group.drop-ok .djs-visual {
|
||||
fill: green;
|
||||
}
|
||||
|
||||
.djs-group.drop-not-ok .djs-visual {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
/**
|
||||
* drag styles
|
||||
*/
|
||||
.djs-dragger {
|
||||
fill: white;
|
||||
opacity: 0.3;
|
||||
stroke: #333;
|
||||
stroke-dasharray: 2,1;
|
||||
}
|
||||
|
||||
.djs-shape.djs-dragging {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/**
|
||||
* hit shape styles
|
||||
*/
|
||||
.djs-hit {
|
||||
stroke-opacity: 0.0;
|
||||
stroke-width: 10px;
|
||||
stroke: fuchsia;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* shape / connection basic styles
|
||||
*/
|
||||
.djs-shape .djs-visual {
|
||||
stroke: black;
|
||||
stroke-width: 2px;
|
||||
fill: #F3F3F3;
|
||||
}
|
||||
|
||||
.djs-connection .djs-visual {
|
||||
stroke-width: 2px;
|
||||
stroke: #333;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.djs-connection .djs-bendpoint {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.djs-connection:hover .djs-bendpoint {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.djs-connection .djs-bendpoint:hover {
|
||||
stroke: #CCC;
|
||||
stroke-width: 1px;
|
||||
fill: yellow;
|
||||
}
|
||||
|
||||
.djs-connection-marker {
|
||||
fill: black;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.djs-menu-vertical, .djs-menu-horizontal {
|
||||
height: 35px;
|
||||
background-color: #52B415;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.djs-menu-vertical > button {
|
||||
display:inline-block;
|
||||
margin-bottom:0;
|
||||
font-weight:400;
|
||||
text-align:center;
|
||||
vertical-align:middle;
|
||||
cursor:pointer;
|
||||
background: #52B415 none;
|
||||
color: white;
|
||||
border: 0px #ffffff;
|
||||
border-right-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-left-width: 1px;
|
||||
white-space:nowrap;
|
||||
padding:5px 12px;
|
||||
font-size:15px;
|
||||
line-height:1.428571429;
|
||||
-webkit-user-select:none;
|
||||
-moz-user-select:none;
|
||||
-ms-user-select:none;
|
||||
user-select:none;
|
||||
}
|
||||
.djs-menu-vertical > .button:focus{
|
||||
outline:thin dotted #333;
|
||||
outline:5px auto -webkit-focus-ring-color;
|
||||
outline-offset:-2px;
|
||||
}
|
||||
.djs-menu-vertical > .btn:hover,.btn:focus {
|
||||
color:#707070;text-decoration:none;
|
||||
}
|
||||
|
||||
.djs-menu-brand {
|
||||
background: url("logo.png") scroll no-repeat transparent;
|
||||
background-size: 35px 35px;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* Button icons*/
|
||||
.djs-undo-button > i:before {
|
||||
font-family: awesomesymbols;
|
||||
content: "\f0e2";
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.djs-redo-button > i:before {
|
||||
font-family: awesomesymbols;
|
||||
content: "\f01e";
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.djs-rect-button > i {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 1px solid;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Fonts */
|
||||
@font-face {
|
||||
font-family: "awesomesymbols";
|
||||
src: url("../fonts/fontawesome-webfont.woff") format('woff');
|
||||
font-weight: normal;
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
var _ = require('lodash');
|
||||
|
||||
function BpmnTraverser(visitor) {
|
||||
|
||||
var elementDiMap = {};
|
||||
var elementGfxMap = {};
|
||||
|
||||
///// Helpers /////////////////////////////////
|
||||
|
||||
function contextual(fn, ctx) {
|
||||
return function(e) {
|
||||
fn(e, ctx);
|
||||
};
|
||||
}
|
||||
|
||||
function is(element, type) {
|
||||
return element.__isInstanceOf(type);
|
||||
}
|
||||
|
||||
function visit(element, di, ctx) {
|
||||
|
||||
// call visitor
|
||||
var gfx = visitor(element, di, ctx);
|
||||
|
||||
// and log returned result
|
||||
elementGfxMap[element.id] = gfx;
|
||||
|
||||
return gfx;
|
||||
}
|
||||
|
||||
////// DI handling ////////////////////////////
|
||||
|
||||
function buildDiMap(definitions) {
|
||||
_.forEach(definitions.diagrams, handleDiagram);
|
||||
}
|
||||
|
||||
function registerDi(element) {
|
||||
var bpmnElement = element.bpmnElement;
|
||||
elementDiMap[bpmnElement.id] = element;
|
||||
}
|
||||
|
||||
function getDi(bpmnElement) {
|
||||
var id = bpmnElement.id;
|
||||
return id ? elementDiMap[id] : null;
|
||||
}
|
||||
|
||||
function handleDiagram(diagram) {
|
||||
handlePlane(diagram.plane);
|
||||
}
|
||||
|
||||
function handlePlane(plane) {
|
||||
registerDi(plane);
|
||||
|
||||
_.forEach(plane.planeElement, handlePlaneElement);
|
||||
}
|
||||
|
||||
function handlePlaneElement(planeElement) {
|
||||
registerDi(planeElement);
|
||||
}
|
||||
|
||||
|
||||
////// Semantic handling //////////////////////
|
||||
|
||||
function handleDefinitions(definitions, diagram) {
|
||||
buildDiMap(definitions);
|
||||
|
||||
// make sure we walk the correct bpmnElement
|
||||
|
||||
var diagrams = definitions.diagrams;
|
||||
|
||||
if (diagram && diagrams.indexOf(diagram) === -1) {
|
||||
throw new Error('diagram not part of bpmn:Definitions');
|
||||
}
|
||||
|
||||
if (!diagram) {
|
||||
diagram = diagrams[0];
|
||||
}
|
||||
|
||||
var rootElement = diagram.plane.bpmnElement;
|
||||
|
||||
if (is(rootElement, 'bpmn:Process')) {
|
||||
handleProcess(rootElement);
|
||||
} else
|
||||
if (is(rootElement, 'bpmn:Collaboration')) {
|
||||
handleCollaboration(rootElement);
|
||||
} else {
|
||||
throw new Error('unsupported root element for bpmndi:Diagram: ' + type.name);
|
||||
}
|
||||
}
|
||||
|
||||
function handleFlowNode(flowNode, context) {
|
||||
var di = getDi(flowNode);
|
||||
|
||||
var childCtx = visit(flowNode, di, context);
|
||||
|
||||
if (is(flowNode, 'bpmn:FlowElementsContainer')) {
|
||||
handleFlowElementsContainer(flowNode, childCtx);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSequenceFlow(sequenceFlow, context) {
|
||||
var di = getDi(sequenceFlow);
|
||||
|
||||
visit(sequenceFlow, di, context);
|
||||
}
|
||||
|
||||
function handleFlowElementsContainer(container, context) {
|
||||
|
||||
var sequenceFlows = [];
|
||||
|
||||
// handle FlowNode(s)
|
||||
_.forEach(container.flowElements, function(e) {
|
||||
if (is(e, 'bpmn:SequenceFlow')) {
|
||||
sequenceFlows.push(e);
|
||||
} else
|
||||
if (is(e, 'bpmn:FlowNode')) {
|
||||
handleFlowNode(e, context);
|
||||
} else {
|
||||
throw new Error('unrecognized element: ' + e);
|
||||
}
|
||||
});
|
||||
|
||||
// handle SequenceFlows
|
||||
_.forEach(sequenceFlows, contextual(handleSequenceFlow, context));
|
||||
}
|
||||
|
||||
function handleParticipant(participant, context) {
|
||||
var di = getDi(participant);
|
||||
|
||||
var newCtx = visit(participant, di, context);
|
||||
|
||||
var process = participant.processRef;
|
||||
if (process) {
|
||||
handleProcess(process, newCtx);
|
||||
}
|
||||
}
|
||||
|
||||
function handleProcess(process, context) {
|
||||
handleFlowElementsContainer(process, context);
|
||||
}
|
||||
|
||||
function handleCollaboration(collaboration) {
|
||||
|
||||
_.forEach(collaboration.participants, contextual(handleParticipant));
|
||||
|
||||
// TODO: handle message flows
|
||||
}
|
||||
|
||||
|
||||
///// API ////////////////////////////////
|
||||
|
||||
return {
|
||||
handleDefinitions: handleDefinitions
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = BpmnTraverser;
|
|
@ -0,0 +1,15 @@
|
|||
var DiagramJS = require('diagram-js');
|
||||
var Importer = require('./Importer');
|
||||
|
||||
function Diagram(container) {
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
Diagram.prototype.importDefinitions = function(definitions, done) {
|
||||
|
||||
this.diagram = new DiagramJS({ canvas: { container: this.container }});
|
||||
|
||||
Importer.importBpmnDiagram(this.diagram, definitions, done);
|
||||
};
|
||||
|
||||
module.exports = Diagram;
|
|
@ -0,0 +1,23 @@
|
|||
var BpmnTreeWalker = require('./BpmnTreeWalker');
|
||||
|
||||
function importBpmnDiagram(diagram, definitions, done) {
|
||||
|
||||
var canvas = diagram.resolve('canvas');
|
||||
|
||||
var visitor = function(element, di, parent) {
|
||||
|
||||
if (di.$type === 'bpmndi:BPMNShape') {
|
||||
var bounds = di.bounds;
|
||||
|
||||
canvas.addShape({ id: element.id, type: element.$type, x: bounds.x, y: bounds.y, width: bounds.width, height: bounds.height });
|
||||
}
|
||||
};
|
||||
|
||||
var walker = new BpmnTreeWalker(visitor);
|
||||
|
||||
walker.handleDefinitions(definitions);
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
module.exports.importBpmnDiagram = importBpmnDiagram;
|
|
@ -0,0 +1,6 @@
|
|||
var Diagram = require('./Diagram'),
|
||||
Model = require('./Model');
|
||||
|
||||
|
||||
module.exports.Diagram = Diagram;
|
||||
module.exports.Model = Model;
|
|
@ -52,7 +52,8 @@
|
|||
"xsd-schema-validator": "0.0.3",
|
||||
"sax": "~0.6.0",
|
||||
"lodash": "~2.4.0",
|
||||
"eve": "0.4.1"
|
||||
"eve": "0.4.1",
|
||||
"brfs": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"moddle": "~0.0.1",
|
||||
|
|
|
@ -23,7 +23,8 @@ module.exports = function(karma) {
|
|||
// browserify configuration
|
||||
browserify: {
|
||||
debug: true,
|
||||
watch: true
|
||||
watch: true,
|
||||
transform: [ 'debowerify', 'brfs' ]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="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" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd" id="_pHDz0KojEeOJhIBv1RySdg" targetNamespace="http://activiti.org/bpmn">
|
||||
<bpmn2:collaboration id="_Collaboration_2">
|
||||
<bpmn2:participant id="_Participant_2" name="Pool" processRef="Process_1"/>
|
||||
<bpmn2:participant id="Participant_1" name="Pool" processRef="Process_2"/>
|
||||
</bpmn2:collaboration>
|
||||
<bpmn2:process id="Process_1" isExecutable="false">
|
||||
<bpmn2:laneSet id="LaneSet_1" name="Lane Set 1">
|
||||
<bpmn2:lane id="Lane_1" name="Lane 1">
|
||||
<bpmn2:childLaneSet xsi:type="bpmn2:tLaneSet" id="LaneSet_2">
|
||||
<bpmn2:lane id="Lane_2" name="Lane 2"/>
|
||||
<bpmn2:lane id="Lane_3" name="Lane 3">
|
||||
<bpmn2:flowNodeRef>Task_1</bpmn2:flowNodeRef>
|
||||
</bpmn2:lane>
|
||||
</bpmn2:childLaneSet>
|
||||
</bpmn2:lane>
|
||||
</bpmn2:laneSet>
|
||||
<bpmn2:task id="Task_1"/>
|
||||
</bpmn2:process>
|
||||
<bpmn2:process id="Process_2" isExecutable="false">
|
||||
<bpmn2:startEvent id="StartEvent_1"/>
|
||||
</bpmn2:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="_Collaboration_2">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Participant_2" bpmnElement="_Participant_2" isHorizontal="true">
|
||||
<dc:Bounds height="356.0" width="540.0" x="222.0" y="0.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Participant_3" bpmnElement="Participant_1" isHorizontal="true">
|
||||
<dc:Bounds height="100.0" width="600.0" x="222.0" y="415.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Lane_2" bpmnElement="Lane_1" isHorizontal="true">
|
||||
<dc:Bounds height="356.0" width="510.0" x="252.0" y="0.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Lane_3" bpmnElement="Lane_2" isHorizontal="true">
|
||||
<dc:Bounds height="215.0" width="480.0" x="282.0" y="0.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Lane_4" bpmnElement="Lane_3" isHorizontal="true">
|
||||
<dc:Bounds height="142.0" width="480.0" x="282.0" y="214.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_3" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds height="36.0" width="36.0" x="324.0" y="448.0"/>
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds height="0.0" width="0.0" x="342.0" y="489.0"/>
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="_BPMNShape_Task_3" bpmnElement="Task_1">
|
||||
<dc:Bounds height="80.0" width="100.0" x="360.0" y="246.0"/>
|
||||
</bpmndi:BPMNShape>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn2:definitions>
|
|
@ -0,0 +1,39 @@
|
|||
var fs = require('fs');
|
||||
|
||||
var BpmnModel = require('../../../lib/Model'),
|
||||
Diagram = require('../../../lib/Diagram');
|
||||
|
||||
var Matchers = require('../Matchers');
|
||||
|
||||
describe('Importer', function() {
|
||||
|
||||
var bpmnModel = BpmnModel.instance();
|
||||
|
||||
function read(xml, opts, callback) {
|
||||
return BpmnModel.fromXML(xml, 'bpmn:Definitions', opts, callback);
|
||||
}
|
||||
|
||||
beforeEach(Matchers.add);
|
||||
|
||||
var container;
|
||||
|
||||
beforeEach(function() {
|
||||
container = document.createElement('div');
|
||||
document.getElementsByTagName('body')[0].appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
container.parentNode.removeChild(container);
|
||||
});
|
||||
|
||||
it('should import simple process', function(done) {
|
||||
|
||||
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
|
||||
|
||||
read(xml, function(err, result) {
|
||||
var diagram = new Diagram(container);
|
||||
|
||||
diagram.importDefinitions(result, done);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
var _ = require('lodash');
|
||||
|
||||
var BpmnModel = require('../../../../lib/Model'),
|
||||
BpmnTreeWalker = require('../../../../lib/BpmnTreeWalker');
|
||||
|
||||
var Helper = require('../Helper'),
|
||||
Matchers = require('../../Matchers');
|
||||
|
||||
|
||||
describe('BpmnTreeWalker', function() {
|
||||
|
||||
function readBpmnDiagram(file) {
|
||||
return Helper.readFile('test/fixtures/bpmn/' + file);
|
||||
}
|
||||
|
||||
function read(xml, root, opts, callback) {
|
||||
return BpmnModel.fromXML(xml, root, opts, callback);
|
||||
}
|
||||
|
||||
function readFile(file, root, opts, callback) {
|
||||
return read(readBpmnDiagram(file), root, opts, callback);
|
||||
}
|
||||
|
||||
beforeEach(Matchers.add);
|
||||
|
||||
var bpmnModel = BpmnModel.instance();
|
||||
|
||||
|
||||
it('should walk simple process', function(done) {
|
||||
|
||||
readFile('simple.bpmn', 'bpmn:Definitions', function(err, definitions) {
|
||||
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
|
||||
var drawn = [];
|
||||
|
||||
var visitor = function(element, di, ctx) {
|
||||
var id = element.id;
|
||||
|
||||
drawn.push({ id: id, parent: ctx });
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
new BpmnTreeWalker(visitor).handleDefinitions(definitions);
|
||||
|
||||
var expectedDrawn = [
|
||||
{ id: 'SubProcess_1', parent: undefined },
|
||||
{ id: 'StartEvent_1', parent: 'SubProcess_1' },
|
||||
{ id: 'Task_1', parent: 'SubProcess_1' },
|
||||
{ id: 'SequenceFlow_1', parent: 'SubProcess_1' } ];
|
||||
|
||||
|
||||
expect(drawn).toDeepEqual(expectedDrawn);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should walk collaboration', function(done) {
|
||||
|
||||
readFile('collaboration.bpmn', 'bpmn:Definitions', function(err, definitions) {
|
||||
|
||||
if (err) {
|
||||
done(err);
|
||||
} else {
|
||||
|
||||
var drawn = [];
|
||||
|
||||
var visitor = function(element, di, ctx) {
|
||||
var id = element.id;
|
||||
|
||||
drawn.push({ id: id, parent: ctx });
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
new BpmnTreeWalker(visitor).handleDefinitions(definitions);
|
||||
|
||||
var expectedDrawn = [
|
||||
{ id : '_Participant_2', parent : undefined },
|
||||
{ id : 'Task_1', parent : '_Participant_2' },
|
||||
{ id : 'Participant_1', parent : undefined },
|
||||
{ id : 'StartEvent_1', parent : 'Participant_1' } ];
|
||||
|
||||
|
||||
expect(drawn).toDeepEqual(expectedDrawn);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue