Programmatically copy/paste extensionElements within subprocesses

I can programmatically copy and paste subprocesses from an XML-template as discussed in Programmatically populate collapsed subprocess from XML using

import inherits from 'inherits';

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

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

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

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

let children = {};
function selectChildren(elementRegistry, id) {
  const elements = elementRegistry.getAll().filter(function(element) {
    // determine children to be copied 
    return element.parent && element.parent.id == id + '_plane';
  });
  return elements;
}

const subProcessModeler = new BpmnModeler();
subProcessModeler.importXML(subProcessTemplates).then( function() {
    const sourceClipboard = subProcessModeler.get('clipboard'),
          sourceCopyPaste = subProcessModeler.get('copyPaste'),
          sourceElementRegistry = subProcessModeler.get('elementRegistry');

    // copy resource template
    sourceCopyPaste.copy(selectChildren(sourceElementRegistry,'ResourceActivityTemplate'));
    // retrieve clipboard contents
    children['Resource'] = sourceClipboard.get();

    // copy request template
    sourceCopyPaste.copy(selectChildren(sourceElementRegistry,'RequestActivityTemplate'));
    // retrieve clipboard contents
    children['Request'] = sourceClipboard.get();

    // copy release template
    sourceCopyPaste.copy(selectChildren(sourceElementRegistry,'ReleaseActivityTemplate'));
    // retrieve clipboard contents
    children['Release'] = sourceClipboard.get();

});

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 };
}

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

  CommandInterceptor.call(this, eventBus);

  function createChildren(evt) {
    const context = evt.context,
          element = context.shape,
          businessObject = element.businessObject;

    const targetClipboard = modeler.get('clipboard'),
          targetCopyPaste = modeler.get('copyPaste'),
          targetElementRegistry = modeler.get('elementRegistry');

    // put into clipboard
    targetClipboard.set(children[businessObject.type]);

    const pasteContext = {
        element,
      point: {x:0, y:0}
    };

//console.log("PASTE",pasteContext);
    // paste tree
    targetCopyPaste.paste(pasteContext);
  }

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

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

  /// other stuff  ...
}

inherits(ResourceUpdater, CommandInterceptor);

ResourceUpdater.$inject = [ 'eventBus', 'modeling', 'elementFactory', 'elementRegistry', 'editorActions',  'contextPad', 'dragging', 'directEditing' ];

However, extensionElements within the XML-template are not copied. From Change type of element but keep already set properties - #2 by philippfromme I learned that extensionElements are not copied by default and I tried adding this

  eventBus.on('moddleCopy.canCopyProperty', function(context) {
    var property = context.property;
    if (is(property, 'bpmn:ExtensionElements')) {
      return property;
    }
  });

which appears to somehow work, but not completely. With this added code, the extensionElements are actually included within the target model and I can see them within the saved XML. If I reload the saved model, everything works as intended and I can see the extensionElements content in my properties panel. However, without reloading I cannot see the content in the properties panel.

@philippfromme Any idea on what is wrong? Do I need to clone property before returning (how can this be done?), do I need to trigger an update of the respective element or business object (how could this be done?), or could it be that the problem is within the properties panel. I’d be happy to share further code if needed to understand the issue. Thanks for any help on this!

1 Like

Your progress on this topic is amazing. Do you have a code sandbox or something to share with us so we can have a look into this a little easier?

@nikku Thanks for taking a look at this. Unfortunately, I didn’t manage to setup a code sandbox (bpmn-js-execution-modeler - CodeSandbox fails to run). My customised modeller is available on https://bpmn.telematique.eu/ and I put a copy of my project here: https://bpmn.telematique.eu/modeller.zip (install and start with npm install and npm run start).

In my project I created several custom shapes, e.g., a resource activity (extends bpmn:subProcess) that looks like this:

ResourceActivity

When such a resource activity is created the respective template in src/modules/resource/SubProcessTemplates.bpmn is copied to populate the content of the subprocess. In the template, there is an event-subprocess named “Allocation” that has the following extension elements that should be copied:

<bpmn2:extensionElements>
  <execution:status>
    <execution:attribute id="Attribute_0z7zzuo" name="consumable" type="xs:boolean" value="false" />
  </execution:status>
</bpmn2:extensionElements>

It appears that, as stated above, the information is actually copied, however, the content is not shown in the properties panel, i.e., in line 42 of src/modules/execution/properties/StatusHandler.js the status.attribute is undefined, although it should give the above attribute. Maybe something like a createReviver function is necessary as used here: GitHub - nikku/bpmn-js-copy-paste-example: An example how to copy and paste between multiple instances of bpmn-js

One issue that I just noticed is that my project works with bpmn.js v9.0.3, but with v9.0.4 (and higher) there appears to be an issue with the placement of labels.

With bpmn.js v.9.0.3, when I create a resource activity, the subprocess content is copied inside of the subprocess:

With bpmn.js v.9.0.4 and later, when I create a resource activity, the labels of the subprocess are incorrectly placed within the main process and not within the subprocess:

This could be a regression introduced in the changes towards v9.0.4.

P.S. The code for copying the subprocess content is found in src/modules/resource/ResourceUpdater.js and triggered by

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

I just created a bug report on Labels misplaced after copy & paste · Issue #1690 · bpmn-io/bpmn-js · GitHub for the above mentioned possible regression.

1 Like

My first guess would be that the property isn’t copied because your second modeler doesn’t know about the extension:

const subProcessModeler = new BpmnModeler();

Have you tried adding the execution extension to that modeler, too?

1 Like

@philippfromme Perfect! This fixed the problem:

const subProcessModeler = new BpmnModeler({  
  moddleExtensions: {
    execution: ExecutionModdleDescriptor,
  }
});
1 Like