Bpmn:connection The saga Continues

I’m trying to create a custom bpmn
This component should have the same behavior as the default bpmn
, but it will be thicker and a different color. I followed what I found in the documentation and managed to create this line, but its behavior is not like a bpmn
. Can someone help? Here’s the code I created:

CustomPalette:

 '
        'create.custom-connection': {
          group: 'connect',
          className: 'bpmn-icon-connection',
          title: translate('Create Custom Connection'),
          action: {
            dragstart: createCustomConnection,
            click: createCustomConnection
          }
        },
...
 function createCustomConnection(event) {     
      const businessObject = moddle.create('tubo:TuboConnection', {
        type: 'tubo:TuboConnection',
        diameter: '10mm',
        length: '100m'
      });    
      const customConnection = elementFactory.create('connection', {
        type: 'tubo:TuboConnection',
        businessObject,
        source: null,
        target: null
      });    
      create.start(event, customConnection);
    }

Index.js:

export default {
  __init__: ['customContextPad', 'customPalette', 'customRenderer', 'customTuboRenderer', 'customRules', 'customCommandInterceptor'],

  customContextPad: ['type', CustomContextPad],
  customPalette: ['type', CustomPalette],
  customRenderer: ['type', CustomRenderer],
  customRules: ['type', CustomRules],
  customTuboRenderer: ['type', CustomTuboRenderer],
  customCommandInterceptor: ['type', CustomCommandInterceptor],
};

CustomRenderer:

import inherits from 'inherits';
import BaseRenderer from 'diagram-js/lib/draw/BaseRenderer';
import { is } from 'bpmn-js/lib/util/ModelUtil';
import { append as svgAppend, create as svgCreate } from 'tiny-svg';

const HIGH_PRIORITY = 1500;

export default function CustomTuboRenderer(eventBus, bpmnRenderer) {
  BaseRenderer.call(this, eventBus, HIGH_PRIORITY);

  this.bpmnRenderer = bpmnRenderer;

  this.canRender = function(element) {
    const result = is(element, 'tubo:TuboConnection');
    console.log('Checking if can render:', element, result);
    return result;
  };

 
  this.drawCustomConnection = function(visuals, element) {
    if (!element.waypoints || element.waypoints.length === 0) {
      // Defina waypoints padrão se não estiverem definidos
      element.waypoints = [
        { x: element.x, y: element.y },
        { x: element.x + 100, y: element.y + 100 }
      ];
    }
    if (!element.waypoints || element.waypoints.length < 2) {
      console.error('Waypoints are not defined correctly.');
      return;
    }

    console.log('Drawing custom connection:', element);

    const line = svgCreate('line');
    svgAppend(visuals, line);

    const waypoints = element.waypoints;
    line.setAttribute('x1', waypoints[0].x);
    line.setAttribute('y1', waypoints[0].y);
    line.setAttribute('x2', waypoints[1].x);
    line.setAttribute('y2', waypoints[1].y);
    line.setAttribute('stroke', '#ff0000');  // Definindo a cor da linha para vermelho
    line.setAttribute('stroke-width', 7.5); // Espessura da linha

    return line;
  };

 
  this.drawShape = function(visuals, element) {
    console.log('Drawing shape:', element);

    if (this.canRender(element)) {
      console.log('Rendering custom connection:', element);
      return this.drawCustomConnection(visuals, element);
    }

    return this.bpmnRenderer.drawShape(visuals, element);
  };
}


inherits(CustomTuboRenderer, BaseRenderer);

CustomTuboRenderer.$inject = ['eventBus', 'bpmnRenderer'];

CustomRules.js

export default function CustomRules(eventBus) {
  eventBus.on('commandStack.connection.create.canExecute', (context) => {
    const { source, target, connection } = context;

    if (connection.type === 'tubo:TuboConnection') {
      // Permitir conexão entre tarefas e outros elementos BPMN
      if (source.type === 'bpmn:Task' && target.type === 'bpmn:Task') {
        return true;
      }
    }
  });
}

