Why connection draw behind group?


#1

11

I connected the connection using customRules,
but the connection is drawn behind the group.

What’s the problem with this?
Which part should I look at ??


#2

What element type are the white rectangles?


#3

That’s Bpmn:SubProcess


#4

There is two places you’d need to look into:

  • Ensure that the diagram is imported in the correct manner
  • Ensure that the connection is rendered / recognized in the correct order, cf. BpmnOrderingProvider that implements this for message flows (always rendered on top),.

I want to implement CustomRules
#5

Share an example of what you’re doing and we’ll be able to help you in a more focussed / efficient manner.


#6

i implemented

 var orders = [
    { type: 'bpmn:SubProcess', order: { level: 6 } },
    {
      type: 'bpmn:SequenceFlow',
      order: {
        level: 9,
        containers: [
          'bpmn:Participant',
          'bpmn:SubProcess',
          'bpmn:FlowElementsContainer'
        ]
      }
    },
    // handle DataAssociation(s) like message flows and render them always on top
    {
      type: 'bpmn:DataAssociation',
      order: {
        level: 9,
        containers: [
          'bpmn:Collaboration',
          'bpmn:Process'
        ]
      }
    },
    {
      type: 'bpmn:MessageFlow', order: {
        level: 9,
        containers: [ 'bpmn:Collaboration' ]
      }
    },
    {
      type: 'bpmn:Association',
      order: {
        level: 6,
        containers: [
          'bpmn:Participant',
          'bpmn:FlowElementsContainer',
          'bpmn:Collaboration'
        ]
      }
    },
    { type: 'bpmn:BoundaryEvent', order: { level: 8 } },
    { type: 'bpmn:FlowElement', order: { level: 5 } },
    { type: 'bpmn:Participant', order: { level: -2 } },
    { type: 'bpmn:Lane', order: { level: -1 } }
  ];

‘bpmn:SequenceFlow’ is higher level than bpmn:SubProcess.
but drawn behind ‘bpmn:SubProcess’


#7

com-crop

It is now working like this.
Please give me a solution.
There is no change in BpmnOrderingProvider,


#8

To be more precise: Share a complete, runnable example of what you are doing and we are able to help you.


#9

<CustomRules.js>

