Create Simple No-Code App with Workflow Designer
Tags: tutorial, javascript, opensource
Published: 2022-08-23 16:28:00

In this tutorial I'll show you how to create a simple No-Code app. We will try to create an app, where a user can design some business logic by a graphical user interface and try to run it.

![Workflow Designer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6mn8rvzuouqu19gaspa.gif)

## 🎯 What is No-Code?

No-Code apps allow non-technical users to create an app. How it is possible? No-Codes provide a simple user interface, much simpler then an interface of programming tools. No-Codes are often adjusted for some industry and are prepared to do some specific job.

Basically, in the No-Codes we can separate two layers:

* **Model** - it contains a definition of a program, made by some graphical user interface. It's prepared mainly by a non technical user and it can be stored in a database or a hard drive.
* **Execution** - this layer executes a model. The model can be processed into anything: a graphical interface, a command line app, a website, etc...

In this tutorial we will focus on workflows. The workflow is a wide concept, but in this case we want to use it to define our no-code application. We want to give a final user possibility to create some logical flow like "read some value", "do something if the value is equal 1" etc. We need a simple graphical user interface to define that kind flow. For that we will use the [Sequential Workflow Designer](https://github.com/b4rtaz/sequential-workflow-designer) package.

The package contains a generic designer. This generic designer allows us to create a designer for specific use-cases. The execution layer is not a part of this component.

Let's back to the workflow concept. The workflow contains a definition of flow. One thing here is important, the flow doesn't have to be linear. It could contain some conditional statements or loops. The smallest part here we called a step. The step represents a one instruction or one task to do. Of course conditions or loops are steps too.

Here we will prepare two simple steps and we will create a simple executor of the workflow.

* **readUserInput** - read a user input to a buffer.
* **sendEmail** - sends an email if the buffer equals some value.

## 👓 Sequential Workflow Designer

To add the package to your project, you can add the below code to your `<head>` section.

```html
<link href="https://cdn.jsdelivr.net/npm/sequential-workflow-designer@0.1.9/css/designer.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/sequential-workflow-designer@0.1.9/css/designer-light.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/sequential-workflow-designer@0.1.9/lib/designer.js"></script>
```

If you use npm in your project you can add the package by npm:

```
npm i sequential-workflow-designer
```

## 🐾 Steps

Steps in the designer are defined in JSON format. Why JSON? Because it is easy to store anywhere (a database, a file, etc.).

In this tutorial we will use only `task` steps. But the designer supports more types (`container`, `switch`). The task component displays as a single rectangle. It doesn't contain children. It has a name, some icon, one input and one output.

![Task step](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/umej03v714bdp475on7k.png)  

Look at the below JSON, this is a definition of the task step.

```json
{
    "id": "unique_id",
    "componentType": "task",
    "type": "my_type",
    "name": "my_name",
    "properties": {
        "setting1": "value1",
        "setting2": "value2"
    }
}
```

* **id** - a unique id of a step. This id refers to placed steps in the designer. If we have 2 steps placed, we have two unique ids.
* **type** - a type of task, in this tutorial we have two types: `readUserInput` and `sendEmail`,
* **name** - it displays on the rectangle,
* **properties** - here we have all step settings, this list depends on the step type.

As I mentioned above, we want to create 2 simple steps.

The first one is the `readUserInput` step. We want to allow a user to change a message of a question. So we will define one property `question`.

```json
{
    "componentType": "task",
    "type": "readUserInput",
    "name": "Read User Input",
    "properties": {
        "question": "Ask me"
    }
}
```

Next one is the `sendEmail` step. Here we want to allow a user to define an `email` and add a condition `ifReqisterEquals`, that enables an e-mail shipping if a user in the last `readUserInput` step entered specific value. Basically, I introduce a simple condition here.

```json
{
    "componentType": "task",
    "type": "sendEmail",
    "name": "Send E-mail",
    "properties": {
        "ifReqisterEquals": "1",
        "email": "x@example.com"
    }
}
```

## 🔨 Toolbox

![Toolbox](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bc9ya0jp1odqy2jkqbj6.png)

The toolbox is an element of the designer that allows drag-and-drop steps to the designing flow. Now we prepare a definition that the toolbox will use to render steps.

```js
const toolboxConfig = {
    groups: [
        {
            name: 'Steps',
            steps: [
                {
                    componentType: 'task',
                    type: 'readUserInput',
                    name: 'Read User Input',
                    properties: {
                        question: 'Ask me'
                    }
                },
                {
                    componentType: 'task',
                    type: 'sendEmail',
                    name: 'Send E-mail',
                    properties: {
                        ifReqisterEquals: '1',
                        email: 'x@example.com'
                    }
                }
            ]
        }
    ]
};
```

The configuration is divided into groups. Each group contains steps. Steps have the same structure as I introduced above with one exception. Here we don't provide the `id` field. This field will be added with a unique ID after a drop.

## 🐝 Icons

We can configure an icon for specific `componentType` and `type` of a step. To do this we need to create a configuration.

```js
const stepsConfig = {
    iconUrlProvider: (componentType, type) => {
        return `./assets/icon-task.svg`
    },

    validator: (step) => true
};
```

In this tutorial we use only one icon for all step types. But you can use a `componentType` and `type` arguments to provide specific icons.

What is a validator? It verifies that the step is configured correctly. In this tutorial we won't use this feature.

## 📇 Editors

![Workflow Editor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1cv8bx6l6oq08jq5txj2.png) 

As I mentioned above, each step contains unique settings. After we drop a step, we need an editor to edit those settings. The designer provides a configuration, that allows us to pass a generator of the editor.

```js
const editorConfig = {
    globalEditorProvider: (definition) => {
        const editor = document.createElement('div');
        editor.innerText = 'Select a step.';
        return editor;
    },
    stepEditorProvider: (step) => {
        // create a step's editor here and return it
    }
}
```

The main problem here is that, we could have many step types (in this tutorial we have 2). So that means, each step requires own editor because each step contains different settings.

Here we have a basic editor of the `readUserInput` step.

```js
function createEditorForReadUserInput(step) {
    const editor = document.createElement('div');
    const label = document.createElement('label');
    label.innerText = 'Question';
    const input = document.createElement('input');
    input.setAttribute('type', 'text');
    input.value = step.properties['question'];
    input.addEventListener('input', () => {
        step.properties['question'] = input.value;
    });

    editor.appendChild(label);
    editor.appendChild(input);
    return editor;
}
```

And here an editor of the `sendEmail` step.

```js
function createEditorForSendEmail(step) {
    const editor = document.createElement('div');
    const propNames = ['ifReqisterEquals', 'email'];
    for (let propName of propNames) {
        const label = document.createElement('label');
        label.innerText = propName;
        const input = document.createElement('input');
        input.setAttribute('type', 'text');
        input.value = step.properties[propName];
        input.addEventListener('input', () => {
            step.properties[propName] = input.value;
        });

        editor.appendChild(label);
        editor.appendChild(input);
    }
    return editor;
}
```

As you can see, these editors update `step.properties` after a user's change.

We have two generators, but we need to put all together into the `stepEditorProvider` property.

```js
stepEditorProvider: (step) => {
  if (step.type === 'readUserInput')
    return createEditorForReadUserInput(step);
  if (step.type === 'sendEmail')
    return createEditorForSendEmail(step);
  throw new Error('Not supported');
}
```

## 📐 Create Designer

Now we can create a designer.

```js
const config = {
	toolbox: toolboxConfig,
	steps: stepsConfig,
	editors: editorConfig
};
```

We can provide a start definition of the workflow. But in this tutorial we won't do this.

```js
const startDefinition = {
	properties: {},
	sequence: []
};
```

That's all. Let's create a designer.

```js
const placeholder = document.getElementById('designer');
const designer = sequentialWorkflowDesigner.create(
    placeholder, 
    startDefinition, 
    config);
```

Now we should see the designer on the screen.

## 🚀 Execution

The last part is an execution. Here we want to execute a designed flow.

We need to prepare a code for the `readUserInput` step. This step is very simple. We ask a user and save a response. The question is defined in the `question` property.

```js
let register = null;

if (step.type === 'readUserInput') {
   register = prompt(step.properties['question']);
}
```

The `sendEmail` step is little more complicated. Here we send an e-mail if a previously picked value from a user is equal some value. The value is defined in the `ifReqisterEquals` property.

```js
if (step.type === 'sendEmail') {
  if (step.properties['ifReqisterEquals'] === register) {
     alert(`E-mail sent to ${step.properties['email']}`);
  }
}
```

But how we can read a definition of the workflow? It's easy.

```js
const definition = designer.getDefinition();
```

You can check the final state here.

```js
function runWorkflow() {
	const definition = designer.getDefinition();

	let register = null;

	for (let step of definition.sequence) {
		if (step.type === 'readUserInput') {
			register = prompt(step['question']);
		}
		else if (step.type === 'sendEmail') {
			if (step.properties['ifReqisterEquals'] === register) {
				alert(`E-mail sent to ${step.properties['email']}`);
			}
		}
	}
}
```

Now we need to call `runWorkflow` function.

## 📦 Examples 

You can test the final project [here](https://b4rtaz.github.io/sequential-workflow-designer/examples/simple-flow.html).

By the way you can check the below online demos of the Sequential Workflow Designer component.

* [❎ Fullscreen](https://b4rtaz.github.io/sequential-workflow-designer/examples/fullscreen.html)
* [🌅 Image Filter](https://b4rtaz.github.io/sequential-workflow-designer/examples/image-filter.html)
* [⛅ Light Dark](https://b4rtaz.github.io/sequential-workflow-designer/examples/light-dark.html)
* [⏩ Live Testing](https://b4rtaz.github.io/sequential-workflow-designer/examples/live-testing.html)
* [🔴 Particles](https://b4rtaz.github.io/sequential-workflow-designer/examples/particles.html)
* [🤖 Code Generator](https://b4rtaz.github.io/sequential-workflow-designer/examples/code-generator.html)