Programmatically populate collapsed subprocess from XML

I can populate a collapsed subprocess as discussed in Programmatically populate collapsed subprocess (thank you @nikku for the help!)

As discussed in Import subprocesses I would like to populate the collapsed subprocess from a separate BPMN-model.

By combining the code from Programmatically populate collapsed subprocess - #2 by rajgoel and GitHub - nikku/bpmn-js-copy-paste-example: An example how to copy and paste between multiple instances of bpmn-js I managed to get below code running which can load an XML file and copy & paste elements when creating my custom subprocesses:

import inherits from 'inherits';

import { is } from 'bpmn-js/lib/util/ModelUtil';

import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';

import ResourceActivity from './ResourceActivity.bpmn';
import BpmnModeler from 'bpmn-js/lib/Modeler';

const subProcessModeler = new BpmnModeler({});
subProcessModeler.importXML(ResourceActivity);

function ifNewResourceActivity(fn) {
  return function(event) {
    var context = event.context,
        element = context.shape;
    if (is(element, 'bpmn:SubProcess') && element.businessObject.type == 'Resource') {
      fn(event);
    }
  };
}

/**
 * A handler responsible for creating children to a resource subprocess when this is created
 */
export default function ResourceUpdater(eventBus, modeling, elementFactory, elementRegistry) {

  CommandInterceptor.call(this, eventBus);

  function createResourceChildren(evt) {
    const sourceClipboard = subProcessModeler.get('clipboard'),
          sourceCopyPaste = subProcessModeler.get('copyPaste'),
          sourceElementRegistry = subProcessModeler.get('elementRegistry');
    const targetClipboard = modeler.get('clipboard'),
          targetCopyPaste = modeler.get('copyPaste'),
          targetElementRegistry = modeler.get('elementRegistry');

    const elements = sourceElementRegistry.getAll().filter(function(element) {
      // determine top-level elements to be copied 
      return element.id != 'Process_Template' && element.parent && element.parent.id == 'Process_Template';
    });

    // copy!
    sourceCopyPaste.copy(elements);
    // retrieve clipboard contents
    var copied = sourceClipboard.get();

    // put into clipboard
    targetClipboard.set(copied);

    var context = evt.context,
        element = context.shape,
        businessObject = element.businessObject;

    // I want to paste into my collapsed subprocess plane:
    const parent = targetElementRegistry.get(businessObject.id + '_plane');

    // Unfortunately I only managed to paste into the main process:
    var pasteContext = {
//      element: parent, 
      element: targetElementRegistry.get('Process_1'),
      point: {x:100, y:100}
    };

    // paste tree
    targetCopyPaste.paste(pasteContext);

  }
  this.postExecute([
    'shape.create'
  ], ifNewResourceActivity(createResourceChildren));

}

inherits(ResourceUpdater, CommandInterceptor);

ResourceUpdater.$inject = [ 'eventBus', 'modeling', 'elementFactory', 'elementRegistry' ];

Unfortunately, I only managed to paste into the main process ('Process_1'), but actually I want to paste into the collapsed subprocess, i.e. the parent in above code. I assume that the pasteContext is not properly defined or that I have to switch to the parent plane before pasting and back afterwards.

Any hints would be highly appreciated!

Interesting, pasting into the collapsed subprocess (and not in its plane) works by:

  var context = evt.context,
      element = context.shape,
      businessObject = element.businessObject;

//  const parent = targetElementRegistry.get(businessObject.id + '_plane');

  // remember size of collapsed subprocess shape
  var x = element.x,
      y = element.y,
      height = element.height, 
      width = element.width;

  var pasteContext = {
    element,
//    element: parent, 
//    element: targetElementRegistry.get('Process_1'),
    point: {x:100, y:100}
  };

  // paste tree
  targetCopyPaste.paste(pasteContext);

  // restore size of collapsed subprocess shape
  modeling.resizeShape(element, { width, height, x, y } );

Unfortunately, a workaround is needed to restore the size of the element, so the solution is probably not perfect.

@nikku: This is almost the same problem as discussed in Programmatically populate collapsed subprocess - #3 by nikku but using the element referring to the subprocess plane doesn’t work in this case :thinking:

I just now found out that the implicit resizing is more than just a code-beautification problem. My workaround to resize the shape later is ok for the shape itself, but a surrounding pool may also be implicitly resized with my approach …

The nicer workaround is to actually prevent resizing before it happens by

  this.preExecute([
    'shape.resize'
  ], ifNewResourceActivity(preventResize));

and

function preventResize(evt) {
  evt.context.newBounds = {
    x: evt.context.shape.x, 
    y: evt.context.shape.y, 
    width: evt.context.shape.width, 
    height: evt.context.shape.height
  };
}

Then we don’t nee to use resizeShape in the end.