function canConnectSequenceFlow(source, target) {
  return isSequenceFlowSource(source) &&
    isSequenceFlowTarget(target) &&
    // isSameScope(source, target) && // deleted
    !(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
}

<CustomReplaceConnectionBehavior.js>

function fixConnection(connection) {

    var source = connection.source,
      target = connection.target,
      parent = connection.parent;

    // do not do anything if connection
    // is already deleted (may happen due to other
    // behaviors plugged-in before)
    if (!parent) {
      return;
    }

    var replacementType,
      remove;

    /**
     * Check if incoming or outgoing connections
     * can stay or could be substituted with an
     * appropriate replacement.
     *
     * This holds true for SequenceFlow <> MessageFlow.
     */

    if (is(connection, 'bpmn:SequenceFlow')) {
      if (!customRules.canConnectSequenceFlow(source, target)) {
        // remove = true;
      }

      if (customRules.canConnectMessageFlow(source, target)) {
        replacementType = 'bpmn:MessageFlow';
      }
    }

    // transform message flows into sequence flows, if possible

    if (is(connection, 'bpmn:MessageFlow')) {

      if (!customRules.canConnectMessageFlow(source, target)) {
        remove = true;
      }

      if (customRules.canConnectSequenceFlow(source, target)) {
        replacementType = 'bpmn:SequenceFlow';
      }
    }

    if (is(connection, 'bpmn:Association') && !bpmnRules.canConnectAssociation(source, target)) {
      remove = true;
    }


    // remove invalid connection, // deleted
    // unless it has been removed already
    // if (remove) {
    //   modeling.removeConnection(connection);
    // }

    // replace SequenceFlow <> MessageFlow

    // if (replacementType) { // deleted
    //   modeling.connect(source, target, {
    //     type: replacementType,
    //     waypoints: connection.waypoints.slice()
    //   });
    // }
  }

and, white rectangle is ‘Bpmn:SubProcess’, blue rectangle is ‘Bpmn:Task’.
and connection is ‘Bpmn:SequenceFlow’.
and other source is same


#10

As a follow up to this, I also have the same issue as this one. We are seeking to allow users to connect sequence flows across swim lanes. We have successfully set up our custom rule to allow this, but are now experiencing the issue above. We have added a custom ordering provider as suggested, but it doesn’t seem to be overriding the order already added to the element, so is being ignored. Is there an example anywhere or some documentation on the right way to override the order in the orderingprovider please?


#11

Can you share a running example of your code?


#12

Yes sure, you can recreate the issue here;

It loads with an empty diagram, but if you add two separate pools, then put a task in one and a gateway in the other, then try to connect them using a sequence flow you will see what I mean. I have added the custom rule to allow the connection, and a custom ordering provider which I think sets the order correctly. Here is an example screenshot as well.

Thanks for your help!


#13

Have you checked which element ends up as the parent when calling findActualParent for the sequence flow? If both flow elements containers and collaborations are possible the parent might still be the flow elements container. If you connect across participants it must be the collaboration.


#14

Is that a question of changing the order I currently have set as follows:

{
  type: 'bpmn:SequenceFlow',
  alwaysOnTop : true,
  order: {
    level: 9,
    containers: [
     'bpmn:FlowElementsContainer',
     'bpmn:Collaboration'
    ]
  }
}

to

{
  type: 'bpmn:SequenceFlow',
  alwaysOnTop : true,
  order: {
    level: 9,
    containers: [
     'bpmn:Collaboration'
    ]
  }
}

(The alwaysOnTop doesn’t seem to do anything by the way, but I saw it in the comments of the custom modeler example so thought I would give it a try!)


#15

Where did you find alwaysOnTop? In the end you’re free to define whatever logic to compute the order as long as you return an object specifying the index and parent.


#16

I got it from the BpmnOrderingProvider.js file in the bpmn-js/lib/features/ordering folder. My example stems from the custom modeler example in your git repo, so maybe it is out of date? The code inside it has the following comment in it.
23

With regard to logic definition you describe can you be a bit more specific please? How do I return the object you are talking about? And is the suggestion I made above correct for this purpose?

Thanks!


#17

Bringing this topic back to the attention of the team if possible please. I think I have implemented a custom ordering provider correctly now, but unfortunately it still doesn’t seem to work. I did everything the same as BpmnOrderingProvider and simply changed the bpmn:SequenceFlow item in the orders array to have a level of 9 and a containers array that only contains ‘bpmn:Collaboration’. This still draws the lines behind the particpant I am joining too. Also, if you then try and move one of the participant the connection is removed altogether.

I have checked the result of findActualParent as Philipp suggested, and this is now definitely returning ‘bpmn:Collaboration’, but this doesn’t seem to solve the issue either. I will put together another example and post it if I can, but any ideas on how I can fix this would be very gratefully received, its the last task on my list!

Also, if you could confirm I am overriding in the right way that would be helpful too please, or if you can point me in the direction of a document saying how to do it correctly. Right now my index.js looks like this;

import CustomElementFactory from './CustomElementFactory';
import CustomRenderer from './CustomRenderer';
import CustomPalette from './CustomPalette';
import CustomRules from './CustomRules';
import CustomUpdater from './CustomUpdater';
import CustomContextPadProvider from './CustomContextPadProvider';
import CustomSnapping from './CustomSnapping';
import CustomOrderingProvider from './CustomOrderingProvider';

export default {
  __init__: [
    'customRenderer',
    'paletteProvider',
    'customRules',
    'customUpdater',
    'contextPadProvider',
    'snapping',
    'bpmnOrderingProvider'
  ],
  elementFactory: [ 'type', CustomElementFactory ],
  customRenderer: [ 'type', CustomRenderer ],
  paletteProvider: [ 'type', CustomPalette ],
  customRules: [ 'type', CustomRules ],
  customUpdater: [ 'type', CustomUpdater ],
  contextPadProvider: [ 'type', CustomContextPadProvider ],
  snapping: [ 'type', CustomSnapping ],
  bpmnOrderingProvider: [ 'type', CustomOrderingProvider ]
};

and the content of CustomOrderingProvider is;

import inherits from 'inherits';

import OrderingProvider from 'diagram-js/lib/features/ordering/OrderingProvider';

import {
	isAny
} from 'bpmn-js/lib/features/modeling/util/ModelingUtil';

import {
	findIndex,
	find
} from 'min-dash';

export default function CustomOrderingProvider(eventBus) {

	OrderingProvider.call(this, eventBus);

	var orders = [
		{ type: 'bpmn:SubProcess', order: { level: 6 } },
		{
			type: 'bpmn:SequenceFlow',
			order: {
				level: 9,
				containers: [
					'bpmn:Collaboration'
				]
			}
		},
		// handle DataAssociation(s) like message flows and render them always on top
		{
			type: 'bpmn:DataAssociation',
			order: {
				level: 9,
				containers: [
					'bpmn:Collaboration',
					'bpmn:Process'
				]
			}
		},
		{
			type: 'bpmn:MessageFlow', order: {
				level: 9,
				containers: ['bpmn:Collaboration']
			}
		},
		{
			type: 'bpmn:Association',
			order: {
				level: 6,
				containers: [
					'bpmn:Participant',
					'bpmn:FlowElementsContainer',
					'bpmn:Collaboration'
				]
			}
		},
		{ type: 'bpmn:BoundaryEvent', order: { level: 8 } },
		{ type: 'bpmn:FlowElement', order: { level: 9 } },
		{ type: 'bpmn:Participant', order: { level: -2 } },
		{ type: 'bpmn:Lane', order: { level: -1 } }
	];

	function computeOrder(element) {
		if (element.labelTarget) {
			return { level: 10 };
		}

		var entry = find(orders, function (o) {
			return isAny(element, [o.type]);
		});

		return entry && entry.order || { level: 1 };
	}

	function getOrder(element) {

		var order = element.order,
			computedOrder = computeOrder(element);

		if (!order) {
			element.order = order = computedOrder;
		}

		if (order !== computedOrder) {
			element.order = order = computedOrder;
		}

		return order;
	}

	function findActualParent(element, newParent, containers) {

		var actualParent = newParent;

		while (actualParent) {

			if (isAny(actualParent, containers)) {
				break;
			}

			actualParent = actualParent.parent;
		}

		if (!actualParent) {
			throw new Error(translate('no parent for {element} in {parent}', {
				element: element.id,
				parent: newParent.id
			}));
		}

		return actualParent;
	}

	this.getOrdering = function (element, newParent) {

		var elementOrder = getOrder(element);


		if (elementOrder.containers) {
			newParent = findActualParent(element, newParent, elementOrder.containers);
		}


		var currentIndex = newParent.children.indexOf(element);

		var insertIndex = findIndex(newParent.children, function (child) {

			// do not compare with labels, they are created
			// in the wrong order (right after elements) during import and
			// mess up the positioning.
			if (!element.labelTarget && child.labelTarget) {
				return false;
			}

			return elementOrder.level < getOrder(child).level;
		});


		// if the element is already in the child list at
		// a smaller index, we need to adjust the inser index.
		// this takes into account that the element is being removed
		// before being re-inserted
		if (insertIndex !== -1) {
			if (currentIndex !== -1 && currentIndex < insertIndex) {
				insertIndex -= 1;
			}
		}

		return {
			index: insertIndex,
			parent: newParent
		};
	};
}

CustomOrderingProvider.$inject = [ 'eventBus', 'translate' ];

inherits(CustomOrderingProvider, OrderingProvider);

Thanks as always for any help you can give!


#18

Here is an updated runnable example of the issue


#19

Hi @Ben_Gallienne,

I tried out your runnable, thanks for sharing. Are you aware of the thrown errors when creating the connection you described?

image

Maybe debugging these errors will help.


#20

Hi Niklas,

Yes I’m aware of the errors thanks. They are being caused because of setting the containers array to only include ‘bpmn:Collaboration’. If I include ‘bpmn:Participant’ in the array as well, the errors stop, but then the arrow appears behind the pool again.

Its caused by this block of code;

// add to new parent
    children = newParent.get(containment);
    children.push(businessObject);
    businessObject.$parent = newParent;

which is at line 601 of bpmn-js/lib/features/modeling/BpmnUpdater.js. The newParent.get(containment) line returns nothing if the parent is the collaboration, which causes the next line to fail. Any ideas what should be happening at this point?

Thanks,

Ben