Add a custom rule to cancel a connection based on a source and target Ids condition

Continuing the discussion from Target and source elements are null in a "connect.end" listener:

This has been bothering me for a while, as the title says, I’m trying to add a custom rules that cancels a connection creation based on a condition that depends of the source and target Id.
I tried a lot of listeners but this is as close as I got:

  • This is what I initially tried using connect.end, I could only get the source id but not the targets and I was able to cancel the creation of the connection by returning false with this listener:
eventBus.on('connect.end',2000, function(event) {
      console.log(event);
      let context=event.context;
      if (context.source.id===context.target.id || !self.checkConnection(context)){
        return false;
      }
    });
  • Then I was advised to not use this listener by Niklas_Kiefer but use commandStack.connection.create.postExecute event instead. I was able to get the source and target id with this event but I was not able to cancel the connection by returning false.
eventBus.on("commandStack.connection.create.postExecuted", function (
      event
    ) {
      const context = event.context;

      const source = context.source;

      const target = context.target;

      console.log("Source:", source);

      console.log("Target:", target);
       if (
        context.source.id === context.target.id || !self.checkConnection(context) /*another condition*/
       ) {
      return false;
       }
    });

Is there an event that allows me to get both source and target Id and still be able to cancel the connection from being madeb ased on them?

codesandbox link.

A detail I forgot to mention, I also tried using this rule in my customrulesprovider

this.addRule("connection.create", 2000, function (context) {
    console.log(context);
    return false;
  });

with this rule which provides what I need (source and target ids + ability to cancel the connection) the only reason I didnt go with it and I went for eventBus listeners instead is that I need to get the xml representation which I will use to set my condition.

And since I’m getting the xml representation like this I need my modeler instance to get the xml representation from:

const result = await this.bpmnJS.saveXML();
const { xml } = result;

So If I’m able to get the xml representation inside the customrulesprovider I guess that could do it as well.
Another codesandboxcode for the second method.

The proper way of doing this is using a rule: https://codesandbox.io/s/custom-connect-rules-example-635nb

Returning false from an event listener will not cancel the operation that fired that event.

I don’t understand why you need the exported XML to evaluate the rule. The XML wouldn’t contain any information that isn’t available at runtime.

1 Like

Thank you for the reply.

I feel like it’s a bit hard to explain but I’ll try my best:
Since I’m trying to allow only “Directed acyclic graph” I’m trying to restrict the creation of connections to follow the rules of such a graph.
For example with this following diagram:
image
Tasks 1 and 2 are what I would call level 1 tasks because they are targets of the start element,
Tasks 3 and 4 are then level 2 tasks because they are targets of level 1 tasks.
And so on…

Now the rule that I’m trying to add is to prevent adding any connection from a level x element(or task) to a level y element where y<=x , like in this picture (marked red are the ones that are invalid) so the diagram’s global direction goes in one way only.
image
The way I was hoping to do this is to add a custom rule when I I try to add a connection that gives me the source and target ids and get all the connections from my xml (which will tell me what level each task is) since I figured I could use this to get all the current connections created (the only way I know how to):

try {
      const result = await this.bpmnJS.saveXML();
      const { xml } = result;
      let domparser = this.domparser.parseFromString(xml, 'text/xml');
      let elements= domparser.getElementsByTagName('bpmn:sequenceFlow');//all connections
    } catch (err) {
      console.log(err);
    }

And then once I have all the connections that are already created, alongside the source and target Ids of the connection I’m trying to create that I’m getting from the customrule I can check whether the connection is valid, and if it’s not valid, the connection shouldnt be allowed therefore canceled.

That is why I said “If I’m able to get the xml representation inside the customrulesprovider I guess that could do it as well” it’s basically just to get all the connections already created in the diagram (their sources and targets).

I hope I was somewhat clear.

I learned a bit about the elementRegistry, and there is something I can’t figure out.
Inside the rule in the codesandbox you kindly provided, is it possible to get the elementRegistry(so I could call elementRegistry.getAll ad I would not have to use xml at all), I already tried elementRegistry and was able to get all the connections (and their respective source and target ids through businessObject) but I used my modeler instance to get the elementRegistry: this.elementRegistry=this.bpmnJS.get('elementRegistry';
And I don’t know if it’s possible or how to get the elementRegistry inside a custom rule
My project’s customruleprovider.

Are you familiar with how our module system is working? You can inject any provided module into your custom one

export default function CustomRules(eventBus, elementRegistry) {
  RuleProvider.call(this, eventBus);

  this._elementRegistry = elementRegistry;

  // now use it in your module, e.g. via this._elementRegistry.getAll();
}

// ...

CustomRules.$inject = ['eventBus', 'elementRegistry'];
1 Like

I did read the walkthrough at the beginning but I don’t think I got much out of it at the time, now that I’m a little more familiar with bpmn I will give it a good second thorough read.

Thanks to your help I was able to use elementRegistry inside a rule like so:

export default function CustomRules(eventBus, elementRegistry) {
  RuleProvider.call(this, eventBus);
  this._elementRegistry = elementRegistry;
}
..
CustomRules.$inject = ['eventBus', 'elementRegistry'];

CustomRules.prototype.init = function () {
  let self = this;
  this.addRule("connection.create", 2000, function (context) {
    console.log(context);
    console.log(self._elementRegistry.getAll());
    return false;
  });
}
1 Like

We’re happy to help :+1:

I see you currently working hard with our toolkits. Maybe you can share what you’ve built once you’re done? We are always interested to see what people are creating out of bpmn.io :slightly_smiling_face:

1 Like

Yeah, I don’t really mind it but it’s nothing really that interesting or complex compared to what I have seen in the forum. I’m just doing simple customizations on bpmn (my first topic sums them up if you recall it) and yet as you can see I’m still struggling a bit with them :sweat_smile: .
This is my starter learning project in web development (using angular, nodejs, and even first javascript/typescript project for that matter). The reason why all those rules and those specific graph restrictions is to simulate Apache-airflow’s dags to later create them through the application.

1 Like