diff --git a/Gruntfile.js b/Gruntfile.js
index 6f071280..78dab1e7 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -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: {
diff --git a/example/index.html b/example/index.html
index c9093419..9062e7f8 100644
--- a/example/index.html
+++ b/example/index.html
@@ -1,18 +1,20 @@
bpmn-js demo
+
Drop files here
-
+
@@ -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);
}
});
diff --git a/example/style.css b/example/style.css
new file mode 100644
index 00000000..de79351a
--- /dev/null
+++ b/example/style.css
@@ -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;
+}
diff --git a/lib/BpmnTreeWalker.js b/lib/BpmnTreeWalker.js
new file mode 100644
index 00000000..f890581a
--- /dev/null
+++ b/lib/BpmnTreeWalker.js
@@ -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;
\ No newline at end of file
diff --git a/lib/Diagram.js b/lib/Diagram.js
new file mode 100644
index 00000000..0d120df8
--- /dev/null
+++ b/lib/Diagram.js
@@ -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;
\ No newline at end of file
diff --git a/lib/Importer.js b/lib/Importer.js
new file mode 100644
index 00000000..438bddb8
--- /dev/null
+++ b/lib/Importer.js
@@ -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;
\ No newline at end of file
diff --git a/lib/main.js b/lib/main.js
index e69de29b..68b5c21e 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -0,0 +1,6 @@
+var Diagram = require('./Diagram'),
+ Model = require('./Model');
+
+
+module.exports.Diagram = Diagram;
+module.exports.Model = Model;
\ No newline at end of file
diff --git a/package.json b/package.json
index 07f99621..ea17380a 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/test/config/karma.unit.js b/test/config/karma.unit.js
index a49e8f3f..94b501ef 100644
--- a/test/config/karma.unit.js
+++ b/test/config/karma.unit.js
@@ -23,7 +23,8 @@ module.exports = function(karma) {
// browserify configuration
browserify: {
debug: true,
- watch: true
+ watch: true,
+ transform: [ 'debowerify', 'brfs' ]
}
});
};
diff --git a/test/fixtures/bpmn/collaboration.bpmn b/test/fixtures/bpmn/collaboration.bpmn
new file mode 100644
index 00000000..7ff95ad4
--- /dev/null
+++ b/test/fixtures/bpmn/collaboration.bpmn
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Task_1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/spec/browser/ImporterSpec.js b/test/spec/browser/ImporterSpec.js
new file mode 100644
index 00000000..b33a40dd
--- /dev/null
+++ b/test/spec/browser/ImporterSpec.js
@@ -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);
+ });
+ });
+});
\ No newline at end of file
diff --git a/test/spec/node/import/BpmnTreeWalkerSpec.js b/test/spec/node/import/BpmnTreeWalkerSpec.js
new file mode 100644
index 00000000..9c1174e0
--- /dev/null
+++ b/test/spec/node/import/BpmnTreeWalkerSpec.js
@@ -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();
+ }
+ });
+ });
+});
\ No newline at end of file