Questions about module system and architecture of Camunda Modeler


#1

I’m writing an extension for Camunda Modeler and along the way I try to get a better understanding of the architecture.

I already know the walkthrough which helped a lot, but is missing some pieces.

I know, that my own modules can access existing objects through names. (e.g. elementRegistry or eventBus).

  • Is this realized through ‘didi’ ? (I don’t quite get, which part of the code interprets the init part of module.exports. Any hint is greatly appreciated.)
  • Is there any easy way to know what objects can be accessed through which name and at what point in time? Other than reading all the code?

And two more concrete problems:

  • Is there a way to access the pure xml of the model through an existing module?
    ’ I did not find any way to access the diagram ID in the bpmn:definitions tag through any of the existing modules (elementRegistry or moddle). What did I overlook?

Thanks for any help.


#2

Hey,

I know that the walkthrough might raise more questions than it answers.

Under the hood bpmn-js which is built on top of diagram-js uses didi for dependency injection. So things like myModule: [ 'type', MyModuleContructor ] are handled by didi. The __init__ and __depends__ properties you saw are something that is not part of didi but is handled by diagram-js when the instance is created. You can see it in action here.

The __init__ property specifies any modules you want to instantiate. This makes sure that even if no other module asks for this component this component will be instantiated and can hook into the applications life cycle. Most of the modules in bpmn-js are instantiated like that and usually you’d want to instantiate your module, too.

The __depends__ property specifies which modules a module depends on. Any modules that are specified as dependencies will be made available so that they can be injected into the module that depends on them.

Unfortunately there is no list of all modules you can inject into your module. :confused:

To answer your last question:

Yes, you can access the XML of your diagram by either taking the XML you imported into bpmn-js in the first place or by injecting the instance of bpmn-js itself which is available like any other module:

function MyModule(bpmnjs) {
  bpmnjs.saveXML(function(err, xml) {
    // there is your XML
  });
}

MyModule.$inject = [ 'bpmnjs' ];

Can you give more context about what you want to do with the XML? If you only want to get the ID of the process you can do that easily by getting the root element from the canvas, which is another module you can inject:

function MyModule(canvas) {
  var root = canvas.getRootElement();

  var businessObject = root.businessObject;

  var id = businessObject.id; // there is your ID
}

MyModule.$inject = [ 'canvas' ];

I hope I could help you.


#3

Very cool. This helps a lot. Thank you!

What I want to do is in some way quite similar to what the comment extension for bpmn-js does. I want to parse the XML and annotate certain shapes with certain predefined Q&A-Lists, that can be edited in a popup frame. It seems that most of it could be done with Overlays, but I’m not yet familiar enough with it, to really tell.
The ID of Model would come in for the second task. I need to save the edited Q&A-Lists for different Models in a central database (probably a JSON file at first). To connect the Q&A-lists with the diagrams a need an ID. To get it through the canvas root element is fine. I’m still not quite sure about the parsing and the annotation part. Whether to store data only in the database or in the XML and the database.


#4

Hi again.
I just tried to get the ID for my diagram with
var root = canvas.getRootElement();
etc.
But I get an error:
index.js:14489 Uncaught TypeError: bo.get is not a function
Which sounds like a but to me. Should I report it or am I doing it wrong?


#5

I appreciate pointers how to improve it :wink:.


#6

There is no bug, this definitely works. Can you share the exact code you’re executing?


#7

Sure I can provide a bit more context. As you might see, this is still a mess, because I’m trying a lot of things.

