DEV Community

Cover image for Create Simple No-Code App with Workflow Designer
b4rtaz
b4rtaz

Posted on • Updated on • Originally published at b4rtaz.Medium

Create Simple No-Code App with Workflow Designer

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

🎯 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 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.

<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>
Enter fullscreen mode Exit fullscreen mode

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

npm i sequential-workflow-designer
Enter fullscreen mode Exit fullscreen mode

🐾 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

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

{
    "id": "unique_id",
    "componentType": "task",
    "type": "my_type",
    "name": "my_name",
    "properties": {
        "setting1": "value1",
        "setting2": "value2"
    }
}
Enter fullscreen mode Exit fullscreen mode
  • 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.

{
    "componentType": "task",
    "type": "readUserInput",
    "name": "Read User Input",
    "properties": {
        "question": "Ask me"
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

{
    "componentType": "task",
    "type": "sendEmail",
    "name": "Send E-mail",
    "properties": {
        "ifReqisterEquals": "1",
        "email": "x@example.com"
    }
}
Enter fullscreen mode Exit fullscreen mode

🔨 Toolbox

Toolbox

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.

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'
                    }
                }
            ]
        }
    ]
};
Enter fullscreen mode Exit fullscreen mode

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.

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

    validator: (step) => true
};
Enter fullscreen mode Exit fullscreen mode

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

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.

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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;
}
Enter fullscreen mode Exit fullscreen mode

And here an editor of the sendEmail step.

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;
}
Enter fullscreen mode Exit fullscreen mode

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.

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

📐 Create Designer

Now we can create a designer.

const config = {
    toolbox: toolboxConfig,
    steps: stepsConfig,
    editors: editorConfig
};
Enter fullscreen mode Exit fullscreen mode

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

const startDefinition = {
    properties: {},
    sequence: []
};
Enter fullscreen mode Exit fullscreen mode

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

const placeholder = document.getElementById('designer');
const designer = sequentialWorkflowDesigner.create(
    placeholder, 
    startDefinition, 
    config);
Enter fullscreen mode Exit fullscreen mode

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.

let register = null;

if (step.type === 'readUserInput') {
   register = prompt(step.properties['question']);
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

const definition = designer.getDefinition();
Enter fullscreen mode Exit fullscreen mode

You can check the final state here.

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']}`);
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we need to call runWorkflow function.

📦 Examples

You can test the final project here.

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

Top comments (0)