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.
🎯 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>
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.
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"
}
}
- 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
andsendEmail
, - 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"
}
}
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"
}
}
🔨 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'
}
}
]
}
]
};
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
};
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
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
}
}
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;
}
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;
}
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');
}
📐 Create Designer
Now we can create a designer.
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.
const startDefinition = {
properties: {},
sequence: []
};
That's all. Let's create a designer.
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.
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.
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.
const definition = designer.getDefinition();
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']}`);
}
}
}
}
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)