├── .babelrc ├── .eslintignore ├── .gitignore ├── .npmrc ├── README.md ├── app ├── app.js ├── css │ └── app.css ├── custom │ ├── CustomContextPad.js │ ├── CustomPalette.js │ └── index.js └── index.html ├── docs └── screenshot.png ├── package.json ├── resources └── diagram.bpmn └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ "env" ] 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | public/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/ -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > This example is part of our [:notebook: custom elements guide](https://github.com/bpmn-io/bpmn-js-examples/tree/master/custom-elements). Checkout the final result [here](https://github.com/bpmn-io/bpmn-js-example-custom-elements). 2 | 3 | 4 | # bpmn-js Example: Custom Controls 5 | 6 | An example of creating custom editing controls for [bpmn-js](https://github.com/bpmn-io/bpmn-js). Use this blueprint to suite the BPMN modeler to your specific needs. 7 | 8 | 9 | ## About 10 | 11 | This example adds controls that allow you to create `bpmn:ServiceTask` elements through the palette and the context pad. 12 | 13 | ![Screencast](docs/screenshot.png) 14 | 15 | ### Creating Custom Controls 16 | 17 | First, let's add the ability to create `bpmn:ServiceTask` elements through the [palette](https://github.com/bpmn-io/diagram-js/blob/master/lib/features/palette/Palette.js). We'll create a new module that will register itself with the palette as a provider for entries. We don't need to override the existing provider since we want to keep the existing entries. Our provider implements a `getPaletteEntries` method that returns a single entry: 18 | 19 | ```javascript 20 | getPaletteEntries(element) { 21 | const { 22 | create, 23 | elementFactory, 24 | translate 25 | } = this; 26 | 27 | function createServiceTask(event) { 28 | const shape = elementFactory.createShape({ type: 'bpmn:ServiceTask' }); 29 | 30 | create.start(event, shape); 31 | } 32 | 33 | return { 34 | 'create.service-task': { 35 | group: 'activity', 36 | className: 'bpmn-icon-service-task', 37 | title: translate('Create ServiceTask'), 38 | action: { 39 | dragstart: createServiceTask, 40 | click: createServiceTask 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | You can use the `group` property to group multiple entries. In this case, we're adding our entry to an existing group. Using the `className` property, you can style your entry through CSS. We're using a class that is part of [bpmn-font](https://github.com/bpmn-io/bpmn-font), so there will be a service task icon in the palette. Using the `action` property, we decide what happens when the user clicks or drags the icon. 48 | 49 | See the entire palette [here](app/custom/CustomPalette.js). 50 | 51 | Now, let's also add the ability to create `bpmn:ServiceTask` elements through the [context-pad](https://github.com/bpmn-io/diagram-js/blob/master/lib/features/context-pad/ContextPad.js): 52 | 53 | ```javascript 54 | 'append.service-task': { 55 | group: 'model', 56 | className: 'bpmn-icon-service-task', 57 | title: translate('Append ServiceTask'), 58 | action: { 59 | click: appendServiceTask, 60 | dragstart: appendServiceTaskStart 61 | } 62 | } 63 | ``` 64 | 65 | See the entire context pad [here](app/custom/CustomContextPad.js). 66 | 67 | Next, let's add our custom controls to bpmn-js. 68 | 69 | ### Adding the Custom Controls to bpmn-js 70 | 71 | When creating a new instance of bpmn-js we need to add our custom controls using the `additionalModules` property: 72 | 73 | ```javascript 74 | import BpmnModeler from 'bpmn-js/lib/Modeler'; 75 | 76 | import customControlsModule from './custom'; 77 | 78 | const bpmnModeler = new BpmnModeler({ 79 | additionalModules: [ 80 | customControlsModule 81 | ] 82 | }); 83 | ``` 84 | 85 | Our custom controls will now be added to bpmn-js. 86 | 87 | ## Run the Example 88 | 89 | You need a [NodeJS](http://nodejs.org) development stack with [npm](https://npmjs.org) installed to build the project. 90 | 91 | To install all project dependencies execute 92 | 93 | ```sh 94 | npm install 95 | ``` 96 | 97 | To start the example execute 98 | 99 | ```sh 100 | npm start 101 | ``` 102 | 103 | To build the example into the `public` folder execute 104 | 105 | ```sh 106 | npm run all 107 | ``` 108 | 109 | 110 | ## License 111 | 112 | MIT 113 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import BpmnModeler from 'bpmn-js/lib/Modeler'; 2 | 3 | import customControlsModule from './custom'; 4 | 5 | import diagramXML from '../resources/diagram.bpmn'; 6 | 7 | const containerEl = document.getElementById('container'); 8 | 9 | // create modeler 10 | const bpmnModeler = new BpmnModeler({ 11 | container: containerEl, 12 | additionalModules: [ 13 | customControlsModule 14 | ] 15 | }); 16 | 17 | // import XML 18 | bpmnModeler.importXML(diagramXML, (err) => { 19 | if (err) { 20 | console.error(err); 21 | } 22 | }); -------------------------------------------------------------------------------- /app/css/app.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | outline: none; 5 | padding: 0; 6 | } 7 | 8 | html, body, #container { 9 | height: 100%; 10 | } -------------------------------------------------------------------------------- /app/custom/CustomContextPad.js: -------------------------------------------------------------------------------- 1 | export default class CustomContextPad { 2 | constructor(config, contextPad, create, elementFactory, injector, translate) { 3 | this.create = create; 4 | this.elementFactory = elementFactory; 5 | this.translate = translate; 6 | 7 | if (config.autoPlace !== false) { 8 | this.autoPlace = injector.get('autoPlace', false); 9 | } 10 | 11 | contextPad.registerProvider(this); 12 | } 13 | 14 | getContextPadEntries(element) { 15 | const { 16 | autoPlace, 17 | create, 18 | elementFactory, 19 | translate 20 | } = this; 21 | 22 | function appendServiceTask(event, element) { 23 | if (autoPlace) { 24 | const shape = elementFactory.createShape({ type: 'bpmn:ServiceTask' }); 25 | 26 | autoPlace.append(element, shape); 27 | } else { 28 | appendServiceTaskStart(event, element); 29 | } 30 | } 31 | 32 | function appendServiceTaskStart(event) { 33 | const shape = elementFactory.createShape({ type: 'bpmn:ServiceTask' }); 34 | 35 | create.start(event, shape, element); 36 | } 37 | 38 | return { 39 | 'append.service-task': { 40 | group: 'model', 41 | className: 'bpmn-icon-service-task', 42 | title: translate('Append ServiceTask'), 43 | action: { 44 | click: appendServiceTask, 45 | dragstart: appendServiceTaskStart 46 | } 47 | } 48 | }; 49 | } 50 | } 51 | 52 | CustomContextPad.$inject = [ 53 | 'config', 54 | 'contextPad', 55 | 'create', 56 | 'elementFactory', 57 | 'injector', 58 | 'translate' 59 | ]; -------------------------------------------------------------------------------- /app/custom/CustomPalette.js: -------------------------------------------------------------------------------- 1 | export default class CustomPalette { 2 | constructor(create, elementFactory, palette, translate) { 3 | this.create = create; 4 | this.elementFactory = elementFactory; 5 | this.translate = translate; 6 | 7 | palette.registerProvider(this); 8 | } 9 | 10 | getPaletteEntries(element) { 11 | const { 12 | create, 13 | elementFactory, 14 | translate 15 | } = this; 16 | 17 | function createServiceTask(event) { 18 | const shape = elementFactory.createShape({ type: 'bpmn:ServiceTask' }); 19 | 20 | create.start(event, shape); 21 | } 22 | 23 | return { 24 | 'create.service-task': { 25 | group: 'activity', 26 | className: 'bpmn-icon-service-task', 27 | title: translate('Create ServiceTask'), 28 | action: { 29 | dragstart: createServiceTask, 30 | click: createServiceTask 31 | } 32 | }, 33 | } 34 | } 35 | } 36 | 37 | CustomPalette.$inject = [ 38 | 'create', 39 | 'elementFactory', 40 | 'palette', 41 | 'translate' 42 | ]; -------------------------------------------------------------------------------- /app/custom/index.js: -------------------------------------------------------------------------------- 1 | import CustomContextPad from './CustomContextPad'; 2 | import CustomPalette from './CustomPalette'; 3 | 4 | export default { 5 | __init__: [ 'customContextPad', 'customPalette' ], 6 | customContextPad: [ 'type', CustomContextPad ], 7 | customPalette: [ 'type', CustomPalette ] 8 | }; -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bpmn-js-example-model-extension 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bpmn-io/bpmn-js-example-custom-controls/feb51c10ce9e5198ba21f834c8bc1a843e98f22d/docs/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bpmn-js-example-custom-rendering", 3 | "version": "0.0.0", 4 | "description": "An example of creating custom rendering for bpmn-js", 5 | "scripts": { 6 | "all": "run-s lint build", 7 | "build": "webpack --mode production", 8 | "dev": "webpack-dev-server --content-base=public --open", 9 | "lint": "eslint .", 10 | "start": "run-s dev" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/bpmn-io/bpmn-js-example-model-extension" 15 | }, 16 | "keywords": [ 17 | "bpmnjs-example" 18 | ], 19 | "author": { 20 | "name": "Philipp Fromme", 21 | "url": "https://github.com/philippfromme" 22 | }, 23 | "contributors": [ 24 | { 25 | "name": "bpmn.io contributors", 26 | "url": "https://github.com/bpmn-io" 27 | } 28 | ], 29 | "license": "MIT", 30 | "devDependencies": { 31 | "copy-webpack-plugin": "^4.6.0", 32 | "eslint": "^5.0.1", 33 | "eslint-plugin-bpmn-io": "^0.5.3", 34 | "npm-run-all": "^4.1.3", 35 | "raw-loader": "^0.5.1", 36 | "webpack": "^4.15.1", 37 | "webpack-cli": "^3.0.8", 38 | "webpack-dev-server": "^3.1.14" 39 | }, 40 | "dependencies": { 41 | "bpmn-js": "^3.2.2", 42 | "diagram-js": "^3.1.3", 43 | "tiny-svg": "^2.2.1" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /resources/diagram.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_1 6 | 7 | 8 | 9 | SequenceFlow_1 10 | SequenceFlow_2 11 | SequenceFlow_5 12 | 13 | 14 | 15 | 16 | SequenceFlow_2 17 | 18 | 19 | 20 | SequenceFlow_5 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 2 | 3 | module.exports = { 4 | entry: { 5 | bundle: ['./app/app.js'] 6 | }, 7 | output: { 8 | path: __dirname + '/public', 9 | filename: 'app.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.bpmn$/, 15 | use: 'raw-loader' 16 | } 17 | ] 18 | }, 19 | plugins: [ 20 | new CopyWebpackPlugin([ 21 | { from: 'assets/**', to: 'vendor/bpmn-js', context: 'node_modules/bpmn-js/dist/' }, 22 | { from: '**/*.{html,css}', context: 'app/' } 23 | ]) 24 | ], 25 | mode: 'development', 26 | devtool: 'source-map' 27 | }; --------------------------------------------------------------------------------