Custom rule for inserting elements in modeler

Hello everyone, :wave:

My goal here would be to add a custom rule to the modeler that prevents “inserting” shapes into a flow.

To be more clear, I am referring to the user action of moving a shape and inserting it onto an existing Sequence Flow. This usually results in the shape being inserted to the path, and automatically connecting it to the previous and next shapes. I want to override this, without removing the possibility of simply moving shapes in the process

I assume creating a custom rule is the correct way to do this. What I (think I) need is the name of the event related to the insert action, something like ‘shape.insert’, or ‘elements.move.insert’, so that I can add a rule over it.

I looked in spec, test, and in the custom rules example but could not find it.

Aside from this problem, is there a place where I can see a full list of those magic words and what the are for ? Generally, I don’t know where to look when I search through the test documentation and the source code itself. Maybe I’m not looking in the right places ?

Thank you for your time. :sunny:

The simplest solution is to disallow that modeling operation:

class MyBehavior {
  constructor(eventBus) {
    eventBus.on([
      'commandStack.shape.create.canExecute',
      'commandStack.elements.move.canExecute'
    ], 10000, ({ context }) => {
      const { target } = context;

      if (target.type === 'bpmn:SequenceFlow') {
        
        // disallow inserting into flow
        return false;
      }
    });
  }
}

MyBehavior.$inject = [ 'eventBus' ];

var bpmnModeler = new BpmnJS({
  // ...
  additionalModules: [{
    __init__: [ 'myBehavior'],
    myBehavior: [ 'type', MyBehavior ]
  }]
});

Another way would be using a RuleProvider. Ultimately it will do the same as the example above.

1 Like

Thanks for your answer philipp. This sort of works, but there is a problem.

I actually did try with a RuleProvider before posting my issue. I wrote a similar custom rule on ‘elements.move’ and eventually I had the same result as when trying out your solution. The cause also appears to be the same, here’s what happened in both cases:

The rule “works” because it does prevent the action when the cursor (and not the grabbed element) hovers on the connector, but you can insert the shape anyway by avoiding the connector with your cursor, while still placing the shape over it.

Grabbing the task by the center and dragging it over another connector :
custom%20rule%202-2

vs. Grabbing it by the corner and avoiding touching the connector with the cursor :
custom%20rule%201-2

The value printed below is context.target, in eventBus.on.

My understanding of it:

When performing the action, that value is set to the Connection element in question only when your cursor is positioned exactly over it. However because you can grab a shape without placing your cursor in the center of it, and also because of snapping, target is set to the parent element as long as your cursor doesn’t touch the connector, so the rule does not catch it.

I hope this helps you in case something is not working as intended. :crossed_fingers:

Thanks again

EDIT: My screencaps were way too small

I have found a workaround to my problem, but it’s cheating. :see_no_evil:

I could not fix the issue I described, with the action being feasible by avoiding the connector with the cursor. But while looking into other things, I found that I could listen to the ‘commandStack.changed’ event, which triggers when any action is performed. Then, I dig into the commandStack itself directly and find the last action executed with the following code :

modeler.on('commandStack.changed', 2000, () => {
    let lastCommand = commandStack._stack[commandStack._stack.length - 1]
    if (lastCommand && lastCommand.id) {
        let lastOperations = commandStack._stack.filter(com => com.id === lastCommand.id)
        // now I can inspect lastOperations to know exactly what the last action did
        // if it contains some operation that I don't want to see happen,
        // , I can just undo it by calling 'commandStack.undo()'
    }
})

In the case of inserting shape into a connector, printing lastOperations looks like this :

lastOp

We can see that there is a shape being moved, a connector being reconnected, a connector being created, one being deleted, etc. In my case, I actually don’t want any action to reconnect connectors. So just for the example, I do this :

        if (lastOperations.some(operation => operation.command.includes('.reconnectEnd')))
            commandStack.undo()

I know this really looks like bad practice. :warning: The dev team will confirm whether it is or not, but at least it worked for me so I’m posting it if it can help anyone. Also to tell @philippfromme that the issue is less important for me now that I have a solution, albeit a cheaty one. :crazy_face:

Don’t hesitate to contribute with a better solution, (that would help me a lot too), or to tell me about the status of the issue discussed before this post.

import RuleProvider from "diagram-js/lib/features/rules/RuleProvider";
class MyRuleProvider extends RuleProvider {
  constructor(bpmnRules, eventBus) {
    super(eventBus);
    bpmnRules.canInsert = function() {
      return false;
    }
  }
}

MyRuleProvider.$inject = ["bpmnRules", "eventBus"];

export default {
  __init__: ["myRuleProvider"],
  myRuleProvider: ["type", MyRuleProvider]
};

Have the same problem as well as you, my solution is adding annother custom ruleProvider,it’s provide no rules but can instead of the default canInsert method.You can try!