Customization options for dmn-js Modeler

We are currently integrating the dmn-js Modeler into our application as a way of allowing the user to define very flexible system configurations.
By convention we expect certain outputs (identified by their name and type) and provide a number of input variables the user can access.

To improve the overall usability in those scenarios, we intend to do the following:

  1. Disable adding a new output column.
  2. Disable removing an existing output column.
  3. Disable changing an output column’s output name.
  4. Disable changing an output column’s type.
  5. Offer additional input column types in the respective dropdowns (to match the ones we added to the DMN engine’s configuration).
  6. Limit the available hit policies (including a subset of the Collect policy).
  7. Limit the available script languages for input expressions (to match the ones we actually provide on the classpath).

As I understand it, all this should theoretically be possible already.
Unfortunately the only documentation on this topic I could find was the comment on the Modeler.js file (on GitHub):

Extending the Modeler

In order to extend the viewer pass extension modules to bootstrap via the additionalModules option.
An extension module is an object that exposes named services.

The following example depicts the integration of a simple logging component that integrates with interaction events:

// logging component
function InteractionLogger(eventBus) {
  eventBus.on('element.hover', function(event) {
    console.log()
  })
}

InteractionLogger.$inject = [ ‘eventBus’ ]; // minification save

// extension module
var extensionModule = {
init: [ ‘interactionLogger’ ],
interactionLogger: [ ‘type’, InteractionLogger ]
};

// extend the viewer
var dmnModeler = new Modeler({ additionalModules: [ extensionModule ] });
dmnModeler.importXML(…);

Customizing / Replacing Components

You can replace individual table components by redefining them in override modules.
This works for all components, including those defined in the core.

Pass in override modules via the options.additionalModules flag like this:

function CustomContextPadProvider(contextPad) {

contextPad.registerProvider(this);

this.getContextPadEntries = function(element) {
// no entries, effectively disable the context pad
return {};
};
}

CustomContextPadProvider.$inject = [ ‘contextPad’ ];

var overrideModule = {
contextPadProvider: [ ‘type’, CustomContextPadProvider ]
};

var dmnModeler = new Modeler({ additionalModules: [ overrideModule ]});

Are there any more examples or a more specific documentation on customization (rather than just replacing components) that might help us achieve those mentioned changes?

Or do we really have to copy-paste the original components, perform the (minor) changes, and then override the original ones via the additionalModules parameter?

Ideally I’d expect a second (optional) parameter on the Modeler function such as an options.configuration array which would be forwarded to all core components. The respective core components could then check for specific keys in that array, e.g. configuration['input-script-languages'] holding a list of supported script languages.
Over time, the core components could be updated (via multiple pull requests) to become more and more configurable without the need for any copy-paste-change-replace stunts (especially if multiple versions of the same component would be required, e.g. in case of meaningful hit policies).

That’s the idea. The JSON object containing the additionalModules property is available as injectible service. Camunda BPM uses this for the dmn-js viewer widget, when the default behavior for the technical details is defined: camunda-commons-ui/lib/widgets/dmn-viewer/cam-widget-dmn-viewer.js at master · camunda/camunda-commons-ui · GitHub

The corresponding implementation to react to the hideDetails property is done in dmn-js: https://github.com/bpmn-io/dmn-js/blob/master/lib/features/hide-tech-control/HideTechControl.js#L20-L22

So the only thing missing would be to support configuration parameters like inputScriptLanguages in the core modules. If you want to, you can create pull requests for them :slight_smile:

Cheers
Sebastian

1 Like

Just to make sure I understand this correctly.

An example for the restriction of the offered script languages would be to instantiate the editor with an additional parameter (here containing JavaScript as single option):

var dmnModeler = new Modeler({ inputScriptLanguages: [ 'JavaScript' ]});

Changing the MappingRow.js core component would include:

  1. the addition of the 'config' injection on line 259

     MappingsRow.$inject = [ 'eventBus', 'sheet', 'elementRegistry', 'graphicsFactory', 'complexCell', 'rules', 'config' ];
    
  2. adding the config parameter to the MappingsRow function (on line 13)

     function MappingsRow(eventBus, sheet, elementRegistry, graphicsFactory, complexCell, rules, config) {
    
  3. applying the (optional) parameter value as options to the dropdown in question (on lines 128-134)

     var languageOptions = ['JavaScript', 'Groovy', 'Python', 'Ruby'];
     if (config.inputScriptLanguages) {
       languageOptions = config.inputScriptLanguages;
     }
     // initializing the comboBox
     var comboBox = new ComboBox({
       label: 'Language',
       classNames: ['dmn-combobox', 'language'],
       options: languageOptions,
       dropdownClassNames: ['dmn-combobox-suggestions']
     });
    

And that should be it, right?
Then just test it, (potentially) create a unit test, create the pull request.
… and repeat for the other configurations.

That’s it, yes. Just make sure that JavaScript is actually a string when instantiating the Modeler :wink:

Fixed that :wink:
Thanks!