diff --git a/lib/features/snapping/BpmnSnapping.js b/lib/features/snapping/BpmnSnapping.js index cba85cd2..85525cdc 100644 --- a/lib/features/snapping/BpmnSnapping.js +++ b/lib/features/snapping/BpmnSnapping.js @@ -2,7 +2,11 @@ var inherits = require('inherits'); -var forEach = require('lodash/collection/forEach'); +var abs = Math.abs; + +var forEach = require('lodash/collection/forEach'), + filter = require('lodash/collection/filter'), + assign = require('lodash/object/assign'); var getBoundingBox = require('diagram-js/lib/util/Elements').getBBox; @@ -88,6 +92,10 @@ function BpmnSnapping(eventBus, canvas, bpmnRules, elementRegistry) { return bpmnRules.canAttach([ shape ], target, null, position) === 'attach'; } + function canConnect(source, target) { + return bpmnRules.canConnect(source, target); + } + /** * Snap boundary events to elements border */ @@ -129,11 +137,38 @@ function BpmnSnapping(eventBus, canvas, bpmnRules, elementRegistry) { } }); + /** + * Snap sequence flows. + */ + eventBus.on([ + 'connect.move', + 'connect.hover', + 'connect.end' + ], HIGH_PRIORITY, function(event) { + var context = event.context, + source = context.source, + target = context.target; - var abs = Math.abs; + var connection = canConnect(source, target) || {}; - var filter = require('lodash/collection/filter'), - assign = require('lodash/object/assign'); + if (!context.initialSourcePosition) { + context.initialSourcePosition = context.sourcePosition; + } + + if (target && connection.type === 'bpmn:SequenceFlow') { + + // snap source + context.sourcePosition = mid(source); + + // snap target + assign(event, mid(target)); + } else { + + // otherwise reset source snap + context.sourcePosition = context.initialSourcePosition; + } + + }); eventBus.on([ diff --git a/test/spec/features/snapping/BpmnSnapping.connect.bpmn b/test/spec/features/snapping/BpmnSnapping.connect.bpmn new file mode 100644 index 00000000..f46e4748 --- /dev/null +++ b/test/spec/features/snapping/BpmnSnapping.connect.bpmn @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/snapping/BpmnSnappingSpec.js b/test/spec/features/snapping/BpmnSnappingSpec.js index 26576196..9b2f1394 100644 --- a/test/spec/features/snapping/BpmnSnappingSpec.js +++ b/test/spec/features/snapping/BpmnSnappingSpec.js @@ -12,7 +12,8 @@ var coreModule = require('../../../../lib/core'), createModule = require('diagram-js/lib/features/create'), resizeModule = require('diagram-js/lib/features/resize'), moveModule = require('diagram-js/lib/features/move'), - rulesModule = require('../../../../lib/features/rules'); + rulesModule = require('../../../../lib/features/rules'), + connectModule = require('diagram-js/lib/features/connect'); describe('features/snapping - BpmnSnapping', function() { @@ -23,7 +24,8 @@ describe('features/snapping - BpmnSnapping', function() { modelingModule, createModule, rulesModule, - moveModule + moveModule, + connectModule ]; describe('on Boundary Events', function() { @@ -541,4 +543,214 @@ describe('features/snapping - BpmnSnapping', function() { }); + + describe('on connect', function() { + + var diagramXML = require('./BpmnSnapping.connect.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + + it('should snap sequence flow on global connect', inject(function(connect, dragging, elementRegistry) { + + // given + var startEvent = elementRegistry.get('StartEvent_1'), + task = elementRegistry.get('Task_1'); + + var mid = { + x: startEvent.x + startEvent.width / 2, + y: startEvent.y + startEvent.height / 2 + }; + + // when + connect.start(canvasEvent({ x: mid.x + 10, y: mid.y + 10 }), startEvent); + + dragging.hover({ + element: task, + gfx: elementRegistry.getGraphics(task) + }); + + dragging.move(canvasEvent({ + x: task.x + task.width / 2, + y: task.y + task.height / 2 + })); + + dragging.end(); + + // then + var expected = [ + { + original: + { + x: startEvent.x + startEvent.width / 2, + y: startEvent.y + startEvent.height / 2 + }, + x: startEvent.x + startEvent.width, + y: startEvent.y + startEvent.height / 2 + }, + { + original: + { + x: task.x + task.width / 2, + y: task.y + task.height / 2 + }, + x: task.x, + y: task.y + task.height / 2 + } + ]; + + expect(startEvent.outgoing[0].waypoints).to.eql(expected); + + })); + + + it('should snap sequence flow on connect', inject(function(connect, dragging, elementRegistry) { + + // given + var startEvent = elementRegistry.get('StartEvent_1'), + task = elementRegistry.get('Task_1'); + + var mid = { x: task.x + task.width / 2, y: task.y + task.height / 2 }; + + // when + connect.start(canvasEvent({ x: 0, y: 0 }), startEvent); + + dragging.hover({ + element: task, + gfx: elementRegistry.getGraphics(task) + }); + + dragging.move(canvasEvent({ x: mid.x + 10, y: mid.y + 10 })); + + dragging.end(); + + // then + var expected = [ + { + original: + { + x: startEvent.x + startEvent.width / 2, + y: startEvent.y + startEvent.height / 2 + }, + x: startEvent.x + startEvent.width, + y: startEvent.y + startEvent.height / 2 + }, + { + original: + { + x: task.x + task.width / 2, + y: task.y + task.height / 2 + }, + x: task.x, + y: task.y + task.height / 2 + } + ]; + + expect(startEvent.outgoing[0].waypoints).to.eql(expected); + + })); + + + it('should NOT snap message flow on global connect', inject(function(connect, dragging, elementRegistry) { + + // given + var task1 = elementRegistry.get('Task_1'), + task2 = elementRegistry.get('Task_2'); + + var task1Mid = { x: task1.x + task1.width / 2, y: task1.y + task1.height / 2 }, + task2Mid = { x: task2.x + task2.width / 2, y: task2.y + task2.height / 2 }; + + // when + connect.start(null, task1, { x: 320, y: task1Mid.y + 20 }); + + dragging.hover({ + element: task2, + gfx: elementRegistry.getGraphics(task2) + }); + + dragging.move(canvasEvent({ + x: 320, + y: task2Mid.y - 20 + })); + + dragging.end(); + + // then + var expected = [ + { + original: + { + x: 320, + y: task1Mid.y + 20 + }, + x: 320, + y: task1.y + task1.height + }, + { + original: + { + x: 320, + y: task2Mid.y - 20 + }, + x: 320, + y: task2.y + } + ]; + + expect(task1.outgoing[0].waypoints).to.eql(expected); + + })); + + + it('should NOT snap message flow on connect', inject(function(connect, dragging, elementRegistry) { + + // given + var task1 = elementRegistry.get('Task_1'), + task2 = elementRegistry.get('Task_2'); + + var task1Mid = { x: task1.x + task1.width / 2, y: task1.y + task1.height / 2 }, + task2Mid = { x: task2.x + task2.width / 2, y: task2.y + task2.height / 2 }; + + // when + connect.start(canvasEvent({ x: 0, y: 0 }), task1); + + dragging.hover({ + element: task2, + gfx: elementRegistry.getGraphics(task2) + }); + + dragging.move(canvasEvent({ + x: task2Mid.x + 20, + y: task2Mid.y - 20 + })); + + dragging.end(); + + // then + expect(task1.outgoing[0].waypoints.length).to.equal(4); + + expect(task1.outgoing[0].waypoints[0]).to.eql({ + original: + { + x: task1Mid.x, + y: task1Mid.y + }, + x: task1Mid.x, + y: task1.y + task1.height + }); + + expect(task1.outgoing[0].waypoints[3]).to.eql({ + original: + { + x: task2Mid.x + 20, + y: task2Mid.y - 20 + }, + x: task2Mid.x + 20, + y: task2.y + }); + + })); + + }); + });