diff --git a/lib/Modeler.js b/lib/Modeler.js
index 14e92f7a..e40e01a0 100644
--- a/lib/Modeler.js
+++ b/lib/Modeler.js
@@ -67,10 +67,13 @@ Modeler.prototype._modelingModules = [
require('diagram-js/lib/features/snapping'),
require('diagram-js/lib/features/move'),
require('diagram-js/lib/features/resize'),
+ require('diagram-js/lib/features/drop'),
require('./features/modeling'),
require('./features/context-pad'),
require('./features/palette'),
- require('./features/resize')
+ require('./features/resize'),
+ require('./features/rules'),
+ require('./features/drop')
];
diff --git a/lib/features/drop/BpmnDrop.js b/lib/features/drop/BpmnDrop.js
new file mode 100644
index 00000000..103e3c76
--- /dev/null
+++ b/lib/features/drop/BpmnDrop.js
@@ -0,0 +1,25 @@
+'use strict';
+
+var _ = require('lodash');
+
+
+
+function BpmnDrop(drop, openSequenceflowHandler, updateSequenceFlowParentHandler) {
+
+ var actions = {
+ 'updateSequenceFlowParent': updateSequenceFlowParentHandler,
+ 'removeOpenSequenceflow': openSequenceflowHandler
+ };
+
+ var self = this;
+
+ this._drop = drop;
+
+ _.forEach(actions, function(action, key) {
+ self._drop.registerAfterDropAction(key, action.execute);
+ });
+}
+
+BpmnDrop.$inject = [ 'drop', 'openSequenceflowHandler', 'updateSequenceFlowParentHandler' ];
+
+module.exports = BpmnDrop;
diff --git a/lib/features/drop/OpenSequenceflowHandler.js b/lib/features/drop/OpenSequenceflowHandler.js
new file mode 100644
index 00000000..1d7170b2
--- /dev/null
+++ b/lib/features/drop/OpenSequenceflowHandler.js
@@ -0,0 +1,67 @@
+'use strict';
+
+var _ = require('lodash');
+
+var self;
+function OpenSequenceflowHandler(modeling) {
+
+ self = this;
+ this._modeling = modeling;
+}
+
+OpenSequenceflowHandler.$inject = [ 'modeling' ];
+
+module.exports = OpenSequenceflowHandler;
+
+
+/**
+ * Removes sequence flows that source or target does not have same parent.
+ */
+OpenSequenceflowHandler.prototype.execute = function(context) {
+
+ var shapes = context.shapes,
+ connections = context.connections,
+ target = context.target;
+
+ self._removeConnections(shapes, connections, target);
+};
+
+
+OpenSequenceflowHandler.prototype._removeConnections = function(shapes, connections, target) {
+
+ var modeling = self._modeling;
+
+ var removeableConnections = getRemoveableConnections(shapes, connections);
+
+ _.forEach(removeableConnections, function(connection) {
+
+ var sourceParent = connection.source.parent,
+ targetParent = connection.target.parent;
+
+ if (sourceParent.id !== targetParent.id) {
+ delete connections[connection.id];
+ modeling.removeConnection(connection);
+ }
+ });
+};
+
+function getRemoveableConnections(shapes, connections) {
+
+ var connectionsToRemove = {};
+
+ _.forEach(shapes, function(shape) {
+
+ var allConnections = _.union(shape.incoming, shape.outgoing);
+
+ _.forEach(allConnections, function(connection) {
+ // if one of the connection endpoints points to a shape that is not part of the map
+ // delete the connection
+ if (!(shapes[connection.source.id] && shapes[connection.target.id])) {
+ connectionsToRemove[connection.id] = connection;
+ }
+ });
+
+ });
+
+ return connectionsToRemove;
+}
diff --git a/lib/features/drop/UpdateSequenceFlowParentHandler.js b/lib/features/drop/UpdateSequenceFlowParentHandler.js
new file mode 100644
index 00000000..0a5a8884
--- /dev/null
+++ b/lib/features/drop/UpdateSequenceFlowParentHandler.js
@@ -0,0 +1,37 @@
+'use strict';
+
+var _ = require('lodash');
+
+var self;
+function UpdateSequenceFlowParentHandler(modeling) {
+
+ self = this;
+ this._modeling = modeling;
+}
+
+UpdateSequenceFlowParentHandler.$inject = [ 'modeling' ];
+
+module.exports = UpdateSequenceFlowParentHandler;
+
+
+
+UpdateSequenceFlowParentHandler.prototype.execute = function(context) {
+
+ var shapes = context.shapes,
+ target = context.target;
+
+ _.forEach(shapes, function(shape) {
+ handleFlow(shape.incoming);
+ handleFlow(shape.outgoing);
+ });
+
+
+ function handleFlow(flows) {
+
+ _.forEach(flows, function(flow) {
+ if (shapes[flow.source.id] && shapes[flow.target.id]) {
+ flow.parent = target;
+ }
+ });
+ }
+};
diff --git a/lib/features/drop/index.js b/lib/features/drop/index.js
new file mode 100644
index 00000000..9df6c56f
--- /dev/null
+++ b/lib/features/drop/index.js
@@ -0,0 +1,11 @@
+'use strict';
+
+module.exports = {
+ __init__: [ 'bpmnDrop', 'openSequenceflowHandler', 'updateSequenceFlowParentHandler' ],
+ __depends__: [
+ require('diagram-js/lib/features/drop')
+ ],
+ bpmnDrop: [ 'type', require('./BpmnDrop') ],
+ openSequenceflowHandler: [ 'type', require('./OpenSequenceflowHandler') ],
+ updateSequenceFlowParentHandler: [ 'type', require('./UpdateSequenceFlowParentHandler') ]
+};
diff --git a/lib/features/modeling/BpmnFactory.js b/lib/features/modeling/BpmnFactory.js
index 36a5ea62..fd2aa3aa 100644
--- a/lib/features/modeling/BpmnFactory.js
+++ b/lib/features/modeling/BpmnFactory.js
@@ -80,4 +80,4 @@ BpmnFactory.prototype.createDiEdge = function(semantic, waypoints, attrs) {
};
-module.exports = BpmnFactory;
\ No newline at end of file
+module.exports = BpmnFactory;
diff --git a/lib/features/modeling/BpmnUpdater.js b/lib/features/modeling/BpmnUpdater.js
index d6ab99ce..8499efdf 100644
--- a/lib/features/modeling/BpmnUpdater.js
+++ b/lib/features/modeling/BpmnUpdater.js
@@ -96,6 +96,7 @@ BpmnUpdater.$inject = [ 'eventBus', 'bpmnFactory', 'connectionDocking'];
BpmnUpdater.prototype.updateShapeParent = function(shape) {
+
var parentShape = shape.parent;
var businessObject = shape.businessObject,
@@ -171,6 +172,7 @@ BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent)
BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
+
connection.businessObject.di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
};
@@ -233,7 +235,6 @@ BpmnUpdater.prototype.reverted = function(commands, callback) {
};
BpmnUpdater.prototype.on = function(commands, suffix, callback) {
-
commands = _.isArray(commands) ? commands : [ commands ];
_.forEach(commands, function(c) {
diff --git a/lib/features/modeling/LabelSupport.js b/lib/features/modeling/LabelSupport.js
index 28e6b55d..fbd32188 100644
--- a/lib/features/modeling/LabelSupport.js
+++ b/lib/features/modeling/LabelSupport.js
@@ -95,4 +95,4 @@ function LabelSupport(eventBus, modeling, bpmnFactory) {
LabelSupport.$inject = [ 'eventBus', 'modeling', 'bpmnFactory' ];
-module.exports = LabelSupport;
\ No newline at end of file
+module.exports = LabelSupport;
diff --git a/lib/features/modeling/Modeling.js b/lib/features/modeling/Modeling.js
index 4ca3727d..253b6749 100644
--- a/lib/features/modeling/Modeling.js
+++ b/lib/features/modeling/Modeling.js
@@ -7,6 +7,7 @@ var BaseModeling = require('diagram-js/lib/features/modeling/Modeling');
var CreateShapeHandler = require('diagram-js/lib/features/modeling/cmd/CreateShapeHandler'),
DeleteShapeHandler = require('diagram-js/lib/features/modeling/cmd/DeleteShapeHandler'),
MoveShapeHandler = require('diagram-js/lib/features/modeling/cmd/MoveShapeHandler'),
+ MoveShapesHandler = require('diagram-js/lib/features/modeling/cmd/MoveShapesHandler'),
ResizeShapeHandler = require('diagram-js/lib/features/modeling/cmd/ResizeShapeHandler'),
AppendShapeHandler = require('diagram-js/lib/features/modeling/cmd/AppendShapeHandler'),
@@ -16,6 +17,7 @@ var CreateShapeHandler = require('diagram-js/lib/features/modeling/cmd/CreateSha
CreateConnectionHandler = require('diagram-js/lib/features/modeling/cmd/CreateConnectionHandler'),
DeleteConnectionHandler = require('diagram-js/lib/features/modeling/cmd/DeleteConnectionHandler'),
MoveConnectionHandler = require('diagram-js/lib/features/modeling/cmd/MoveConnectionHandler'),
+ MoveConnectionsHandler = require('diagram-js/lib/features/modeling/cmd/MoveConnectionsHandler'),
LayoutConnectionHandler = require('diagram-js/lib/features/modeling/cmd/LayoutConnectionHandler');
@@ -40,6 +42,7 @@ Modeling.prototype.registerHandlers = function(commandStack) {
commandStack.registerHandler('shape.create', CreateShapeHandler);
commandStack.registerHandler('shape.delete', DeleteShapeHandler);
commandStack.registerHandler('shape.move', MoveShapeHandler);
+ commandStack.registerHandler('shapes.move', MoveShapesHandler);
commandStack.registerHandler('shape.resize', ResizeShapeHandler);
commandStack.registerHandler('shape.append', AppendShapeHandler);
@@ -49,6 +52,7 @@ Modeling.prototype.registerHandlers = function(commandStack) {
commandStack.registerHandler('connection.create', CreateConnectionHandler);
commandStack.registerHandler('connection.delete', DeleteConnectionHandler);
commandStack.registerHandler('connection.move', MoveConnectionHandler);
+ commandStack.registerHandler('connections.move', MoveConnectionsHandler);
commandStack.registerHandler('connection.layout', LayoutConnectionHandler);
};
@@ -85,20 +89,6 @@ Modeling.prototype.appendTextAnnotation = function(source, type, position) {
return this.appendShape(source, { type: type }, position, null, { attrs: { type: 'bpmn:Association' } });
};
-
-Modeling.prototype.canConnect = function(source, target) {
- if (source.labelTarget || target.labelTarget) {
- return null;
- }
-
- return source.businessObject.$parent === target.businessObject.$parent &&
- source.businessObject.$instanceOf('bpmn:FlowNode') &&
- !source.businessObject.$instanceOf('bpmn:EndEvent') &&
- !target.businessObject.$instanceOf('bpmn:StartEvent') &&
- target.businessObject.$instanceOf('bpmn:FlowElement');
-};
-
-
Modeling.prototype.connect = function(source, target, attrs) {
var sourceBo = source.businessObject,
@@ -117,4 +107,4 @@ Modeling.prototype.connect = function(source, target, attrs) {
}
return this.createConnection(source, target, attrs, source.parent);
-};
\ No newline at end of file
+};
diff --git a/lib/features/modeling/index.js b/lib/features/modeling/index.js
index 0845fb21..c9296b58 100644
--- a/lib/features/modeling/index.js
+++ b/lib/features/modeling/index.js
@@ -3,7 +3,8 @@ module.exports = {
__depends__: [
require('../label-editing'),
require('diagram-js/lib/command'),
- require('diagram-js/lib/features/change-support')
+ require('diagram-js/lib/features/change-support'),
+ require('diagram-js/lib/features/drop')
],
bpmnFactory: [ 'type', require('./BpmnFactory') ],
bpmnUpdater: [ 'type', require('./BpmnUpdater') ],
@@ -12,4 +13,4 @@ module.exports = {
labelSupport: [ 'type', require('./LabelSupport') ],
layouter: [ 'type', require('./Layouter') ],
connectionDocking: [ 'type', require('diagram-js/lib/layout/CroppingConnectionDocking') ]
-};
\ No newline at end of file
+};
diff --git a/lib/features/rules/BpmnRules.js b/lib/features/rules/BpmnRules.js
new file mode 100644
index 00000000..413b78aa
--- /dev/null
+++ b/lib/features/rules/BpmnRules.js
@@ -0,0 +1,15 @@
+'use strict';
+
+var _ = require('lodash');
+
+var DropAction = require('./DropRules'),
+ ConnectHandler = require('./ConnectRules');
+
+
+function BpmnRules(rules) {
+ rules.registerRule('drop', 'validateSubProcess', DropAction.validateSubProcess);
+}
+
+BpmnRules.$inject = [ 'rules' ];
+
+module.exports = BpmnRules;
diff --git a/lib/features/rules/ConnectRules.js b/lib/features/rules/ConnectRules.js
new file mode 100644
index 00000000..5ac460c7
--- /dev/null
+++ b/lib/features/rules/ConnectRules.js
@@ -0,0 +1,22 @@
+'use strict';
+
+var _ = require('lodash');
+
+
+function can(context) {
+
+ var source = context.source,
+ target = context.target;
+
+ if (source.labelTarget || target.labelTarget) {
+ return null;
+ }
+
+ return source.businessObject.$parent === target.businessObject.$parent &&
+ source.businessObject.$instanceOf('bpmn:FlowNode') &&
+ !source.businessObject.$instanceOf('bpmn:EndEvent') &&
+ !target.businessObject.$instanceOf('bpmn:StartEvent') &&
+ target.businessObject.$instanceOf('bpmn:FlowElement');
+}
+
+module.exports.can = can;
diff --git a/lib/features/rules/DropRules.js b/lib/features/rules/DropRules.js
new file mode 100644
index 00000000..2c373593
--- /dev/null
+++ b/lib/features/rules/DropRules.js
@@ -0,0 +1,26 @@
+'use strict';
+
+var _ = require('lodash');
+
+function validateSubProcess(context) {
+
+ var target = context.target,
+ shapes = context.shape,
+ di = target.businessObject.di;
+
+ // can't drop anything to a collapsed subprocess
+ if (!di.isExpanded) {
+ return false;
+ }
+
+ // Elements that can't be dropped to a subprocess
+ _.forEach(shapes, function(shape) {
+ if (_.contains(['bpmn:Participant', 'bpmn:Lane'], shape.type)) {
+ return false;
+ }
+ });
+
+ return true;
+}
+
+module.exports.validateSubProcess = validateSubProcess;
diff --git a/lib/features/rules/index.js b/lib/features/rules/index.js
new file mode 100644
index 00000000..2061c19d
--- /dev/null
+++ b/lib/features/rules/index.js
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = {
+ __init__: [ 'bpmnRules' ],
+ bpmnRules: [ 'type', require('./BpmnRules') ]
+};
diff --git a/test/fixtures/bpmn/features/drop/drop.bpmn b/test/fixtures/bpmn/features/drop/drop.bpmn
new file mode 100644
index 00000000..784bf965
--- /dev/null
+++ b/test/fixtures/bpmn/features/drop/drop.bpmn
@@ -0,0 +1,41 @@
+
+
+
+
+
+ ID_Sequenceflow_1
+
+
+ ID_Sequenceflow_1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/fixtures/bpmn/features/drop/recursive-task.bpmn b/test/fixtures/bpmn/features/drop/recursive-task.bpmn
new file mode 100644
index 00000000..39f0c17a
--- /dev/null
+++ b/test/fixtures/bpmn/features/drop/recursive-task.bpmn
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+ ID_sequenceflow_1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/ModelerSpec.js b/test/spec/ModelerSpec.js
index a2ae611a..1a9df85c 100644
--- a/test/spec/ModelerSpec.js
+++ b/test/spec/ModelerSpec.js
@@ -146,10 +146,11 @@ describe('modeler', function() {
createModeler(xml, function(err, modeler) {
expect(modeler.get('bpmnjs')).toBe(modeler);
+
done(err);
});
});
});
-});
\ No newline at end of file
+});
diff --git a/test/spec/features/context-pad/ContextPadProviderSpec.js b/test/spec/features/context-pad/ContextPadProviderSpec.js
index f97eb9c5..d37f7743 100644
--- a/test/spec/features/context-pad/ContextPadProviderSpec.js
+++ b/test/spec/features/context-pad/ContextPadProviderSpec.js
@@ -31,5 +31,4 @@ describe('features - context-pad', function() {
}));
});
-
-});
\ No newline at end of file
+});
diff --git a/test/spec/features/drop/DropSpec.js b/test/spec/features/drop/DropSpec.js
new file mode 100644
index 00000000..56d2da66
--- /dev/null
+++ b/test/spec/features/drop/DropSpec.js
@@ -0,0 +1,132 @@
+'use strict';
+
+var Matchers = require('../../../Matchers'),
+ TestHelper = require('../../../TestHelper');
+
+/* global bootstrapModeler, inject */
+
+var _ = require('lodash');
+
+var fs = require('fs');
+
+var modelingModule = require('../../../../lib/features/modeling'),
+ dropModule = require('../../../../lib/features/drop'),
+ coreModule = require('../../../../lib/core');
+
+
+describe('features/drop ', function() {
+
+ beforeEach(Matchers.addDeepEquals);
+
+
+ var diagramXML = fs.readFileSync('test/fixtures/bpmn/features/drop/drop.bpmn', 'utf-8');
+ var diagramXML2 = fs.readFileSync('test/fixtures/bpmn/features/drop/recursive-task.bpmn', 'utf-8');
+
+ var testModules = [ coreModule, modelingModule, dropModule ];
+
+
+ describe('elements', function() {
+
+ beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
+
+ it('should update parent', inject(function(elementRegistry, modeling, drop) {
+
+ // given
+
+ var task_1 = elementRegistry.get('ID_Task_1'),
+ parent = elementRegistry.get('ID_SubProcess_1');
+
+ // when
+ modeling.moveShape(task_1, { x: 0, y: 200 }, parent);
+
+ // then
+ expect(task_1.parent).toBe(parent);
+ expect(task_1.businessObject.$parent).toBe(parent.businessObject);
+ }));
+
+ it('should update parents', inject(function(elementRegistry, modeling, drop) {
+
+ // given
+ var task_1 = elementRegistry.getById('ID_Task_1'),
+ task_2 = elementRegistry.get('ID_Task_2'),
+ parent = elementRegistry.getById('ID_SubProcess_1');
+
+ // when
+ modeling.moveShapes([ task_1, task_2 ], { x: 0, y: 200 }, parent);
+
+ // then
+ expect(task_1.parent).toBe(parent);
+ expect(task_1.businessObject.$parent).toBe(parent.businessObject);
+ expect(task_2.parent).toBe(parent);
+ expect(task_2.businessObject.$parent).toBe(parent.businessObject);
+ }));
+ });
+
+
+
+ describe('Sequence Flows', function() {
+
+ beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
+
+
+ it('should remove flow if target and source have different parents',
+ inject(function(elementRegistry, modeling, drop) {
+
+ // given
+ var task_1 = elementRegistry.get('ID_Task_1'),
+ parent = elementRegistry.get('ID_SubProcess_1'),
+ flow = elementRegistry.get('ID_Sequenceflow_1');
+
+ // when
+ modeling.moveShape(task_1, { x: 0, y: 200 }, parent);
+
+
+ // then
+ expect(flow.parent).toBe(null);
+ expect(flow.businessObject.$parent).toBe(null);
+ }));
+
+
+ it('should update flow parent if target and source have same parents',
+ inject(function(elementRegistry, modeling, drop) {
+
+ // given
+ var task_1 = elementRegistry.get('ID_Task_1'),
+ task_2 = elementRegistry.get('ID_Task_2'),
+ parent = elementRegistry.get('ID_SubProcess_1'),
+ flow = elementRegistry.get('ID_Sequenceflow_1');
+
+ // when
+ modeling.moveShapes([task_1, task_2], { x: 0, y: 250 }, parent);
+
+ // then
+ expect(flow.parent).toBe(parent);
+ expect(flow.businessObject.$parent).toBe(parent.businessObject);
+ }));
+ });
+
+ describe('recursion', function() {
+
+ beforeEach(bootstrapModeler(diagramXML2, { modules: testModules }));
+
+ it('should update parent', inject(function(elementRegistry, modeling, drop) {
+
+ // given
+
+ var task_1 = elementRegistry.get('ID_task_1'),
+ parent = elementRegistry.get('ID_subprocess_1'),
+ sequenceFlow = elementRegistry.get('ID_sequenceflow_1');
+
+ // when
+ modeling.moveShape(task_1, { x: 0, y: 200 }, parent);
+
+ // then
+ expect(task_1.parent).toBe(parent);
+ expect(task_1.businessObject.$parent).toBe(parent.businessObject);
+
+ expect(sequenceFlow.parent).toBe(parent);
+ expect(sequenceFlow.businessObject.$parent).toBe(parent.businessObject);
+ }));
+ });
+
+});
diff --git a/test/spec/features/modeling/AppendShapeSpec.js b/test/spec/features/modeling/AppendShapeSpec.js
index 052ef18e..0b1904cf 100644
--- a/test/spec/features/modeling/AppendShapeSpec.js
+++ b/test/spec/features/modeling/AppendShapeSpec.js
@@ -459,4 +459,4 @@ describe('features/modeling - append shape', function() {
});
-});
\ No newline at end of file
+});
diff --git a/test/spec/features/modeling/MoveConnectionSpec.js b/test/spec/features/modeling/MoveConnectionSpec.js
index b782883f..63fddf74 100644
--- a/test/spec/features/modeling/MoveConnectionSpec.js
+++ b/test/spec/features/modeling/MoveConnectionSpec.js
@@ -106,4 +106,4 @@ describe('features/modeling - move connection', function() {
});
-});
\ No newline at end of file
+});
diff --git a/test/spec/features/modeling/MoveShapeSpec.js b/test/spec/features/modeling/MoveShapeSpec.js
index fc65a857..eda0d49c 100644
--- a/test/spec/features/modeling/MoveShapeSpec.js
+++ b/test/spec/features/modeling/MoveShapeSpec.js
@@ -207,4 +207,4 @@ describe('features/modeling - move shape', function() {
});
-});
\ No newline at end of file
+});