diff --git a/lib/features/modeling/behavior/SubProcessPlaneBehavior.js b/lib/features/modeling/behavior/SubProcessPlaneBehavior.js
new file mode 100644
index 00000000..83756f1f
--- /dev/null
+++ b/lib/features/modeling/behavior/SubProcessPlaneBehavior.js
@@ -0,0 +1,247 @@
+import inherits from 'inherits';
+
+import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
+
+import { find } from 'min-dash';
+import { isExpanded } from '../../../util/DiUtil';
+import { getBusinessObject, is } from '../../../util/ModelUtil';
+import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
+import { getBBox } from 'diagram-js/lib/util/Elements';
+import { asPlaneId, planeId } from '../../../util/DrilldownUtil';
+
+
+/**
+ * Creates diPlanes and canvas planes when collapsed subprocesses are created.
+ *
+ *
+ * @param {Canvas} canvas
+ * @param {EventBus} eventBus
+ * @param {Modeling} modeling
+ * @param {ElementFactory} elementFactory
+ * @param {BpmnFactory} bpmnFactory
+ * @param {Bpmnjs} bpmnjs
+ * @param {ElementRegistry} elementRegistry
+ */
+export default function SubProcessPlaneBehavior(
+ canvas, eventBus, modeling,
+ elementFactory, bpmnFactory, bpmnjs, elementRegistry) {
+
+ CommandInterceptor.call(this, eventBus);
+
+ this._canvas = canvas;
+ this._eventBus = eventBus;
+ this._modeling = modeling;
+ this._elementFactory = elementFactory;
+ this._bpmnFactory = bpmnFactory;
+ this._bpmnjs = bpmnjs;
+ this._elementRegistry = elementRegistry;
+
+ var self = this;
+
+ function isCollapsedSubProcess(element) {
+ return is(element, 'bpmn:SubProcess') && !isExpanded(element);
+ }
+
+ function createRoot(context) {
+ var shape = context.shape,
+ rootElement = context.newRootElement;
+
+ var businessObject = getBusinessObject(shape);
+
+ rootElement = self._addDiagram(rootElement || businessObject);
+
+ context.newRootElement = canvas.addRootElement(rootElement);
+ }
+
+ function removeRoot(context) {
+ var shape = context.shape;
+
+ var businessObject = getBusinessObject(shape);
+ self._removeDiagram(businessObject);
+
+ var rootElement = context.newRootElement = elementRegistry.get(planeId(businessObject));
+
+ canvas.removeRootElement(rootElement);
+ }
+
+ // add plane elements for newly created sub-processes
+ // this ensures we can actually drill down into the element
+ this.executed('shape.create', function(context) {
+ var shape = context.shape;
+ if (!isCollapsedSubProcess(shape)) {
+ return;
+ }
+
+ createRoot(context);
+ }, true);
+
+
+ this.reverted('shape.create', function(context) {
+ var shape = context.shape;
+ if (!isCollapsedSubProcess(shape)) {
+ return;
+ }
+
+ removeRoot(context);
+ }, true);
+
+
+ // rename secondary elements (roots) when the primary element changes
+ // this ensures rootElement.id = element.id + '_plane'
+ this.executed('element.updateProperties', function(context) {
+ var shape = context.element;
+
+ if (!isCollapsedSubProcess(shape)) {
+ return;
+ }
+
+ var properties = context.properties;
+ var oldProperties = context.oldProperties;
+
+ var oldId = oldProperties.id,
+ newId = properties.id;
+
+ if (oldId === newId) {
+ return;
+ }
+
+ var planeElement = elementRegistry.get(asPlaneId(oldId));
+
+ if (!planeElement) {
+ return;
+ }
+
+ elementRegistry.updateId(planeElement, asPlaneId(newId));
+ }, true);
+
+
+ this.reverted('element.updateProperties', function(context) {
+ var shape = context.element;
+
+ if (!isCollapsedSubProcess(shape)) {
+ return;
+ }
+
+ var properties = context.properties;
+ var oldProperties = context.oldProperties;
+
+ var oldId = oldProperties.id,
+ newId = properties.id;
+
+ if (oldId === newId) {
+ return;
+ }
+
+ var planeElement = elementRegistry.get(asPlaneId(newId));
+
+ if (!planeElement) {
+ return;
+ }
+
+ elementRegistry.updateId(planeElement, asPlaneId(oldId));
+ }, true);
+}
+
+inherits(SubProcessPlaneBehavior, CommandInterceptor);
+
+
+/**
+* Adds a given diagram to the definitions and returns a .
+*
+* @param {Object} planeElement
+*/
+SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) {
+ var bpmnjs = this._bpmnjs;
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+
+ if (!planeElement.businessObject) {
+ planeElement = this._createNewDiagram(planeElement);
+ }
+
+ diagrams.push(planeElement.di.$parent);
+
+ return planeElement;
+};
+
+/**
+* Adds a given rootElement to the bpmnDi diagrams.
+*
+* @param {Object} rootElement
+* @returns {Object} planeElement
+*/
+SubProcessPlaneBehavior.prototype._addDiagram = function(planeElement) {
+ var bpmnjs = this._bpmnjs;
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+
+ if (!planeElement.businessObject) {
+ planeElement = this._createNewDiagram(planeElement);
+ }
+
+ diagrams.push(planeElement.di.$parent);
+
+ return planeElement;
+};
+
+
+/**
+* Creates a new plane element for the given sub process.
+*
+* @param {Object} bpmnElement
+*
+* @return {Object} new diagram element
+*/
+SubProcessPlaneBehavior.prototype._createNewDiagram = function(bpmnElement) {
+ var bpmnFactory = this._bpmnFactory;
+ var elementFactory = this._elementFactory;
+
+ var diPlane = bpmnFactory.create('bpmndi:BPMNPlane', {
+ bpmnElement: bpmnElement
+ });
+ var diDiagram = bpmnFactory.create('bpmndi:BPMNDiagram', {
+ plane: diPlane
+ });
+ diPlane.$parent = diDiagram;
+
+ // add a virtual element (not being drawn),
+ // a copy cat of our BpmnImporter code
+ var planeElement = elementFactory.createRoot({
+ id: planeId(bpmnElement),
+ type: bpmnElement.$type,
+ di: diPlane,
+ businessObject: bpmnElement,
+ collapsed: true
+ });
+
+ return planeElement;
+};
+
+/**
+ * Removes the diagram for a given root element
+ *
+ * @param {Object} rootElement
+ * @returns {Object} removed bpmndi:BPMNDiagram
+ */
+SubProcessPlaneBehavior.prototype._removeDiagram = function(rootElement) {
+ var bpmnjs = this._bpmnjs;
+
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+
+ var removedDiagram = find(diagrams, function(diagram) {
+ return diagram.plane.bpmnElement.id === rootElement.id;
+ });
+
+ diagrams.splice(diagrams.indexOf(removedDiagram), 1);
+
+ return removedDiagram;
+};
+
+
+SubProcessPlaneBehavior.$inject = [
+ 'canvas',
+ 'eventBus',
+ 'modeling',
+ 'elementFactory',
+ 'bpmnFactory',
+ 'bpmnjs',
+ 'elementRegistry'
+];
diff --git a/lib/features/modeling/behavior/index.js b/lib/features/modeling/behavior/index.js
index 6c99ee6f..045613d3 100644
--- a/lib/features/modeling/behavior/index.js
+++ b/lib/features/modeling/behavior/index.js
@@ -28,6 +28,7 @@ import ResizeLaneBehavior from './ResizeLaneBehavior';
import RemoveElementBehavior from './RemoveElementBehavior';
import SpaceToolBehavior from './SpaceToolBehavior';
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
+import SubProcessPlaneBehavior from './SubProcessPlaneBehavior';
import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour';
import UnclaimIdBehavior from './UnclaimIdBehavior';
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
@@ -66,6 +67,7 @@ export default {
'toggleElementCollapseBehaviour',
'spaceToolBehavior',
'subProcessStartEventBehavior',
+ 'subProcessPlaneBehavior',
'unclaimIdBehavior',
'unsetDefaultFlowBehavior',
'updateFlowNodeRefsBehavior'
@@ -101,6 +103,7 @@ export default {
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
spaceToolBehavior: [ 'type', SpaceToolBehavior ],
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
+ subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ]
diff --git a/lib/util/DrilldownUtil.js b/lib/util/DrilldownUtil.js
new file mode 100644
index 00000000..f512a646
--- /dev/null
+++ b/lib/util/DrilldownUtil.js
@@ -0,0 +1,29 @@
+import { is } from './ModelUtil';
+
+
+export var planePostfix = '_plane';
+
+/**
+ * Returns the ID of the plane associated with an element.
+ *
+ * @param {djs.model.Base|ModdleElement} element
+ * @returns {String} id of the associated plane
+ */
+export function planeId(element) {
+ if (is(element, 'bpmn:SubProcess')) {
+ return element.id + planePostfix;
+ }
+
+ return element.id;
+}
+
+/**
+ * Returns returns the plane ID for a given ID, as if it was associated with a
+ * subprocess.
+ *
+ * @param {String} shape ID
+ * @returns
+ */
+export function asPlaneId(string) {
+ return string + planePostfix;
+}
\ No newline at end of file
diff --git a/test/spec/features/modeling/behavior/SubProcessBehavior.multiple-planes.bpmn b/test/spec/features/modeling/behavior/SubProcessBehavior.multiple-planes.bpmn
new file mode 100644
index 00000000..70a6dc86
--- /dev/null
+++ b/test/spec/features/modeling/behavior/SubProcessBehavior.multiple-planes.bpmn
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/spec/features/modeling/behavior/SubProcessBehavior.planes.bpmn b/test/spec/features/modeling/behavior/SubProcessBehavior.planes.bpmn
new file mode 100644
index 00000000..25d99d99
--- /dev/null
+++ b/test/spec/features/modeling/behavior/SubProcessBehavior.planes.bpmn
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/spec/features/modeling/behavior/SubProcessPlaneBehaviorSpec.js b/test/spec/features/modeling/behavior/SubProcessPlaneBehaviorSpec.js
new file mode 100644
index 00000000..563626c7
--- /dev/null
+++ b/test/spec/features/modeling/behavior/SubProcessPlaneBehaviorSpec.js
@@ -0,0 +1,209 @@
+import {
+ bootstrapModeler,
+ inject
+} from 'test/TestHelper';
+
+import coreModule from 'lib/core';
+import modelingModule from 'lib/features/modeling';
+import replaceModule from 'lib/features/replace';
+import { is } from 'lib/util/ModelUtil';
+
+describe('features/modeling/behavior - subprocess planes', function() {
+
+ var diagramXML = require('./SubProcessBehavior.planes.bpmn');
+
+ beforeEach(bootstrapModeler(diagramXML, {
+ modules: [
+ coreModule,
+ modelingModule,
+ replaceModule
+ ]
+ }));
+
+
+ describe('create', function() {
+
+ it('should create new diagram for collapsed subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) {
+
+ // given
+ var subProcess = elementFactory.createShape({
+ type: 'bpmn:SubProcess',
+ isExpanded: false
+ });
+
+ // when
+ modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(2);
+ expect(canvas.findRoot(planeId(subProcess))).to.exist;
+ }));
+
+
+ it('should not create new plane for expanded subprocess', inject(function(elementFactory, modeling, canvas, bpmnjs) {
+
+ // given
+ var subProcess = elementFactory.createShape({
+ type: 'bpmn:SubProcess',
+ isExpanded: true
+ });
+
+ // when
+ modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(1);
+ expect(canvas.findRoot(planeId(subProcess))).to.not.exist;
+ }));
+
+
+ it('should undo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) {
+
+ // given
+ var subProcess = elementFactory.createShape({
+ type: 'bpmn:SubProcess',
+ isExpanded: false
+ });
+ modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
+
+ // when
+ commandStack.undo();
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(1);
+ expect(canvas.findRoot(planeId(subProcess))).to.not.exist;
+ }));
+
+
+ it('should redo', inject(function(elementFactory, modeling, commandStack, canvas, bpmnjs) {
+
+ // given
+ var subProcess = elementFactory.createShape({
+ type: 'bpmn:SubProcess',
+ isExpanded: false
+ });
+ modeling.createShape(subProcess, { x: 300, y: 300 }, canvas.getRootElement());
+ var plane = canvas.findRoot(planeId(subProcess));
+
+ // when
+ commandStack.undo();
+ commandStack.redo();
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(2);
+ expect(canvas.findRoot(planeId(subProcess))).to.exist;
+ expect(canvas.findRoot(planeId(subProcess))).to.equal(plane);
+ }));
+
+ });
+
+
+ describe('replace', function() {
+
+ describe('task -> collapsed subprocess', function() {
+
+ it('should add new diagram for collapsed subprocess', inject(
+ function(elementRegistry, bpmnReplace, bpmnjs, canvas) {
+
+ // given
+ var task = elementRegistry.get('Task_1'),
+ collapsedSubProcess;
+
+ // when
+ collapsedSubProcess = bpmnReplace.replaceElement(task, {
+ type: 'bpmn:SubProcess',
+ isExpanded: false
+ });
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(2);
+ expect(canvas.findRoot(planeId(collapsedSubProcess))).to.exist;
+ }
+ ));
+
+
+ it('should undo', inject(
+ function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) {
+
+ // given
+ var task = elementRegistry.get('Task_1'),
+ collapsedSubProcess = bpmnReplace.replaceElement(task, {
+ type: 'bpmn:SubProcess',
+ isExpanded: false
+ });
+
+ // when
+ commandStack.undo();
+
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(1);
+ expect(canvas.findRoot(planeId(collapsedSubProcess))).to.not.exist;
+ }
+ ));
+
+
+ it('should redo', inject(
+ function(elementRegistry, bpmnReplace, bpmnjs, canvas, commandStack) {
+
+ // given
+ var task = elementRegistry.get('Task_1'),
+ collapsedSubProcess = bpmnReplace.replaceElement(task, {
+ type: 'bpmn:SubProcess',
+ isExpanded: false
+ });
+
+ // when
+ commandStack.undo();
+ commandStack.redo();
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(2);
+ expect(canvas.findRoot(planeId(collapsedSubProcess))).to.exist;
+ }
+ ));
+ });
+
+ describe('task -> expanded subprocess', function() {
+
+ it('should not add new diagram for collapsed subprocess', inject(
+ function(elementRegistry, bpmnReplace, bpmnjs, canvas) {
+
+ // given
+ var task = elementRegistry.get('Task_1'),
+ collapsedSubProcess;
+
+ // when
+ collapsedSubProcess = bpmnReplace.replaceElement(task, {
+ type: 'bpmn:SubProcess',
+ isExpanded: true
+ });
+
+ // then
+ var diagrams = bpmnjs.getDefinitions().diagrams;
+ expect(diagrams.length).to.equal(1);
+ expect(canvas.findRoot(planeId(collapsedSubProcess))).to.not.exist;
+ }
+ ));
+
+ });
+
+ });
+
+});
+
+
+function planeId(element) {
+ if (is(element, 'bpmn:SubProcess')) {
+ return element.id + '_plane';
+ }
+
+ return element.id;
+}