diff --git a/lib/features/modeling/behavior/SubProcessStartEventBehavior.js b/lib/features/modeling/behavior/SubProcessStartEventBehavior.js new file mode 100644 index 00000000..0bb405c0 --- /dev/null +++ b/lib/features/modeling/behavior/SubProcessStartEventBehavior.js @@ -0,0 +1,69 @@ +import inherits from 'inherits'; + +import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; + +import { is } from '../../../util/ModelUtil'; +import { isExpanded } from '../../../util/DiUtil.js'; + +/** + * Add start event child by default when creating an expanded subprocess + * with create.start or replacing a task with an expanded subprocess. + */ +export default function SubProcessStartEventBehavior(eventBus, modeling) { + CommandInterceptor.call(this, eventBus); + + eventBus.on('create.start', function(event) { + var shape = event.context.shape, + hints = event.context.hints; + + hints.shouldAddStartEvent = is(shape, 'bpmn:SubProcess') && isExpanded(shape); + }); + + this.postExecuted('shape.create', function(event) { + var shape = event.context.shape, + hints = event.context.hints, + position; + + if (!hints.shouldAddStartEvent) { + return; + } + + position = calculatePositionRelativeToShape(shape); + + modeling.createShape({ type: 'bpmn:StartEvent' }, position, shape); + }); + + this.postExecuted('shape.replace', function(event) { + var oldShape = event.context.oldShape, + newShape = event.context.newShape, + position; + + if ( + !is(newShape, 'bpmn:SubProcess') || + !is(oldShape, 'bpmn:Task') || + !isExpanded(newShape) + ) { + return; + } + + position = calculatePositionRelativeToShape(newShape); + + modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape); + }); +} + +SubProcessStartEventBehavior.$inject = [ + 'eventBus', + 'modeling' +]; + +inherits(SubProcessStartEventBehavior, CommandInterceptor); + +// helpers ////////// + +function calculatePositionRelativeToShape(shape) { + return { + x: shape.x + shape.width / 6, + y: shape.y + shape.height / 2 + }; +} diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js index b622a00d..d0d18eeb 100644 --- a/lib/features/modeling/behavior/index.js +++ b/lib/features/modeling/behavior/index.js @@ -20,6 +20,7 @@ import RemoveParticipantBehavior from './RemoveParticipantBehavior'; import ReplaceElementBehaviour from './ReplaceElementBehaviour'; import ResizeLaneBehavior from './ResizeLaneBehavior'; import RemoveElementBehavior from './RemoveElementBehavior'; +import SubProcessStartEventBehavior from './SubProcessStartEventBehavior'; import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour'; import UnclaimIdBehavior from './UnclaimIdBehavior'; import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior'; @@ -50,6 +51,7 @@ export default { 'replaceElementBehaviour', 'resizeLaneBehavior', 'toggleElementCollapseBehaviour', + 'subProcessStartEventBehavior', 'unclaimIdBehavior', 'unsetDefaultFlowBehavior', 'updateFlowNodeRefsBehavior' @@ -77,6 +79,7 @@ export default { resizeLaneBehavior: [ 'type', ResizeLaneBehavior ], removeElementBehavior: [ 'type', RemoveElementBehavior ], toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ], + subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ], unclaimIdBehavior: [ 'type', UnclaimIdBehavior ], updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ], unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ] diff --git a/test/spec/features/modeling/behavior/SubProcessBehavior.start-event.bpmn b/test/spec/features/modeling/behavior/SubProcessBehavior.start-event.bpmn new file mode 100644 index 00000000..25d99d99 --- /dev/null +++ b/test/spec/features/modeling/behavior/SubProcessBehavior.start-event.bpmn @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/SubProcessStartEventBehaviorSpec.js b/test/spec/features/modeling/behavior/SubProcessStartEventBehaviorSpec.js new file mode 100644 index 00000000..debd8b18 --- /dev/null +++ b/test/spec/features/modeling/behavior/SubProcessStartEventBehaviorSpec.js @@ -0,0 +1,127 @@ +import { + bootstrapModeler, + inject +} from 'test/TestHelper'; + +import coreModule from 'lib/core'; +import createModule from 'diagram-js/lib/features/create'; +import draggingModule from 'diagram-js/lib/features/create'; +import modelingModule from 'lib/features/modeling'; +import replaceModule from 'lib/features/replace'; + +import { is } from 'lib/util/ModelUtil'; +import { createCanvasEvent as canvasEvent } from 'test/util/MockEvents'; + +describe('features/modeling/behavior - subprocess start event', function() { + + var diagramXML = require('./SubProcessBehavior.start-event.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: [ + coreModule, + createModule, + draggingModule, + modelingModule, + replaceModule + ] + })); + + + describe('create', function() { + + it('should contain start event child', inject( + function(canvas, elementFactory, create, dragging) { + + // given + var rootElement = canvas.getRootElement(), + subProcess = elementFactory.createShape({ + type: 'bpmn:SubProcess', + isExpanded: true + }), + startEvents; + + // when + create.start(canvasEvent({ x: 0, y: 0 }), subProcess); + + dragging.hover({ element: rootElement }); + + dragging.move(canvasEvent({ x: 600, y: 150 })); + + dragging.end(); + + // then + startEvents = getChildStartEvents(subProcess); + + expect(startEvents).to.have.length(1); + } + )); + + }); + + + describe('replace', function() { + + describe('task -> expanded subprocess', function() { + + it('should add start event child to subprocess', inject( + function(elementRegistry, bpmnReplace) { + + // given + var task = elementRegistry.get('Task_1'), + expandedSubProcess, + startEvents; + + // when + expandedSubProcess = bpmnReplace.replaceElement(task, { + type: 'bpmn:SubProcess', + isExpanded: true + }); + + // then + startEvents = getChildStartEvents(expandedSubProcess); + + expect(startEvents).to.have.length(1); + } + )); + + }); + + + describe('task -> collapsed subprocess', function() { + + it('should NOT add start event child to subprocess', inject( + function(elementRegistry, bpmnReplace) { + + // given + var task = elementRegistry.get('Task_1'), + collapsedSubProcess, + startEvents; + + // when + collapsedSubProcess = bpmnReplace.replaceElement(task, { + type: 'bpmn:SubProcess', + isExpanded: false + }); + + // then + startEvents = getChildStartEvents(collapsedSubProcess); + + expect(startEvents).to.have.length(0); + } + )); + + }); + + }); + +}); + +// helpers ////////// + +function isStartEvent(element) { + return is(element, 'bpmn:StartEvent'); +} + +function getChildStartEvents(element) { + return element.children.filter(isStartEvent); +}