├── .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 |  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 |