feat(lib/core): use directly linked data-model

Closes #91
This commit is contained in:
Nico Rehwaldt 2014-07-16 16:15:23 +02:00
parent 5da37a24c4
commit f380a4b044
12 changed files with 433 additions and 500 deletions

View File

@ -1,7 +1,7 @@
'use strict';
var _ = require('lodash');
var Refs = require('object-refs');
function hasLabel(semantic) {
return semantic.$instanceOf('bpmn:Event') ||
@ -13,8 +13,8 @@ function hasLabel(semantic) {
}
function isCollapsed(semantic, di) {
return semantic.$instanceOf('bpmn:SubProcess') && di && !di.isExpanded;
function isCollapsed(semantic) {
return semantic.$instanceOf('bpmn:SubProcess') && !semantic.di.isExpanded;
}
function getWaypointsMid(waypoints) {
@ -35,14 +35,16 @@ function getWaypointsMid(waypoints) {
* Returns the bounds of an elements label, parsed from the elements DI or
* generated from its bounds.
*/
function getLabelBounds(di, data) {
function getLabelBounds(semantic, element) {
var mid,
size;
size,
bounds,
di = semantic.di,
label = di.label;
var label = di.label;
if (label && label.bounds) {
var bounds = label.bounds;
bounds = label.bounds;
size = {
width: Math.max(150, bounds.width),
@ -55,12 +57,12 @@ function getLabelBounds(di, data) {
};
} else {
if (data.waypoints) {
mid = getWaypointsMid(data.waypoints);
if (element.waypoints) {
mid = getWaypointsMid(element.waypoints);
} else {
mid = {
x: data.x + data.width / 2,
y: data.y + data.height - 5
x: element.x + element.width / 2,
y: element.y + element.height - 5
};
}
@ -93,98 +95,92 @@ BpmnImporter.$inject = [ 'eventBus', 'canvas' ];
/**
* Add bpmn element (semantic, di) to the canvas onto the
* specified parent element.
* Add bpmn element (semantic) to the canvas onto the
* specified parent shape.
*/
BpmnImporter.prototype.add = function(semantic, di, parent) {
BpmnImporter.prototype.add = function(semantic, parentShape) {
var events = this._eventBus,
canvas = this._canvas;
var shape;
/**
* fire element specific event
*/
function fire(type, shape) {
events.fire('bpmn.element.' + type, {
semantic: semantic, di: di, diagramElement: shape
});
}
var element,
di = semantic.di;
/**
* add label for the element
*/
function addLabel(semantic, di, data) {
if (!hasLabel(semantic)) {
return;
}
function addLabel(semantic, element) {
var labelBounds = getLabelBounds(semantic, element);
var labelBounds = getLabelBounds(di, data);
var label = _.extend({
var label = canvas.create('label', _.extend({
id: semantic.id + '_label',
attachedId: semantic.id,
labelTarget: element,
type: 'label',
hidden: data.hidden
}, labelBounds);
hidden: element.hidden,
parent: element.parent,
businessObject: semantic
}, labelBounds));
canvas.addShape(label);
// we wire data and label so that
// the label of a BPMN element can be quickly accessed via
// element.label in various components
data.label = label;
}
// handle the special case that we deal with a
// invisible root element (process or collaboration)
if (di.$instanceOf('bpmndi:BPMNPlane')) {
// we still fire the added event, making sure our
// infrastructure pics it up properly
fire('add', null);
fire('added', null);
return null;
}
// add a virtual element (not being drawn)
element = canvas.create('root', _.extend({
id: semantic.id,
type: semantic.$type,
businessObject: semantic
}));
} else
if (di.$instanceOf('bpmndi:BPMNShape')) {
var bounds = di.bounds;
var collapsed = isCollapsed(semantic, di);
var hidden = parent && (parent.hidden || parent.collapsed);
var collapsed = isCollapsed(semantic);
var hidden = parentShape && (parentShape.hidden || parentShape.collapsed);
shape = {
id: semantic.id, type: semantic.$type,
x: bounds.x, y: bounds.y,
width: bounds.width, height: bounds.height,
element = canvas.create('shape', {
id: semantic.id,
type: semantic.$type,
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height,
collapsed: collapsed,
hidden: hidden,
parent: parent
};
parent: parentShape,
businessObject: semantic
});
fire('add', shape);
canvas.addShape(shape);
canvas.addShape(element);
} else {
var waypoints = _.collect(di.waypoint, function(p) {
return { x: p.x, y: p.y };
});
shape = { id: semantic.id, type: semantic.$type, waypoints: waypoints };
element = canvas.create('connection', {
id: semantic.id,
type: semantic.$type,
waypoints: waypoints,
parent: parentShape,
businessObject: semantic
});
fire('add', shape);
canvas.addConnection(shape);
element = canvas.addConnection(element);
}
fire('added', shape);
if (hasLabel(semantic)) {
addLabel(semantic, element);
}
// add label if needed
addLabel(semantic, di, shape);
this._eventBus.fire('bpmnElement.added', { element: element });
return shape;
return element;
};

View File

@ -1,65 +0,0 @@
'use strict';
var _ = require('lodash');
/**
* @class
*
* A registry that keeps track of bpmn semantic / di elements and the
* corresponding shapes.
*
* @param {EventBus} events
* @param {ElementRegistry} elementRegistry
*/
function BpmnRegistry(events, elementRegistry) {
var elements = {
di: {},
semantic: {},
diagramElement: {}
};
// we attach to element.add rather than element.added to ensure
// the meta-data (semantic, di) for the element is already present
// during rendering
events.on('bpmn.element.add', function(e) {
var semantic = e.semantic,
id = semantic.id;
elements.di[id] = e.di;
elements.semantic[id] = e.semantic;
elements.diagramElement[id] = e.diagramElement;
});
events.on('bpmn.element.removed', function(e) {
var semantic = e.semantic,
id = semantic.id;
delete elements.di[id];
delete elements.semantic[id];
delete elements.diagramElement[id];
});
function get(type) {
var collection = elements[type];
return function(element) {
var id = _.isObject(element) ? element.id : element;
// strip label suffix
id = id.replace(/_label$/, '');
return collection[id];
};
}
// API
this.getSemantic = get('semantic');
this.getDi = get('di');
this.getDiagramElement = get('diagramElement');
}
BpmnRegistry.$inject = [ 'eventBus', 'elementRegistry' ];
module.exports = BpmnRegistry;

View File

@ -1,5 +1,3 @@
module.exports = {
__init__: [ 'bpmnRegistry' ],
bpmnRegistry: [ 'type', require('./BpmnRegistry') ],
bpmnImporter: [ 'type', require('./BpmnImporter') ]
};

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@ var minBounds = {
};
function LabelEditingProvider(eventBus, canvas, directEditing, commandStack, bpmnRegistry) {
function LabelEditingProvider(eventBus, canvas, directEditing, commandStack) {
directEditing.registerProvider(this);
commandStack.registerHandler('bpmnElement.updateText', UpdateTextHandler);
@ -38,7 +38,6 @@ function LabelEditingProvider(eventBus, canvas, directEditing, commandStack, bpm
directEditing.complete();
});
this._bpmnRegistry = bpmnRegistry;
this._canvas = canvas;
this._commandStack = commandStack;
}
@ -46,8 +45,8 @@ function LabelEditingProvider(eventBus, canvas, directEditing, commandStack, bpm
LabelEditingProvider.prototype.activate = function(element) {
var semantic = this._bpmnRegistry.getSemantic(element),
di = this._bpmnRegistry.getDi(element);
var semantic = element.businessObject,
di = semantic.di;
var text = LabelUtil.getLabel(semantic);
@ -111,6 +110,6 @@ LabelEditingProvider.prototype.update = function(element, newText, oldText) {
};
LabelEditingProvider.$inject = [ 'eventBus', 'canvas', 'directEditing', 'commandStack', 'bpmnRegistry' ];
LabelEditingProvider.$inject = [ 'eventBus', 'canvas', 'directEditing', 'commandStack' ];
module.exports = LabelEditingProvider;

View File

@ -7,14 +7,12 @@ var LabelUtil = require('../LabelUtil');
* A handler that updates the text a BPMN element.
*
* @param {EventBus} eventBus
* @param {BpmnRegistry} bpmnRegistry
* @param {ElementRegistry} elementRegistry
*/
function UpdateTextHandler(eventBus, bpmnRegistry, elementRegistry) {
function UpdateTextHandler(eventBus) {
function setText(element, text) {
var semantic = bpmnRegistry.getSemantic(element);
var semantic = element.businessObject;
LabelUtil.setLabel(semantic, text);
@ -43,6 +41,6 @@ function UpdateTextHandler(eventBus, bpmnRegistry, elementRegistry) {
}
UpdateTextHandler.$inject = [ 'eventBus', 'bpmnRegistry' ];
UpdateTextHandler.$inject = [ 'eventBus' ];
module.exports = UpdateTextHandler;

View File

@ -1,9 +1,13 @@
'use strict';
var _ = require('lodash');
function BpmnTreeWalker(handler) {
var Refs = require('object-refs');
var elementDiMap = {};
var elementGfxMap = {};
var diRefs = new Refs({ name: 'bpmnElement', enumerable: true }, { name: 'di' });
function BpmnTreeWalker(handler) {
// list of containers already walked
var handledProcesses = [];
@ -20,9 +24,9 @@ function BpmnTreeWalker(handler) {
return element.$instanceOf(type);
}
function visit(element, di, ctx) {
function visit(element, ctx) {
var gfx = elementGfxMap[element.id];
var gfx = element.gfx;
// avoid multiple rendering of elements
if (gfx) {
@ -30,12 +34,7 @@ function BpmnTreeWalker(handler) {
}
// call handler
gfx = handler.element(element, di, ctx);
// and log returned result
elementGfxMap[element.id] = gfx;
return gfx;
return handler.element(element, ctx);
}
function visitRoot(element, diagram) {
@ -43,10 +42,8 @@ function BpmnTreeWalker(handler) {
}
function visitIfDi(element, ctx) {
var di = getDi(element);
if (di) {
return visit(element, di, ctx);
if (element.di) {
return visit(element, ctx);
}
}
@ -56,24 +53,17 @@ function BpmnTreeWalker(handler) {
////// DI handling ////////////////////////////
function buildDiMap(definitions) {
_.forEach(definitions.diagrams, handleDiagram);
}
function registerDi(di) {
var bpmnElement = di.bpmnElement;
function registerDi(element) {
var bpmnElement = element.bpmnElement;
if (bpmnElement) {
elementDiMap[bpmnElement.id] = element;
diRefs.bind(bpmnElement, 'di');
bpmnElement.di = di;
} else {
logError('no bpmnElement for <' + element.$type + '#' + element.id + '>', { element: element });
logError('no bpmnElement for <' + di.$type + '#' + di.id + '>', { element: di });
}
}
function getDi(bpmnElement) {
var id = bpmnElement.id;
return id ? elementDiMap[id] : null;
}
function handleDiagram(diagram) {
handlePlane(diagram.plane);
}

View File

@ -13,19 +13,18 @@ var BpmnTreeWalker = require('./BpmnTreeWalker');
*/
function importBpmnDiagram(diagram, definitions, done) {
var importer = diagram.get('bpmnImporter'),
commandStack = diagram.get('commandStack');
var importer = diagram.get('bpmnImporter');
var warnings = [];
var visitor = {
root: function(element, di) {
return importer.add(element, di);
root: function(element) {
return importer.add(element);
},
element: function(element, di, parent) {
return importer.add(element, di, parent);
element: function(element, parentShape) {
return importer.add(element, parentShape);
},
error: function(message, context) {
@ -39,9 +38,6 @@ function importBpmnDiagram(diagram, definitions, done) {
// import
walker.handleDefinitions(definitions);
// clear command stack
commandStack.clear();
done(null, warnings);
} catch (e) {
done(e);

View File

@ -1,12 +1,9 @@
'use strict';
function isExpandedPool(semantic) {
module.exports.isExpandedPool = function(semantic) {
return !!semantic.processRef;
}
};
function isExpanded(semantic, di) {
return di.isExpanded;
}
module.exports.isExpandedPool = isExpandedPool;
module.exports.isExpanded = isExpanded;
module.exports.isExpanded = function(semantic) {
return semantic.di.isExpanded;
};

View File

@ -1,37 +0,0 @@
'use strict';
var Matchers = require('../../Matchers'),
TestHelper = require('../../TestHelper');
/* global bootstrapBpmnJS, inject */
var fs = require('fs');
var bpmnCoreModule = require('../../../../lib/core');
describe('core', function() {
beforeEach(Matchers.add);
var diagramXML = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf-8');
var testModules = [ bpmnCoreModule ];
beforeEach(bootstrapBpmnJS(diagramXML, { modules: testModules }));
describe('BpmnRegistry', function() {
it('should get process semantic by ID', inject(function(bpmnRegistry) {
expect(bpmnRegistry.getSemantic('Process_1')).toBeDefined();
}));
});
});

View File

@ -44,11 +44,11 @@ describe('features - label-editing', function() {
}));
it('should cancel on <ESC>', inject(function(elementRegistry, bpmnRegistry, directEditing, eventBus) {
it('should cancel on <ESC>', inject(function(elementRegistry, directEditing, eventBus) {
// given
var shape = elementRegistry.getById('task-nested-embedded');
var task = bpmnRegistry.getSemantic('task-nested-embedded');
var shape = elementRegistry.getById('task-nested-embedded'),
task = shape.businessObject;
var oldName = task.name;
@ -69,11 +69,11 @@ describe('features - label-editing', function() {
}));
it('should submit on <canvas.click>', inject(function(elementRegistry, bpmnRegistry, directEditing, eventBus) {
it('should submit on <canvas.click>', inject(function(elementRegistry, directEditing, eventBus) {
// given
var shape = elementRegistry.getById('task-nested-embedded');
var task = bpmnRegistry.getSemantic('task-nested-embedded');
var shape = elementRegistry.getById('task-nested-embedded'),
task = shape.businessObject;
// activate
eventBus.fire('shape.dblclick', { element: shape });
@ -96,13 +96,13 @@ describe('features - label-editing', function() {
});
var bpmnRegistry,
var elementRegistry,
eventBus,
directEditing;
beforeEach(inject([ 'bpmnRegistry', 'eventBus', 'directEditing', function(_bpmnRegistry, _eventBus, _directEditing) {
bpmnRegistry = _bpmnRegistry;
beforeEach(inject([ 'elementRegistry', 'eventBus', 'directEditing', function(_elementRegistry, _eventBus, _directEditing) {
elementRegistry = _elementRegistry;
eventBus = _eventBus;
directEditing = _directEditing;
}]));
@ -136,8 +136,8 @@ describe('features - label-editing', function() {
it('should update via command stack', function() {
// given
var diagramElement = bpmnRegistry.getDiagramElement('user-task');
var semantic = bpmnRegistry.getSemantic('user-task');
var diagramElement = elementRegistry.getById('user-task'),
semantic = diagramElement.businessObject;
var listenerCalled;
@ -157,8 +157,8 @@ describe('features - label-editing', function() {
it('should undo via command stack', inject(function(commandStack) {
// given
var diagramElement = bpmnRegistry.getDiagramElement('user-task');
var semantic = bpmnRegistry.getSemantic('user-task');
var diagramElement = elementRegistry.getById('user-task'),
semantic = diagramElement.businessObject;
var oldLabel = LabelUtil.getLabel(semantic);
@ -181,8 +181,8 @@ describe('features - label-editing', function() {
it('on shape change', function() {
// given
var diagramElement = bpmnRegistry.getDiagramElement('user-task');
var semantic = bpmnRegistry.getSemantic('user-task');
var diagramElement = elementRegistry.getById('user-task'),
semantic = diagramElement.businessObject;
var listenerCalled;
@ -204,8 +204,8 @@ describe('features - label-editing', function() {
it('on connection on change', function() {
// given
var diagramElement = bpmnRegistry.getDiagramElement('sequence-flow-no');
var semantic = bpmnRegistry.getSemantic('sequence-flow-no');
var diagramElement = elementRegistry.getById('sequence-flow-no'),
semantic = diagramElement.businessObject;
var listenerCalled;
@ -230,10 +230,10 @@ describe('features - label-editing', function() {
function directEdit(elementId) {
return inject(function(bpmnRegistry, eventBus, directEditing) {
return inject(function(elementRegistry, eventBus, directEditing) {
var diagramElement = bpmnRegistry.getDiagramElement(elementId);
var semantic = bpmnRegistry.getSemantic(elementId);
var diagramElement = elementRegistry.getById(elementId),
semantic = diagramElement.businessObject;
var label = LabelUtil.getLabel(semantic);

View File

@ -59,7 +59,7 @@ describe('import - importer', function() {
describe('event emitter', function() {
it('should fire <bpmn.element.add> during import', function(done) {
it('should fire <shape.added> during import', function(done) {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
@ -67,7 +67,7 @@ describe('import - importer', function() {
var eventCount = 0;
// log events
diagram.get('eventBus').on('bpmn.element.add', function(e) {
diagram.get('eventBus').on('bpmnElement.added', function(e) {
eventCount++;
});
@ -94,12 +94,12 @@ describe('import - importer', function() {
var events = [];
// log events
diagram.get('eventBus').on('bpmn.element.add', function(e) {
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events.push({
type: 'add',
semantic: e.semantic.id,
di: e.di.id,
diagramElement: e.diagramElement && e.diagramElement.id
semantic: e.element.businessObject.id,
di: e.element.businessObject.di.id,
diagramElement: e.element && e.element.id
});
});
@ -108,7 +108,7 @@ describe('import - importer', function() {
// then
expect(events).toEqual([
{ type: 'add', semantic: 'Process_1', di: 'BPMNPlane_1', diagramElement: null },
{ type: 'add', semantic: 'Process_1', di: 'BPMNPlane_1', diagramElement: 'Process_1' },
{ type: 'add', semantic: 'SubProcess_1', di: '_BPMNShape_SubProcess_2', diagramElement: 'SubProcess_1' },
{ type: 'add', semantic: 'StartEvent_1', di: '_BPMNShape_StartEvent_2', diagramElement: 'StartEvent_1' },
{ type: 'add', semantic: 'Task_1', di: '_BPMNShape_Task_2', diagramElement: 'Task_1' },
@ -130,12 +130,12 @@ describe('import - importer', function() {
var events = [];
// log events
diagram.get('eventBus').on('bpmn.element.add', function(e) {
diagram.get('eventBus').on('bpmnElement.added', function(e) {
events.push({
type: 'add',
semantic: e.semantic.id,
di: e.di.id,
diagramElement: e.diagramElement && e.diagramElement.id
semantic: e.element.businessObject.id,
di: e.element.businessObject.di.id,
diagramElement: e.element && e.element.id
});
});
@ -144,7 +144,7 @@ describe('import - importer', function() {
// then
expect(events).toEqual([
{ type: 'add', semantic: '_Collaboration_2', di: 'BPMNPlane_1', diagramElement: null },
{ type: 'add', semantic: '_Collaboration_2', di: 'BPMNPlane_1', diagramElement: '_Collaboration_2' },
{ type: 'add', semantic: 'Participant_2', di: '_BPMNShape_Participant_2', diagramElement: 'Participant_2' },
{ type: 'add', semantic: 'Lane_1', di: '_BPMNShape_Lane_2', diagramElement: 'Lane_1' },
{ type: 'add', semantic: 'Lane_2', di: '_BPMNShape_Lane_3', diagramElement: 'Lane_2' },
@ -160,23 +160,87 @@ describe('import - importer', function() {
});
describe('command stack integration', function() {
it('should clear stack after import', function(done) {
describe('model wiring', function() {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/simple.bpmn', 'utf8');
var commandStack = diagram.get('commandStack');
var elements;
beforeEach(function(done) {
elements = [];
// log events
diagram.get('eventBus').on('bpmnElement.added', function(e) {
elements.push(e.element);
});
runImport(diagram, xml, done);
});
it('should wire parent child relationship', function() {
// when
runImport(diagram, xml, function(err, warnings) {
var subProcessShape = elements[1];
var startEventShape = elements[2];
// then
expect(commandStack.getStack()).toEqual([]);
done(err);
expect(startEventShape.type).toBe('bpmn:StartEvent');
expect(startEventShape.parent).toBe(subProcessShape);
});
it('should wire label relationship', function() {
// when
var startEventShape = elements[2];
var label = startEventShape.label;
// then
expect(label).toBeDefined();
expect(label.id).toBe(startEventShape.id + '_label');
expect(label.labelTarget).toBe(startEventShape);
});
it('should wire businessObject', function() {
// when
var subProcessShape = elements[1];
var startEventShape = elements[2];
var subProcess = subProcessShape.businessObject,
startEvent = startEventShape.businessObject;
// then
expect(subProcess).toBeDefined();
expect(subProcess.$instanceOf('bpmn:SubProcess')).toBe(true);
expect(startEvent).toBeDefined();
expect(startEvent.$instanceOf('bpmn:StartEvent')).toBe(true);
});
it('should wire di', function() {
// when
var subProcessShape = elements[1];
var startEventShape = elements[2];
var subProcess = subProcessShape.businessObject,
startEvent = startEventShape.businessObject;
var subProcessDi = subProcess.di,
startEventDi = startEvent.di;
// then
expect(subProcessDi).toBeDefined();
expect(subProcessDi.bpmnElement).toBe(subProcess);
expect(startEventDi).toBeDefined();
expect(startEventDi.bpmnElement).toBe(startEvent);
});
});
@ -189,8 +253,6 @@ describe('import - importer', function() {
// given
var xml = fs.readFileSync('test/fixtures/bpmn/error/invalid-flow-element.bpmn', 'utf8');
var commandStack = diagram.get('commandStack');
// when
runImport(diagram, xml, function(err, warnings) {