ProcessMaker BPMN modeler
@processmaker/modeler
is a Vue.js based BPMN modeler scaffolded using Vue CLI 3.
Project setup
Clone the repository and cd
into the modeler
directory:
git clone git@github.com:ProcessMaker/modeler.git
cd modeler
You can now run any of the below commands. Most commands make calls vue-cli-service
. You can read more about those commands at https://cli.vuejs.org/guide/cli-service.html#cli-service.
npm run serve
npm run build
npm run build-bundle
npm run lint
Testing
Tests are set up using Cypress. For more information, visit https://cli.vuejs.org/config/#cypress. Tests can be run locally with the following commands:
npm test
npm run test-ci
Tests are run automatically on CircleCI when new branches are created and updated. The CI uses the npm run test-ci
command to run tests.
Architecture
The entry point for the application is src/main.js
; this is the "starting point" which is used when running npm run serve
or npm run build
.
Global event bus
window.ProcessMaker.EventBus
points to an independent Vue instance which acts as the application's global event bus. The modeler currently emits two events on the event bus which can be listened to in application code to hook into, customize, and extend the modeler's behaviour: modeler-init
, and modeler-start
.
modeler-init
This event is fired before the modeler and BpmnModdle are set up and mounted. Listeners to this event are passed an object with three methods: registerInspectorExtension
, registerBpmnExtension
, and registerNode
.
import bpmnExtension from '@processmaker/processmaker-bpmn-moddle/resources/processmaker.json';
import { intermediateMessageCatchEvent } from '@/components/nodes';
window.ProcessMaker.EventBus.$on('modeler-init', ({ registerBpmnExtension, registerNode, registerInspectorExtension }) => {
registerBpmnExtension('pm', bpmnExtension);
registerNode(intermediateMessageCatchEvent);
registerInspectorExtension(intermediateMessageCatchEvent, {
id: 'pm-condition',
component: 'FormInput',
config: {
label: 'Condition',
helper: 'Expression to be evaluated on webhook to activate event. Leave blank to accept always.',
name: 'pm:condition',
},
});
});
modeler-start
This event is fired after the modeler has been set up and mounted. Listeners to this event are passed an object with a single method, loadXML
.
window.ProcessMaker.EventBus.$on('modeler-start', ({ loadXML }) => {
loadXML('<?xml version="1.0" encoding="UTF-8"?> ... </bpmn:definitions>');
});
For the modeler to function correctly, loadXML
must be called when the application loads.
modeler-validate
This event is fired during validation, and can be used to add custom validation rules. See Adding validation rules during runtime.
modeler-change
This event is fired anytime a change is made to the modeler that causes the underlying XML to change. This event is fired immediately after new state is pushed to the undo/redo stack.
window.ProcessMaker.EventBus.$on('modeler-change', () => {
console.log('The diagram has changed');
});
Undo/redo store
The undo/redo feature is implemented using Vuex, with the undo/redo Vuex store initialized in src/undoRedoStore.js
. The undo/redo store keeps track of every change in the underlying BPMN XML, recording a copy of the XML string in a stack. Traversing the undo/redo stack simply uses the loadXML
function to load the XML string from the current position in the stack.
Validation
Adding a new lint rule
By default, the modeler automatically validates your diagram as you create it. This validation can be toggled on and off using the switch in the status bar. Validation is handled using https://github.com/bpmn-io/bpmnlint.
To add a new validation rule, create a new file in processmaker-plugin/rules
named after your rule, for example, node-id.js
. This file should export a function that returns an object with a check
method. The check
method will receive two arguments—node
and reporter
—and must return undefined
if validation passes, or reporter.report
to raise an error. For exmaple:
module.exports = function() {
function check(node, reporter) {
if (typeof node.id === 'string' && !node.id.startsWith('node_')) {
reporter.report(node.id, 'Node ID must start with the string "node_"');
}
}
return { check };
};
When you are done writing the rule, add it to processmaker-plugin/index.js
:
module.exports = {
configs: {
all: {
rules: {
'processmaker/custom-validation': 'error',
'processmaker/gateway-direction': 'error',
'processmaker/node-id': 'error',
},
},
},
};
For more examples, see the list of default rules at https://github.com/bpmn-io/bpmnlint/tree/master/rules.
Adding validation rules during runtime
To add custom validation when the liner runs, use the global event bus:
window.ProcessMaker.EventBus.$on('modeler-validate', (node, reporter) => {
if (typeof node.id === 'string' && !node.id.startsWith('node_')) {
reporter.report(node.id, 'Node ID must start with the string "node_"');
}
});
Examples
Adding a new component
Creating a new modeler component requires creating a Vue component, a component config, and calling the registerNode
method to register your component.
First, create your component config, and save it in a .js
file:
export default {
id: 'unique-custom-component-id',
component: CustomComponent,
bpmnType: 'custom-namespace:CustomComponent',
control: true,
category: 'BPMN',
icon: require('@/assets/toolpanel/scriptTask.svg'),
label: 'Script Task',
definition(moddle) {
return moddle.create('bpmn:ScriptTask', {
name: 'New Script Task',
});
},
diagram(moddle) {
return moddle.create('bpmndi:BPMNShape', {
bounds: moddle.create('dc:Bounds', {
height: taskHeight,
width: 116,
}),
});
},
inspectorConfig: [
{
name: 'CustomComponent',
items: [
{
component: 'FormText',
config: {
label: 'Custom Component Label',
fontSize: '2em',
},
},
],
},
],
};
Then, create a Vue component for your custom element:
// CustomComponent.vue
<template>
<div />
</template>
<script>
import Task from '@/components/nodes/task/task';
export default {
extends: Task,
mounted() {
// Do things with this.shape
},
};
</script>
Finally, register your custom component:
registerNode(CustomComponentConfig);