Develop a modeler plugin to add predefined fields

Hi,
I am trying to create a plugin to listen for changes in UserTask and add predefiend key-value pair in a map type input.

I already achieved adding new fields to map with below code.

However, the fields added by the plugin are not displayed until I do another action on the modeler like clicking on another button.

The issue is that fields are added but proeprties panel is not rerendered.

I tried to force refresh with commandStack.changed event, as it is told here, but it didn’t work.

Any help would be appreciated.

Thanks.

class MyCustomPlugin {
	
	addIfNotExist (definition, varName, varDefault){
		if(!definition.entries)
			definition.entries = [];
		
		var entryExists = definition.entries.find( (item) => {return item.key === varName});
		
		if( !entryExists){
			var moddleParams = {key: varName, $body: varDefault};
			var newParameter = this.moddle.createAny('camunda:entry', 'http://camunda.org/schema/1.0/bpmn', moddleParams);
			definition.get("entries").push(newParameter);
		}
	};
	
	getDoParameter(element){
		try{
			if(element.type === "bpmn:UserTask"){

				var extensions = element.businessObject.extensionElements;
				if(extensions && extensions.values){					
					var inputOutputs = extensions.values.find((v) => { return v.$type === "camunda:InputOutput"; });

					if(inputOutputs && inputOutputs.inputParameters){
						var inputParameters = inputOutputs.inputParameters;
						
						var doParameter = inputParameters.find((i) => {return i.name === "DO"});
						if(doParameter && doParameter.definition && doParameter.definition.$type === "camunda:Map"){
							return doParameter;
						}
					}
				}
			}
		}catch(e){
			console.log(e);
		}
		return null;
	}

	constructor(eventBus, moddle) {
		this.eventBus = eventBus;
		this.moddle = moddle;

		eventBus.on('propertiesPanel.changed', (event) => {
			try{
				var currentElement = event.current.element;
				var doParameter = this.getDoParameter(currentElement);
				if(doParameter){
					// this is where I add new fields to map and fire the event
					this.addIfNotExist(doParameter.definition, "isNextStepAsync", "false");
					this.addIfNotExist(doParameter.definition, "textMessage", "");
					this.addIfNotExist(doParameter.definition, "pageHeader", "");
					this.addIfNotExist(doParameter.definition, "textType", "");
					this.eventBus.fire('commandStack.changed', {element: currentElement});
				}
			}catch(e){
				console.log(e);
			}
		});
	}
}

MyCustomPlugin.$inject = [ 'eventBus' , 'moddle', 'canvas', 'commandStack']; 

export default {
  __init__: [ 'MyCustomPlugin' ],
  MyCustomPlugin: [ 'type', MyCustomPlugin ]
};

1 Like

I just managed to refresh the view by using the updateProperties in Modeling.js in bpmn.

First I tried to initiate a new Modeling object by injecting the correct modules to it, but I got some other errors. So I decided to copy the content of updateProperties method into my class and that worked just fine.

Updating the component triggered another event of same type and it went into a loop. So I used a boolean flag to detect the loop and break it.

Final code is as below, I hope someone else can make use of it.

class AddFieldsPlugin {
	
	/*
	*	refresh the modeler view, same code inside Modeler.js
	* 	https://github.com/bpmn-io/bpmn-js/blob/develop/lib/features/modeling/Modeling.js
	*/
	updateProperties(element, properties){
		
		this.commandStack.execute('element.updateProperties', {
			element: element,
			properties: properties
		  });
	}
	
	/*
	* 	create new map entry with parameters and push it into array
	*/
	addIfNotExist (definition, varName, varDefault){
		if(!definition.entries)
			definition.entries = [];
		
		var entryExists = definition.entries.find( (item) => {return item.key === varName});
		
		if( !entryExists){
			var moddleParams = {key: varName, $body: varDefault};
			var newParameter = this.moddle.createAny('camunda:entry', 'http://camunda.org/schema/1.0/bpmn', moddleParams);
			definition.get("entries").push(newParameter);
			
		}
	};
	
	/*
	* 	This method returns the inputParameter if its name is DO" and is of type Map
	*/
	getDoParameter(element){
		try{
			if(element.type === "bpmn:UserTask"){

				var extensions = element.businessObject.extensionElements;
				if(extensions && extensions.values){					
					var inputOutputs = extensions.values.find((v) => { return v.$type === "camunda:InputOutput"; });

					if(inputOutputs && inputOutputs.inputParameters){
						var inputParameters = inputOutputs.inputParameters;
						
						var doParameter = inputParameters.find((i) => {return i.name === "DO"});
						if(doParameter && doParameter.definition && doParameter.definition.$type === "camunda:Map"){
							return doParameter;
						}
					}
				}
			}
		}catch(e){
			console.log(e);
		}
		return null;
	}

  constructor(eventBus, moddle, commandStack) {
	
	this.eventBus = eventBus;
	this.moddle = moddle;
	this.commandStack = commandStack;
	
	this.isUpdated = false;
	
	// catch change event on properties panel
	eventBus.on('propertiesPanel.changed', (event) => {
		
		// if this event fired after an update this handler, do not run it again
		if(this.isUpdated){
			this.isUpdated = false;
			return;
		}
		
		try{
			var currentElement = event.current.element;
			var doParameter = this.getDoParameter(currentElement);
			if(doParameter){
				this.addIfNotExist(doParameter.definition, "isNextStepAsync", "false");
				this.addIfNotExist(doParameter.definition, "textMessage", "");
				this.addIfNotExist(doParameter.definition, "pageHeader", "");
				this.addIfNotExist(doParameter.definition, "textType", "");
				
				// update properties method fires another event so it gets into a loop
				// to break from the loop, use a variable
				this.isUpdated = true;
			
				// extensionElements has new fiels already, just call updateProperties to refresh the view
				var extElements = currentElement.businessObject.extensionElements;
				this.updateProperties(currentElement, {
					extensionElements: extElements
				});
			}
		}catch(e){
			console.log(e);
		}
    });
  }
}

AddFieldsPlugin.$inject = [ 'eventBus' , 'moddle', 'commandStack']; 

export default {
  __init__: [ 'AddFieldsPlugin' ],
  AddFieldsPlugin: [ 'type', AddFieldsPlugin ]
};
3 Likes

Thanks a lot for posting y our solution!
This is a pretty common requirement so I’m sure this is going to help some people.