A Lightweight GUI Library for Low-code Platform
Tags: angular, frontend, lowcode, javascript
Published: 2024-04-17 08:52:53

## Preface

The GUI config panel can be used in any low-code platform, such as prototyping tools ([Figma](https://www.figma.com/)), website building platforms ([Webflow](https://webflow.com/), [Framer](https://www.framer. com/)), BI reports ([Power BI](https://www.microsoft.com/en-us/power-platform/products/power-bi), [Looker Studio](https://lookerstudio.google .com/)), 3D model building ([Spline](https://spline.design/), [Womp](https://alpha.womp.com/)), animation editor ([Rive](https ://rive.app/)), etc., these are some very famous online tools.

Acrodata GUI is a lightweight GUI library for low-code platforms, now it's open sourced. 🎉

GitHub: https://github.com/acrodata/gui
Playground: https://acrodata.github.io/gui/playground

## What is GUI

In front-end, we rarely use the description like GUI, so many people mistakenly think that `GUI = UI library`.

So what exactly is a GUI? For ease of understanding, we can refer to the more famous GUI project [dat.gui](https://github.com/dataarts/dat.gui). For those who have done 3D visualization or ThreeJS must be very familiar with this library. The main purpose of `dat.gui` is to convert configuration items into graphical controls to debug parameters.

![dat.gui](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/se5d3ltcqtlxedz7oqx3.png)

In addition to [dat.gui](https://github.com/dataarts/dat.gui), there are several other GUI projects that are also doing well, [tweakpane](https://github.com/cocopon/ tweakpane), [lil-gui](https://github.com/georgealways/lil-gui), [leva](https://github.com/pmndrs/leva).

## Configuration panel in low-code platform

For those who have used low-code platforms or developed similar products, the layout of low-code platforms has become commonplace. On the right side of the layout is usually a configuration panel. Of course, the same goes for a lot of the software we use. Just post a few screenshots of some tools.

![Webflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c1j8euaz7lymmu3u7dvb.jpg)

![Spline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gn7yhvpgwo81vqfh2zy.jpg)

![Looker Studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xj2hawf0rp65qs6aste5.jpg)

First of all, not every low-code product requires GUI generation configuration. For example, in the Webflow (first screenshot), the configuration items of all its components are the same (all are CSS visual configurations). In this case, it will be simpler to develop a common component.

However, for products like Looker Studio (third screenshot), each chart component has a different configuration and it allows users to customize chart component, so this type of product is in great need of a flexible and easy-to-use GUI library.

On the homepage of the Acrodata GUI documentation site, I used the GUI to create a complex **CSS gradient generator**. It is very similar to the configuration panel in the low-code platform. You are welcome to try it out.

![Acrodata GUI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szk6z4udjk8616uenylq.gif)

👉 [View CSS gradient generator source code](https://github.com/acrodata/gui/blob/main/projects/docs/src/app/shared/gradient-generator/gradient-generator.component.ts)

## How to design a lightweight GUI?

Due to the complexity of low-code platforms, GUI libraries must be designed to be able to combine arbitrary data structures while being simple and easy to use.

### JSON-based configuration items

To support custom components, GUI libraries are more suitable for configuration using JSON data. This is very different from the use of the GUI libraries mentioned above. Let's take `dat.gui` and Acrodata GUI as examples.

Assume that the configuration items of a certain component are as follows:

```js
const options = {
   content: 'Hello world',
   opacity: 0.3,
   visible: false,
}
```

The usage of `dat.gui` is as follows:

```js
const gui = new dat.GUI();

gui.add(options, 'content');
gui.add(options, 'opacity', 0, 1).step(0.1);
gui.add(options, 'visible');
```

Although the usage of `dat.gui` is very simple, this functional declaration method is not suitable for dynamic components and is not conducive to data storage.

The usage in Acrodata GUI is like this:

```html
<gui-form [config]="config" />
```
```js
const config = {
   content: {
     type: 'text',
     name: 'content',
     default: 'Hello world'
   },
   opacity: {
     type: 'slider',
     name: 'opacity',
     min: 0,
     max: 1,
     step: 0.1,
     default: 0.3
   },
   visible: {
     type: 'switch',
     name: 'visible',
     default: false
   },
}
```

The structure of the above GUI configuration items is the same as that of the component. You only need to convert the value of each field in `options` into the JSON declaration of the UI control.

👉 [View more basic control examples](https://acrodata.github.io/gui/basic-controls)

### Nested data structure

If you want to ensure that the GUI can generate any data structure, you need to design the definition format of JSON configuration items for five basic data (`string`, `number`, `boolean`, `object`, `array`). The above example has shown how three basic data types are defined.

The definition of `object` in Acrodata GUI is as follows:

```json
{
   "size": {
     "type": "group",
     "name": "Size",
     "children": {
       "width": {
         "name": "Width",
         "type": "number",
         "default": 1920,
         "suffix": "px"
       },
       "height": {
         "name": "Height",
         "type": "number",
         "default": 1080,
         "suffix": "px"
       }
     }
   }
}
```

The last array type is the most complex. Commonly used arrays include basic data arrays and object arrays, and each array also supports dynamic deletion of array items.

The following is how a dynamically deletable object array is defined:

```json
{
   "series": {
     "type": "tabs",
     "name": "Series",
     "default": [
       { "id": 1, "name": "bar" },
       { "id": 2, "name": "foo" }
     ],
     "template": {
       "name": "No.<%= i + 1 %>",
       "children": {
         "id": {
           "type": "number",
           "name": "ID"
         },
         "name": {
           "type": "text",
           "name": "Name"
         }
       }
     }
   }
}
```

If the array items of the object array are not the same, they must be defined with the `tab` type:

```json
{
   "misc": {
     "type": "tabs",
     "name": "Misc",
     "children": [
       {
         "type": "tab",
         "name": "Full Name",
         "children": {
           "firstName": {
             "type": "text",
             "name": "First Name",
             "default": "James"
           },
           "lastName": {
             "type": "text",
             "name": "Last Name",
             "default": "Bob"
           }
         }
       },
       {
         "type": "tab",
         "name": "Contact",
         "children": {
           "phone": {
             "type": "text",
             "name": "Phone",
             "default": "5550100"
           }
}
       }
     ]
   }
}
```

👉 [View more group control examples](https://acrodata.github.io/gui/group-controls)

With the above five basic controls, any data structure can be generated through nested combination.

### Limitations of JSON Schema

**_Why not use JSON Schema?_** Many people may think that using JSON Schema to define JSON data will be more standardized and more general. This idea makes sense, but JSON Schema also has certain limitations.

First of all, JSON Schema can only define the data type of the field, but cannot define the UI type of the field, so some dynamic form solutions that use JSON Schema will also add UI Schema, such as [react-jsonschema-form](https://github. com/rjsf-team/react-jsonschema-form).

In addition, the format of JSON Schema is relatively complex, and the mapping relationship between component configuration items and JSON Schema is very unintuitive.

## Build a GUI based on reactive forms

Acrodata GUI is built based on Angular's reactive form, with only about 200 lines of core code ([View source code](https://github.com/acrodata/gui/blob/main/projects/gui/gui-form.ts) ).

In order to facilitate traversing data in the template, you  need to convert the GUI `config` object into an array, and use the `registerControl` of the reactive form to register all form controls in the instance of `FormGroup`. Then use the reactive form directives `formGroupName`, `formControlName`, `formArrayName` in the template to bind controls of different `type`.

Acrodata GUI uses [Angular Material](https://material.angular.io/) as the basic component library. All styles and components are imported in modules, so redundant code will not be generated. Other component libraries can also be used.

### Start using GUI components

```html
<gui-form [config]="config" [model]="model" [form]="form" />
```

`config` represents the JSON configuration items of the GUI. The configuration items of different types of controls are slightly different, [see the documentation for details](https://acrodata.github.io/gui/). In addition to using `default` to define the default value of the control, you can also use `model` (form value, equivalent to the component's configuration item options) to define or update the default value of the form, thanks to Angular's reactive form` patchValue` method.

If you need to monitor the state or value changes of the form, you can use the `form` parameter, which can track all state changes of the form.

```ts
form = new FormGroup({});

this.form.valueChanges.subscribe(v =>{...});
this.form.get('opacity').valueChanges.subscribe(v =>{...});
```

## Summarize

The GUI shown above is very powerful, but GUI can't completely replace the dynamic forms and not all configuration items are suitable for using GUI. Because the GUI has limited control types and does not have complex logic, maybe you should make a choice to use or not in a low-code platform.

If you like [Acrodata GUI](https://github.com/acrodata/gui) or have better ideas, please feel free to communicate with me!