function ActivatePQCPalette(canvas, create, eventBus, palette, elementFactory, overlays, moddle, bpmnjs, elementRegistry, toolManager, editPQCTool) {
  this._create = create;
  this._elemntFactory = elementFactory;
  this._editPQCTool = editPQCTool;
  palette.registerProvider(this);

  var root = canvas.getRootElement();
  var businessObject = root.businessObject;
  var id = businessObject.id;
  console.log(id);

  eventBus.on('shape.added', function(event){
    var ev_element = event.element;
    
    if(ev_element.type === 'bpmn:EndEvent'){
      overlays.add( ev_element.id,{
        position:{
          bottom: 0,
          right: 0
        },
        html: ActivatePQCPalette.ACCORDION_HTML
      });
    }
  });

  console.log(palette);
  
  eventBus.on('diagram.init', function(event){
    console.log(event);
  });
}

ActivatePQCPalette.$inject = ['canvas', 'create', 'eventBus', 'palette', 'elementFactory', 'overlays', 'moddle', 'bpmnjs', 'elementRegistry', 'toolManager', 'editPQCTool'];

The error message I get is:

unhandled error in event listener                                         index.js:103595
TypeError: bo.get is not a function                                        index.js:103596
    at getTemplateId (index.js:14489)
    at getTemplateOptions (index.js:15512)
    at module.exports (index.js:15458)
    at createGeneralTabGroups (index.js:13627)
    at CamundaPropertiesProvider.getTabs (index.js:13871)
    at PropertiesPanel.update (index.js:8913)
    at index.js:8743
    at invokeFunction (index.js:103679)
    at EventBus._invokeListener (index.js:103581)
    at EventBus._invokeListeners (index.js:103569)

If this works I’m still not sure if this is the ID I’m looking for. Because this would be the ID of the RootElement. I’m looking for the ID in the XML Header.

I’m seeing this on Camunda Modeler Nightly from 2 days ago.

One more question:
I’m seeing no difference between using the line

ActivatePQCPalette.$inject = ['canvas', 'create', 'eventBus', 'palette', 'elementFactory', 'overlays', 'moddle', 'bpmnjs', 'elementRegistry', 'toolManager', 'editPQCTool'];

and not using this line in my extension. I can simply pass the names of these modules in my function and it works without the $inject part. Should I use it or leave it?

Thanks for all your help by the way. I’m very glad about it.


#8

I’ll take notes while writing my extension and try to help with my experiences on the way.


#9

The error acutally occurs when updating the properties panel in order to show available element templates for the currently selected element. Did you do anything with element templates or introduce custom elements?

Regarding your question wether or not to set the $inject property. You MUST set this property, the sequence of the modules listed there must correspond with the sequence of parameters of the constructor function.


#10

No. So far I only added a new tool to the tool palette and fooled around a bit with overlays. But I didn’t do anything with element templates and didn’t introduce any custom elements. If you need to see all the code, I can upload it later. (Didn’t do it so far, because it is very ugly)


#11

If it is required to set the $inject property, I would recommend this for the module part of the documentation. Many extension examples currently don’t set this property and if you don’t dive deeper into the code, you can actually miss it.


#12

Your’re right. The reason it might still work is that didi attempts to automatically figure out what to inject if the $inject property wasn’t set. This will fail as soon as you minify the code for production use.


#13

To close up this thread and as a hint for others, who are looking for the diagram-ID that is stored in the XML definitions tag:
The most convenient way to get the ID for me was:

function MyModule(bpmnjs) {
    // getDefinitions() will only return a result, if the loading of the diagram is finished
    // Todo: Need to figure out the exact point in time, when the definitons are available
    var id = bpmnjs.getDefinitions().id;
}

MyModule.$inject = ['bpmnjs']

For context: The name ‘bpmnjs’ gives you an instance of the Viewer object


#14

You may listen to import events to figure out when a diagram got imported.

To quote from the Viewer#importXML documentation:

During import the viewer will fire life-cycle events:

   * import.parse.start (about to read model from xml)
   * import.parse.complete (model read; may have worked or not)
   * import.render.start (graphical import start)
   * import.render.complete (graphical import finished)
   * import.done (everything done)

#15

Very nice. Thanks for pointing this out. I missed it :see_no_evil: