Comprehensive Guide: Integrating a Drag-and-Drop Form Builder for Camunda.
Tags: camunda, opensource, react, frontend
Published: 2024-06-10 09:16:49

### Introduction:
Camunda is a Open Source powerful business process management (BPM) solution that provides a flexible and scalable platform for process automation. It supports BPMN for process modeling, CMMN for case management, and DMN for decision modeling.

Optimajet team receives a large number of questions regarding the integration of the form builder for Camunda. We have written a detailed guide so that all developers can connect [Optimajet Formengine to [Camunda](https://github.com/camunda).

[Formengine](https://formengine.io) is a Drag & Drop Form Builder Library for React. OptimaJet FormBuilder is a lightweight front-end tool that offers an easy and flexible approach to adding drag-and-drop form functionality to your React applications.

In this guide, we will use an example from the Camunda
repository, [Using React Forms with Tasklist](https://github.com/camunda/camunda-bpm-examples/tree/master/usertask/task-form-embedded-react), and modify it so that the forms are displayed using Optimajet FormEngine.

### Requirements

To follow along, you will need the following:

1. [Java Development Kit (JDK) 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html)
2. [Camunda 7 Community Edition](https://downloads.camunda.cloud/release/camunda-bpm/tomcat/7.21/camunda-bpm-tomcat-7.21.0.zip)
3. [Camunda Modeler](https://camunda.com/download/modeler/)

Ensure that both Camunda 7 Community Edition and Camunda Modeler are installed on your system if they are not already.

## Starting with the React Example

The Camunda repository on GitHub provides a simple and
clear [instruction](https://github.com/camunda/camunda-bpm-examples/blob/master/usertask/task-form-embedded-react/README.md) for using React
in Tasklist. Let's walk through it together.

1. **Add `loadReact.js` to Camunda Tasklist:**

   Download [loadReact.js](https://github.com/camunda/camunda-bpm-examples/blob/master/usertask/task-form-embedded-react/config/react/loadReact.js)
   and place it in the `app/tasklist/scripts/react` directory of the Camunda Tasklist webapp. For example, if you are using Tomcat, the path
   will be `/webapps/camunda/app/tasklist/scripts/react`.

   This script will load React and ReactDOM from a CDN and add them to the global scope. If you prefer to use different versions of React,
   adjust the import paths in the script accordingly.

2. **Add the loader as a custom script:**

   Modify the `app/tasklist/scripts/config.js` file of the Camunda Tasklist webapp to include the loader script. For Tomcat, this file is
   located at `/webapps/camunda/app/tasklist/scripts/config.js`. Update the file as shown in
   the [example](https://github.com/camunda/camunda-bpm-examples/blob/master/usertask/task-form-embedded-react/config/config.js).

**config.js**
   ```javascript 
   customScripts: [
     'scripts/react/loadReact.js'
   ]
   ```

Launch Camunda if it is not already running. Next, we need to upload the process definition and forms from GitHub, then upload them to
Camunda using Camunda Modeler and start the process. Let's do it step by step:

1. **Download the following files:**

- [react-example.bpmn](https://raw.githubusercontent.com/camunda/camunda-bpm-examples/master/usertask/task-form-embedded-react/src/main/resources/react-example.bpmn)
- [start-form.html](https://raw.githubusercontent.com/camunda/camunda-bpm-examples/master/usertask/task-form-embedded-react/src/main/webapp/start-form.html)
- [task-form.html](https://raw.githubusercontent.com/camunda/camunda-bpm-examples/master/usertask/task-form-embedded-react/src/main/webapp/task-form.html)

2. **Open Camunda Modeler and load the `react-example.bpmn` file:**

![Camunda Modeler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/284mny861p0fni6vo3s9.png)

3. **Update the form keys:**

- Click the "Invoice Received" element and change the Form key from `embedded:app:start-form.html`
  to `embedded:deployment:start-form.html`:
![Camunda Modeler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhiu8ngocg7vvh296bsk.png)

- Click the "Approve Invoice" element and change the Form key from `embedded:app:task-form.html` to `embedded:deployment:task-form.html`:
 
![Camunda Modeler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tgg9vjesre3wnl322l5.png)

4. **Deploy the process:**

- Click the Rocket button at the bottom of the screen, then click the plus button next to "Include additional files" and add the
  previously downloaded `start-form.html` and `task-form.html` files:
  
![Camunda Modeler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1woccjwji8owbxqi3r7.png)

- Click the "Deploy" button. You should see a message indicating that the Process Definition has been successfully deployed:
  
![Camunda Modeler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gd4jbq9bwwibujla52p8.png)

To ensure everything works correctly, follow these steps to test the setup in the Camunda web interface. If Camunda is running locally, the
address will be something like [http://localhost:8080/camunda-welcome/index.html](http://localhost:8080/camunda-welcome/index.html):

1. **Open the Camunda web interface:**

![Camunda admin](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bfe0huixkbbru5jb65ma.png)

2. **Access the Tasklist:**

- Click on the Tasklist image.
- Log in using the credentials **demo**/**demo**:
  
![Camunda login](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gh2ooy9m6wblr57mtauf.png)

3. **Start the process:**

- Click on the "Start Process" button on the top panel:
![Camunda Task](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9hofiy5dmb0io69cj52.png)

- Select "React example" in the "Start process" window:
![Camunda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zwn2giw8ltlaszenu0yl.png)

4. **Fill in the start form:**

- The form for starting the process, uploaded from `start-form.html`, should now be displayed.
- Fill in the form with the necessary data and click the "Start" button:

![Camunda start form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/98cc4btrodylw9mafosc.png)


5. **View the task list:**

- The process has started. Now click on "All Tasks" on the left panel.
- You should see your task in the task list:

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pwse55kgzumuw6prdczw.png)

6. **Claim the task:**

- Click on the task:

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4yfd6u5ppyn71xsjwscz.png)
- Claim the task by clicking on the "Claim" link, which will change to "Demo Demo":

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uc7f6l66ddd1aljyfbn4.png)

7. **Complete the task:**

- The form you see is uploaded from the `task-form.html` file.
- Fill out the form by clicking on the "I approve this Invoice" checkbox, then click the "Complete" button:

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pf9r6v1sljuzt6zy76ca.png)

8. **Verify completion:**

- The task will be completed:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/35kyxn5m564vx7bdrwst.png)

## Creating forms

To connect FormEngine to Camunda, we will use a package that includes a set of components based on React Suite. These components are
utilized in our [demo](https://demo.formengine.io/).

First, we need two forms to replace the React forms from the Camunda example. We will omit the process of creating these forms, as it is
straightforward to accomplish. For instance, you can use our [demo](https://demo.formengine.io/). Simply drag and drop the necessary
components onto the form and configure their properties as required. Below are the JSON files containing the forms themselves.

**start-form.json**

{% details Click to view start-form.json %} 

```JSON
{
  "version": "1",
  "actions": {
    "onChange": {
      "body": "    const setInvoiceDocument = document => e.store.formData.state['invoiceDocument'] = document;\n\n    const blobFile = e.args[0]?.[0]?.blobFile;\n    if (blobFile) {\n        const reader = new FileReader();\n        reader.readAsDataURL(blobFile);\n        reader.onload = () => {\n            setInvoiceDocument(reader.result.replace(/^data:(.*;base64,)?/, ''));\n        };\n        reader.onerror = () => {\n            setInvoiceDocument(undefined);\n        }\n    } else {\n        setInvoiceDocument(undefined);\n    }",
      "params": {}
    }
  },
  "form": {
    "key": "Screen",
    "type": "Screen",
    "props": {},
    "children": [
      {
        "key": "RsContainer 1",
        "type": "RsContainer",
        "props": {},
        "children": [
          {
            "key": "RsLabel 1",
            "type": "RsLabel",
            "props": {
              "text": {
                "value": "Invoice Document:"
              }
            }
          },
          {
            "key": "invoiceDocument",
            "type": "RsUploader",
            "props": {
              "autoUpload": {
                "value": false
              }
            },
            "events": {
              "onChange": [
                {
                  "name": "onChange",
                  "type": "code"
                }
              ]
            }
          }
        ]
      },
      {
        "key": "creditor",
        "type": "RsInput",
        "props": {
          "label": {
            "value": "Creditor:"
          },
          "placeholder": {
            "value": "e.g. \"Super Awesome Pizza\""
          },
          "size": {
            "value": "md"
          }
        }
      },
      {
        "key": "amount",
        "type": "RsNumberFormat",
        "props": {
          "label": {
            "value": "Amount:"
          },
          "placeholder": {
            "value": "e.g. \"30.00\""
          },
          "allowNegative": {
            "value": false
          }
        }
      },
      {
        "key": "invoiceCategory",
        "type": "RsDropdown",
        "props": {
          "label": {
            "value": "Invoice Category:"
          },
          "data": {
            "value": [
              {
                "value": "Travel Expenses",
                "label": "Travel Expenses"
              },
              {
                "value": "Business Meals",
                "label": "Business Meals"
              },
              {
                "value": "Other",
                "label": "Other"
              }
            ]
          },
          "value": {
            "value": ""
          }
        }
      },
      {
        "key": "invoiceNumber",
        "type": "RsInput",
        "props": {
          "placeholder": {
            "value": "e.g. \"I-12345\""
          },
          "label": {
            "value": "Invoice Number:"
          }
        }
      }
    ]
  },
  "localization": {},
  "languages": [
    {
      "code": "en",
      "dialect": "US",
      "name": "English",
      "description": "American English",
      "bidi": "ltr"
    }
  ],
  "defaultLanguage": "en-US"
}
```

{% enddetails %}


You can see the "start-form" in the screenshot below:

![Optimajet Form Engine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyil4g75xsxsvew0yjky.png)

The second form is similar to the first.

**task-form.json**

{% details Click to view task-form %} 

```JSON
{
  "version": "1",
  "actions": {
    "onChange": {
      "body": "    const setInvoiceDocument = document => e.store.formData.state['invoiceDocument'] = document;\n\n    const blobFile = e.args[0]?.[0]?.blobFile;\n    if (blobFile) {\n        const reader = new FileReader();\n        reader.readAsDataURL(blobFile);\n        reader.onload = () => {\n            setInvoiceDocument(reader.result.replace(/^data:(.*;base64,)?/, ''));\n        };\n        reader.onerror = () => {\n            setInvoiceDocument(undefined);\n        }\n    } else {\n        setInvoiceDocument(undefined);\n    }",
      "params": {}
    }
  },
  "form": {
    "key": "Screen",
    "type": "Screen",
    "props": {},
    "children": [
      {
        "key": "RsContainer 1",
        "type": "RsContainer",
        "props": {},
        "children": [
          {
            "key": "RsLabel 1",
            "type": "RsLabel",
            "props": {
              "text": {
                "value": "Invoice Document:"
              }
            }
          },
          {
            "key": "invoiceDocument",
            "type": "RsUploader",
            "props": {
              "autoUpload": {
                "value": false
              }
            },
            "events": {
              "onChange": [
                {
                  "name": "onChange",
                  "type": "code"
                }
              ]
            }
          }
        ]
      },
      {
        "key": "creditor",
        "type": "RsInput",
        "props": {
          "label": {
            "value": "Creditor:"
          },
          "placeholder": {
            "value": "e.g. \"Super Awesome Pizza\""
          },
          "size": {
            "value": "md"
          }
        }
      },
      {
        "key": "amount",
        "type": "RsNumberFormat",
        "props": {
          "label": {
            "value": "Amount:"
          },
          "placeholder": {
            "value": "e.g. \"30.00\""
          },
          "allowNegative": {
            "value": false
          }
        }
      },
      {
        "key": "invoiceCategory",
        "type": "RsDropdown",
        "props": {
          "label": {
            "value": "Invoice Category:"
          },
          "data": {
            "value": [
              {
                "value": "Travel Expenses",
                "label": "Travel Expenses"
              },
              {
                "value": "Business Meals",
                "label": "Business Meals"
              },
              {
                "value": "Other",
                "label": "Other"
              }
            ]
          },
          "value": {
            "value": ""
          }
        }
      },
      {
        "key": "invoiceNumber",
        "type": "RsInput",
        "props": {
          "placeholder": {
            "value": "e.g. \"I-12345\""
          },
          "label": {
            "value": "Invoice Number:"
          }
        }
      }
    ]
  },
  "localization": {},
  "languages": [
    {
      "code": "en",
      "dialect": "US",
      "name": "English",
      "description": "American English",
      "bidi": "ltr"
    }
  ],
  "defaultLanguage": "en-US"
}
```

{% enddetails %}


This is what the second form looks like:

![Optimajet Form Engine](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sx2sc5kw448jfl3bvx84.png)

## Connecting FormEngine to Camunda

When connecting FormEngine to Camunda, we decided to use a [bundle](/installation#cdn) designed for use on any web page. This method does
not require a separate React connection.

During the connection process, we discovered that Camunda uses a strict Content Security Policy, which prohibits some inline CSS used in the
bundle. Therefore, we will connect the component styles separately.

1. **Add `loadFormEngine.js`:**

   Place `loadFormEngine.js` in `app/tasklist/scripts/formEngine` of the Camunda Tasklist webapp (e.g., for Tomcat, it will
   be `/webapps/camunda/app/tasklist/scripts/formEngine`).

loadFormEngine.js
   ```javascript
   const formEngine = document.createElement('script');
   formEngine.crossOrigin = true;
   formEngine.src = 'https://unpkg.com/@react-form-builder/viewer-bundle@1.2.0/dist/index.umd.js';
   document.body.append(formEngine);
   ```

2. **Add the loader to `config.js`:**

   Modify `app/tasklist/scripts/config.js` of the Camunda Tasklist webapp to include the loader script. For Tomcat, the path will
   be `/webapps/camunda/app/tasklist/scripts/config.js`.

config.js
   ```javascript
   customScripts: [
     'scripts/react/loadReact.js',
      // highlight-next-line
     'scripts/formEngine/loadFormEngine.js'
   ]
   ```

3. **Download and add the CSS files:**

   Download the [rsuite-no-reset.min.css](https://unpkg.com/@react-form-builder/viewer-bundle@1.2.0/dist/rsuite-no-reset.min.css) file and
   the [formengine-rsuite.css](https://unpkg.com/@react-form-builder/viewer-bundle@1.2.0/dist/formengine-rsuite.css) file. Place them in
   the `app/tasklist/styles` folder. To avoid configuring the CSP policy, download the styles locally.

4. **Modify `user-styles.css`:**  
   
   Add the following highlighted lines to `app/tasklist/styles/user-styles.css`:

user-styles.css
   ```css 
   /*
   .navbar-brand {
     text-indent: -999em;
     background-image: url(./path/to/the/logo.png);
     width: 80px;
   }
   
   [cam-widget-header] {
     border-bottom-color: blue;
   }
   */
   
   @import url('./rsuite-no-reset.min.css');
   @import url('./formengine-rsuite.css');

   .rs-picker-select-menu.rs-picker-popup {
     z-index: 2000;
   }
   ```

## Modifying Forms

In the code of both forms, we will use a simple `renderFormEngineForm` function that will render the form into an HTML element.

The function accepts the following parameters:

1. `form` is the JSON of the form.
2. `container` is the HTML element where the form will be rendered.
3. `additionalProps` are the additional [properties](/api-reference/interfaces/react_form_builder_core.FormViewerProps) of
   the [FormViewer](/api-reference/modules/react_form_builder_core#formviewer) component.

```javascript
function renderFormEngineForm(form, container, additionalProps) {
  const viewerRef = {current: null};
  const viewerBundle = window.FormEngineViewerBundle;
  const components = viewerBundle.rSuiteComponents;
  const view = components.view.withViewerWrapper(components.RsLocalizationWrapper);
  const props = {
    getForm: () => form,
    view,
    viewerRef,
    ...additionalProps
  };
  viewerBundle.renderFormViewerTo(container, props);
  return viewerRef;
}
```

Each form's code will have its own `renderCamundaForm` function that will link the FormEngine form and the Camunda form, which is stored in
the `camForm` object. In general, the form code is similar to the forms from the React example. See the form code below for reference.

**start-form.html**
{% details Click to view start-form %} 

```HTML
<script>
  function renderFormEngineForm(form, container, additionalProps) {
    const viewerRef = {current: null}
    const viewerBundle = window.FormEngineViewerBundle;
    const components = viewerBundle.rSuiteComponents;
    const view = components.view
      .withViewerWrapper(components.RsLocalizationWrapper);
    const props = {
      getForm: () => form,
      view,
      viewerRef,
      ...additionalProps
    }
    viewerBundle.renderFormViewerTo(container, props);
    return viewerRef;
  }

  function onSubmit(camForm, formRef) {
    const formData = formRef.current.formData.data;
    // the file data was saved via a user action to a user state
    const userState = formRef.current.formData.state;

    camForm.variableManager.createVariable({
        'name': 'invoiceDocument',
        'type': 'File',
        'value': userState.invoiceDocument,
        'valueInfo': {filename: 'invoice.pdf'},
        isDirty: true
      }
    );
    camForm.variableManager.createVariable({
        'name': 'creditor',
        'type': 'String',
        'value': formData.creditor,
        isDirty: true
      }
    );
    camForm.variableManager.createVariable({
        'name': 'amount',
        'type': 'Double',
        'value': formData.amount,
        isDirty: true
      }
    );
    camForm.variableManager.createVariable({
        'name': 'category',
        'type': 'String',
        'value': formData.invoiceCategory,
        isDirty: true
      }
    );
    camForm.variableManager.createVariable({
        'name': 'invoiceID',
        'type': 'String',
        'value': formData.invoiceNumber,
        isDirty: true
      }
    );
  }

  function renderCamundaForm(elementId, camForm) {
    const form = `
{
  "version": "1",
  "actions": {
    "onChange": {
      "body": "    const setInvoiceDocument = document => e.store.formData.state['invoiceDocument'] = document;\\n\\n    const blobFile = e.args[0]?.[0]?.blobFile;\\n    if (blobFile) {\\n        const reader = new FileReader();\\n        reader.readAsDataURL(blobFile);\\n        reader.onload = () => {\\n            setInvoiceDocument(reader.result.replace(/^data:(.*;base64,)?/, ''));\\n        };\\n        reader.onerror = () => {\\n            setInvoiceDocument(undefined);\\n        }\\n    } else {\\n        setInvoiceDocument(undefined);\\n    }",
      "params": {}
    }
  },
  "form": {
    "key": "Screen",
    "type": "Screen",
    "props": {},
    "children": [
      {
        "key": "RsContainer 1",
        "type": "RsContainer",
        "props": {},
        "children": [
          {
            "key": "RsLabel 1",
            "type": "RsLabel",
            "props": {
              "text": {
                "value": "Invoice Document:"
              }
            }
          },
          {
            "key": "invoiceDocument",
            "type": "RsUploader",
            "props": {
              "autoUpload": {
                "value": false
              }
            },
            "events": {
              "onChange": [
                {
                  "name": "onChange",
                  "type": "code"
                }
              ]
            }
          }
        ]
      },
      {
        "key": "creditor",
        "type": "RsInput",
        "props": {
          "label": {
            "value": "Creditor:"
          },
          "placeholder": {
            "value": "e.g. \\"Super Awesome Pizza\\""
          },
          "size": {
            "value": "md"
          }
        }
      },
      {
        "key": "amount",
        "type": "RsNumberFormat",
        "props": {
          "label": {
            "value": "Amount:"
          },
          "placeholder": {
            "value": "e.g. \\"30.00\\""
          },
          "allowNegative": {
            "value": false
          }
        }
      },
      {
        "key": "invoiceCategory",
        "type": "RsDropdown",
        "props": {
          "label": {
            "value": "Invoice Category:"
          },
          "data": {
            "value": [
              {
                "value": "Travel Expenses",
                "label": "Travel Expenses"
              },
              {
                "value": "Business Meals",
                "label": "Business Meals"
              },
              {
                "value": "Other",
                "label": "Other"
              }
            ]
          },
          "value": {
            "value": ""
          }
        }
      },
      {
        "key": "invoiceNumber",
        "type": "RsInput",
        "props": {
          "placeholder": {
            "value": "e.g. \\"I-12345\\""
          },
          "label": {
            "value": "Invoice Number:"
          }
        }
      }
    ]
  },
  "localization": {},
  "languages": [
    {
      "code": "en",
      "dialect": "US",
      "name": "English",
      "description": "American English",
      "bidi": "ltr"
    }
  ],
  "defaultLanguage": "en-US"
}`

    const viewerContainer = document.getElementById(elementId);
    const formRef = renderFormEngineForm(form, viewerContainer);

    camForm.on('submit', () => {
      onSubmit(camForm, formRef)
    });
  }
</script>

<form class='form-horizontal'>
  <div id="formViewerContainer"></div>

  <script cam-script type='text/form-script'>
    renderCamundaForm('formViewerContainer', camForm);
  </script>
</form>
```

{% enddetails %}

**task-form.html**

{% details Click to view task-form %} 

title="task-form.html" {2,18,20,51,150-168,173,194}

```html 
<script>
  function renderFormEngineForm(form, container, additionalProps) {
    const viewerRef = {current: null}
    const viewerBundle = window.FormEngineViewerBundle;
    const components = viewerBundle.rSuiteComponents;
    const view = components.view
      .withViewerWrapper(components.RsLocalizationWrapper);
    const props = {
      getForm: () => form,
      view,
      viewerRef,
      ...additionalProps
    }
    viewerBundle.renderFormViewerTo(container, props);
    return viewerRef;
  }

  function renderCamundaForm(elementId, camForm, scope) {
    const camVars = camForm.variableManager.variables;
    const invoiceUrl = camVars.invoiceDocument.contentUrl;

    const form = `{
  "version": "1",
  "form": {
    "key": "Screen",
    "type": "Screen",
    "props": {},
    "children": [
      {
        "key": "RsContainer 1",
        "type": "RsContainer",
        "props": {},
        "children": [
          {
            "key": "RsLabel 1",
            "type": "RsLabel",
            "props": {
              "text": {
                "value": "Download Invoice:"
              }
            }
          },
          {
            "key": "invoiceDocument",
            "type": "RsLink",
            "props": {
              "text": {
                "value": "invoice.pdf"
              },
              "href": {
                "value": "${invoiceUrl}"
              }
            }
          }
        ]
      },
      {
        "key": "amount",
        "type": "RsNumberFormat",
        "props": {
          "label": {
            "value": "Amount:"
          },
          "placeholder": {
            "value": ""
          },
          "allowNegative": {
            "value": false
          },
          "readOnly": {
            "value": false
          },
          "disabled": {
            "value": true
          }
        }
      },
      {
        "key": "creditor",
        "type": "RsInput",
        "props": {
          "label": {
            "value": "Creditor:"
          },
          "placeholder": {
            "value": ""
          },
          "size": {
            "value": "md"
          },
          "disabled": {
            "value": true
          }
        }
      },
      {
        "key": "category",
        "type": "RsInput",
        "props": {
          "label": {
            "value": "Invoice Category:"
          },
          "disabled": {
            "value": true
          }
        }
      },
      {
        "key": "invoiceID",
        "type": "RsInput",
        "props": {
          "placeholder": {
            "value": ""
          },
          "label": {
            "value": "Invoice Number:"
          },
          "disabled": {
            "value": true
          }
        }
      },
      {
        "key": "approve",
        "type": "RsCheckbox",
        "props": {
          "children": {
            "value": "I approve this Invoice"
          },
          "checked": {
            "value": false
          }
        }
      }
    ]
  },
  "localization": {},
  "languages": [
    {
      "code": "en",
      "dialect": "US",
      "name": "English",
      "description": "American English",
      "bidi": "ltr"
    }
  ],
  "defaultLanguage": "en-US"
}`

    const additionalProps = {
      initialData: {
        amount: camVars.amount.value,
        creditor: camVars.creditor.value,
        invoiceID: camVars.invoiceID.value,
        approved: camVars.approved.value,
        category: camVars.category.value
      },
      onFormDataChange: ({data, errors}) => {
        camForm.variableManager.variableValue('approved', data.approve);
        if (data.approve !== camVars.approved.value) {
          // Activate 'save' button
          scope.$$camForm.$dirty = true;
        }
      }
    }

    const viewerContainer = document.getElementById(elementId);
    renderFormEngineForm(form, viewerContainer, additionalProps);
  }
</script>

<form class='form-horizontal'>
  <div id='formViewerContainer'/>

  <script cam-script type='text/form-script'>

    // Fetch Variables and create new ones
    camForm.on('form-loaded', function () {
      camForm.variableManager.createVariable({
        'name': 'approved',
        'type': 'Boolean',
        'value': false,
        isDirty: true
      });

      camForm.variableManager.fetchVariable('amount');
      camForm.variableManager.fetchVariable('creditor');
      camForm.variableManager.fetchVariable('invoiceID');
      camForm.variableManager.fetchVariable('invoiceDocument');
      camForm.variableManager.fetchVariable('category');
    });

    camForm.on('variables-applied', function () {
      renderCamundaForm('formViewerContainer', camForm, $scope);
    });
  </script>
</form>
```
{% enddetails %}

**The JSON for the form and the basic code for rendering the form are included in HTML files for this example. In practice, it's likely
better to use a separate JavaScript module.**

## Running FormEngine Forms in Camunda

1. **Deploy the FormEngine Forms:**

- Open Camunda Modeler and click the rocket icon button.
- Delete the selected forms `start-form.html` and `task-form.html`.
- Add the forms created for FormEngine.
- Click the Deploy button.

![Camunda Modeler](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fm93yogju28unuccdchj.png)

2. **Open the Camunda Tasklist Web Interface:**

- Navigate to [http://localhost:8080/camunda/app/tasklist/](http://localhost:8080/camunda/app/tasklist/) and refresh the page.

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kws9kro60x8d1yjbz1im.png)

3. **Start the Process:**

- Click on the “Start Process” button on the top panel.
- Select "React example" in the "Start process" window. You should see the form made with FormEngine.

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7y0bu06yl8h7wx9wk5gz.png)

4. **Fill Out the Form and Start the Process:**

- Fill out the form and click the Start button.
- The process has started. Now click on "All Tasks" on the left panel.

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3goomzjf49v6ce2qfdav.png)

5. **Select and Claim the Task:**

- Select the created task from the top.

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ffwtunim5mkuz5rva5b.png)
- Claim the task by clicking on the "Claim" link. The link text will change to "Demo Demo".

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1gh6jsaud0vdmjhbq9r5.png)

6. **Verify Task Variables and Fill Out the Form:**

- You should see that the variables have been populated. Click on the link next to the highlighted text "React Example".

![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1dufqngl97otfv6z70hb.png)
- The form should be correctly filled out.
![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iu7k7rn2a0eu8hayazb2.png)
- Fill out the form and click Complete.
![Camunda Tasklist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/idoatt7hku21vn4i6s2v.png)

That's it! Your FormEngine forms are now running in Camunda.

## Conclusion

In this article, we have successfully connected FormEngine as a form rendering engine for Camunda. This allows you to use your custom
components to render forms by passing a set of your components through properties.

**Your feedback is very important to us**
It helps us understand whether this guide was useful to you, how clearly it was written, and what else you would like to learn about. Please ask your questions in the comments or start discussions on [GitHub](https://github.com/optimajet/formengine/discussions).