Skip to content

Commit

Permalink
feat: add custom button example
Browse files Browse the repository at this point in the history
  • Loading branch information
Niklas Kiefer authored and fake-join[bot] committed Nov 28, 2023
1 parent 6b15215 commit 214d73c
Show file tree
Hide file tree
Showing 17 changed files with 6,102 additions and 0 deletions.
6 changes: 6 additions & 0 deletions custom-button/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": [
"plugin:bpmn-io/node",
"plugin:bpmn-io/browser"
]
}
25 changes: 25 additions & 0 deletions custom-button/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
public
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
27 changes: 27 additions & 0 deletions custom-button/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# form-js Custom Button Example

This example uses [form-js](https://github.com/bpmn-io/form-js) to implement a custom button component.

![form-js custom button example screenshot](./docs/screenshot.png)

## About

This example builds on the general [custom components example](./../custom-components).

It demonstrates how to implement a custom feedback button component that triggers a custom behavior on click. Furthermore, it shows how to use the form-js [FEEL tooling](https://docs.camunda.io/docs/components/modeler/feel/what-is-feel/) to make the feedback message dynamic.

## Building

You need a [NodeJS](http://nodejs.org) development stack with [npm](https://npmjs.org) installed to build the project.

To install all project dependencies execute

```
npm install
```

Spin up a development setup by executing

```
npm run dev
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { get } from 'min-dash';

import { useVariables } from '@bpmn-io/form-js';

import {
FeelTemplatingEntry,
isFeelEntryEdited,
isTextFieldEntryEdited,
TextFieldEntry
} from '@bpmn-io/properties-panel';


export class CustomPropertiesProvider {
constructor(propertiesPanel) {
propertiesPanel.registerProvider(this, 500);
}

/**
* @param {any} field
* @param {function} editField
*
* @return {(Object[]) => (Object[])} groups middleware
*/
getGroups(field, editField) {

/**
* @param {Object[]} groups
*
* @return {Object[]} modified groups
*/
return (groups) => {

if (field.type !== 'feedbackButton') {
return groups;
}

const generalIdx = findGroupIdx(groups, 'general');

/* insert group after general */
groups.splice(generalIdx + 1, 0, {
id: 'feedback',
label: 'Feedback',
entries: Entries(field, editField)
});

return groups;
};
}
}

CustomPropertiesProvider.$inject = [ 'propertiesPanel' ];

function Entries(field, editField) {

const onChange = (key) => {
return (value) => {
editField(field, [ key ], value);
};
};

const getValue = (key) => {
return () => {
return get(field, [ key ]);
};
};

return [

{
id: 'endpoint',
component: Endpoint,
getValue,
field,
isEdited: isTextFieldEntryEdited,
onChange
},
{
id: 'message',
component: Message,
getValue,
field,
isEdited: isFeelEntryEdited,
onChange
}
];

}

function Endpoint(props) {
const {
field,
getValue,
id,
onChange
} = props;

const debounce = (fn) => fn;

return TextFieldEntry({
debounce,
element: field,
getValue: getValue('endpoint'),
id,
label: 'Endpoint',
setValue: onChange('endpoint')
});
}

function Message(props) {
const {
field,
getValue,
id,
onChange
} = props;

const debounce = (fn) => fn;

const variables = useVariables().map(name => ({ name }));

return FeelTemplatingEntry({
debounce,
element: field,
getValue: getValue('message'),
id,
label: 'Message',
setValue: onChange('message'),
variables
});
}

// helper //////////////////////

function findGroupIdx(groups, id) {
return groups.findIndex(g => g.id === id);
}
6 changes: 6 additions & 0 deletions custom-button/app/extension/propertiesPanel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { CustomPropertiesProvider } from './CustomPropertiesProvider';

export default {
__init__: [ 'feedbackButtonPropertiesProvider' ],
feedbackButtonPropertiesProvider: [ 'type', CustomPropertiesProvider ]
};
60 changes: 60 additions & 0 deletions custom-button/app/extension/render/FeedbackButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
Button,
useTemplateEvaluation
} from '@bpmn-io/form-js';

import {
html,
useCallback
} from 'diagram-js/lib/ui';

import './styles.css';

import FeedbackIcon from './feedback.svg';

export const type = 'feedbackButton';

export function FeedbackButtonRenderer(props) {

const {
disabled,
readonly,
field
} = props;

const {
endpoint,
message
} = field;

const evaluatedMessage = useTemplateEvaluation(message, { debug: true, strict: true });

const onClick = useCallback(() => {

if (disabled || readonly) {
return;
}

// send the message to the configured endpoint
alert(`Send message to ${ endpoint }:\n\n${ evaluatedMessage }`);
}, [ disabled, readonly, endpoint, evaluatedMessage ]);

return html`<div class="feedback-button-container" onClick=${onClick}>
<${Button}
{...props}
field=${{
...field,
acton: 'feedback'
}}></${Button}>
</div>`;
}

FeedbackButtonRenderer.config = {
...Button.config,
type: type,
label: 'Feedback',
iconUrl: `data:image/svg+xml,${ encodeURIComponent(FeedbackIcon) }`,
propertiesPanelEntries: [
'label'
]
};
1 change: 1 addition & 0 deletions custom-button/app/extension/render/feedback.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions custom-button/app/extension/render/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FeedbackButtonRenderer, type } from './FeedbackButton';

class CustomFormFields {
constructor(formFields) {
formFields.register(type, FeedbackButtonRenderer);
}
}


export default {
__init__: [ 'feedbackButton' ],
feedbackButton: [ 'type', CustomFormFields ]
};
3 changes: 3 additions & 0 deletions custom-button/app/extension/render/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.feedback-button-container {
width: 100%;
}
16 changes: 16 additions & 0 deletions custom-button/app/form.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"type": "default",
"components": [
{
"type": "textarea",
"key": "message",
"label": "Provide feedback"
},
{
"type": "feedbackButton",
"label": "Send feedback",
"endpoint": "https://www.example.com/feedback",
"message": "=message"
}
]
}
12 changes: 12 additions & 0 deletions custom-button/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>form-js Custom Button example</title>
</head>
<body>
<div id="form"></div>
<script src="index.js"></script>
</body>
</html>
24 changes: 24 additions & 0 deletions custom-button/app/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FormPlayground } from '@bpmn-io/form-js';

import RenderExtension from './extension/render';
import PropertiesPanelExtension from './extension/propertiesPanel';

import '@bpmn-io/form-js/dist/assets/form-js.css';
import '@bpmn-io/form-js/dist/assets/form-js-editor.css';
import '@bpmn-io/form-js/dist/assets/form-js-playground.css';

import './style.css';

import schema from './form.json';

new FormPlayground({
container: document.querySelector('#form'),
schema: schema,
data: {},
additionalModules: [
RenderExtension
],
editorAdditionalModules: [
PropertiesPanelExtension
]
});
11 changes: 11 additions & 0 deletions custom-button/app/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
body, html {
height: 100%;
margin: 0;
padding: 0;
font-family: sans-serif;
}

#form {
max-width: 100%;
height: 100%;
}
Binary file added custom-button/docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 214d73c

Please sign in to comment.