CustomRules.$inject = ['eventBus'];

CustomCommandInterveptor:


import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { assign } from 'min-dash';

export default function CustomCommandInterceptor(eventBus) { 
  CommandInterceptor.call(this, eventBus);

  this.preExecute(["connection.create"], function(context) {
    var connection = context.connection;

    if (connection.type === 'tubo:TuboConnection') {
![imagem-linha|1365x396](upload://gl7kD5IWgP7LZlWuh0ccRNtdHsR.png)

      assign(connection, {
        businessObject: {
          $type: 'tubo:TuboConnection'
        }
      });
    }
  }, true);
}

inherits(CustomCommandInterceptor, CommandInterceptor);

CustomCommandInterceptor.$inject = ['eventBus'];

As you can see in the image, when dragging the arrow to the diagram, it does not allow adding and does not allow connecting one component to another like a standard TextAnnotation. Any help is welcome, I have been researching and trying to create this for weeks. Is there any limitation to this behavior? Is it possible, has anyone done something like this and can share or help? Thank you in advance.
imagem-linha

The descritor:

{
    "name": "Custom BPMN",
    "uri": "http://example.com/custom-bpmn",
    "prefix": "tubo",
    "types": [
      {
        "name": "TuboConnection",
        "superClass": ["bpmn:SequenceFlow"],
        "properties": [
          {
            "name": "diameter",
            "isAttr": true,
            "type": "String"
          },
          {
            "name": "length",
            "isAttr": true,
            "type": "String"
          }
        ]
      }
    ]
  }
  

Can you please paste the code to a codesandbox? I’d like to debug your code as I can’t see the problem by just reading.

Hello, thank you for your interest in helping. I created this sandbox with the code snippet that represents this problem. Here is the link:

https://codesandbox.io/p/sandbox/bpmn-js-sandbox-forked-4zf7xv?file=%2Fsrc%2Findex.js%3A2%2C47-6%2C62

What I want with this code is to create a custom arrow without direction that allows connecting the components. It has a different thickness and color.

I appreciate your help in advance.


Let me know if you need any further changes!

I checked your code, and it fails when the app tries to gather replace menu entries for the newly created element: bpmn-js/lib/features/popup-menu/ReplaceMenuProvider.js at develop · bpmn-io/bpmn-js · GitHub

Since you specified that you want to connect the elements with your new element, and in the moddle schema you make it a child of bpmn:SequenceFlow, I’d suggest to rethink how you want to add this new connection.

Currently, the way of creating this “connection” in the codesandbox is to make it free-floating which leads to the errors. Note that bpmn.io apps are meant to be editors, not drawing tools. So a connection needs to have a source and a target which is impossible if you just drag an element from the palette.

I think it makes much more sense to use the Connect API, and create a tool similar to global connect (but with a specific connection type).

1 Like

Thank you for the time you spent reviewing my code. I will try a different approach with connect as you suggested and post it here on the forum. Once again, thank you for your help.

I made the change in approach, extended the GlobalConnection, and managed to add the behavior I needed to the connection. However, my new component, when rendering, does not recognize the customized type and always returns the type as bpmn:sequenceFlow. I updated my sample code here: I can’t identify what is missing. I tried to follow the documentation, but there must be a detail that I overlooked.

In my use case, there should be two types of SequenceFlow: the standard one from bpmn-js with its default behavior, and a customized one, which in this case would be “tubo,” with a different thickness and color.

https://codesandbox.io/p/sandbox/bpmn-js-sandbox-forked-4zf7xv?file=%2Fsrc%2Flib%2FCustomGlobalRenderer.js%3A22%2C42

The reason why your connection is perceived as sequence flow by the app is that it is in fact a sequence flow. “Superclass” in moddle schema defines the inheritance parent, so in JavaScript terms it looks like class TuboConnection extends SequenceFlow.
You could change the superclass of the TuboConnection to something more general. What is the desired usage of your connection? If it doesn’t have any executable meaning, it could be an Association.