feat(modeling): implement create via palette/context-pad

Related to #137
This commit is contained in:
Nico Rehwaldt 2014-12-07 13:08:50 +01:00
parent 982033074b
commit 6041717c6c
11 changed files with 234 additions and 226 deletions

View File

@ -5,21 +5,22 @@ var _ = require('lodash');
/**
* A provider for BPMN 2.0 elements context pad
*
* @param {ContextPad} contextPad
*/
function ContextPadProvider(contextPad, directEditing, modeling, selection, connect) {
function ContextPadProvider(contextPad, directEditing, modeling, selection, elementFactory, connect, create) {
contextPad.registerProvider(this);
this._selection = selection;
this._directEditing = directEditing;
this._modeling = modeling;
this._modeling = modeling;
this._selection = selection;
this._elementFactory = elementFactory;
this._connect = connect;
this._create = create;
}
ContextPadProvider.$inject = [ 'contextPad', 'directEditing', 'modeling', 'selection', 'connect' ];
ContextPadProvider.$inject = [ 'contextPad', 'directEditing', 'modeling', 'selection', 'elementFactory', 'connect', 'create' ];
ContextPadProvider.prototype.getContextPadEntries = function(element) {
@ -27,7 +28,9 @@ ContextPadProvider.prototype.getContextPadEntries = function(element) {
var directEditing = this._directEditing,
modeling = this._modeling,
selection = this._selection,
connect = this._connect;
elementFactory = this._elementFactory,
connect = this._connect,
create = this._create;
var actions = {};
@ -39,77 +42,58 @@ ContextPadProvider.prototype.getContextPadEntries = function(element) {
function startConnect(event, element) {
connect.start(event, element, null);
event.preventDefault();
}
function append(element, type) {
var target;
if (type === 'bpmn:TextAnnotation') {
target = modeling.appendTextAnnotation(element, type);
} else {
target = modeling.appendFlowNode(element, type);
}
var target = modeling.appendShape(element, { type: type });
selection.select(target);
directEditing.activate(target);
}
if (bpmnElement.$instanceOf('bpmn:FlowNode') &&
!bpmnElement.$instanceOf('bpmn:EndEvent')) {
_.extend(actions, {
'action.model-event': {
group: 'model',
className: 'icon-end-event',
action: function(event, element) {
append(element, 'bpmn:EndEvent');
}
},
'action.model-gateway': {
group: 'model',
className: 'icon-gateway',
action: function(e) {
append(element, 'bpmn:ExclusiveGateway');
}
},
'action.model-task': {
group: 'model',
className: 'icon-task',
action: function() {
append(element, 'bpmn:Task');
}
},
'action.model-intermediate-event': {
group: 'model',
className: 'icon-intermediate-event',
action: function() {
append(element, 'bpmn:IntermediateThrowEvent');
}
},
'action.model-text-annotation': {
group: 'model',
className: 'icon-text-annotation',
action: function() {
append(element, 'bpmn:TextAnnotation');
}
},
'action.connect': {
group: 'connect',
className: 'icon-connection',
action: {
click: startConnect,
dragstart: startConnect
function appendAction(type, className) {
return {
group: 'model',
className: className,
action: {
dragstart: function(event, element) {
var shape = elementFactory.createShape({ type: type });
create.start(event, shape, element);
},
click: function(event, element) {
append(element, type);
}
}
});
};
}
if (bpmnElement.$instanceOf('bpmn:FlowNode')) {
if (!bpmnElement.$instanceOf('bpmn:EndEvent')) {
_.extend(actions, {
'append.end-event': appendAction('bpmn:EndEvent', 'icon-end-event'),
'append.gateway': appendAction('bpmn:ExclusiveGateway', 'icon-gateway'),
'append.append-task': appendAction('bpmn:Task', 'icon-task'),
'append.intermediate-event': appendAction('bpmn:IntermediateThrowEvent', 'icon-intermediate-event'),
'connect': {
group: 'connect',
className: 'icon-connection',
action: {
click: startConnect,
dragstart: startConnect
}
}
});
}
_.extend(actions, {
'append.text-annotation': appendAction('bpmn:TextAnnotation', 'icon-text-annotation')
});
}
_.extend(actions, {
'action.delete': {
'delete': {
group: 'edit',
className: 'icon-trash',
action: function(e) {

View File

@ -4,6 +4,7 @@ module.exports = {
require('diagram-js/lib/features/context-pad'),
require('diagram-js/lib/features/selection'),
require('diagram-js/lib/features/connect'),
require('diagram-js/lib/features/create'),
require('../modeling')
],
__init__: [ 'contextPadProvider' ],

View File

@ -22,8 +22,16 @@ ElementFactory.$inject = [ 'bpmnFactory' ];
module.exports = ElementFactory;
ElementFactory.prototype.baseCreate = BaseElementFactory.prototype.create;
ElementFactory.prototype.createWithBpmn = function(elementType, attrs) {
ElementFactory.prototype.create = function(elementType, attrs) {
// no special magic for labels,
// we assume their businessObjects have already been created
// and wired via attrs
if (elementType === 'label') {
return this.baseCreate(elementType, _.extend({ type: 'label' }, LabelUtil.DEFAULT_LABEL_SIZE, attrs));
}
attrs = attrs || {};
@ -61,24 +69,9 @@ ElementFactory.prototype.createWithBpmn = function(elementType, attrs) {
id: businessObject.id
}, size, attrs);
return this.create(elementType, attrs);
return this.baseCreate(elementType, attrs);
};
ElementFactory.prototype.createRoot = function(attrs) {
return this.createWithBpmn('root', attrs);
};
ElementFactory.prototype.createLabel = function(attrs) {
return this.create('label', _.extend({ type: 'label' }, LabelUtil.DEFAULT_LABEL_SIZE, attrs));
};
ElementFactory.prototype.createShape = function(attrs) {
return this.createWithBpmn('shape', attrs);
};
ElementFactory.prototype.createConnection = function(attrs) {
return this.createWithBpmn('connection', attrs);
};
ElementFactory.prototype._getDefaultSize = function(semantic) {
@ -103,4 +96,6 @@ ElementFactory.prototype._getDefaultSize = function(semantic) {
if (semantic.$instanceOf('bpmn:Event')) {
return { width: 36, height: 36 };
}
return { width: 100, height: 80 };
};

View File

@ -35,17 +35,22 @@ function LabelSupport(eventBus, modeling, bpmnFactory) {
// indicate label is dragged during move
eventBus.on('shape.move.start', 50000, function(e) {
// we need to add labels to the list of selected
// shapes before the visuals get drawn.
//
// Hence this awesome magic number.
//
eventBus.on('shape.move.start', function(e) {
var dragContext = e.dragContext,
shapes = dragContext.shapes;
var context = e.context,
shapes = context.shapes;
var labels = [];
_.forEach(shapes, function(element) {
var label = element.label;
if (label && !label.hidden && dragContext.shapes.indexOf(label) === -1) {
if (label && !label.hidden && context.shapes.indexOf(label) === -1) {
labels.push(label);
}
});

View File

@ -24,15 +24,16 @@ var CreateShapeHandler = require('diagram-js/lib/features/modeling/cmd/CreateSha
* BPMN 2.0 modeling features activator
*
* @param {EventBus} eventBus
* @param {ElementFactory} elementFactory
* @param {CommandStack} commandStack
*/
function Modeling(eventBus, commandStack) {
BaseModeling.call(this, eventBus, commandStack);
function Modeling(eventBus, elementFactory, commandStack) {
BaseModeling.call(this, eventBus, elementFactory, commandStack);
}
Modeling.prototype = Object.create(BaseModeling.prototype);
Modeling.$inject = [ 'eventBus', 'commandStack' ];
Modeling.$inject = [ 'eventBus', 'elementFactory', 'commandStack' ];
module.exports = Modeling;
@ -63,30 +64,6 @@ Modeling.prototype.updateLabel = function(element, newLabel) {
};
/**
* Append a flow node to the element with the given source
* at the specified position.
*/
Modeling.prototype.appendFlowNode = function(source, type, position) {
position = position || {
x: source.x + source.width + 100,
y: source.y + source.height / 2
};
return this.appendShape(source, { type: type }, position, null, { attrs: { type: 'bpmn:SequenceFlow' } });
};
Modeling.prototype.appendTextAnnotation = function(source, type, position) {
position = position || {
x: source.x + source.width / 2 + 75,
y: source.y - (source.height / 2) - 100
};
return this.appendShape(source, { type: type }, position, null, { attrs: { type: 'bpmn:Association' } });
};
Modeling.prototype.connect = function(source, target, attrs) {
var sourceBo = source.businessObject,

View File

@ -0,0 +1,49 @@
function AppendBehavior(eventBus, elementFactory) {
// assign the correct connection
// when appending a shape to another shape
eventBus.on('commandStack.shape.append.preExecute', function(event) {
var context = event.context,
source = context.source,
shape = context.shape,
parent = context.parent || source.parent;
if (!context.position) {
if (shape.businessObject.$instanceOf('bpmn:TextAnnotation')) {
context.position = {
x: source.x + source.width / 2 + 75,
y: source.y - (50) - shape.height / 2
};
} else {
context.position = {
x: source.x + source.width + 80 + shape.width / 2,
y: source.y + source.height / 2
};
}
}
if (!context.connection) {
var connectionAttrs;
// connect flow nodes in the same container
if (shape.businessObject.$instanceOf('bpmn:FlowNode') && parent.children.indexOf(source) !== -1) {
connectionAttrs = { type: 'bpmn:SequenceFlow' };
} else {
// association always works
connectionAttrs = { type: 'bpmn:Association' };
}
context.connection = elementFactory.create('connection', connectionAttrs);
}
});
}
AppendBehavior.$inject = [ 'eventBus', 'elementFactory' ];
module.exports = AppendBehavior;

View File

@ -1,4 +1,5 @@
module.exports = {
__init__: [ 'dropBehavior' ],
dropBehavior: [ 'type', require('./Drop') ]
__init__: [ 'dropBehavior', 'appendBehavior' ],
dropBehavior: [ 'type', require('./Drop') ],
appendBehavior: [ 'type', require('./Append') ]
};

View File

@ -32,7 +32,8 @@ ModelingRules.prototype.init = function() {
source.businessObject.$instanceOf('bpmn:FlowNode') &&
!source.businessObject.$instanceOf('bpmn:EndEvent') &&
!target.businessObject.$instanceOf('bpmn:StartEvent') &&
target.businessObject.$instanceOf('bpmn:FlowElement');
(target.businessObject.$instanceOf('bpmn:FlowNode') ||
target.businessObject.$instanceOf('bpmn:TextAnnotation'));
});
@ -71,6 +72,12 @@ ModelingRules.prototype.init = function() {
return true;
}
if (businessObject.$instanceOf('bpmn:TextAnnotation') &&
targetBusinessObject.$instanceOf('bpmn:FlowElementsContainer')) {
return true;
}
return false;
}
@ -98,4 +105,31 @@ ModelingRules.prototype.init = function() {
});
});
this.addRule([ 'shape.create', 'shape.append' ], function(context) {
var target = context.parent,
shape = context.shape,
source = context.source;
// ensure we do not drop the element
// into source
var t = target;
while (t) {
if (t === source) {
return false;
}
t = t.parent;
}
if (!target) {
return false;
}
if (target.labelTarget) {
return null;
}
return canDrop(shape.businessObject, target.businessObject, target.businessObject.di);
});
};

View File

@ -1,113 +1,75 @@
'use strict';
var _ = require('lodash');
var _ = require('lodash'),
$ = require('jquery');
/**
* A palette provider for BPMN 2.0 elements.
*/
function PaletteProvider(palette, modeling, elementRegistry, canvas) {
function PaletteProvider(palette, create, elementFactory) {
this._modeling = modeling;
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._create = create;
this._elementFactory = elementFactory;
palette.registerProvider(this);
}
module.exports = PaletteProvider;
PaletteProvider.$inject = [ 'palette', 'modeling', 'elementRegistry', 'canvas' ];
PaletteProvider.$inject = [ 'palette', 'create', 'elementFactory' ];
PaletteProvider.prototype.getPaletteEntries = function(element) {
function createAction(type, group, className, title, options) {
return {
group: group,
className: className,
title: title || 'Create ' + type,
action: {
dragstart: function(event) {
var shape = elementFactory.createShape(_.extend({ type: type }, options));
if (options) {
shape.businessObject.di.isExpanded = options.isExpanded;
}
create.start(event, shape);
}
}
};
}
var actions = {},
modeling = this._modeling,
elementRegistry = this._elementRegistry,
canvas = this._canvas;
create = this._create,
elementFactory = this._elementFactory;
var root = elementRegistry.getRoot();
var vbox = canvas.viewbox();
var center = {
x: Math.round(vbox.outer.width * 1 / vbox.scale / 2),
y: Math.round(vbox.outer.height * 1 / vbox.scale / 2)
};
_.extend(actions, {
'add.start-event': {
group: 'model-event',
className: 'icon-start-event',
alt: 'Start Event',
action: function(event, element) {
modeling.createShape({
type: 'bpmn:StartEvent'
}, center, root);
}
},
'add.intermediate-throw-event': {
group: 'model-event',
className: 'icon-intermediate-event',
alt: 'Intermediate Throw Event',
action: function(event, element) {
modeling.createShape({
type: 'bpmn:IntermediateThrowEvent'
}, center, root);
}
},
'add.end-event': {
group: 'model-event',
className: 'icon-end-event',
alt: 'End Event',
action: function(event, element) {
modeling.createShape({
type: 'bpmn:EndEvent'
}, center, root);
}
},
'add.exclusive-gateway': {
group: 'model-gateway',
className: 'icon-gateway',
alt: 'Exclusive Gateway',
action: function(event, element) {
modeling.createShape({
type: 'bpmn:ExclusiveGateway'
}, center, root);
}
},
'add.task': {
group: 'model-activity',
className: 'icon-task',
alt: 'Task',
action: function(event, element) {
modeling.createShape({
type: 'bpmn:Task'
}, center, root);
}
},
'add.subProcess-collapsed': {
group: 'model-activity',
className: 'icon-subprocess-collapsed',
alt: 'Sub-Process (collapsed)',
action: function(event, element) {
modeling.createShape({
type: 'bpmn:SubProcess',
isExpanded: false
}, center, root);
}
},
'add.subProcess-expanded': {
group: 'model-activity',
className: 'icon-subprocess-expanded',
alt: 'Sub-Process (expanded)',
action: function(event, element) {
modeling.createShape({
type: 'bpmn:SubProcess',
isExpanded: true
}, center, root);
}
}
'create.start-event': createAction(
'bpmn:StartEvent', 'event', 'icon-start-event'
),
'create.intermediate-event': createAction(
'bpmn:IntermediateThrowEvent', 'event', 'icon-intermediate-event'
),
'create.end-event': createAction(
'bpmn:EndEvent', 'event', 'icon-end-event'
),
'create.start-event': createAction(
'bpmn:StartEvent', 'event', 'icon-start-event'
),
'create.exclusive-gateway': createAction(
'bpmn:ExclusiveGateway', 'gateway', 'icon-gateway'
),
'create.task': createAction(
'bpmn:Task', 'activity', 'icon-task'
),
'create.subprocess-collapsed': createAction(
'bpmn:SubProcess', 'activity', 'icon-subprocess-collapsed', 'Sub Process (collapsed)',
{ isExpanded: false }
),
'create.subprocess-expanded': createAction(
'bpmn:SubProcess', 'activity', 'icon-subprocess-expanded', 'Sub Process (expanded)',
{ isExpanded: true }
)
});
return actions;

View File

@ -36,7 +36,7 @@ describe('features/modeling - append shape', function() {
var startEventShape = elementRegistry.get('StartEvent_1');
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
// then
@ -55,7 +55,7 @@ describe('features/modeling - append shape', function() {
subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
// then
@ -79,7 +79,7 @@ describe('features/modeling - append shape', function() {
subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
// then
@ -99,7 +99,7 @@ describe('features/modeling - append shape', function() {
subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:EndEvent'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:EndEvent' }),
target = targetShape.businessObject;
var label = targetShape.label;
@ -108,7 +108,7 @@ describe('features/modeling - append shape', function() {
expect(label).toBeDefined();
expect(elementRegistry.get(label.id)).toBeDefined();
expect(label.x).toBe(443);
expect(label.x).toBe(441);
expect(label.y).toBe(278);
expect(label.width).toBe(LabelUtil.DEFAULT_LABEL_SIZE.width);
expect(label.height).toBe(LabelUtil.DEFAULT_LABEL_SIZE.height);
@ -125,7 +125,7 @@ describe('features/modeling - append shape', function() {
subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:EndEvent'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:EndEvent' }),
target = targetShape.businessObject;
// then
@ -150,7 +150,7 @@ describe('features/modeling - append shape', function() {
subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
@ -176,7 +176,7 @@ describe('features/modeling - append shape', function() {
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
// when
@ -197,7 +197,7 @@ describe('features/modeling - append shape', function() {
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:EndEvent'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:EndEvent' }),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
@ -227,7 +227,7 @@ describe('features/modeling - append shape', function() {
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
@ -260,7 +260,7 @@ describe('features/modeling - append shape', function() {
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
@ -289,10 +289,10 @@ describe('features/modeling - append shape', function() {
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var targetShape2 = modeling.appendFlowNode(targetShape, 'bpmn:UserTask');
var targetShape2 = modeling.appendShape(targetShape, { type: 'bpmn:UserTask' });
// when
commandStack.undo();
@ -323,7 +323,7 @@ describe('features/modeling - append shape', function() {
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:Task'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:Task' }),
target = targetShape.businessObject;
var connection = _.find(subProcess.get('flowElements'), function(e) {
@ -356,7 +356,7 @@ describe('features/modeling - append shape', function() {
var startEventShape = elementRegistry.get('StartEvent_1');
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:ExclusiveGateway'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:ExclusiveGateway' }),
target = targetShape.businessObject;
// then
@ -375,7 +375,7 @@ describe('features/modeling - append shape', function() {
subProcess = subProcessShape.businessObject;
// when
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:ExclusiveGateway'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:ExclusiveGateway' }),
target = targetShape.businessObject;
// then
@ -392,7 +392,7 @@ describe('features/modeling - append shape', function() {
var startEvent = startEventShape.businessObject,
subProcess = subProcessShape.businessObject;
var targetShape = modeling.appendFlowNode(startEventShape, 'bpmn:ExclusiveGateway'),
var targetShape = modeling.appendShape(startEventShape, { type: 'bpmn:ExclusiveGateway' }),
target = targetShape.businessObject;
// when

View File

@ -16,7 +16,7 @@ var modelingModule = require('../../../../../lib/features/modeling'),
var LabelUtil = require('../../../../../lib/util/Label');
ddescribe('features/modeling - append text-annotation', function() {
describe('features/modeling - append text-annotation', function() {
beforeEach(Matchers.addDeepEquals);
@ -39,7 +39,7 @@ ddescribe('features/modeling - append text-annotation', function() {
process = elementRegistry.get('Participant_1').businessObject.processRef;
// when
var annotationShape = modeling.appendTextAnnotation(eventShape, 'bpmn:TextAnnotation'),
var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' }),
annotation = annotationShape.businessObject;
var connectingConnection = _.find(annotationShape.incoming, function(c) {
@ -71,7 +71,7 @@ ddescribe('features/modeling - append text-annotation', function() {
var eventShape = elementRegistry.get('IntermediateThrowEvent_1');
// when
var annotationShape = modeling.appendTextAnnotation(eventShape, 'bpmn:TextAnnotation'),
var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' }),
annotation = annotationShape.businessObject;
var connectingConnection = _.find(annotationShape.incoming, function(c) {
@ -104,7 +104,7 @@ ddescribe('features/modeling - append text-annotation', function() {
var eventShape = elementRegistry.get('IntermediateCatchEvent_1'),
process = elementRegistry.get('Participant_1').businessObject.processRef;
var annotationShape = modeling.appendTextAnnotation(eventShape, 'bpmn:TextAnnotation'),
var annotationShape = modeling.appendShape(eventShape, { type: 'bpmn:TextAnnotation' }),
annotation = annotationShape.businessObject;
var connectingConnection = _.find(annotationShape.incoming, function(c) {