External label for custom elements

Hi,

I can create external labels for my custom elements as follows:

export default function MyCustomLabelProvider(eventBus, modeling) {
  eventBus.on('commandStack.shape.create.postExecute', function(event) {
    var context = event.context,
        element = context.shape;
    if ( /* is my custom element */  ) {
      // create external label
      modeling.createLabel(element, 
        { x: element.x + element.width/2, 
          y: element.y + element.height + 16
        }, {
          id: element.businessObject.id + '_label',
          businessObject: element.businessObject
        }
      );
    }
  });
}

MyCustomLabelProvider.$inject = [
  'eventBus',
  'modeling',
  'bpmnFactory'
];

I can edit and move the label as intended and when I save the diagram I can see the label in the XML:

<bpmn2:process id="Process_1" isExecutable="false">
  <bpmn2:subProcess id="Activity_0vjevpw" name="My External Label" resources:type="Request" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
  <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
    <bpmndi:BPMNShape id="Activity_0vjevpw_di" bpmnElement="Activity_0vjevpw">
      <dc:Bounds x="540" y="270" width="100" height="40" />
      <bpmndi:BPMNLabel>
         <dc:Bounds x="546" y="316" width="89" height="14" />
      </bpmndi:BPMNLabel>
    </bpmndi:BPMNShape>
  </bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
<bpmndi:BPMNDiagram id="BPMNDiagram_0l3vhdw">
  <bpmndi:BPMNPlane id="BPMNPlane_17o9w0a" bpmnElement="Activity_0vjevpw" />
</bpmndi:BPMNDiagram>

However, when I load the diagram again, the external label is not shown :frowning:

Do I have to tell my custom renderer to create the external label and how could I do this?

So far I have

MyCustomRenderer.prototype.canRender = function(element) {
  if ( /* is my custom element */ && element.type != 'label') {
    return true;
  }
};

Any advice would be appreciated.

I can now create the lable after loading the file by adding:

eventBus.on('shape.added', function(event) {
  var element = event.element;
  if ( /* is my custom element */ &&
       element.type != 'label' && !element.label && element.di && element.di.label && element.di.label.bounds
     ) {
    // create external label
    modeling.createLabel(element, 
      { x: element.di.label.bounds.x, 
        y: element.di.label.bounds.y,
        width: element.di.label.bounds.width,
        height: element.di.label.bounds.height
      }, {
        id: element.businessObject.id + '_label',
        businessObject: element.businessObject,
      }
    );
  }
});

The remaining problem is that the custom elements with the added label can no longer be copy & pasted:

unhandled error in event listener Error: element <Activity_015eq2a_plane> already exists
    _ensureValid webpack://bpmn-js-modeler/./node_modules/diagram-js/lib/core/Canvas.js?:797
    addRootElement webpack://bpmn-js-modeler/./node_modules/diagram-js/lib/core/Canvas.js?:639
    createRoot webpack://bpmn-js-modeler/./node_modules/bpmn-js/lib/features/modeling/behavior/SubProcessPlaneBehavior.js?:74
    SubProcessPlaneBehavior webpack://bpmn-js-modeler/./node_modules/bpmn-js/lib/features/modeling/behavior/SubProcessPlaneBehavior.js?:96
   ...

Any idea?

Not sure if you’re setting this property anywhere but we don’t. The event listener might get called twice. Once after creating the custom element and then again after adding the label with element.type != 'label' still being false. Try and debug it.

Interesting, I am pretty sure I don’t set element.type: 'label'. I found that after modeling.createLabel(...) and pasting a copy, I receive two 'shape.added' events: one for my custom element and one for the label element. Both elements have the same business object, but only the label element has type: 'label'.

When I paste the custom element (i.e. a customized subprocess), a new shape for all elements belonging to the subprocess is created, including the label element. I assume that this causes an attempt to create the activity plane again which was already created for the custom element. Probably I would not have the problem if I removed the label element from the clipboard before pasting. Can I do this? Or can I prevent the label to be created when pasting?

P.S. I double checked and both event listeners are only called once and when expected.

Found a solution (a bit complicated, but works for me):

import {
  assign
} from 'min-dash';

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


/**
 * Provide external labels for custom subprocesses.
 */
export default function MyCustomLabelProvider(eventBus, modeling, elementRegistry) {

  eventBus.on("commandStack.shape.create.preExecute", function (event) {
    var context = event.context,
        element = context.shape;
    if ( /* is my custom element */
         && element.type == 'label' && element.businessObject
       ) {
      // prevent copying of external label
      element.oldBusinessObject = element.businessObject;
      element.businessObject = undefined;
      return false;
    }
  });

  eventBus.on('commandStack.shape.create.postExecute', function(event) {
    var context = event.context,
        element = context.shape;
    if ( element.type == 'label' && !element.businessObject
       ) {
      // remove and re-create external label
      const businessObject = element.oldBusinessObject;
      modeling.removeElements([ element ]);

      element = elementRegistry.get(businessObject.id);
      element.label = modeling.createLabel(element, 
        { x: element.x + element.width/2, 
          y: element.y + element.height + 16
        }, {
          id: element.businessObject.id + '_label',
          businessObject: element.businessObject
        }
      );
      if ( element.name ) {
        modeling.updateLabel(element.label, element.name);
      }
    }
    else if ( /* is my custom element */
              && element.type != 'label' && !element.label 
       ) {
      // create external label
      modeling.createLabel(element, 
        { x: element.x + element.width/2, 
          y: element.y + element.height + 16
        }, {
          id: element.businessObject.id + '_label',
          businessObject: element.businessObject
        }
      );
    }
  });

  eventBus.on('shape.added', function(event) {
    var element = event.element;
    if ( /* is my custom element */ 
         && element.type != 'label' && !element.label 
         && element.di && element.di.label && element.di.label.bounds
       ) {
      // create external label
      modeling.createLabel(element, 
        { x: element.di.label.bounds.x, 
          y: element.di.label.bounds.y,
          width: element.di.label.bounds.width,
          height: element.di.label.bounds.height
        }, {
          id: element.businessObject.id + '_label',
          businessObject: element.businessObject,
        }
      );
    }
  });
}

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

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.