# OpenFlower overview

## What is OpenFlower?

OpenFlower is an easy-to-use open-source low-code platform to build customer-facing, embeddable Saas applications within minutes.

Traditionally, building an application requires complex frontend and backend interactions with hundreds and thousands of lines of code, not to mention work on packaging, integration, and deployment. OpenFlower significantly reduces the work you need to do to build and publish an application.

In OpenFlower, all you need to do is drag and drop pre-built or self-customized components onto the What-You-See-Is-What-You-Get (WYSIWYG) canvas, along with ready-to-connect databases and APIs, OpenFlower helps you build an app quickly and focus on business logic.

<figure><img src="/files/z0lEAYtoCo2FjHhI2OA6" alt=""><figcaption></figcaption></figure>

## Why choose OpenFlower?

* **Open source:** For cloud and self-hosted deployment. Makes your ideas more feasible.
* **All-in-one platform:** Connection to all kinds of data sources and APIs such as Databases, Streaming sources, and Restful APIs, and ensures your data security.
* **Embed Apps everywhere:** Apps, that are created with OpenFlower can get embedded natively into Websites, Apps, and Mobile Apps.
* **High scalability:** The deployment options allow a single container image, but also a scalable multi-container deployment, so any kind of load is manageable.
* **Extensibility:** Add functionality in OpenFlower at multiple places. From simple custom components and external libraries to new data source plugins.
* **Clean design:** Follows the principles of [Ant Design](https://ant.design/) and supports display on screens of different sizes for Desktop and Mobile Applications.
* **Built-in features:** Multi-tenant management, Multi-workspaces management, theming, mobile screen, navigation support, fine-grained access control, and audit logs.

## How to build Apps in OpenFlower?

Building an app [Hello World Tutorial](https://github.com/Flowerappeng-org/openflower/blob/main/docs/OpenFlower-overview/the-hello-world-tutorial.md):

* Quickly and securely connect to your data sources, including PostgreSQL, MongoDB, and online APIs.
* Create Data queries visually or by SQL or set up request parameters to connect APIs.
* Use our 100+ UI components to build your app UI and bind and display query data with UI components like Tables, Charts, and Lists.
* Set up event handlers to trigger queries, control components, or other actions in reaction to user interactions.
* Preview and share your app with others.


# The "Hello World" tutorial

A Hello World tutorial

1. At your first contact with OpenFlower you will see the Login Screen.

<figure><img src="/files/9X0kXAwBMzcx9ft2x2Dt" alt=""><figcaption><p>OpenFlower Login Screen</p></figcaption></figure>

If you haven't created your account yet, you can register a new account with the url /user/auth/register or the Sign up Link on the login page.

<figure><img src="/files/yAcWGae5aSGKTrMXRUzo" alt=""><figcaption><p>Sign up screen to create a new user.</p></figcaption></figure>

When you create a User without Invite-Link, this User will automatically get the role of an Administrator with their own Workspace.

The first Screen of the Admin Panel of OpenFlower shows the overview of all Applications (in the current selected Workplace). For new Installations & logins, the Workplace is named by the email of the logged in User.

<figure><img src="/files/GJ1HPERkHetLjEJJtbvF" alt=""><figcaption><p>Application overview as welcome screen</p></figcaption></figure>

2. In OpenFlower you can create and find the building blocks of applications:

* Navigations
* Mobile Navigations
* Apps
* Modules
* Folder

All these building blocks are displayed on the home screen.\
You can order these building blocks by folders.

<figure><img src="/files/7qrM9NXPkRhwiEjBC3LF" alt=""><figcaption><p>Folder for Application building blocks</p></figcaption></figure>

3. With the help of the blue "New" Button you can select and create a fresh, empty application. You will arrive at the App Editor Screen.

<figure><img src="/files/LNFz9ZS7Ar50ZrRrxTjo" alt=""><figcaption><p>Application Editor - standard screen.</p></figcaption></figure>

The main screen is divided into 4 sections. To create an Application typically starts to place any component from the red section to the application-space, yellow in the middle of the screen.

4. Components can get placed next to each other or through Layers, so components can visually overlap.

The green area helps you to gather and prepare Data for your Application. Here data queries for multiple datasources as also data transformations and even javascript functions can get managed.

The blue area helps to see a hierarchical view of the components of an application as also to see in realtime their application state with current data and events.

The blue and red area consist of 2 panels between users can switch. (green marked areas)

<figure><img src="/files/bucYDKzzkS0TJaL9wzCQ" alt=""><figcaption><p>Screen Settings and Component details</p></figcaption></figure>

In the blue section, general settings for an application can get managed, such as the applied theme, local Javascript and Style (CSS) embeds as also screen settings for expected resolutions.

In the red section, which opens automatically as soon as you click on a specific component in the application-space, all detail settings for the selected component can be made.

* Data Binding to show selected data with the component
* Event Bindings to take an user event and trigger an action
* Layout Settings to adjust positions for labels, orientation and visibility
* Style Settings to select colors, sizes and margins

5. As soon as you like, you can share an Application with selected users to test together.

<figure><img src="/files/kPf5qsZ8xgMeR9J6SRdb" alt="" width="375"><figcaption><p>You can use the "Share" Button to invite other OpenFlower Users to use the Application</p></figcaption></figure>

<figure><img src="https://github.com/Flowerappeng-org/openflower/blob/main/docs/.gitbook/assets/Application%20Editor%20%20Share%20Application.png" alt=""><figcaption><p>Select individual Users or make the Application public for everyone.</p></figcaption></figure>

Based on your settings you can invite special users only or make the application public accessible, so everyone can see the application. If you want to invite co-editors for your application, you need to give them the desired rights / role.

After all tests are done, your application is ready to hit the market. You can use the "Preview Button" to explore how your Application looks for a standard logged in user.

<figure><img src="/files/lfFOfLWg8ioiJBZ8WhR5" alt=""><figcaption></figcaption></figure>

6. With the same button, but by help of the small arrow on the right, you can finally publish the application in it's current state. Make sure, the application is then accessible (Share menu) as you expect. (public or for selected users only)


# Videos

Explore our collection of educational videos designed to help you master OpenFlower. Whether you're a beginner or an advanced user, the step-by-step tutorials, feature deep dives, and expert tips will guide you through everything you need to know.

💡 **What You'll Find:**\
✅ Beginner-friendly introductions\
✅ Advanced feature walkthroughs\
✅ Tips and best practices\
✅ Updates on new features

Start learning today and unlock the full potential of OpenFlower!

[Watch OpenFlower in 100 seconds](/readme-1/openflower-100-seconds)


# OpenFlower in 100 seconds

OpenFlower in 100 seconds

{% embed url="<https://www.youtube.com/watch?v=z1XXrKL6sNQ>" %}


# Cloud & Private Cloud

OpenFlower is available online. You can start directly to build Applications.

[OpenFlower Playground - Start here](https://prod-us1.openflower.org)

We offer a hosted version of OpenFlower in this shared public cloud for free. We take care of updates, security, and scaling of the underlying infrastructure.

The public cloud has a central database for all users and applications. We suggest using the public cloud to get to know OpenFlower and make your first tests and Applications.

OpenFlower is hosted in Google data centers. Backups are organized every night for all infrastructure components and the Databases as Snapshots.


# Self-hosting

In this article, you will be guided through how to host OpenFlower on your own server using Docker-Compose or Docker.

There are multiple ways of installation. We directly support:

* [Single Docker Image](https://github.com/Flowerappeng-org/openflower/deploy/docker) to run with a single line of command.
* Multi-Docker Image deployment for scaling scenarios with [Docker Compose](https://github.com/Flowerappeng-org/openflower/blob/main/deploy/docker/docker-compose-multi.yaml)
* Kubernetes-based deployment with [HELM Charts](https://github.com/Flowerappeng-org/openflower/tree/main/deploy/helm).
* [Heroku based deployment](/setup-and-run/self-hosting/heroku)
* [Google Cloud Platform](/setup-and-run/self-hosting/google-cloud-platform)

## 1) Start easy:

For easy setup and deployment, we provide an [all-in-one image](https://hub.docker.com/r/flowerappengorg/openflower) that bundles frontend, backend, and data persistence services altogether in one single container.

### All-in-one image <a href="#all-in-one" id="all-in-one"></a>

#### Prerequisites

* [Docker](https://docs.docker.com/get-docker/) (version 20.10.7 or above)
* [Docker Compose](https://docs.docker.com/compose/install/) (version 1.29.2 or above)

{% hint style="info" %}
Recommended system spec: 1-core CPU and 2 GB RAM.

Windows users are recommended to use PowerShell for running the commands below.
{% endhint %}

In your working directory, run the following commands to make a directory named `OpenFlower` to store the data of OpenFlower:

```bash
mkdir openflower
cd openflower
```

#### Deploy

{% tabs %}
{% tab title="Docker Compose (Recommended)" %}
Follow the steps below:

1. Download the configuration file by clicking [docker-compose.yml](https://raw.githubusercontent.com/Flowerappeng-org/openflower/main/deploy/docker/docker-compose.yaml) or running the curl command:

{% code overflow="wrap" %}

```
curl https://raw.githubusercontent.com/Flowerappeng-org/openflower/main/deploy/docker/docker-compose.yaml -o $PWD/docker-compose.yml
```

{% endcode %}

2. Start the Docker container by running this command:

   ```bash
   docker compose up -d
   ```

   \
   The docker image, about 400 MB, is downloaded during the initial start-up.

   After downloading, it usually takes less than 30 seconds to start the service.
3. Check the logs by running this command:

   ```bash
   docker logs -f openflower
   ```

   When you see `frontend`, `backend`, `redis`, and `mongo` `entered the RUNNING state`, the OpenFlower service has officially started:
4. Visit [**http://localhost:3000**](http://localhost:3000) and click **Sign up**. OpenFlower will automatically create a workspace for you, then you can start building your apps and invite members to your workspace.
   {% endtab %}

{% tab title="Docker" %}
Run the command below:

{% code overflow="wrap" %}

```bash
docker run -d --name openflower -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks" flowerappengorg/openflower
```

{% endcode %}
{% endtab %}
{% endtabs %}

#### Update to the latest version

{% tabs %}
{% tab title="Docker-Compose" %}
Run the following commands to update to the latest OpenFlower image:

`````bash
docker-compose pull
docker-compose rm -fsv openflower
docker-compose up -d

</div>

<div data-gb-custom-block data-tag="tab" data-title='Docker'>

Run the following commands to update to the latest OpenFlower image:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

```bash
docker pull flowerappengorg/openflower
docker rm -fv OpenFlower
docker run -d --name OpenFlower -p 3000:3000 -v "$PWD/stacks:/OpenFlower-stacks" flowerappengorg/openflower
```

</div>

</div>

</div>

## 2) Scale

For bigger expected loads that need scaling in a cluster environment: [separate images of backend and frontend services](https://hub.docker.com/u/flowerappengorg) with a customizable Dockerfile.

Multi-Image Installation to scale. API-Service & Node-Service can get scaled independently.

</div>

### Separate images: services in stateless containers <a href="#multi" id="multi"></a>

For bigger expected loads that need scaling in a cluster environment, we offer separate images for stateless containers of the backend and frontend service with a customizable Dockerfile. A well-functioning OpenFlower deployment consists of below services:

- **api-service**: Backend service.
- **node-service**: Backend service.
- **frontend**: Frontend service.
- **MongoDB**: Used for persisting data of users, apps, data sources, etc.
- **Redis**: Used for maintaining user sessions, rate-limiter, etc.

#### Prerequisites

- [Docker-Compose](https://docs.docker.com/compose/install/) (version 1.29.2 or above)

#### Deploy

1.  In your working directory, run the following commands to make a directory named `lowcoder` to store the data of OpenFlower:

    ```bash
    mkdir openflower
    cd openflower
    ```

2.  Download the configuration file by clicking [docker-compose-multi.yml](https://github.com/Flowerappeng-org/openflower/blob/main/deploy/docker/docker-compose-multi.yaml) or running the curl command:

    <pre class="language-bash" data-overflow="wrap"><code class="lang-bash"><strong>curl https://github.com/Flowerappeng-org/openflower/blob/main/deploy/docker/docker-compose-multi.yaml -o $PWD/docker-compose-multi.yml
    </strong></code></pre>

3.  Modify service configurations in the downloaded Dockerfile according to your needs:
    - **mongodb**: Start a new MongoDB instance on your host. You can delete this part and modify the environment variable `MONGODB_URI` of the **api-service** to use your own MongoDB.
    - **redis**: Start a new Redis instance on your host. You can delete this part and modify the environment variable `REDIS_URI` of the **api-service** to use your own Redis.
    - **api-service**: Required.
    - **node-service**: Required.
    - **frontend**: Required. Can be optional if you deploy the frontend on CDN.
4.  Start Docker containers by running this command:

    ```bash
    docker-compose -f docker-compose-multi.yml up -d
    ```

5.  Visit [**http://localhost:3000**](http://localhost:3000) and click **Sign up**. OpenFlower will automatically create a workspace for you, then you can start building your apps and invite members to your workspace.

## 3) Update to the latest version <a href="#update-multi" id="update-multi"></a>

Run the following commands to update services to the latest in a Docker Compose installation:

<div data-gb-custom-block data-tag="hint" data-style='info'>

Please also have a look at this [migration / update guide](lowcoder-version-update.md)

</div>

```bash
docker-compose -f docker-compose-multi.yml pull
docker-compose -f docker-compose-multi.yml up -d
```

## 4) Customize the deployment

This section shows how to customize deployment configurations by setting environment variables.

If you have already started Docker containers, you need to restart the containers for new configurations to take effect. For example, the way to **restart** your container running an all-in-one image is:

<div data-gb-custom-block data-tag="tabs">

<div data-gb-custom-block data-tag="tab" data-title='Docker-Compose (Recommend)'>

One single command:

```bash
docker-compose down
docker-compose up -d
```

It picks up configuration changes by stopping containers already in service and recreating new ones.

</div>

<div data-gb-custom-block data-tag="tab" data-title='Docker'>

Run the following commands to stop, remove the container already in service, and start up a new one using the newly customized deployment command.

```bash
docker stop openflower
docker rm openflower
# run your new docker run command
```

</div>

</div>

Below are examples of configuring the all-in-one image by setting environment variables in `docker-compose.yml`. If you are self-hosting with separate images, modify the `openflower-api-service` part of the `docker-compose-multi.yml` file instead.

<div data-gb-custom-block data-tag="hint" data-style='info'>

For more information about configurations and environment variables, see [Configuration](https://github.com/Flowerappeng-org/openflower/tree/main/deploy/docker).

</div>

### Use your own MongoDB and Redis

By default, OpenFlower uses the built-in MongoDB and Redis installed inside the container, and you can replace them with your own MongoDB and Redis clusters.

<div data-gb-custom-block data-tag="tabs">

<div data-gb-custom-block data-tag="tab" data-title='Docker-Compose'>

Add environment variables `MONGODB_URI` and `REDIS_URI` in `docker-compose.yml` downloaded in your working directory.\

</div>

<div data-gb-custom-block data-tag="tab" data-title='Docker'>

Add environment variables `MONGODB_URI` and `REDIS_URI` to the deployment command, as shown below:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

```bash
docker run -d --name openflower -e MONGODB_URI=YOUR_MONGODB_URI REDIS_URI=YOUR_REDIS_URI -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks flowerappengorg/openflower
```

</div>

</div>

</div>

### Run as non-root user

By default, the supervisor will run under the user `uid=9001`. You can specify the uid by adding a docker environment variable `LOCAL_USER_ID` and setting its value to a different **number**, such as `10010`.

<div data-gb-custom-block data-tag="tabs">

<div data-gb-custom-block data-tag="tab" data-title='Docker-Compose'>

Add an environment variable `LOCAL_USER_ID` in `docker-compose.yml` downloaded in your working directory.\

</div>

<div data-gb-custom-block data-tag="tab" data-title='Docker'>

Add an environment variable `LOCAL_USER_ID` to the deployment command, as shown below:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

```bash
docker run -d --name openflower -e LOCAL_USER_ID=10010 -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks" flowerappengorg/openflower
```

</div>

</div>

</div>

## 5) Secure your deployment

### Install SSL certificate

With an SSL certificate, you can securely visit self-hosted OpenFlower with HTTPS protocol. Here are the steps to install your SSL certificate before starting a container:

<div data-gb-custom-block data-tag="tabs">

<div data-gb-custom-block data-tag="tab" data-title='Docker-Compose'>

1. Copy `fullchain.pem` and `privkey.pem` to the `$PWD/stacks/ssl` directory.
2. In `$PWD/docker-compose.yml`, change the value of `ports` to `"3443:3443"`.\

</div>

<div data-gb-custom-block data-tag="tab" data-title='Docker'>

1. Copy `fullchain.pem` and `privkey.pem` to the `$PWD/stacks/ssl` directory.
2. Change the `ports` in the deployment command to `3443:3443`, as shown below:

<div data-gb-custom-block data-tag="code" data-overflow='wrap'>

```bash
docker run -d --name openflower -p 3443:3443 -v "$PWD/stacks:/lowcoder-stacks" flowerappengorg/openflower
```

</div>

</div>

</div>

<div data-gb-custom-block data-tag="hint" data-style='info'>

In cases where you have certificates with names: `server.crt` and `server.key`, you need to rename them first as follows:\
`server.crt` => `fullchain.pem`\
`server.key` => `privkey.pem`

</div>

## 6) Configure the Environment

<div data-gb-custom-block data-tag="hint" data-style='info'>

ENV Variables for Helm, Docker Compose, and Docker installations are to find in the most current version on our Github Repository.

</div>

### ENV Variables for the all-in-one image

This image contains all services needed to run OpenFlower platform in one container.

<table><thead><tr><th width="355">Environment variable</th><th width="263">Description</th><th>Default-Value</th></tr></thead><tbody><tr><td><code>LOWCODER_REDIS_ENABLED</code></td><td>If <strong>true</strong> redis server is started in the container</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_MONGODB_ENABLED</code></td><td>If <strong>true</strong> mongo database is started in the container</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_MONGODB_EXPOSED</code></td><td>If <strong>true</strong> mongo database accept connections from outside the docker</td><td><code>false</code></td></tr><tr><td><code>LOWCODER_API_SERVICE_ENABLED</code></td><td>If <strong>true</strong> openflower api-service is started in the container</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_NODE_SERVICE_ENABLED</code></td><td>If <strong>true</strong> openflower node-service is started in the container</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_FRONTEND_ENABLED</code></td><td>If <strong>true</strong> openflower web frontend is started in the container</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_PUID</code></td><td>ID of user running services. It will own all created logs and data.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_PGID</code></td><td>ID of group of the user running services.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_MONGODB_URL</code></td><td>Mongo database connection string</td><td><code>mongodb://localhost:27017/lowcoder?authSource=admin</code></td></tr><tr><td><code>LOWCODER_REDIS_URL</code></td><td>Redis server URL</td><td><code>redis://localhost:6379</code></td></tr><tr><td><code>LOWCODER_DB_ENCRYPTION_PASSWORD</code></td><td>Encryption password</td><td><code>lowcoder.org</code></td></tr><tr><td><code>LOWCODER_DB_ENCRYPTION_SALT</code></td><td>Salt used for encrypting password</td><td><code>lowcoder.org</code></td></tr><tr><td><code>LOWCODER_CORS_DOMAINS</code></td><td>CORS allowed domains</td><td><code>*</code></td></tr><tr><td><code>LOWCODER_MAX_REQUEST_SIZE</code></td><td>OpenFlower max request size</td><td><code>20m</code></td></tr><tr><td><code>LOWCODER_MAX_QUERY_TIMEOUT</code></td><td>OpenFlower max query timeout (in seconds)</td><td><code>120</code></td></tr><tr><td><code>LOWCODER_API_SERVICE_URL</code></td><td>OpenFlower API service URL</td><td><code>http://localhost:8080</code></td></tr><tr><td><code>LOWCODER_NODE_SERVICE_URL</code></td><td>OpenFlower Node service (js executor) URL</td><td><code>http://localhost:6060</code></td></tr><tr><td><code>LOWCODER_MAX_ORGS_PER_USER</code></td><td>Default maximum organizations per user</td><td><code>100</code></td></tr><tr><td><code>LOWCODER_MAX_MEMBERS_PER_ORG</code></td><td>Default maximum members per organization</td><td><code>1000</code></td></tr><tr><td><code>LOWCODER_MAX_GROUPS_PER_ORG</code></td><td>Default maximum groups per organization</td><td><code>100</code></td></tr><tr><td><code>LOWCODER_MAX_APPS_PER_ORG</code></td><td>Default maximum applications per organization</td><td><code>1000</code></td></tr><tr><td><code>LOWCODER_MAX_DEVELOPERS</code></td><td>Default maximum developers</td><td><code>100</code></td></tr><tr><td><code>LOWCODER_WORKSPACE_MODE</code></td><td>SAAS to activate, ENTERPRISE to switch off - Workspaces</td><td><code>SAAS</code></td></tr><tr><td><code>LOWCODER_EMAIL_SIGNUP_ENABLED</code></td><td>Control if users create their own Workspace automatic when Sign Up</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_CREATE_WORKSPACE_ON_SIGNUP</code></td><td>IF LOWCODER_WORKSPACE_MODE = SAAS, controls if a own workspace is created for the user after sign up</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_MARKETPLACE_PRIVATE_MODE</code></td><td>Control if not to show Apps on the local Marketplace to anonymous users</td><td><code>true</code></td></tr></tbody></table>

Also, you should set the API-KEY secret, whcih should be a string of at least 32 random characters On linux/mac, generate one eg. with: head /dev/urandom | head -c 30 | shasum -a 256

<table><thead><tr><th width="354">Environment variable</th><th width="263">Description</th><th>Default-Value</th></tr></thead><tbody><tr><td><code>LOWCODER_API_KEY_SECRET</code></td><td>String to encrypt/sign API Keys that users may create</td><td></td></tr></tbody></table>

### ENV Variables for the api-service image

<table><thead><tr><th width="352">Environment variable</th><th width="267">Description</th><th>Default-Value</th></tr></thead><tbody><tr><td><code>LOWCODER_PUID</code></td><td>ID of user running services. It will own all created logs and data.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_PGID</code></td><td>ID of group of the user running services.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_MONGODB_URL</code></td><td>Mongo database connection string</td><td><code>mongodb://localhost:27017/lowcoder?authSource=admin</code></td></tr><tr><td><code>LOWCODER_REDIS_URL</code></td><td>Redis server URL</td><td><code>redis://localhost:6379</code></td></tr><tr><td><code>LOWCODER_DB_ENCRYPTION_PASSWORD</code></td><td>Encryption password</td><td><code>lowcoder.org</code></td></tr><tr><td><code>LOWCODER_DB_ENCRYPTION_SALT</code></td><td>Salt used for encrypting password</td><td><code>lowcoder.org</code></td></tr><tr><td><code>LOWCODER_CORS_DOMAINS</code></td><td>CORS allowed domains</td><td><code>*</code></td></tr><tr><td><code>LOWCODER_MAX_ORGS_PER_USER</code></td><td>Default maximum organizations per user</td><td><code>100</code></td></tr><tr><td><code>LOWCODER_MAX_MEMBERS_PER_ORG</code></td><td>Default maximum members per organization</td><td><code>1000</code></td></tr><tr><td><code>LOWCODER_MAX_GROUPS_PER_ORG</code></td><td>Default maximum groups per organization</td><td><code>100</code></td></tr><tr><td><code>LOWCODER_MAX_APPS_PER_ORG</code></td><td>Default maximum applications per organization</td><td><code>1000</code></td></tr><tr><td><code>LOWCODER_MAX_DEVELOPERS</code></td><td>Default maximum developers</td><td><code>100</code></td></tr><tr><td><code>LOWCODER_MAX_QUERY_TIMEOUT</code></td><td>OpenFlower max query timeout (in seconds)</td><td><code>120</code></td></tr><tr><td><code>LOWCODER_MAX_REQUEST_SIZE</code></td><td>OpenFlower max request size</td><td><code>20m</code></td></tr><tr><td><code>LOWCODER_WORKSPACE_MODE</code></td><td>SAAS to activate, ENTERPRISE to switch off - Workspaces</td><td><code>SAAS</code></td></tr><tr><td><code>LOWCODER_EMAIL_SIGNUP_ENABLED</code></td><td>Control is users can create their own Workspace when Sign Up</td><td><code>true</code></td></tr></tbody></table>

### ENV Variables for the node-service image

<table><thead><tr><th width="358">Environment variable</th><th width="263">Description</th><th>Default-Value</th></tr></thead><tbody><tr><td><code>LOWCODER_PUID</code></td><td>ID of user running services. It will own all created logs and data.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_PGID</code></td><td>ID of group of the user running services.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_API_SERVICE_URL</code></td><td>OpenFlower API service URL</td><td><code>http://localhost:8080</code></td></tr></tbody></table>

### ENV Variables for the web frontend image

<table><thead><tr><th width="360">Environment variable</th><th width="263">Description</th><th>Default-Value</th></tr></thead><tbody><tr><td><code>LOWCODER_PUID</code></td><td>ID of user running services. It will own all created logs and data.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_PGID</code></td><td>ID of group of the user running services.</td><td><code>9001</code></td></tr><tr><td><code>LOWCODER_MAX_QUERY_TIMEOUT</code></td><td>OpenFlower max query timeout (in seconds)</td><td><code>120</code></td></tr><tr><td><code>LOWCODER_MAX_REQUEST_SIZE</code></td><td>OpenFlower max request size</td><td><code>20m</code></td></tr><tr><td><code>LOWCODER_API_SERVICE_URL</code></td><td>OpenFlower API service URL</td><td><code>http://localhost:8080</code></td></tr><tr><td><code>LOWCODER_NODE_SERVICE_URL</code></td><td>OpenFlower Node service (js executor) URL</td><td><code>http://localhost:6060</code></td></tr></tbody></table>

## Health checks

The API Service has an included health check endpoint from v2.4.1 on.

You can check the health of the running API Service and it's connected MongoDB & Redis by the following HTTP Call:

````bash
```
GET /api/status/health HTTP/1.1
```
`````

In response, you will get a 200 Status code if the service is up and running and a JSON like this:

```json
{
  "status": "UP",
  "components": {
    "mongo": {
      "status": "UP",
      "components": {
        "reactiveMongoSlaveTemplate": {
          "status": "UP"
        },
        "reactiveMongoTemplate": {
          "status": "UP"
        }
      }
    },
    "ping": {
      "status": "UP"
    },
    "redis": {
      "status": "UP"
    }
  }
}
```

{% endtab %}
{% endtabs %}


# Access local database or API

By default, docker-hosted instances use a [bridge network](https://docs.docker.com/network/bridge/), which does not support access to services on the docker-host via `127.0.0.1` or `localhost` from inside of the docker.

Here we take PostgreSQL as an example and show you how to access local API or database in different operating systems (OS) for docker-hosted OpenFlower.

## Preparation

Assume that a Postgres service is ready on your local host with port `5432`.

On Linux, you can check if Postgres runs locally with the command\
`pgrep -u postgres -fa -- -D`

## How to access a local database or API from OpenFlower which runs inside the Docker

{% tabs %}
{% tab title="Linux" %}

1. Enter `ifconfig docker0` in the terminal, and verify the IP configuration of Docker's virtual bridge (VB).

   Having set up Docker, the OS would automatically create `docker0` using the IP address `172.17.0.1` (by default), through which the Docker service communicates with the host.
2. Enter the same IP address into OpenFlower to configure the Postgres data source (`172.17.0.1` in this example, but use the actual IP address displayed on your terminal in the real case).

{% hint style="warning" %}
On some OS (such as Ubuntu), port access might be blocked by the firewall. To solve that, configure in the following steps:

1. Stop the firewall from blocking access from <mark style="background-color:yellow;">`docker0`</mark> by typing in the terminal:\ <mark style="background-color:yellow;">`iptables -A INPUT -p tcp -i docker0 --dport YOUR_DB_PORT -j ACCEPT`</mark>\
   `Note: In this case`Here we use Postgres port <mark style="background-color:yellow;">`5432`</mark> for <mark style="background-color:yellow;">`YOUR_DB_PORT`</mark>, and you should replace it with your own.
2. Save the firewall configuration by typing in the terminal:\ <mark style="background-color:yellow;">`iptables-save > /etc/iptables.up.rules`</mark>
   {% endhint %}
   {% endtab %}

{% tab title="Windows & Mac" %}
On Windows and macOS, Docker does not create the virtual bridge `docker0`. Then the following host name is helpful to resolve the host IP:

```
host.docker.internal
```

{% endtab %}
{% endtabs %}

On Windows and Mac:

<figure><img src="/files/RZlTB6IZT57eaJhnbnVB" alt=""><figcaption><p>Access a Database on your local network from OpenFlower which runs in Docker</p></figcaption></figure>

<figure><img src="/files/KB6kQKZvp31CgBnscIDK" alt=""><figcaption><p>Access an API on your local network from OpenFlower which runs in Docker</p></figcaption></figure>


# Google Cloud Platform

How to Setup Seperate Images on Google Cloud Platform

## Google Cloud Console OpenFlower Setup

### Creating a VPC Network

1. In the Google Cloud Console, in VPC Network, click on VPC Networks
   * Enable Compute Engine API
2. Create a VPC Network
   * Name the VPC Network
   * Disable IPv6
   * Create a custom subnet
     * Name the subnet
     * Choose the region best for your
     * Choose an IP Range
     * Private Google Access: On
     * Flow Logs: Off
     * In the subnet, setting turn on Private Google Access (it is necessary to enable this setting otherwise the cloud-run services will not be able to communicate with one another)
       * This will be in the Edit subnet section using a custom subnet creation mode
       * If the subnet creation mode is automatic, you will have to go to the region of your VPC network and enable the Private Google Access setting from there once the VPC is created.

<figure><img src="/files/e3bo5bwne8ZC1sJX52Ug" alt=""><figcaption></figcaption></figure>

### Creating a Serverless VPC Access Connector

1. In the VPC Network, create a Connector
   * Enable serverless VPC access API
2. Create a Serverless VPC Connector
   * Name the Serverless VPC Connector
   * Choose the same region as the VPC Network
   * Network: Select the VPC Network that was previously created
   * Subnet:
     * Custom IP range
     * IP range (cannot be the same subnet that was created before)

<figure><img src="/files/yj9vOIu3UZsN8cclwIRZ" alt=""><figcaption></figcaption></figure>

### Creating a Cloud NAT

* Creating a Cloud NAT is necessary because when connecting the lowcoder services to MongoDB, you must whitelist an IP address that can access the MongoDB cluster.
  * Google Cloud services will use different IP addresses each time to communicate with the internet unless a Cloud NAT is set up on the VPC

1. Within the “Network Services” in the Google Cloud Console click on “Cloud NAT” and then “Create Cloud NAT gateway”
2. Name the Cloud NAT gateway
3. Set the NAT type to public
4. Select a Cloud Router
   * Connect it to the VPC Network created previously
   * Select the region for your Router (the one VPC are running on)
   * Create a New Router
     * Name the router
5. Cloud NAT mapping
   * Source: Primary and secondary ranges for all subnets
   * Cloud NAT IP Addresses: Manual
     * IP Addresses:
       * Premium Network Service Tier
       * IP address:
         * Create and Name an IP address and then click on “Reserve”
       * IP draining: Off (default)
6. Click on Create Cloud NAT
7. After creating a Cloud NAT IP address, whitelist this IP address on your MongoDB cluster

<figure><img src="/files/GbH5WKwt8CkF6aTa1env" alt=""><figcaption></figcaption></figure>

### Creating a Redis Instance

1. Within Google Cloud Console in Memorystore, on the Redis page, create a Redis instance
2. Enable Google Cloud Memorystore for Redis API
3. Create a Redis Instance:
   * Name Redis instance in the Instance ID
   * Basic Tier
   * 1 GB of capacity is more than sufficient in our environment however if you face trouble then you can always adjust the capacity as necessary
   * Choose the same region as the VPC Network
   * Set up Connection: connect your Redis instance to the VPC network created previously

<figure><img src="/files/JmkFbuYwfchUW5sBB09k" alt=""><figcaption></figcaption></figure>

### Setting up the Node Service

1. In the Google Console go to Cloud Run
2. Create a Service
3. In the Container Image URL input: flowerappengorg/openflower-node-service
   * This is from the lowcoder docker hub (<https://hub.docker.com/r/flowerappengorg/openflower-node-service>)
4. Select the Region, same as the region for the VPC Network
5. CPU allocation and pricing: CPU is only allocated during request processing
6. Ingress Control is set to Internal (this is necessary to not expose the Node Service to the rest of the internet)
7. Authentication: Allow unauthenticated access invocations

<figure><img src="/files/DvxuNmS8ZvNd1VB3B0DH" alt=""><figcaption></figcaption></figure>

#### Container Settings

1. Container port: 6060
2. CPU allocation:
   * Only allocated during the request processing
3. Capacity (allocate as necessary)
   * Memory: 1 GiB
   * CPU: 1
4. Execution environment: Default

<figure><img src="/files/huUocyK0wZTqEasEIm4z" alt=""><figcaption></figcaption></figure>

5. Environment Variable:

* Name 1: LOWCODER\_API\_SERVICE\_URL
* Value 1: Paste the API Service URL (Once the API Service is created after the next step

<figure><img src="/files/5BTXChtudPzyjJxSuakk" alt=""><figcaption></figcaption></figure>

#### Networking Settings

* Connect to a VPC for outbound traffic
  * Use Serverless VPC Access Connectors
    * Network: Select the VPC network that was created
  * Traffic routing:
    * Route only requests to private IPs to the VPC (this is necessary to not expose the Node Service to the rest of the internet)

<figure><img src="/files/Av7moF8xCncQODFFENNP" alt=""><figcaption></figcaption></figure>

### Setting up the API Service

1. In the Google Console go to Cloud Run
2. Create a Service
3. In the Container Image URL input: flowerappengorg/openflower-api-service
   * This is from the lowcoder docker hub (<https://hub.docker.com/r/flowerappengorg/openflower-api-service>)
4. Select the Region, same as the region for the VPC Network
5. CPU allocation and pricing: CPU is only allocated during request processing
6. Ingress Control is set to Internal (this is necessary to not expose the API Service to the rest of the internet)
7. Authentication: Allow unauthenticated access invocations

<figure><img src="/files/TLv4XjTpn7LGCrf5IYR3" alt=""><figcaption></figcaption></figure>

#### Container Settings

1. Container port: 8080
2. Capacity (allocate as necessary)
   * Memory: 1 GiB
   * CPU: 1
3. Execution environment: Second generation

<figure><img src="/files/TaGigrakRZgAF8ZKHw6m" alt=""><figcaption></figcaption></figure>

4. Environment Variable: add any other environment variable as per your requirement (list of environment variables <https://raw.githubusercontent.com/lowcoder-org/lowcoder/main/deploy/docker/docker-compose-multi.yaml>)

* Variable 1:
  * Name 1: REDIS\_URL
  * Value 1: use: redis\://10.0.0.0:6379?db=databasename and replace 10.0.0.0 with your Redis instance IP address and database name with the name of your database
* Variable 2:
  * Name 2: MONGODB\_URL
  * Value 2: Paste the MongoDB URL
* Variable 3:
  * Name 3: LOWCODER\_NODE\_SERVICE\_URL
  * Value 3: Paste the Node Service URL
* Variable 4:
  * Name 4: ENABLE\_USER\_SIGN\_UP
  * Value 4: TRUE
    * If it is a new setup then set it to true; if it will be used for an existing setup then set it to FALSE
* Variable 5:
  * Name 5: ENCRYPTION\_PASSWORD
  * Value 5: lowcoder.org
* Variable 6:
  * Name 6: ENCRYPTION\_SALT
  * Value 6: lowcoder.org
* Variable 7:
  * Name 7: CORS\_ALLOWED\_DOMAINS
  * Value 7: \*

<figure><img src="/files/EX0b6PDNALM1xcKQcBcS" alt=""><figcaption></figcaption></figure>

#### Networking Settings

* Connect to a VPC for outbound traffic
  * Use Serverless VPC Access Connectors
    * Network: Select the VPC network that was created
  * Traffic routing:
    * Route all traffic to the VPC

<figure><img src="/files/9V4kdT4s4g0KRFnlXKag" alt=""><figcaption></figcaption></figure>

### Setting up the Front-End Service

1. In the Google Console go to Cloud Run
2. Create a Service
3. In the Container Image URL input: flowerappengorg/openflower-frontend
   * This is from the lowcoder docker hub (<https://hub.docker.com/r/flowerappengorg/openflower-frontend>)
4. Select the Region, same as the region for the VPC Network
5. CPU allocation and pricing: CPU is only allocated during request processing
6. Ingress Control is set to all (the front-end should be exposed to the internet)
7. Authentication: Allow unauthenticated access invocations

<figure><img src="/files/QdPnX2coEu4bLF8LSXx6" alt=""><figcaption></figcaption></figure>

#### Container Settings

1. Container port: 3000
2. Capacity (allocate as necessary)
   * Memory: 512 MiB
   * CPU: 1
3. Execution environment: Default

<figure><img src="/files/eoQUaT9y5nk7TlTtcmWU" alt=""><figcaption></figcaption></figure>

4. Environment Variable: add any other environment variable as per your requirement (list of environment variables <https://raw.githubusercontent.com/lowcoder-org/lowcoder/main/deploy/docker/docker-compose-multi.yaml>)

* Variable 1:
  * Name 1: LOWCODER\_API\_SERVICE\_URL
  * Value 1: Paste the API Service URL
* Variable 2:
  * Name 2: LOWCODER\_NODE\_SERVICE\_URL
  * Value 2: Paste the Node Service URL

<figure><img src="/files/zIHeTE30JBEcHEdbEKjs" alt=""><figcaption></figcaption></figure>

#### Networking Settings

* Connect to a VPC for outbound traffic
  * Use Serverless VPC Access Connectors
    * Network: Select the VPC network that was created
  * Traffic routing:
    * Route all traffic to the VPC

<figure><img src="/files/JiM0SQMtprpq7G7umMTG" alt=""><figcaption></figcaption></figure>

#### Post Deployment Settings

* Can set up a DNS for the front URL through Google Domains

Author: Eshaan V Saxena, (10/10/2023)


# Heroku

## Deploy

1. [Sign up](https://signup.heroku.com/) for a new Heroku account, or [log in](https://id.heroku.com/login) to get started.
2. Click to start Heroku [one-click deployment](https://heroku.com/deploy?template=https://github.com/flowerappeng-org/OpenFlower).
3. Set the **App name** which will be part of the app URL later, and choose a region.
4. (Not required) Fill in the **Config Vars** according to the descriptions. These are all optional variables used for environment-specific configuration. You can skip this step and manage environment variables later.
5. Click the **Deploy app** button.

It usually takes around three minutes to start the app.

Click the **View** button to open the new app, which should be available at **<https://your\\_app\\_name.herokuapp.com/>**.

On the welcome page, Click **Sign up** to get started. OpenFlower will automatically create a workspace for you. Then you can start building your apps and invite members to your workspace.

## Configure app settings

On the [Heroku Dashboard](https://dashboard.heroku.com/apps), go to the **Settings** tab at the top to configure app settings.

### Manage environment variables

You can manage the environment variables of your app in the **Config Vars** section.

{% hint style="info" %}
For more help, see [Managing config vars](https://devcenter.heroku.com/articles/config-vars#managing-config-vars) in the Heroku documentation.
{% endhint %}

### Install SSL Certificates

You can configure the SSL Certificates of your app in the **SSL Certificates** section. Heroku provides [Automated Certificate Management (ACM)](https://devcenter.heroku.com/articles/automated-certificate-management) and [Manual Certificate](https://devcenter.heroku.com/articles/ssl#manually-uploading-certificates-and-intermediaries) options.

Choose **Manual Certificate**, and you can install your SSL Certificates. A public certificate file usually ends in `.pem` or `.crt`.\\

### Customize domain names

1. Click the **Add Domain** button in the **Domains** section.
2. Fill in your **Domain name** and click **Next**.\\
3. Copy and paste the **DNS Target** provided by Heroku to complete the domain setup.

{% hint style="info" %}
For more help, see [Custom Domain Names for Apps](https://devcenter.heroku.com/articles/custom-domains) in the Heroku documentation.
{% endhint %}

## Update using Heroku CLI

You can use the [Heroku CLI](https://devcenter.heroku.com/categories/command-line) to update your Docker image to the latest version. The basic logic behind the following steps is to pull the newest code from the GitHub repository, build an image locally using the Docker engine, and deploy the updated image using the Heroku CLI. Before you start, make sure you have [Heroku CLI](https://devcenter.heroku.com/categories/command-line) and [Docker](https://www.docker.com/) installations locally (required).

1\. Get the latest code by cloning the [`OpenFlower-org/OpenFlower`](https://github.com/Flowerappeng-org/openflower) repository from GitHub.

```bash
git clone https://github.com/Flowerappeng-org/openflower
```

2\. Move to the `heroku` folder.

```bash
cd OpenFlower/deploy/heroku
```

3\. Log in to Heroku CLI.

```bash
heroku login
```

4\. Make sure you have a Docker engine running locally. Then log in to Container Registry.

```bash
heroku container:login
```

5\. Get the app name of your Heroku account.

```bash
heroku apps
```

6\. Build the image and push it to Container Registry.

```bash
heroku container:push web -a your_app_name
```

7\. Release the image to your app.

```bash
heroku container:release web -a your_app_name
```

It might take a few minutes to build the new image and push it to Heroku. Then you can open the app in your browser to check if it is running.

```bash
heroku open -a your_app_name
```


# Migration from Openblocks

With **OpenFlower v1.1.8** we created a drop-in replacement for **Openblocks** **v1.1.8**.\
To run it, no changes were required.

However, to **migrate** from Openblocks or OpenFlower 1.1.8 to **OpenFlower 2.0,0**, some changes are required and these are the steps to be followed:

### Migrating from Openblocks (or OpenFlower v1.1.8) to **OpenFlower v2.0.0**

{% hint style="info" %}
if not using OpenFlower v1.1.8 or the latest Openblocks released version (v1.1.8), please upgrade first to Openblocks v1.1.8
{% endhint %}

### 1) stop your Openblocks/OpenFlower:

{% tabs %}
{% tab title="All-in-one Docker image" %}
`docker compose down`
{% endtab %}

{% tab title="Multi-Image Deployment" %}
`docker compose -f docker-compose-multi.yaml down`
{% endtab %}
{% endtabs %}

### 2) create a backup copy of the **openblocks-stacks** folder

### 3) rename the **openblocks-stacks** folder to **lowcoder-stacks**

\
`mv openblocks-stacks lowcoder-stacks`

### 4) Download the latest docker-compose file.

download [docker-compose.yaml](https://raw.githubusercontent.com/lowcoder-org/lowcoder/main/deploy/docker/docker-compose.yaml) or [docker-compose-multi.yaml](https://raw.githubusercontent.com/lowcoder-org/lowcoder/main/deploy/docker/docker-compose-multi.yaml) file depending on which installation you prefer (all-in-one or multi-images)\\

### 5) Adapt standard values to your Installation

Edit the docker-compose file and make sure to update these settings - values have to match exactly what you had in your Openblocks version:

```
ENCRYPTION_PASSWORD: 
ENCRYPTION_SALT: 
MONGODB_URL:
```

### 6) start your OpenFlower

{% tabs %}
{% tab title="All-in-one Docker image" %}
`docker compose up -d`
{% endtab %}

{% tab title="Multi-Image Deployment" %}
`docker compose -f docker-compose-multi.yaml up -d`
{% endtab %}
{% endtabs %}


# Update MongoDB Versions

## Version Update of MongoDB

MongoDB stopped the support for versions < 5 already and will stop the support for version < 6 soon. It is suggested to update the MongoDB Server / Infrastructure.

The lowcoder application continues to operate smoothly on MongoDB version 4.4. However, to ensure compatibility with official MongoDB releases, an update is necessary. This update is specifically required for the all-in-one deployment of lowcoder, which includes MongoDB.

It is important to note that MongoDB version 5.0 and above requires the AVX (Advanced Vector Extensions) feature of the processor. While this feature is standard in all newer processors, it may not be supported by older hardware. Therefore, users with older systems might encounter issues running MongoDB 5.0+.

An incremental update procedure is possible without losing data. However, the major versions of MongoDB cannot be overjumped. You cannot jump to v 6 from v 4; you would first need to adapt to v 5.

Suppose you currently run v4 and want to update to v7- these are the necessary steps for the all-in-one docker image. If you operate a multi-image installation, adap the paths to your data location for your MongoDB and the database must be stopped before the process is started.

### 1) Stop OpenFlower

Stop your OpenFlower instance and wait for it to completely stop the service.

```bash
docker compose down
```

### 2) Create a Backup Copy of the **lowcoder-stacks** folder

```bash
cp -rp lowcoder-stacks lowcoder-stacks.backup
```

### 3) Run upgrade-mongo-4x-to-5x docker compose file

{% file src="/files/YFG38tL85H621s85KpI7" %}
Update MongoDB from v4 to v5
{% endfile %}

```bash
docker compose -f upgrade-mongo-4x-to-5x.yaml up -d && sleep 30
```

### 4) Set MongoDB Compatibility-Version to 5

```bash
docker exec mongodb-5 /usr/bin/mongosh --eval 'use admin' --eval 'db.adminCommand( { setFeatureCompatibilityVersion: "5.0" } )'
```

### 5) Stop and remove MongoDB 5 Update-Container

```bash
docker compose -f upgrade-mongo-4x-to-5x.yaml down
```

### 6) Run upgrade-mongo-5x-to-6x docker compose file

{% file src="/files/lgLvkXeKLIxBR2N137Lr" %}
Update MongoDB from v5 to v6
{% endfile %}

```bash
docker compose -f upgrade-mongo-5x-to-6x.yaml up -d && sleep 30
```

### 7) Set MongoDB Compatibility-Version to 6

```bash
docker exec mongodb-6 /usr/bin/mongosh --eval 'use admin' --eval 'db.adminCommand( { setFeatureCompatibilityVersion: "6.0" } )'
```

### 8) Stop and remove MongoDB 6 Update-Container

```bash
docker compose -f upgrade-mongo-5x-to-6x.yaml down
```

### 9) Run upgrade-mongo-6x-to-7x docker compose file

{% file src="/files/5a4bH1L1kIEvFWnSWsjy" %}
Update MongoDB from v6 to v7
{% endfile %}

```bash
docker compose -f upgrade-mongo-6x-to-7x.yaml up -d && sleep 30
```

### 10) Stop and remove MongoDB 7 Update-Container

```bash
docker compose -f upgrade-mongo-6x-to-7x.yaml down
```

As result your MongoDB is now updated to Version 7 without Data loss.


# OpenFlower Version Update

## OpenFlower Versioning Scheme

We try to respect as good as possible the Semantic Versioning for OpenFlower Versions

v2.4.1 for example, means:

* Major Version **"2"**. It changes when there are truly big updates that might break old features or add significant new ones. It comes when we introduce fundamentally new concepts inti the Software
* Minor Version **"4"**. It changes when new features are added in a way that doesn’t break existing ones. It should be possible to update the version and expect a working continuation of existing apps.
* Patch Version **"1"**. It changes when we do bug-fixes or improvements, that don't affect overall functionality.

We work with initiatives to add new functionality. This comes normally with the increase of the minor version. When we have a new bigger feature set ready, we introduce it as 2.4.0 for example. The Patch Version is "0". This updates should work in general, but we never can fully exclude not having introduced bugs or unexpected behaviour. Naturally we increase every day the tests and production quality and invest a lot of development time to keep also new features backward compatible.

{% hint style="warning" %}
It is suggested to test a new Minor Version with a Patch Version "0" separately as a Test-Installation. We do not suggest to update this simply on the existing older version.
{% endhint %}

After a new Minor Version we deliver typically the next patch version quite fast, considering all bugs, that we found or that was reported. The first and second Patch Versions we can count in typical cases as Stabe Versions.

{% hint style="info" %}
The patch versions > 0 are typically seen as Stable Version. It should be ok to install these directly as update.
{% endhint %}

### OpenFlower SDK

The SDK has an independent versioning - yet still following the Semantic Versioning Scheme.

Since OpenFlower v2.4.0 we follow the principle, that the SDK will have the same Major and Minor Version as the Main OpenFlower Version - but it can have an independent Patch-Version.

### External Component Plugins

Also external Component Plugins follow the Sematic Versioning Scheme with Major, Minor, and Patch versions. However, they are independent from the OpenFlower or OpenFlower SDK Versions.

### Docker Images

The provided Docker Images from OpenFlower-Org follow the Sematic Versioning of OpenFlower. For every Release of the Codebase with a new Version, we also offer the pre-built Docker Containers for the All-in-one and Multi-Docker installation.

{% embed url="<https://hub.docker.com/r/flowerappengorg/openflower/tags>" %}
OpenFlower pre-built Docker Images on Docker Hub
{% endembed %}

Next to the released Version Tags of OpenFlower there are 2 more tags with relevance:

* /latest - These images contain typically a state of development which is somewhat stable - but not yet a released Patch Version. Critical Bugs, that we solved are fastest to get in the /latest tag. The /latest tag always based on the previous last published Patch Version.
* /dev - these images can be seen like Nightly Builds. They contain evetually the latest stage of our development and are only for testing purposes.

{% hint style="danger" %}
Do not use a /dev tag image in production. These images are only to use in test installations aside.
{% endhint %}

{% hint style="info" %}
We suggest only a Multi-Docker Image installation for Production use. The all-in-one image "flowerappengorg/openflower" is suitable to test and introduce OpenFlower in an easy way.
{% endhint %}

## Version Update

To **update** from an older to a newer version or OpenFlower, please follow this guide:

{% hint style="warning" %}
Please always make a Backup / Copy of your /lowcoder-stacks folder.
{% endhint %}

The magic behind an easy update is to make use of the /lowcoder-stacks mounted folder in the docker installations. That enables you to change the Versions of OpenFlower but not touch the Application Data. In the mounted /lowcoder-stacks folder, all MongoDB application data as also Logs and Assets are stored.

In other words, if you not use an external MongoDB Service, the OpenFlower MongoDB stores the Application Data of all your workspaces and Apps there, enabling you to exchange the docker without needing further backup and restore actions.

Based on the lowcoder-stacks folder, the data of a OpenFlower Installation can be transitioned without losses when you update to a new version of OpenFlower.

<figure><img src="/files/afQpjH4umCPqdbEehRUq" alt="" width="563"><figcaption><p>Mount local folder - f.e. in Docker Desktop</p></figcaption></figure>

### 1) Stop your OpenFlower Docker Containers:

With Docker Compose

{% tabs %}
{% tab title="All-in-one Docker image" %}
`docker compose down`
{% endtab %}

{% tab title="Multi-Image Deployment" %}
`docker compose -f docker-compose-multi.yaml down`
{% endtab %}
{% endtabs %}

With pure Docker

<pre class="language-bash"><code class="lang-bash"><strong>docker stop [container-id]
</strong></code></pre>

Or in Docker Desktop

<figure><img src="/files/0KUvmR097QT1NPHeuVEc" alt=""><figcaption><p>This deletes the current Conatiner Instance - not the Docker Image itself.</p></figcaption></figure>

Stop and delete the "Container" you did run based on the old version.

{% hint style="info" %}
If you did follow the /lowcoder-stacks folder mounting, no data would be lost.
{% endhint %}

### 2) Create a backup copy of the **lowcoder-stacks** Folder

Even if it should be never a problem, still we ask you kindly always to make a copy of the whole folder. Sometimes we have no other channce than to introduce breaking changes and we want to make sure you will never loss any data.

### 3) Download the latest OpenFlower Docker images.

#### With Docker Compose

download [docker-compose.yaml](https://raw.githubusercontent.com/lowcoder-org/lowcoder/main/deploy/docker/docker-compose.yaml) or [docker-compose-multi.yaml](https://raw.githubusercontent.com/lowcoder-org/lowcoder/main/deploy/docker/docker-compose-multi.yaml) file depending on which installation you prefer (all-in-one or multi-images)

#### With pure Docker

If you use Docker Desktop, you can pull the /latest tag or a specific version tag.

<figure><img src="/files/VQpVGnzOBFPfIAUjB8gJ" alt=""><figcaption></figcaption></figure>

### 4) Adapt standard values to your Installation

{% hint style="warning" %}
For every Major Version, we likely introduce new ENV Variables. This is the reason to download the latest docker compose file. Naturally, you also can update your local copy with the new ENV Variables and other new config structures.
{% endhint %}

Edit the docker-compose file to your needs, but make sure to update only the settings. The following values must match your previous version to keep the OpenFlower MongoDB accessible for your stored Data Source Secrets.

* LOWCODER\_DB\_ENCRYPTION\_PASSWORD
* LOWCODER\_DB\_ENCRYPTION\_SALT

{% hint style="info" %}
If you lose or change the Encryption Password & Salt, the OpenFlower Database on MongoDB will work as expected - only the existing stored Credentials to Data Sources are "lost" / not accessible.
{% endhint %}

### 5) Run the new OpenFlower Docker-Image

After the image is pulled, you can run the image (create a new container) and configure directly the Ports as well as the ENV Variables.

{% hint style="warning" %}
Mind to re-connect (mount) the /lowcoder-stacks folder
{% endhint %}

#### With Docker Compose

{% tabs %}
{% tab title="All-in-one Docker image" %}
`docker compose up -d`
{% endtab %}

{% tab title="Multi-Image Deployment" %}
`docker compose -f docker-compose-multi.yaml up -d`
{% endtab %}
{% endtabs %}

## Multi Image Docker Version handling

Technically, the Docker Versions and releases go "Hand in Hand". That means that you should always operate the same backend version of API-Service-Image & Node-Service-Image as the Frontend-Image. However, in special situations, the separation of these concerns into different images gives you a degree of freedom to update only one of the images and check the compatibility.


# Traefik loadbalancer

Thanks to the amazing contribution of <https://github.com/timconsidine> OpenFlower can work smoothly with the Traefik as Loadbalancer.

This is a docker-compose.yaml file, which shows the necessary settings.

```yaml
version: "3"

services:
  ## Start OpenFlower (all-in-one)
  lowcoder-api-service:
    image: flowerappengorg/openflower:latest
    container_name: lowcoder
    environment:
      REDIS_ENABLED: "true"
      MONGODB_ENABLED: "true"
      API_SERVICE_ENABLED: "true"
      NODE_SERVICE_ENABLED: "true"
      FRONTEND_ENABLED: "true"
      PUID: "1000"
      PGID: "1000"
      DEFAULT_ORGS_PER_USER: 100
      DEFAULT_ORG_MEMBER_COUNT: 1000
      DEFAULT_ORG_GROUP_COUNT: 100
      DEFAULT_ORG_APP_COUNT: 1000
      DEFAULT_DEVELOPER_COUNT: 50
      MONGODB_URL: "mongodb://localhost:27017/lowcoder?authSource=admin"
      REDIS_URL: "redis://localhost:6379"
      ENABLE_USER_SIGN_UP: "false"
      ENCRYPTION_PASSWORD: "somethingveryunique"
      ENCRYPTION_SALT: "somethingalsounique"
      CORS_ALLOWED_DOMAINS: "*"
      LOWCODER_API_KEY_SECRET: "b37717ee6de1003921c4445a3088af17ee316a6fad270737282a8ce2acf29832"
      LOWCODER_API_SERVICE_URL: "http://localhost:8080"
      LOWCODER_NODE_SERVICE_URL: "http://localhost:6060"
      LOWCODER_MAX_REQUEST_SIZE: 20m
      LOWCODER_MAX_QUERY_TIMEOUT: 120
    volumes:
      - ./lowcoder-stacks:/lowcoder-stacks
    restart: unless-stopped
    labels:
      - traefik.docker.network=traefik
      - traefik.port=3000
      - traefik.port=3443
      - "traefik.enable=true"
      - "traefik.http.routers.lowcoder.rule=Host(`lowcoder.domain.tld`)"
      - "traefik.http.routers.lowcoder.entrypoints=websecure"
      - "traefik.http.routers.lowcoder.tls.certresolver=letsencrypt"
      - "traefik.http.services.lowcoder.loadbalancer.server.port=3000"
    networks:
      - traefik

networks:
  traefik:
    external: true
```


# SMTP Server

To enable a secure Password Reset Flow and a future eMail check on Sign-up, you need your own SMTP Server ready. To configure the SMTP Server, you can use the following ENV Variables at the **API-Server Image**.

<table><thead><tr><th width="426">Environment Variable</th><th width="223">Description</th><th>Default Value</th></tr></thead><tbody><tr><td><code>LOWCODER_ADMIN_SMTP_HOST</code></td><td>SMTP Hostname of your Mail Relay Server</td><td></td></tr><tr><td><code>LOWCODER_ADMIN_SMTP_PORT</code></td><td>Port number for the SMTP service</td><td><code>587</code></td></tr><tr><td><code>LOWCODER_ADMIN_SMTP_USERNAME</code></td><td>Username for SMTP authentication</td><td></td></tr><tr><td><code>LOWCODER_ADMIN_SMTP_PASSWORD</code></td><td>Password for SMTP authentication</td><td></td></tr><tr><td><code>LOWCODER_ADMIN_SMTP_AUTH</code></td><td>Enable SMTP authentication</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_ADMIN_SMTP_SSL_ENABLED</code></td><td>Enable SSL encryption</td><td><code>false</code></td></tr><tr><td><code>LOWCODER_ADMIN_SMTP_STARTTLS_ENABLED</code></td><td>Enable STARTTLS encryption</td><td><code>true</code></td></tr><tr><td><code>LOWCODER_ADMIN_SMTP_STARTTLS_REQUIRED</code></td><td>Require STARTTLS encryption</td><td><code>true</code></td></tr></tbody></table>


# Security

## Does OpenFlower store data from my data sources?

No, OpenFlower doesn't store any data returned from your APIs or database queries. Only the connection data of your created Datasources are stored and encrypted.

OpenFlower only serves as a proxy between the client side and your data sources. When you query your APIs or databases, the OpenFlower server connects to the data source with your credentials, forwards the request, and returns the result data to the browser. During the whole process, OpenFlower doesn't store any data from your requests or responses.

OpenFlower also provides a self-hosted version. You can deploy self-hosted OpenFlower images on your own device, on-premise deployment ensures you have total control over your resources and that your data is securely stored.

## Is it safe to log in to my databases on OpenFlower?

Yes, it is safe for the following reasons:

* All sensitive credentials, such as database passwords, are encrypted with [AES-256 encryption](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard).
* Sensitive credentials will never be sent to the browser from the server. They are only used at the server side (in the API service) and are never exposed to the browser, the browser will display "Encrypted on the server side" as a placeholder for existing Datasources.

## Other security measures within OpenFlower

OpenFlower provides secure-by-default service.

* All connections on OpenFlower Cloud are encrypted with [TLS](https://en.wikipedia.org/wiki/Public_key_certificate).
* Sensitive credentials, such as database access credentials, are encrypted with AES-256.
* Self-hosted OpenFlower instances can be configured with unique encryption salt values.
* OpenFlower Cloud only connects to your databases or APIs through allowed IPs, ensuring that your data won't be exposed to unknown IPs while using our cloud service.

## Feedback

Should you have any questions about data security in OpenFlower, please feel free to contact us. We welcome any feedback on our service from security experts and all users.


# Workspaces

OpenFlower supports Workspaces. These are isolated rooms to organize:

* User-Groups
* Users
* [Data Sources](/connect-your-data/data-source-basics)
* [Predefined Data Queries](/workspaces-and-teamwork/query-library)
* [Apps](/build-applications/create-a-new-app) & App Folders
* [Themes](/build-applications/themes-and-styling)
* Global includes (Javascript / CSS)

{% hint style="info" %}
To activate this function, please add the [ENV Variable](https://github.com/Flowerappeng-org/openflower/tree/main/deploy/docker#configuration): **COMMON\_WORKSPACE\_MODE**=SAAS
{% endhint %}

{% hint style="info" %}
From Version 2.3.3, you can add the [ENV Variable](https://github.com/Flowerappeng-org/openflower/tree/main/deploy/docker#configuration) **LOWCODER\_CREATE\_WORKSPACE\_ON\_SIGNUP** to control the behavior of your users (Member role) on self-hosted installation.\
\
true - members can use their own workspace when they sign up.\
false - members will not have their own workspace when they sign up.
{% endhint %}

{% hint style="warning" %}
If you want to close the possibility that anyone can Sign in and create an account, you can set the ENV Variable **LOWCODER\_EMAIL\_SIGNUP\_ENABLED** to "false".\
\
The Sign In / Sign Up Screen will still be shown, but the Sign Up will return an Error message and new users cannot create Accounts.\
\
This can be helpful if you have your own Identity / OAuth Provider listed and want to be sure that users only with these Identity / OAuth Providers can create Accounts on your OpenFlower installation.
{% endhint %}

The Workspace is a tool to use for organizing groups or user access to Apps. It is also used to apply a theme to a set of Apps.

<figure><img src="/files/OWIyYpEcxG71y5pmAfPo" alt=""><figcaption><p>A list of Workspaces</p></figcaption></figure>

In the public OpenFlower Cloud, the amount of Workspaces is limited to 10.

In the upper right corner, at the profile, you can find the possibility to switch between your Workspaces.

<figure><img src="/files/8NKdVkRN6tSJdM49plsG" alt="" width="563"><figcaption><p>Switch between Workspaces</p></figcaption></figure>

### Apps & App Folders

In OpenFlower, you can create Apps. Each App represents what you can create on a single Screen. Often, a complete application consists of multiple Apps. You can then organize these relationships in App Folders or separate them by Workspaces.

<figure><img src="/files/7qrM9NXPkRhwiEjBC3LF" alt=""><figcaption><p>Organize Apps into Folders to create order and show relationships</p></figcaption></figure>

### Themes

You can create multiple [Themes](/build-applications/themes-and-styling) for your apps and select a style theme for each app individually. However, each Workspace has its own collection of Themes.

<figure><img src="/files/3nLNBSy23GD2qcgAVXbr" alt=""><figcaption><p>Themes helps to style Applications individually</p></figcaption></figure>

### Global Includes

It is possible to include global CSS styles to fine-tune the visuals of Apps. Also, it may be necessary to include a Javascript script, which is executed for all Apps of a Workspace. With the help of global includes, you can achieve Workspace.

<figure><img src="/files/u65dAKcUAKf3rNqxRNIq" alt=""><figcaption></figcaption></figure>

### Further administrative Settings

On self-hosted installations, you can set up thresholds for workspaces with the help of [ENV Variables](https://github.com/Flowerappeng-org/openflower/tree/main/deploy/docker#configuration)

* **LOWCODER\_MAX\_ORGS\_PER\_USER**
  * Sets the default maximum number of Workspaces that a single user (as Admin) can create. This limit ensures that users can manage a reasonable number of organizations without overwhelming your installation.
  * **Default Value**: 100
* **LOWCODER\_MAX\_MEMBERS\_PER\_ORG**
  * Defines the default maximum number of members (including users and roles) allowed in a single Workspace.
  * **Default Value**: 1000
* **LOWCODER\_MAX\_GROUPS\_PER\_ORG**
  * **Description**: Specifies the default maximum number of groups that can be created within a Workspace. Groups are used for organizing members with similar roles or access levels, and this limit helps in keeping the group structure manageable.
  * **Default Value**: 100
* **LOWCODER\_MAX\_APPS\_PER\_ORG**
  * **Description**: Determines the default maximum number of applications that could be developed or maintained in a Workspace.
  * **Default Value**: 1000
* **LOWCODER\_MAX\_DEVELOPERS**
  * **Description**: Sets the default maximum number of Users with Developer-Role that can be registered in the Workspace.
  * **Default Value**: 100


# Members and Groups

In OpenFlower, you can organize the members of your workspace using different groups, and assign roles to groups to manage [permissions for resources](/workspaces-and-teamwork/permissions-for-resources). You can find members and groups settings in **Settings** on the OpenFlower Admin Page.

## Roles

There are the following Roles for Users in OpenFlower:

### For Workspaces

* Admin
* Member

<figure><img src="/files/MPLXVCPWNfUCeHCyQkKT" alt=""><figcaption><p>User Roles in Worspaces</p></figcaption></figure>

### For Apps

* Owner
* Editor
* Viewer

<figure><img src="/files/jAqaTg5D7Y2GL6AAiBGv" alt=""><figcaption><p>User Roles for Apps</p></figcaption></figure>

### For Datasources

* User
* Owner

<figure><img src="/files/WSXH70sIGlr1y9O8Btnm" alt=""><figcaption><p>User Roles for Datasources</p></figcaption></figure>

A User in OpenFlower is an admin for his own Workspace when registered without an invite link. However, each user can be a member of one or multiple Worspaces.

In OpenFlower, you can organize the members of your workspace using different groups, and assign roles to groups to manage permissions for resources. You can find members and groups settings in **Settings** on OpenFlower Admin Page.

## Workspace members

In the **Members** tab, there are two pre-built groups by default. This helps you fast to differentiate between your colleagues who you want to invite to develop Apps together and everyone else - including the Users of these Apps. The predefined Usergroups are:

* All Members
* Developer

Independent from these Usergroups you can define your own Usergroups to manage permissions and keep the overview of everyone who is registered and using Apps in your Workspaces.

### Invite new members

You can invite members to your workspace by managing **All Members** group. Open the group and click **Invite Members**, then you can copy the invitation link.

<figure><img src="/files/9WM2DoBiQHlP1iO7yhqU" alt=""><figcaption></figcaption></figure>

You can also find the shortcut to **Invite members** on the bottom left of the OpenFlower Admin Page.

{% hint style="info" %}
Permission to invite new members to a Workspace is restricted to workspace **admins** and **developers group**.
{% endhint %}

### Workspace roles and permissions

<table><thead><tr><th width="201">Role</th><th>Workspace and group level permissions</th></tr></thead><tbody><tr><td>Admin</td><td><p>Workspace</p><ul><li>Modify workspace information (name, logo, etc.)</li><li>Delete workspace</li><li>Manage workspace members and set their roles</li><li>View workspace members</li></ul><p>Groups</p><ul><li>Create groups</li><li>Delete groups</li><li>Manage group members and set their roles</li></ul></td></tr><tr><td>Member</td><td>None</td></tr></tbody></table>

Note that the workspace creator is automatically granted admin permissions.

## User Groups

Group-based management helps to organize members from different functional departments in your workspace. You can create User Groups in the **Members** tab.

### The default Group "Developers"

OpenFlower creates a **Developers** group by default which cannot be deleted, and workspace admins can add members to it. The members of the Developers Group can create new resources including apps, modules, navigations, folders, and data sources, and can manage the query library. Workspace admins can add members to the Developers Group.

### The default Group "All members"

To overview all Members of a Workspace, the default User Group "All members" can get used. Here, all Members of a Workspace regardless of their role or connection to other User Groups are listed.

### Own User Groups

In the **Members** tab, workspace admins can click **+ Create Group** to add a new group. The group creator automatically will be the group admin of the new User Group.

Group admins can add members to groups.

### Roles and permissions in own User Groups

The Administrator of a User Group can set an **Admin** or **Member** role for each group member. The permissions are listed in the table below.

<table><thead><tr><th width="145">Role</th><th>Permissions</th></tr></thead><tbody><tr><td>Admin</td><td><ul><li>Change group name</li><li>Delete groups</li><li>Manage group members and set their roles</li><li>View group members</li></ul></td></tr><tr><td>Member</td><td>View group members</td></tr></tbody></table>


# Permissions for Resources

OpenFlower implements [Role-based Access Control](https://en.wikipedia.org/wiki/Role-based_access_control) (RBAC) by assigning a set of permissions to different roles. These permissions determine the actions users can take on resources, including apps, modules, navigations, folders, query library, and data sources.

Only workspace admins and members of **Developers** group are allowed to create resources. Workspace admins are in fact the owner of all resources.

## 1) Application Folders

Folders help you sort out the Apps, Modules, and Navigations in your Workspace. Only workspace **Admins** and members of **Developers** group have the following permissions for folders:

* Create
* Rename
* Delete
* Move Apps, Modules and Navigations into or out of folders

## 2) Apps, modules, **navigations**

The way permissions are applicable for Apps, Modules, and Navigations are the same.

<figure><img src="/files/CpHE0Pr0JHrqh4K47fQu" alt=""><figcaption><p>Apps, Modules, and Navigations works the same way for permissions.</p></figcaption></figure>

The owners of an App, a Module, or a Navigation can add members and assign different roles to them. To set Permissions for Users or Usergroups use the **Share** button in the App Editor at the top right, then click **Add members**.

<figure><img src="/files/jAqaTg5D7Y2GL6AAiBGv" alt=""><figcaption><p>Set Permissions based on Roles for an App</p></figcaption></figure>

Choose members and / or groups from the list and set roles for them.

The available roles and their corresponding permissions are listed in the table below.

<table><thead><tr><th width="179">Role</th><th>Permissions</th></tr></thead><tbody><tr><td><ul><li>Viewer</li></ul></td><td><ul><li>View and use the app, module and navigation</li></ul></td></tr><tr><td><ul><li>Editor</li></ul></td><td><ul><li>View and use the app, module and navigation</li><li>Rename</li><li>Edit</li><li>Publish</li><li>Duplicate</li><li>Export</li></ul></td></tr><tr><td><ul><li>Owner</li></ul></td><td><ul><li>All permissions of Editor</li><li>Delete the app or module</li><li>Set roles for members</li></ul></td></tr></tbody></table>

### Anonymous User Access for Apps

If you want to make an app or a module or a navigation public to all users (including anonymous users), you can toggle on **Make the app public** in **Share** settings.

<figure><img src="/files/U8ZiTM6DO3oXmyLx4w5y" alt=""><figcaption><p>Apps can be public viewable without OpenFlower Login</p></figcaption></figure>

## 3) Data sources

Only workspace admins and developers can view the **Data Sources** page and create new data sources.

<figure><img src="/files/SK3LCIiR09cLXpPuA4b3" alt=""><figcaption><p>Overview of all Datasources of a Workspace</p></figcaption></figure>

For each data source, workspace admins and the creator can change its **Access Control**. Go to OpenFlower homepage > **Data Sources**, and click `···` > **Access Control**.

<figure><img src="/files/WSXH70sIGlr1y9O8Btnm" alt=""><figcaption><p>Set Permissions based on Roles for an Datasource</p></figcaption></figure>

In **Access Control** settings, add members or groups from the workspace, and then choose their roles (**Can use** or **Can manage**).

The permissions of these two roles are listed in the following table:

<table><thead><tr><th width="217">Role</th><th>Permissions</th></tr></thead><tbody><tr><td><ul><li>Can use</li></ul></td><td><ul><li>Use data source when editing an app/module/navigation/library query</li></ul></td></tr><tr><td><ul><li>Can manage</li></ul></td><td><ul><li>Use data source when editing an app/module/navigation/library query</li><li>Edit data source</li><li>Delete data source</li><li>Manage data source permission roles</li></ul></td></tr></tbody></table>

## 4) Query library

Workspace Admins and members of Developers group can create Query Library in OpenFlower and have read, write, use, and delete permissions for queries (if they can use the query's corresponding data source).

<figure><img src="/files/CJalGXp0oHaj4qQD8UGJ" alt=""><figcaption></figcaption></figure>


# OAuth

Since OpenFlower 2.1.3 we support OAuth for 4 standard sources. The list will increase and be completed by a generic OAUTH configuration ability.

As for now we support:

* Google
* Github
* ORY
* KeyCloak

Next on the list are:

* Atlassian
* Gitlab
* Microsoft Graph (AD / B2C) as OAuth
* LinkedIn
* Facebook

### Use OAuth providers in 2 ways.

* By Invite Link
* By workspace welcome Page

### Invite Link

When an Admin or User with appropriate rights sends an Invite Link, then this Invite Link contains the information in which workspace the new users get invited. In this way, the List of OAuth Providers at the Sign in and Sign up Screens is adapted.

<figure><img src="/files/rpgJuXRH5v9yUh7wDELM" alt=""><figcaption></figcaption></figure>

### Workspace welcome page

Based on a URL pattern you can guide new Users to your Workspace welcome page, where they will find the List of OAuth Providers at the Sign in and Sign up Screens too.

```
// URL pattern to show the Workspace welcome page
// The Workspace-ID == the Org ID.

https://<your OpenFlower location>/org/<your org Id>/auth/login?redirectUrl=...
```

### Manage Login Methods (Auth Providers)

In the Admin area of OpenFlower select Settings > Auth Providers. Here you see the list of currently active auth providers. At the beginning, you will always see the standard-provider "Email"

{% hint style="warning" %}
In OpenFlower, Auth Providers except Email are bound to the Workspace. That means, that per workspace you can have individual settings and Auth Provider.
{% endhint %}

<figure><img src="/files/YnIqzavylCHzYshkA436" alt=""><figcaption><p>the list of Auth Providers per Workspace can be different -exept the Standard Provider Email</p></figcaption></figure>

{% hint style="info" %}
As OAuth Providers are bound to a single Workspace, they will not appear on the general Sign In or Sign up screens, but only on the Workspace-related screens.
{% endhint %}


# KeyCloak

### KeyCloak as Identity Provider

To use KeyCloak as an Auth Provider, you must install and maintain an own installation of KeyCloak. A docker installation is fast achieved.

<https://www.keycloak.org/getting-started/getting-started-docker>

In KeyCloak you have to set up first a "Realm". This is a tenant within KeyCloak. Select (or create) your Realm with the top left corner menu.

In your Realm, you can then create and configure a Client. We show here the most minimal configuration to enable Sign in and Sign up with KeyCloak for OpenFlower.

### Setup a KeyCloak Client

Choose a name and id for your Client.

<figure><img src="/files/Z89De2Pji9jnqE5GbsA2" alt=""><figcaption></figcaption></figure>

Make sure the "Standard Flow" is activated, as also "Client authentication" and "Authorization". Only when checked these options, KeyCloak will issue the needed Client-Secret.

<figure><img src="/files/F8xh8cFGTJ8vso6EJWu5" alt=""><figcaption></figcaption></figure>

Configure the OpenFlower redirect URLs.

{% hint style="info" %}
For the cloud, the "Valid redirect URI" is <https://prod-us1.openflower.org>
{% endhint %}

<figure><img src="/files/qawF9F8UOKecmXOkRjzp" alt=""><figcaption></figcaption></figure>

After the setup, you can now look for the generated Client-Secret.

<figure><img src="/files/idDVYp4hAhLodPqIUj12" alt=""><figcaption><p>copy the Client Secret for the setup of KeyCloak as Auth Provioder in OpenFlower</p></figcaption></figure>

Check the Settings to make sure the right capabilities are activated...

<figure><img src="/files/Wrf4bGprvDiDgP989aqS" alt=""><figcaption></figcaption></figure>

### KeyCloak as OpenFlower Auth Provider

in OpenFlower, go to Settings > OAuth Provider and select "KeyCloak" in the "Add OAuth Provider Dialogue"

<figure><img src="/files/3aBhK4c6pHrhBHKix5Bz" alt=""><figcaption></figcaption></figure>

Now you can enter all settings for KeyCloak

{% hint style="info" %}
Instance ID is the Base URL of your KeyCloak Installation
{% endhint %}

<figure><img src="/files/tz3gn3soPA0WwKs3tMOP" alt=""><figcaption></figcaption></figure>

You can also change settings later.

<figure><img src="/files/DVL4xdCQGaumYqkgJ419" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
The minimal scope to setup is "offline\_access openid".
{% endhint %}

Thats it! Now you can use KeyCloak to offer your Users to Sign in and Sign up with KeyCloak.

<figure><img src="/files/nBlXdvTZZzK4ATY9RGWu" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/zvVxVe65sIrErgpxorUH" alt=""><figcaption></figcaption></figure>


# Google

### Google as OAuth Identity Provider

To use Google as Auth Provider, you must setup a so-called "OAuth 2.0 Client ID". You can do so in the Google Cloud Console. <https://console.cloud.google.com/apis/credentials>

<figure><img src="/files/cjhimPh58zvYWERpMTWw" alt=""><figcaption><p>You can create many OAuth 2.0 Client IDs</p></figcaption></figure>

Use the Form to create your Client ID (app registration)

<figure><img src="/files/jRwgrDwkDG9I9W5ymawd" alt=""><figcaption><p>You can set multiple redirect URLs</p></figcaption></figure>

On the right side, you will find the Client-ID and Client Secret. Now you go back to OpenFlower Settings > Auth Providers and click "Add OAuth Provider" and select Google from the list of Auth Providers.

<figure><img src="/files/KU2C8NGPpZ3rUZWoACsw" alt=""><figcaption><p>select Google as Auth Provider from the List</p></figcaption></figure>

You can now copy and paste the Client ID and Client Secret from the Google Cloud Console.

<figure><img src="/files/ork0z9ZxJTEtqwARcOCl" alt=""><figcaption></figcaption></figure>

Thats it! Now you can invite new Users to OpenFlower. They can choose Google to Sign Up (register) or Sign in / log in.

<figure><img src="/files/TCILd0j6r5YAhn2zbjPy" alt=""><figcaption><p>Based on an invite Link, users can now login with the OAuth provider Google.</p></figcaption></figure>


# GitHub

### GitHub as OAuth Identity Provider

To use GitHub as Auth Provider, you must setup a so-called "OAuth App". You can do so in your Github Profile > Settings <https://github.com/settings/developers>

<figure><img src="/files/ERwM7neli5xCd0Gqjq9b" alt=""><figcaption></figcaption></figure>

On GitHub the creation of an OAuth Client Registration is comparatively simple.

<figure><img src="/files/V7eOIYbH4vwLTEcjEkjR" alt=""><figcaption><p>GitHub Client Registration Settings</p></figcaption></figure>

After configuring the Callback URLs, you can "Generate a new client secret".

{% hint style="warning" %}
Make sure you copy the client secret directly, as it is displayed only once. Later you cannot copy it again.
{% endhint %}

Now you go back to OpenFlower Settings > Auth Providers and click "Add OAuth Provider" and select GitHub from the list of Auth Providers.

<figure><img src="/files/KU2C8NGPpZ3rUZWoACsw" alt=""><figcaption></figcaption></figure>

You can copy and paste now the Client ID and Client Secret from the GitHub Client App Registration.

<figure><img src="/files/3bpfyKeOsqNjtsPH96Ag" alt=""><figcaption></figcaption></figure>

Thats it! Now you can invite new Users to OpenFlower. They can choose GitHub to Sign Up (register) or Sign in / log in.

<figure><img src="/files/TCILd0j6r5YAhn2zbjPy" alt=""><figcaption></figcaption></figure>

When users chose to Sign in or Sign up with GitHub, they will get redirected to the GitHub Login page of the registered Client App

<figure><img src="/files/ixBWfnssnoVMNjj95Uz1" alt=""><figcaption></figcaption></figure>

Here they have to Authorize your registered Client App to act and impersonate your users.

<figure><img src="/files/VxC9ZyKEqOn9hFJOPmhx" alt=""><figcaption></figcaption></figure>


# Generic OAuth Provider

Since OpenFlower v2.4.0, a generic OAuth Provider has been introduced. The goal is to cover as many OAuth providers as possible without special implementation but give you, as an Admin, the freedom to connect to any OAuth Provider using a flexible configuration.

{% hint style="info" %}
As communicated and voted by the community, we introduced the functionality in v2.4.0 but are still optimizing it, based on your feedback. Latest by OpenFlower v2.5.x this function is stable. (We expect anyhow already before that version a stable function of it.)
{% endhint %}

OAuth Providers are configured individually per Workspace.

The configuration has three parts:

* Meta-Data
* OAuth Provider Configuration
* Information Mapping

### Setup a generic OAuth provider

As the Admin of your Workspace, go to Settings > User Authentication. Here, you will find a List of your configured User Authentication Providers.

<figure><img src="/files/APrfEAa7rqkPAVFKfTvt" alt=""><figcaption><p>Overview of configured Aouth Providers for your Workspace</p></figcaption></figure>

Now, you can use the button in the upper right corner to add a new OAuth Provider. Select "Generic".

<figure><img src="/files/xsbhxXpC54Ii9HF3njAy" alt="" width="563"><figcaption></figcaption></figure>

### .well\_known URI

The `.well-known/openid-configuration` URI is specifically part of the OpenID Connect (OIDC) standard. If a provider supports OpenID Connect, this endpoint provides a JSON document with the configuration details for OAuth and OIDC operations. OpenFlower will try to use this configuration data and will fill out the standard OAuth Provider Configuration fields as well as possible in the next screens of the Generic OAuth Provider Configurator.

<figure><img src="/files/iWrJpKlen0BQKN7eXstg" alt="" width="563"><figcaption><p>Enter your Well Known URI to auto-fill the configuration in Step 2</p></figcaption></figure>

### Popular Services

Here are some popular services and their OpenID configuration Endpoints

#### Google

```
https://accounts.google.com/.well-known/openid-configuration
```

#### Facebook

```
https://www.facebook.com/.well-known/openid-configuration
```

#### Microsoft

```
https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
```

#### LinkedIn

```
https://www.linkedin.com/oauth/.well-known/openid-configuration
```

#### Apple

```
https://appleid.apple.com/.well-known/openid-configuration
```

#### Amazon Cognito

```
https://cognito-idp.{region}.amazonaws.com/{userPoolId}/.well-known/openid-configuration
```

#### Salesforce

```
https://login.salesforce.com/.well-known/openid-configuration
```

#### Dropbox

```
https://www.dropbox.com/.well-known/openid-configuration
```

#### PayPal

```
https://www.paypal.com/.well-known/openid-configuration
```

#### Slack

```
https://slack.com/.well-known/openid-configuration
```

### Popular IDMs that you can self-host

#### Keycloak

```
https://{yourKeycloakDomain}/auth/realms/{yourRealm}/.well-known/openid-configuration
```

#### Okta

```
// Self-Host
https://{yourOktaDomain}/.well-known/openid-configuration
// Cloud (domain depends on the region)
https://{yourApp}.okta-emea.com/.well-known/openid-configuration
```

#### Auth0

```
// Self-Host
https://{yourAuth0Domain}/.well-known/openid-configuration
// Cloud
https://{yourApp}.auth0.com/.well-known/openid-configuration
```

#### Ory

```
// Self-Host
https://{yourOryHydraDomain}/.well-known/openid-configuration
// Cloud
https://{yourApp}.projects.oryapis.com/.well-known/openid-configuration
```

#### Authentik

```
https://{yourAuthentikDomain}/application/o/.well-known/openid-configuration
```

#### IBM Security Access Manager (ISAM)

```
https://{yourISAMDomain}/mga/sps/oauth/oauth20/.well-known/openid-configuration
```

### OAuth Source Meta-Data

Meta Data describes the Auth Source, allowing you to influence the visual representation of the OAuth Provider in the Sign In / Sign Up screen.

<figure><img src="/files/kSVHwuBnPwRJ0rCPKNXl" alt="" width="563"><figcaption><p>Enter a Displayed Name, Category, Icon and a Description</p></figcaption></figure>

{% hint style="danger" %}
In OpenFlower v2.4.0 you must set the field "Source" to GENERIC
{% endhint %}

From OpenFlower v2.4.1, the field "Source" will be hidden and filled out automatically.

### OAuth Configuration

In Step 2, you can now set up the configuration Data for your OAuth Provider. We introduced this in v2.4.0, and the screen will surely be extended with features in the upcoming versions.

<figure><img src="/files/U0a7vSipTRLXYyxtcpCZ" alt="" width="563"><figcaption></figcaption></figure>

{% hint style="danger" %}
Scopes must be set with a space character between the scopes, not comma-separated.
{% endhint %}

{% hint style="info" %}
Some providers do only support OAuth - but not (yet?) OpenID. This means that the User Introspection Endpoints /userinfo are not available. For this case you can activate or deactivate this Introspection. (This function comes into effect at OpenFlower v2.4.1)
{% endhint %}

### Provider-Side Configuration

At your IDM you would need to prepare an OAuth Client resp. a OAuth Client Application. There are multiple settings.

* Redirect URL. Here you enter your domain of your installation like **<http://localhost:3000>** or **<https://prod-us1.openflower.org>**
* Scopes: You should set the following Scopes if possible. **offline\_access** and **profile** are important.
  * openid
  * offline\_access
  * email
  * profile
* Supported OAuth2 flows: To handle User-Logins you need to activate the **Authorization Code Flow.** Also you may need to activate the **Refresh Token** possibility.
* Client authentication mechanism: Here, you can set **HTTP Body**

### OAuth Data Mapping

We introduce the possibility of mapping Data from OAuth providers to OpenFlower. We just started it in v2.4.0, and in this version, we enabled 4 attributes.

* UID (The User-ID in the IDM System)
* Email (The Email Address of the User in the IDM System)
* Username (The Name of the User)
* Avatar (The profile picture of the User)

<figure><img src="/files/es0GhtyJzSSQtNLCJs48" alt="" width="563"><figcaption><p>OData Mapping</p></figcaption></figure>

<figure><img src="/files/1McSKXrhqdUt3bKiC0vc" alt=""><figcaption></figcaption></figure>

The second screen shows how the Avatar and Username come into effect after the Mapping.

{% hint style="warning" %}
In v2.4.0 we support mapping out of the JWT (access\_token) from the IDM. In future versions, we will also support the mapping of Attributes from the OpenID /userInfo endpoint.
{% endhint %}

{% hint style="info" %}
In future versions of OpenFlower, we will also support Attribute Matching of Token Claims to User Groups and Roles.
{% endhint %}


# Query library

The **Query Library** lets you store, reuse and share Data Queries for all Data Sources in a workspace. You can create and run queries in the Query Library, and call them from any OpenFlower app. This helps you to organize and pre-define important queries for colleagues that work with you on Apps for example.

The main function of the Query Library is to create and manage reusable Queries with Versioning.

## 1) Create a new query

1. Click **+ New** and select a data source. You can rename the query on demand.

<figure><img src="/files/XiG7tBB2Zdq7mxVqxoqU" alt=""><figcaption><p>Overview of Data Queries and Action Buttons to create new Data Queries</p></figcaption></figure>

Write code and click ▶ **Run** to get results. The results will display in the bottom pane of the window.

<figure><img src="/files/CJalGXp0oHaj4qQD8UGJ" alt=""><figcaption><p>You can create Data Queries for example as SQL Script for Databases</p></figcaption></figure>

If available based on the Datasource, helping Metadata will get displayed on the right side to create Queries faster.

### Input parameters

There are cases where you want to pass dynamic parameters to a query. OpenFlower supports that by introducing input configuration of a query.

Click **Add** in the **Inputs** tab in the right upper pane to add input parameters.

With the template notation `{{}}`, you can use the dynamic Input parameters in the Query. To test the Input Parameter, you can set test values that are directly used in the Query.

<figure><img src="/files/iXwtMYC5rGBoPXplmV8z" alt=""><figcaption><p>Using Input Parameters for dynamic Queries.</p></figcaption></figure>

{% hint style="warning" %}
You can only reference variables instead of writing JavaScript code in `{{}}`.
{% endhint %}

To edit the name and description of the input variables, click `...` and select **Edit** or directly click the title of the variable.

## 2) Publish Queries

When you finish writing a version of the query, you can publish it. Click `···` > **Publish**.

<figure><img src="/files/r4ZMXpRDKrhPCt7xGPYx" alt=""><figcaption><p>Queries need to get published to be usable in Apps</p></figcaption></figure>

### History versions of Queries

To view and restore the history versions of a Query, go to `···` > **History version**.

You can restore your current draft to a history version by selecting the target version from the history version list and clicking **Restore this version**. Click **Exit** to return to edit mode.

### Version management of Data Queries

Queries may be updated on demand. You can make an adjustment to a query to a newer version and at the same time maintain the older version. Such version management is achievable in the query library. Click `···` in the top right corner to publish different versions of queries, and you can view and restore history versions.

The Query Library automatically suggests an Up-Versioning Format to choose from, (Minor to Major Version Change) so you can use [semantic versioning](https://semver.org/) (also known as SemVer) to name your version.

<figure><img src="/files/kkl6JnPaPsg8UmZai25T" alt=""><figcaption><p>Semantiv Versioning to Data Queries</p></figcaption></figure>

## 3) Using Data Queries from Query Library

In an app or module, Click **+ New** in the **Query editor** in the bottom pane, then select **Import from Query Library**.

Select a query from the library from the dropdown list, choose your desired version and pass in the corresponding input parameters.

<figure><img src="/files/LE3qFM3Hpp3eznSTGCMi" alt=""><figcaption></figcaption></figure>

## 4) Export and Import of Queries

### Export of Data Queries

You can export a Query as a JSON Definition file. This helps to use a Query across different Lowcocer installations for example, or simply as a Backup.

{% hint style="info" %}
Be aware, that the connected Datasource of a Query may have different security credentials at different installation locations.
{% endhint %}

<figure><img src="/files/bB8mwLjiSMLVXGEYwLmI" alt=""><figcaption><p>With "Export to JSON" you can backup a query or store it externally to use it in other OpenFlower Installations.</p></figcaption></figure>

### Import Queries

Based on the JSON Definition file, you can import Queries into OpenFlower. On the Query Library Overview Screen, you can use the Button "Import from File"

<figure><img src="/files/58tO3LqJqADxPC4nZNSe" alt=""><figcaption><p>Import Data Queries from a JSON Definition File.</p></figcaption></figure>

## Permissions

Workspace **Admin** and members of **Developers** group have access to the **Query Library** and have read and write permissions to all queries in the library.


# OpenFlower Marketplace

The OpenFlower Application Marketplace is a platform where the OpenFlower Community comes together to share and explore applications and modules.

It's the spot for app developers and business people to find innovative solutions in OpenFlower that can help streamline operations and enhance project outcomes. Here, users can contribute their own creations, tapping into a pool of shared knowledge and resources.

For us It's all about harnessing the power of collaboration to bring the best out of the OpenFlower platform, making it easier for everyone to achieve their goals with speed of light.

## Global and Local App Marketplace

The place where Apps are built and shared on the Marketplace of [prod-us1.openflower.org](https://prod-us1.openflower.org), we call the "global Marketplace".

If you operate a self-hosted installation you will be able to see and use the Apps from the global Marketplace always. However, you can share apps in a local Marketplace too, which is only available across all workspaces of your self-hosted installation.

In self-hosted installations, both Marketplaces are available and show the apps that are shared on these Marketplaces.

## Use Apps from Marketplace

{% embed url="<https://app.supademo.com/demo/FAj2tiD2WnnveGkLUW4A8>" %}

## Publish Apps to Marketplace

{% embed url="<https://app.supademo.com/demo/me6zeg4Oe9GCKtAiMWTCu>" %}

{% hint style="info" %}
If you publish your App in the OpenFlower Editor on [prod-us1.openflower.org](https://prod-us1.openflower.org), your App will be listed on the "global Marketplace" and every OpenFlower User can see and use the App.
{% endhint %}

{% hint style="warning" %}
If you publish your App in the OpenFlower Editor on your self-hosted Installation, only Users inside your self-hosted Installation can see and use these Apps.
{% endhint %}

## Adding Title, Description, Category and Icon

In the left App-Settings Menu, you can add Title, Description, Category, and an Icon to your App, so the display on the Marketplace is informative.

{% embed url="<https://app.supademo.com/demo/KsbwqBCpWK6OZascdMI-E>" %}

## Before you publish your App to the Marketplace

#### 1. Audit Your Application for Sensitive Data

* **Review Local Data Sources**: Examine all local data sources within your application to identify any that contain sensitive information. This includes user information, API keys, passwords, and any other data that should not be publicly accessible.
* **Check Local Data Queries**: Look through your local data queries to ensure they do not reference or pull in sensitive data inadvertently.

#### 2. Remove or Anonymize Sensitive Data

* **Anonymize Data**: If your application requires example data to demonstrate functionality, replace real data with anonymized or fictional data that does not relate to any real individuals or entities.

#### 3. Replace Local Data Queries and Sources with Neutral Equivalents

* **Implement Neutral Data Queries**: Replace queries that access sensitive data with neutral data queries. These should demonstrate the app's functionality without using or exposing real data.
* **Use Neutral Data Sources**: Switch out any local data sources containing sensitive information with neutral, example-based sources. Ensure these sources clearly illustrate how the app operates without relying on real data. Remove any Data Source from your application, which uses Passwords or other Backend Side encrypted credencials. This includes development environment configurations that might contain API keys or database credentials. You can use anonymous accessible APIs or place Demo-Data in [Temporary States](/business-logic-in-apps/write-javascript/temporary-state) and bind queries to these Temporary States for demonstration purposes.

#### 4. Place a documentation in the app

To help others to use your app, you can use a Text-Display component with Markdown at the bottom and write a README, so other users understand how to use the App and its components & logic.


# Create a new App

## App or Module?

A [Module](/build-applications/create-a-new-app/modules) is a special type of App, which can be **re-used** inside of other Modules or Apps and especially it is possible to [embed](/publish-apps/embedd-an-app) the Module directly in your native Web-Application outside of OpenFlower.

The Editor for Apps and Modules is the same, only Modules have some extra settings and parameters regarding the embedding.

If you would like to [embed](/publish-apps/embedd-an-app) or re-use your Application, choose "Module". If you want only to build a fullscreen Application and eventually hang it in a Navigation, use "App".

{% hint style="info" %}
Be aware that nested Modules "in each other" can cause a slowdown of the Application. It is always a consideration of listed elements in terms of the re-usability of child components or faster Application behavior. When creating Modules, testing regarding the speed of the overall Application and consumption of resources is necessary.
{% endhint %}

## Create a new Application or Module

Click **New > App** (or **Module**) on the upper right of OpenFlower Admin Page. The App or Module will get created and the view switches automatically to the App Editor.

Only workspace **admins** and members of **Developers** group can create apps.

<figure><img src="/files/GJ1HPERkHetLjEJJtbvF" alt=""><figcaption><p>Creat Applications with the upper right button.</p></figcaption></figure>

You can rename the App at the left-top of the App editor.

<figure><img src="/files/NHyoRg2fqnTn6zUki6Zr" alt="" width="563"><figcaption><p>Renaming an App with the top left Menu</p></figcaption></figure>

## Export and import an app

### Export an app

Apps can get exported to a JSON Definition file at multiple places. For example, click your app name and select **Export to JSON**, the app will be exported to a JSON file.

<figure><img src="/files/DNbcq5WMwi8ocTduyoa7" alt="" width="563"><figcaption></figcaption></figure>

### Import an app

You can import an existing app in a JSON format by clicking **New > Import**.

<figure><img src="/files/miOl2GjI6VlB0GY79XYQ" alt="" width="563"><figcaption></figcaption></figure>


# Modules

One of the most **powerful concepts of OpenFlower** is **Modules**. OpenFlower Modules are Applications, that can get **included and repeated** in other Applications. You can insert a module into an app or other modules (referred to as *external apps* in the rest of this document) and it **functions just like a single component**. Also, Modules can get [**integrated/embedded natively in other Web Applications**](/publish-apps/embedd-an-app)**!**

When building an app, you want to reuse a bundle of components and queries across different apps. Imagine a User-Profile Card. Profile-Image, Name, Contacts. If you create these Elements inside of a Module, you can integrate this in all other Apps as it would be a single component.

## Module basics

### Create a new module

Select **New > Module** at the top right of the Admin Page to enter the module editor.

<figure><img src="/files/T2ea7v8DJtIDbC894Khe" alt=""><figcaption><p>Create a new Module like a normal App</p></figcaption></figure>

### Editing of Layout, Components, and Data Queries

There are no differences in editing a Module or App other than the Module Settings. Placement of Components and Dataqueries behaves the same as in the App Editor. You can insert components and create queries the same way as your normal App-building process.

In the Module Editor, you can resize a module by dragging the bottom-right corner to the desirable default size. Be aware, that the screen in the editor does not show the real horizontal size of the module, as this size is defined at the App, where the Module is placed.

{% hint style="info" %}
Any changes you made at Modules are only available and active for Apps that use the Module after the new publishing of the Module!
{% endhint %}

<figure><img src="/files/tQZk0YUmQOEGxDGGQgBE" alt=""><figcaption><p>Modules get created and edited as normal Apps.</p></figcaption></figure>

Toggle the button in the **Properties** panel to control whether the **component height scales with the container**. This only works for the fixed-height components within the module.

### Insert module to apps or other modules

When editing an App or Module, select **Insert > Extensions > Modules** to display the modules that you have access to.

<figure><img src="/files/I5ei3eZqA4UeobQtWEbr" alt=""><figcaption><p>You can select available modules in the Extension Panel</p></figcaption></figure>

Then drag and drop the module onto the canvas, set input parameters, and change styles inside the module's **Properties** panel.

<figure><img src="/files/ZLfZykl4xhCJ9ceKu0Vp" alt=""><figcaption><p>Modules are placed liko other Components too</p></figcaption></figure>

The horizontal size is free to choose from. However, the Module inside considers the bounding box size as 100% of its horizontal size.

<figure><img src="/files/0EhcbN3RycNweMLY9NSM" alt=""><figcaption><p>The Module Width scales by the placement in the App</p></figcaption></figure>

### Module settings

As modules can get embedded, they need to be able to interact with outside apps or websites. There are four elements to support communication with a Module.

* **Inputs**: dynamic parameters passed into the current module from external apps.
* **Outputs**: set data exposed to external apps.
* **Methods**: methods exposed to external apps. For example, you can define a module with a `resetTitle` method, so that external apps can call it to reset the module's title.
* **Events**: module events can be handled by external apps. For example, you can define an event named `dataChanged`, so that external apps can add event handlers to it once the `dataChanged` event is fired by the module.

<figure><img src="/files/Z6gWjyW2B7IfM8Cc3qaK" alt=""><figcaption><p>Modules have Settings &#x26; possibility to test these - to communicate with the outside App or Website.</p></figcaption></figure>

## Module settings

### Input

Module inputs are parameters passed to the module from external Apps. The supported input types are **data**, **string**, **number**, **array**, **boolean**, and **query**. The first five are data types. Choose **data** to not restrict it to a specific data type. When choosing the type of **query**, you can pass in a query from external apps and trigger it inside the module. Then you can reference a module input parameter by its **name**.

#### Add a new Input

In the **Settings** tab (green), click **+** **Add** to create a new **input**. Click the input to rename it and select a data type.

#### Input Test

To quickly test the effectiveness of Input parameters during editing a Module, you can use the **Input Test** function. In the **Module Editor**, select the whole Module to conduct tests with simulated input data in the **Properties** panel of the Module (blue).

<figure><img src="/files/kTaLQg0AcXhgTMY5JgmZ" alt=""><figcaption><p>Modules can receive dynamically input data</p></figcaption></figure>

{% hint style="info" %}
Make sure, that you remove any test data when you publish the Module.
{% endhint %}

### Output

Module outputs are the data exposed to an app or other modules. You can inspect the outputs of a module in the data browser and access them via JavaScript code in `{{ }}`. Then you can check out and reference the outputs of a module in apps or other modules using it.

{% hint style="info" %}
We suggest to chose output data names carefully.

* Make sure they express the module origin
* Make sure they express the inner source (from data, from action etc.)
  {% endhint %}

### Method

Methods are used to call functions / action in a Module from outside - your App or Webpage where you embed a Module.

#### Add a new Method

In the **Settings** tab, click **+** **Add** to create a new Module Method. Click the Method to rename it and select an action.

<figure><img src="/files/mKRPzg5yruXU0h7jANOi" alt=""><figcaption><p>From an App, one can call Methods of a embedded Module.</p></figcaption></figure>

{% hint style="warning" %}
When creating a Method for a Module, you cannot create input parameters for the Method and can only reference parameters within the scope of the current Module.
{% endhint %}

#### Call a Method

When using modules, you can call a module method in two ways:

* In event handlers, select **Action** > **Control component**, and then select the **component** and **method**.
* Use dot notation in JavaScript queries. For example, `module1.clearAll()` calls the method `clearAll()` of `module1`.

<figure><img src="/files/e48blvIHoRnxAHOJvqOS" alt=""><figcaption><p>In the App, Methods of an embedded Module are accessible like for other Components.</p></figcaption></figure>

#### Method Test

You can simulate testing method calling in the **Method Test** function.

### Event

Events are used to transfer signals from a module to external apps, such as defining `orderPlaced` `OrderModified` events for an order management module. You can add and fire events within a module, and deal with them using [Event Handlers](#event-handlers) externally.

#### Add a new event

In the **Settings** tab, click **+** **Add** to create a new module event. Click the event to set its name.

#### Trigger an event

In the following example, the event `contentChange` is triggered when the content in any input box changes. You can trigger it in two ways:

* In **Event handlers** of both input components, click **+ Add**, select **Change** as the component event and **Trigger module event** as the action, and finally select the module event `contentChange`.
* Use dot notation in JavaScript queries: `contentChange.trigger()`.

#### Event handlers

When an app uses a module, you can configure how the current app reacts to the module's events. For example, in **Event handlers** of `module1`, click **+ Add**, and select the module event `contentChange`and the action **Show notification**.

#### Event Test

The following example conducts event tests in the **Event Test** section.

## Permissions

End users are automatically granted permission to view modules in apps or other modules they have viewing permissions with. If you are an editor and wish to use a module in external apps, you should at least have the viewer's role of that module.

## Releases and versions

OpenFlower stores the historical versions of your modules for your reference. Click **Preview > Publish** at the top right. The release management of a module is identical to that of an app. For detailed information, see [version history](/build-applications/create-a-new-app/version-and-release-management).

{% hint style="info" %}
For an unpublished module, the apps and modules use its latest version; while for a published one, you need to publish it again to sync your latest changes.
{% endhint %}


# Version and Release Management

Version and release management aims at providing a stable live version for end users and allows you to build and test your app safely.

By default, end users interact with the latest version of your app. After drafting and modifying your ideas, you can preview and compare with different history versions, and finally publish the version that meets all your needs. The newly published version now becomes live, and all your future updates will not affect the live version until you publish again.

## History versions

OpenFlower automatically stores all your history changes during the construction of an app, such as the addition or deletion of the components, adjustment of the property of the components, rearrangement of the layout, and update of a custom function. You can use it to refer to your earlier ideas or revert to a previous version.

App history versions are only accessible to editors and admins.

### View a history version

You can view any version of your previous drafts. Click **Preview** **>** **History**.

<figure><img src="/files/ONsdJoYjLm1WVrXSxveB" alt="" width="375"><figcaption></figcaption></figure>

### Restore a history version

You can restore your current draft to a history version.

1. Select the target version from the history version list
2. Click **Restore this version** on the topbar

<figure><img src="/files/PfcjYy8Q1NxNzV2CN62u" alt=""><figcaption></figcaption></figure>

## Release management

Apps are usually upgraded on a regular basis to include new features, improve business processes, and correct failures. However, the development of a new version of an app takes time and you need to ensure that user experience is not affected during this progress.

By default, end users interact with the latest version of your app. Once you finalize the sketch for a newer version of your app, you can proceed to publish it. Click **Preview > Publish**.

Now the newly released version becomes live for all end users, and all your future updates will not affect the live version until you publish again.


# App Editor

In OpenFlower, the Application Designer is where you assemble components, create queries to interact with data, and publish your apps. It consists of five areas:

1. Toolbar on top
2. Canvas (yellow)
3. Data browser (blue)
4. Query editor (green)
5. Component and property pane (red)

<figure><img src="/files/LNFz9ZS7Ar50ZrRrxTjo" alt=""><figcaption><p>The standard view of the App Editor</p></figcaption></figure>

The left pane is switchable, so you can view the Data browser or App-wide settings for layout and page includes (Javascript and CSS). You can:

* Set Canvas size
* [Set App Theme](/build-applications/themes-and-styling)
* Manage included JavaScripts and CSS style

The right pane is switchable too and offers the searchable list of all Components that can get placed in your App. In the "properties Mode" you can see all customization details for the currently selected Component on the Canvas.

<figure><img src="/files/bucYDKzzkS0TJaL9wzCQ" alt=""><figcaption><p>The Details / Settings Panels of the App Editor Screen</p></figcaption></figure>

## Toolbar

The Toolbar is made up of a top bar and a left sidebar.

On the top bar, you can:

* Change the App name
* Export to a JSON file
* Control the visibility of the left and right panes as also the query editor (middle)
* Share your App
* Preview and deploy your App
* Check the history versions on an App

On the left sidebar, you can:

* Show or hide the **Data Browser**
* Start tutorial for application designer

## Canvas

The canvas (yellow) at the center of the editor is where you design and see the overall UI. Drag and drop components onto the canvas. You can imagine it like a blank page at the beginning.

The canvas has an automatic, magnetic Grid system. Components will visually always snap to the nearest left and top grid line. This allows a fast but ordered layout of the Application.

<figure><img src="/files/P8XpS2q0RSTHJf774OV0" alt="" width="563"><figcaption></figcaption></figure>

You can influence the background color as also the horizontal amount of grid cells in the [theme](/build-applications/themes-and-styling).

## Data Browser

The data browser (blue) on the left pane contains **Queries**, **Components,** and **Globals** sections. Here you can inspect the data in a tree structure in real-time. For example, to check the returned data of a specific query or component, find its node in the belonging section and click to expand.

<figure><img src="/files/4ovbBXmz2wCHFxRsW4cy" alt=""><figcaption></figcaption></figure>

## Query editor

The query editor (green) is at the bottom and consists of two sections: query list (with **Queries** and **Metadata** tabs) on the left, and query settings ( with **General**, **Notification,** and **Advanced** tabs) on the right. Create queries to interact with data sources here.

<figure><img src="/files/VjWUhKnBKFq0Vp5Rfjtz" alt=""><figcaption></figcaption></figure>

Queries link data sources to your apps and perform CRUD (create, read, update, and delete) operations on data sources.

The query editor is flexible and adapts the options to build queries to the data source. You can compare SQL Database and OpenAPI queries for example.

## Component and property pane

The component and property pane (red) is located on the right of the window. Drag components onto the canvas from the **Insert** tab and edit the properties of the components in the **Properties** tab.

When a component is selected on the canvas, the **Properties** tab will be activated automatically and display the properties of that component.


# Visual Components

With Version 2.0.0 of OpenFlower there are 56 Components.

These components share a number of [common settings and configurations](/build-applications/app-editor/visual-components/common-component-settings).

### Data visualization Components

<table data-column-title-hidden data-view="cards" data-full-width="false"><thead><tr><th>button</th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Text</td><td></td><td></td><td><a href="/files/8EXpxFB3scCC1hRDoupv">/files/8EXpxFB3scCC1hRDoupv</a></td></tr><tr><td>Table</td><td><a href="/pages/Xpu4ngLa4Un1UwYKTN60">/pages/Xpu4ngLa4Un1UwYKTN60</a></td><td></td><td><a href="/files/JoFSo9erBkKIkKXLZp6q">/files/JoFSo9erBkKIkKXLZp6q</a></td></tr><tr><td>Image</td><td><a href="/pages/TZPcluIkDfyScOOEKk1K">/pages/TZPcluIkDfyScOOEKk1K</a></td><td><a href="/pages/TZPcluIkDfyScOOEKk1K">/pages/TZPcluIkDfyScOOEKk1K</a></td><td><a href="/files/0tyFni9zcOi9V86KLb3D">/files/0tyFni9zcOi9V86KLb3D</a></td></tr><tr><td>List View</td><td><a href="/pages/rgXsXgGrE1SoM0NVJqKg">/pages/rgXsXgGrE1SoM0NVJqKg</a></td><td><a href="/pages/rgXsXgGrE1SoM0NVJqKg">/pages/rgXsXgGrE1SoM0NVJqKg</a></td><td><a href="/files/49I57Ww4hLQb9vraeLnw">/files/49I57Ww4hLQb9vraeLnw</a></td></tr><tr><td>Charts</td><td><a href="/pages/PYyMM1M1LdEXTXMTMQzW">/pages/PYyMM1M1LdEXTXMTMQzW</a></td><td><a href="/pages/PYyMM1M1LdEXTXMTMQzW">/pages/PYyMM1M1LdEXTXMTMQzW</a></td><td><a href="/files/VScBVhtj68MlmAuHlthK">/files/VScBVhtj68MlmAuHlthK</a></td></tr><tr><td>Rating</td><td></td><td></td><td><a href="/files/mwoZxSBNvipb1vZpucoh">/files/mwoZxSBNvipb1vZpucoh</a></td></tr><tr><td>Calendar</td><td><a href="/pages/i0TKmM4JwgVI21dMxZAZ">/pages/i0TKmM4JwgVI21dMxZAZ</a></td><td></td><td><a href="/files/F514WuaX4yorV1hSpKYR">/files/F514WuaX4yorV1hSpKYR</a></td></tr><tr><td>Link</td><td></td><td></td><td><a href="/files/DSNPXeEr53CJNIKgLoF6">/files/DSNPXeEr53CJNIKgLoF6</a></td></tr><tr><td>Progress</td><td></td><td></td><td><a href="/files/RqTdN1O6MwXaFljqYCnu">/files/RqTdN1O6MwXaFljqYCnu</a></td></tr><tr><td>Progress - Circle</td><td></td><td></td><td><a href="/files/ZiAlNwN8vY4Na19R41ME">/files/ZiAlNwN8vY4Na19R41ME</a></td></tr><tr><td>File Viewer</td><td></td><td></td><td><a href="/files/js9JZGDTdGy4y9MM2SZ1">/files/js9JZGDTdGy4y9MM2SZ1</a></td></tr><tr><td>QR Code Display</td><td></td><td></td><td><a href="/files/yBGtNMTzvRlwa4m97q0Z">/files/yBGtNMTzvRlwa4m97q0Z</a></td></tr><tr><td>Json Explorer</td><td></td><td></td><td><a href="/files/8iwqThPMEZsGMisWfLMC">/files/8iwqThPMEZsGMisWfLMC</a></td></tr><tr><td>Tree</td><td></td><td></td><td><a href="/files/SxoZfu5zDQJfjUhVEBkO">/files/SxoZfu5zDQJfjUhVEBkO</a></td></tr><tr><td>Audio</td><td></td><td></td><td><a href="/files/OOU54GLkDjWe0W1X7i1p">/files/OOU54GLkDjWe0W1X7i1p</a></td></tr><tr><td>Video</td><td></td><td></td><td><a href="/files/pvdfWzgaycCJpKfW9XKp">/files/pvdfWzgaycCJpKfW9XKp</a></td></tr><tr><td>Carousel</td><td></td><td></td><td><a href="/files/5HUEtbK9Byuw78T4Rd6S">/files/5HUEtbK9Byuw78T4Rd6S</a></td></tr><tr><td>Iframe</td><td></td><td></td><td><a href="/files/15LS2FXntHtjz9FQbOK4">/files/15LS2FXntHtjz9FQbOK4</a></td></tr></tbody></table>

### Data entry Components

<table data-column-title-hidden data-view="cards"><thead><tr><th>button</th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td>Form</td><td></td><td><a href="/files/cbyZc2efDYwAVEVJiBaG">/files/cbyZc2efDYwAVEVJiBaG</a></td></tr><tr><td>Json Schema based Form</td><td></td><td><a href="/files/GMMpP8tLrpS3zgGAmPw7">/files/GMMpP8tLrpS3zgGAmPw7</a></td></tr><tr><td>Rich Text Editor</td><td></td><td><a href="/files/keyllo43sO2lMbCJeKxG">/files/keyllo43sO2lMbCJeKxG</a></td></tr><tr><td>Table</td><td><a href="/pages/Xpu4ngLa4Un1UwYKTN60">/pages/Xpu4ngLa4Un1UwYKTN60</a></td><td><a href="/files/JoFSo9erBkKIkKXLZp6q">/files/JoFSo9erBkKIkKXLZp6q</a></td></tr><tr><td>Input</td><td></td><td><a href="/files/RAeaDJJNxjNC50woY5jk">/files/RAeaDJJNxjNC50woY5jk</a></td></tr><tr><td>Text Area</td><td></td><td><a href="/files/Xmcyx4Sxm9qsQwtFvMgf">/files/Xmcyx4Sxm9qsQwtFvMgf</a></td></tr><tr><td>Password Field</td><td></td><td><a href="/files/n5C9czAv5Lirr4hdBb6Y">/files/n5C9czAv5Lirr4hdBb6Y</a></td></tr><tr><td>Number Input Field</td><td></td><td><a href="/files/7iQKEb1WEopbZFH3bal2">/files/7iQKEb1WEopbZFH3bal2</a></td></tr><tr><td>Select Field</td><td></td><td><a href="/files/F2vXAlVDTOqdA9Z918nQ">/files/F2vXAlVDTOqdA9Z918nQ</a></td></tr><tr><td>Multi Select Field</td><td></td><td><a href="/files/2cQKjavZnPH3a9Uvkhy5">/files/2cQKjavZnPH3a9Uvkhy5</a></td></tr><tr><td>Tree Select</td><td></td><td><a href="/files/DinV0KCXSksmshAMBMOx">/files/DinV0KCXSksmshAMBMOx</a></td></tr><tr><td>Cascader</td><td></td><td><a href="/files/LPnZebYQgaOYzxGG25X0">/files/LPnZebYQgaOYzxGG25X0</a></td></tr><tr><td>Slider</td><td></td><td><a href="/files/xzTEVmdZFWEBFYe63OTa">/files/xzTEVmdZFWEBFYe63OTa</a></td></tr><tr><td>Range Slider</td><td></td><td><a href="/files/VqOoBMGH61A4xWkGXV8u">/files/VqOoBMGH61A4xWkGXV8u</a></td></tr><tr><td>Segmented Control</td><td></td><td><a href="/files/uvvlVnKwENTE59PZhgnu">/files/uvvlVnKwENTE59PZhgnu</a></td></tr><tr><td>Dropdown</td><td></td><td><a href="/files/OkXspLRC3ws57bbcx0sa">/files/OkXspLRC3ws57bbcx0sa</a></td></tr><tr><td>Switch</td><td></td><td><a href="/files/T4jSmVrghF5gTR8nMY1e">/files/T4jSmVrghF5gTR8nMY1e</a></td></tr><tr><td>Checkbox Field</td><td></td><td><a href="/files/8vU7Zc6IhEtsBH48ucz7">/files/8vU7Zc6IhEtsBH48ucz7</a></td></tr><tr><td>Radio Buttons</td><td></td><td><a href="/files/eEMsaOJ9DWfSKGvrnE84">/files/eEMsaOJ9DWfSKGvrnE84</a></td></tr><tr><td>Rating</td><td></td><td><a href="/files/mwoZxSBNvipb1vZpucoh">/files/mwoZxSBNvipb1vZpucoh</a></td></tr><tr><td>Navigation</td><td></td><td><a href="/files/Rt7ddmuEu6jzgXdHTJWL">/files/Rt7ddmuEu6jzgXdHTJWL</a></td></tr><tr><td>File Upload</td><td><a href="/pages/pvvlTFOaKhoPSJDnZWdG">/pages/pvvlTFOaKhoPSJDnZWdG</a></td><td><a href="/files/brptLZKzBmBgfUtvmvmY">/files/brptLZKzBmBgfUtvmvmY</a></td></tr><tr><td>QR &#x26; 1D Code Scanner</td><td></td><td><a href="/files/vNld0IOAYcHPdlwlX3Sw">/files/vNld0IOAYcHPdlwlX3Sw</a></td></tr><tr><td>Signature Input Field</td><td></td><td><a href="/files/y9SHqYCEY1m6Zk8xTvK4">/files/y9SHqYCEY1m6Zk8xTvK4</a></td></tr><tr><td>Calendar</td><td></td><td><a href="/files/F514WuaX4yorV1hSpKYR">/files/F514WuaX4yorV1hSpKYR</a></td></tr><tr><td>Date Input Field</td><td></td><td><a href="/files/IV9vPChe6wzxyonHq8q9">/files/IV9vPChe6wzxyonHq8q9</a></td></tr><tr><td>Date Range Input Field</td><td></td><td><a href="/files/SHr3PLqkXjMBucgJoy3r">/files/SHr3PLqkXjMBucgJoy3r</a></td></tr><tr><td>Time Input Field</td><td></td><td><a href="/files/DKSOCKSjmbI6x9zkAX6u">/files/DKSOCKSjmbI6x9zkAX6u</a></td></tr><tr><td>Time Range Input Field</td><td></td><td><a href="/files/GhXnozcVZmKn2RSllWbW">/files/GhXnozcVZmKn2RSllWbW</a></td></tr><tr><td>Json Editor</td><td></td><td><a href="/files/pt5TjiHd6FdYikNKhclU">/files/pt5TjiHd6FdYikNKhclU</a></td></tr><tr><td>Image Editor</td><td></td><td><a href="/files/pTdkCfdII1ZTxdWaolWp">/files/pTdkCfdII1ZTxdWaolWp</a></td></tr></tbody></table>

### Layout Components

<table data-column-title-hidden data-view="cards"><thead><tr><th>button</th><th data-hidden data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Container</td><td></td><td><a href="/files/clooK9QQxb8rGX2xoTbd">/files/clooK9QQxb8rGX2xoTbd</a></td><td></td></tr><tr><td>Tabbed Container</td><td></td><td><a href="/files/bNE3pQzqcx6G6iLuT0Yr">/files/bNE3pQzqcx6G6iLuT0Yr</a></td><td></td></tr><tr><td>Modal</td><td></td><td><a href="/files/yJw8HsUWN6RNVFaCnn1I">/files/yJw8HsUWN6RNVFaCnn1I</a></td><td></td></tr><tr><td>List View</td><td><a href="/pages/rgXsXgGrE1SoM0NVJqKg">/pages/rgXsXgGrE1SoM0NVJqKg</a></td><td><a href="/files/49I57Ww4hLQb9vraeLnw">/files/49I57Ww4hLQb9vraeLnw</a></td><td><a href="/pages/rgXsXgGrE1SoM0NVJqKg">/pages/rgXsXgGrE1SoM0NVJqKg</a></td></tr><tr><td>Toggle Button</td><td></td><td><a href="/files/NKMFs0uwnratvgGKHULO">/files/NKMFs0uwnratvgGKHULO</a></td><td></td></tr><tr><td>Divider</td><td></td><td><a href="/files/7dc35DbNQrvf3Kec4QeL">/files/7dc35DbNQrvf3Kec4QeL</a></td><td></td></tr><tr><td>Grid</td><td></td><td><a href="/files/q3Ivx8y4EnCVhMJ4LBoR">/files/q3Ivx8y4EnCVhMJ4LBoR</a></td><td><a href="/pages/rgXsXgGrE1SoM0NVJqKg">/pages/rgXsXgGrE1SoM0NVJqKg</a></td></tr><tr><td>Drawer</td><td><a href="/pages/xSf9tPeIDZ4AY0qS2nqo">/pages/xSf9tPeIDZ4AY0qS2nqo</a></td><td><a href="/files/Eb2JhIYXyfURd1xkwFcB">/files/Eb2JhIYXyfURd1xkwFcB</a></td><td></td></tr><tr><td>Collapsible Container</td><td></td><td><a href="/files/16X8dXGv9LOqndhetehO">/files/16X8dXGv9LOqndhetehO</a></td><td></td></tr></tbody></table>

### Special Components

<table data-column-title-hidden data-view="cards"><thead><tr><th>button</th><th data-hidden data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Json Editor</td><td></td><td><a href="/files/pt5TjiHd6FdYikNKhclU">/files/pt5TjiHd6FdYikNKhclU</a></td><td></td></tr><tr><td>Json Explorer</td><td></td><td><a href="/files/8iwqThPMEZsGMisWfLMC">/files/8iwqThPMEZsGMisWfLMC</a></td><td></td></tr><tr><td>Json Schema based Form</td><td></td><td><a href="/files/GMMpP8tLrpS3zgGAmPw7">/files/GMMpP8tLrpS3zgGAmPw7</a></td><td></td></tr><tr><td>QR Code Display</td><td></td><td><a href="/files/yBGtNMTzvRlwa4m97q0Z">/files/yBGtNMTzvRlwa4m97q0Z</a></td><td></td></tr><tr><td>QR &#x26; 1D Code Scanner</td><td></td><td><a href="/files/vNld0IOAYcHPdlwlX3Sw">/files/vNld0IOAYcHPdlwlX3Sw</a></td><td></td></tr><tr><td>Signature Input Field</td><td></td><td><a href="/files/y9SHqYCEY1m6Zk8xTvK4">/files/y9SHqYCEY1m6Zk8xTvK4</a></td><td></td></tr><tr><td>File Viewer</td><td></td><td><a href="/files/js9JZGDTdGy4y9MM2SZ1">/files/js9JZGDTdGy4y9MM2SZ1</a></td><td></td></tr><tr><td>Tree</td><td></td><td><a href="/files/SxoZfu5zDQJfjUhVEBkO">/files/SxoZfu5zDQJfjUhVEBkO</a></td><td></td></tr><tr><td>Calendar</td><td></td><td><a href="/files/F514WuaX4yorV1hSpKYR">/files/F514WuaX4yorV1hSpKYR</a></td><td><a href="/pages/i0TKmM4JwgVI21dMxZAZ">/pages/i0TKmM4JwgVI21dMxZAZ</a></td></tr><tr><td>Image Editor</td><td></td><td><a href="/files/pTdkCfdII1ZTxdWaolWp">/files/pTdkCfdII1ZTxdWaolWp</a></td><td></td></tr><tr><td>Iframe</td><td></td><td><a href="/files/15LS2FXntHtjz9FQbOK4">/files/15LS2FXntHtjz9FQbOK4</a></td><td></td></tr></tbody></table>

### Extensible Components

<table data-column-title-hidden data-view="cards"><thead><tr><th>button</th><th data-hidden data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Chart / EChart</td><td><a href="/pages/PYyMM1M1LdEXTXMTMQzW">/pages/PYyMM1M1LdEXTXMTMQzW</a></td><td><a href="/files/VScBVhtj68MlmAuHlthK">/files/VScBVhtj68MlmAuHlthK</a></td><td><a href="/pages/PYyMM1M1LdEXTXMTMQzW">/pages/PYyMM1M1LdEXTXMTMQzW</a></td></tr><tr><td>Json Schema Form</td><td></td><td><a href="/files/GMMpP8tLrpS3zgGAmPw7">/files/GMMpP8tLrpS3zgGAmPw7</a></td><td></td></tr><tr><td>Custom Component</td><td></td><td><a href="/files/7dlDa16OIOiShEMOCBuM">/files/7dlDa16OIOiShEMOCBuM</a></td><td><a href="/pages/fN9nXBvesByaTI73YjJr">/pages/fN9nXBvesByaTI73YjJr</a></td></tr></tbody></table>


# Common Component Settings

The visual Components of OpenFlower share some general Settings and Configurations. You may find nevertheless Components, for which these or that Setting may not apply - and so it's not shown in the Application Editor.

<figure><img src="/files/9w58UWTMSireR34XfIGV" alt=""><figcaption><p>Click on any Component opens the Properties panel to show all Settings &#x26; Configurations.</p></figcaption></figure>

### Component Name

The component Name is important as it is the same time the identifier to address a visual Component via Javascript or in any dynamic way.

{% hint style="info" %}
It makes sense to decide in general between [Snake- and Camel-Case](https://www.freecodecamp.org/news/snake-case-vs-camel-case-vs-pascal-case-vs-kebab-case-whats-the-difference/). Kebab case is not suggested, as the "-" between words can be interpreted in Javascript as minus-operation in edge cases.
{% endhint %}

<figure><img src="/files/UYfjKfb3bRnek2OXEGwD" alt=""><figcaption><p>Make sure you give every component a self-speaking name</p></figcaption></figure>

<figure><img src="/files/pQmetUMoED75987WVQ6Q" alt=""><figcaption><p>The component name is a selector for example in ui based Event handlers</p></figcaption></figure>

<figure><img src="/files/b9P6T0t2ZzRRxjUvYkOh" alt=""><figcaption><p>Also in Javascript you use the same name to address the Object, its Attributes and Functions.</p></figcaption></figure>

### Label

Many Components have the possibility to show Labels for the user, so it is clealy communicated what for example an input field is meant for.

<figure><img src="/files/tduynnxkAxTxvXWAXxIY" alt=""><figcaption><p>Labels helps the users of Apps to understand interaction.</p></figcaption></figure>

#### Orientation

<figure><img src="/files/bfrkjj52r27JgaE4l25S" alt=""><figcaption><p>Labels can have different orientations.</p></figcaption></figure>

<figure><img src="/files/sY2V6lIxAGAmPbzhiGPd" alt=""><figcaption><p>Labels can be oriented left or top of a component. The text itself can additionally be oriented left-side or right side.</p></figcaption></figure>

#### Tooltip

<figure><img src="/files/g2umIKTNaD8P13zl1kxL" alt=""><figcaption><p>Additionally it can be helpful for users to use Tooltips for descriptions and suggestions.</p></figcaption></figure>

#### Internationalization

Based on the language information of the browser, one can translate text in OpenFlower on the fly.

<figure><img src="/files/kxE14nHvwIQ3D2Jaw3lK" alt=""><figcaption><p>get the User-Language from the Browser</p></figcaption></figure>

For Labels and Tooltips, a [Handlebar Javascript expression](/build-applications/app-editor/data-selection-and-javascript) can get used to reading from a translation register (JSON).

```javascript
// reading the User Language from Browser 
{{navigator.language || navigator.userLanguage}}
```

```json
// a Translation Register as JSON
{
	"product_price": [{
		"en-US": "Price"
	}, {
		"de-DE": "Preis"
	}, {
		"fr-FR": "Prix"
	}],
	"product_name": [{
		"en-US": "Product Name"
	}, {
		"de-DE": "Produktname"
	}, {
		"fr-FR": "Nom de produit"
	}]
}
```

<figure><img src="/files/eiuuPmfLVMgNtYsb2Wa8" alt=""><figcaption><p>Use a Translation Register as JSON</p></figcaption></figure>

```
// dynamically reading from the Translation Register JSON for the label
{{translations.value.product_price.find(item => item.hasOwnProperty(userLanguage.value))[userLanguage.value]}}
```

### Layout

The Layout Settings section can display different settings, based on the selected Component. I most of the cases here you find Settings for the placement of a Component.

* Orientation (left center right justify) (where applicable)

<figure><img src="/files/Ex1F2WTX6r5N0uQSG1jS" alt=""><figcaption></figcaption></figure>

* Vertical space (fixed, automatic) (where applicable)

<figure><img src="/files/Er3BjTlGz17urlmYs1mS" alt=""><figcaption></figcaption></figure>

* Prefix and Suffix Icons (where applicable)

<figure><img src="/files/7LIFA103KwSMAXSzsmfJ" alt=""><figcaption></figcaption></figure>

### Disable & hide

Most of the Components can be hidden and/or disabled dynamically by a [Handlebar Javascript expression](/build-applications/app-editor/data-selection-and-javascript).

#### Disable Components

<figure><img src="/files/OQJgqKFoMLdiuXKCJXO8" alt=""><figcaption><p>Many components can set in "disabled" State to not hide Components - but indicate that User cannot enter Data right now.</p></figcaption></figure>

#### Hide Components

Nearly every Component can get "hidden and shown" dynamically. This is useful to switch dynamically between Application elements upon user input or interaction.

<figure><img src="/files/JnfXIe8ATMDLi0OD3ClJ" alt=""><figcaption><p>Components can get hidden by static or dynamic Setting</p></figcaption></figure>

#### Vertical space of hidden Components

When Components are hidden, they do not consume vertical space. This is a special ability, which comes with a small challenge in the Application Editor. The advantage is, that multiple Components could get dynamically switched (shown or hidden), so you can dynamically react to user inputs or interactions.

<figure><img src="/files/TKrojusXt4uDocGjLSVg" alt=""><figcaption><p>hidden Components does not consume vertical space, but can get selected.</p></figcaption></figure>

As soon as a Component is hidden, a "small closed eye icon" indicates this state in the App Editor. The component can still get dragged and selected. As soon as selected, it will show as long as selected its true height.

<figure><img src="/files/LqLnaJz3MYmmxiracdaB" alt=""><figcaption><p>As soon as selected or dragged, a Component shows it height.</p></figcaption></figure>

You can now place other components in the same vertical place as the hidden Component. This can lead to a small challenge in the App Editor to drag and place Components that overlap. It is suggested to work then with static true/false setting for the attribute "Hidden", so you can find the desired Component displayed with its height when you need it.

<figure><img src="/files/lpCYiHvSdaPe1Onl51gc" alt=""><figcaption><p>Components that are hidden can pe placed vertically at the same position.</p></figcaption></figure>

{% hint style="info" %}
In OpenFlower v2.0.0 the fields "Hidden" and "Disabled" are unfortunately at different places in the Properties Panel across the components. We will fix and order this in a future version.
{% endhint %}

### Event handlers

One of the powerful concepts of OpenFlower are the Event handlers. Based on User interaction almost every Component can release Events, on which you can react with different actions.

<figure><img src="/files/aolTWruczpSdazrXUFDy" alt=""><figcaption><p>For each components a set of possible User interaction Events is accessible</p></figcaption></figure>

#### User Interaction Event Types

* Change (when user input data changes)
* Focus (when the user points to the component
* Blur (when the user left the Component)
* Submit (when the user submits a Form, where the Component belongs to)
* Click (when a button like Component is clicked)
* Open / Close (for collapsible Components)
* Switch Tab (for Tabbed containers)

{% hint style="info" %}
You can find a list of Event Handlers here: \[TODO list of event handlers)
{% endhint %}

When a Component supports User interaction Events, you can have one or multiple actions based on a single Event type or different Event types.

<figure><img src="/files/q39gfyY7e7CmsC1uRvxD" alt=""><figcaption><p>Each Component which supports Events can have multiple Handlers (Actions) for Event Types.</p></figcaption></figure>

#### Actions for Events

Each Event handler has an Action out of a list of possible Actions. Each action has individual Settings.

* Run Query
* Control Component
* Run Javascript
* Set temporary State
* Go to App
* Show Notification
* Go to URL
* Copy to Clipboard
* Export Data

[More about the Event Handlers you can find here](/build-applications/app-interaction/event-handlers).

#### Order of Event Actions

{% hint style="info" %}
In a future version of OpenFlower, we will make sure it is possible to change the order of the Event Handlers. As for now, it is not possible and you would need to plan the order of Event Actions if your App needs multiple Event Handlers for a single Component.
{% endhint %}

### Style

Components support a variety of [Styling settings](/build-applications/themes-and-styling) like colors, borders, and backgrounds.

<figure><img src="/files/oU9KlpDSuTIXG9OBqWip" alt=""><figcaption><p>Different Components have different support for styleable Attributes</p></figcaption></figure>


# File upload

<figure><img src="/files/3ZdHD5vTm7bwPqd6zJ8y" alt="" width="256"><figcaption><p>File Upload Component</p></figcaption></figure>

## Basics

The File upload helps you to able users to upload binary and text Files and process this data further in OpenFlower to send it to local or remote Storage and other Backends.

### File type

You can input an array of strings to restrict the types of the files to be uploaded. The default value of file type is empty, meaning that no limitation is pre-defined. Each string value in a specified file type array should be a [unique file type specifier](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers) in one of the following formats.

* A valid case-insensitive filename extension, starting with a period character ("."), such as `.png`, `.txt`, and `.pdf`.
* A valid string in [MIME format](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) without an extension.
* String `audio/*` indicating "any audio file".
* String `video/*` indicating "any video file".
* String `image/*` indicating "any image file".

For example, when the value of file type is `[".pdf", ".mp4", "image/*"]`, you can upload PDF files, MP4 files, and any type of image files.

### Upload type

You can decide whether to upload a single file, multiple files, or a directory.

<figure><img src="/files/s4VztwY5KwvzTZJ1ofBU" alt=""><figcaption><p>Configure the File Upload to accept individual, multiple Files or whole Folders</p></figcaption></figure>

### Display uploaded files

Switch on or off **Show upload list** to display or hide the list of the uploaded files. You can also set this property via JS code. By default, its value is "true".

The upload list presents the file names of all uploaded files in chronological order. You can also access the name of the uploaded files via the property `files[index].name`. When hovering your mouse over a file, the 🗑️ icon appears and you can click it to delete the corresponding file.

### Parse files

Toggle **Parse files** and OpenFlower will try to parse the uploaded file data structure into objects, arrays, or strings. This does not work with binary data but with structured text data like Excel, JSON, and CSV files.

<figure><img src="/files/zmD7oa2EyGzepkVgJ2pw" alt=""><figcaption><p>OpenFlower can try to parse the content of the files so you can directly access it.</p></figcaption></figure>

You can access the parsed result via the property `parsedValue`.

For each uploaded file OpenFlower will try to parse and you can access the data of the files then in the array.

<pre><code>// access the structured content of an uploaded File in OpenFlower Apps
<strong>file1.parsedValue[0]
</strong><strong>// the array contains the data for each uploaded file.
</strong>file1.parsedValue[0], file1.parsedValue[1], file1.parsedValue[2]
</code></pre>

<figure><img src="/files/HH8WjRKYahN76lbf22Rj" alt=""><figcaption><p>You can access the contents of Excel, CSV and JSON Files as JSON Data object after upload and parse</p></figcaption></figure>

## Validation

Under the validation tab, you can configure how many files are allowed to be uploaded, as well as the minimum and maximum size of a single file to be uploaded.

<figure><img src="/files/8cUOjN8c0uk6QxNeH6oV" alt=""><figcaption><p>You can define the minimal and maximal size of files that can get uploaded</p></figcaption></figure>

### Max files

When the upload type is "Multiple" or "Directory", you can set **Max files** to limit the maximum number of files to upload. If the number of files to be uploaded exceeds this threshold, the latest uploaded files will replace the oldest ones.

<figure><img src="/files/dAGjzNSOeGF7KVBJ5PkX" alt=""><figcaption><p>If multiple files can get uploaded, you can define the maximal amount of files</p></figcaption></figure>

### File size

You can set the minimum and maximum size of the files to upload, using KB, MB, GB, or TB units. The default unit for file size is byte. When the size of the uploaded file exceeds the limit, you will see a global alert.

### Access uploaded files

Files uploaded via the file upload component are stored in browser cache memory in **base64-encoded** string format. To store these files in data sources, you need to build queries to connect to databases or APIs. You can view the properties of the uploaded files in the data browser in the left pane, or access property names in `{{}}` or JS queries via JS code. Commonly used properties are as follows.

* `value`: A list of the content of the uploaded files, encoded in base64.

```
// Access the File content (represented as base64)
{{file1.value[0]}} // will return "WwogIHsKICAgICJpZCI6ICJkODE5NGI3Mi1lZGNiLTRhMWMtYTRlMC1lZT...
```

* `files`: A list of metadata of the uploaded files, including `uid`, `name`, `type`, `size`, and `lastModified`.

```
// Access the File metadata information. 

{{file1.files[0].name}} // will return "open_issues.json"

{{file1.files[0].type}} // will return "application/json"

{{file1.files[0].type}} // will return "247285" (in bytes)
```

* `parsedValue`: A list of the value of the parsed files.


# Charts and graphs

Charts and graphs are visual representations of data that are used to simplify complex information and make it easier to understand. They can help highlight key insights and provide a quick summary of data that would otherwise be difficult to interpret. Charts and graphs come in different forms, such as bar graphs, line graphs, pie charts, scatter plots, and more, each suited for different types of data and analytical purposes.

OpenFlower allows you to insert multiple forms of charts and graphs into your apps to satisfy your needs in different use cases.

## UI mode

### Data format

In UI mode, the **Chart** component supports presenting data stored as an array of JS objects. Each object field corresponds to a column in tabular format. The objects in the following array contain three fields: `date`, `fruit`, and `count`.

```json
[
  { "date": "2022-03-01", "fruit": "apple", "count": 4 },
  { "date": "2022-03-01", "fruit": "banana", "count": 6 },
  { "date": "2022-04-01", "fruit": "grape", "count": 10 },
  { "date": "2022-04-01", "fruit": "apple", "count": 3 },
  { "date": "2022-04-01", "fruit": "banana", "count": 2 }
]
```

You can also use JS code in `{{}}` to reference data from other components or queries, or to transform data to meet specific needs.

For example, the query result of `query1` is as follows.

```json
{
  "date": [
    "2022-03-01",
    "2022-03-01",
    "2022-04-01",
    "2022-04-01",
    "2022-04-01"
  ],
  "fruit": ["apple", "banana", "grape", "apple", "banana"],
  "count": [4, 6, 10, 3, 2]
}
```

You can transform it using transformer `transformer1` with the following JS code.

```javascript
let dates = query1.data.date
let fruits = query1.data.fruit
let counts = query1.data.count
let result = []
for (let i = 0; i < dates.length; i++) {
  result.push( {'date': dates[i], 'fruit': fruits[i], 'count': counts[i]} )
}
return result;
```

Then reference the value of the transformer `{{transformer1.value}}` as the data for the chart.

### Chart type

OpenFlower supports four types of charts: bar chart, line chart, scatter chart, and pie chart. You can select the chart type in **Properties** > **Data** > **Chart type**. You can also customize the layout and style of your chart in **Properties** tab.

### X-axis

Bar charts, line charts, and pie charts map values to categorical variables. Thus, in such charts, the X-axis usually shows non-numeric data—for example, date or department.

By default, OpenFlower automatically detects the X-axis data and its type. You can also manually select one among "Category axis", "Value axis", "Time axis", or "Log axis". For detailed information, see [X axis type](https://echarts.apache.org/en/option.html#xAxis.type).

### Chart series

In most types of charts, the **Chart series** (Y-axis) presents numeric values for the categories on X-axis. By default, OpenFlower populates all numeric fields to Y-axis. You can hide unnecessary fields in **Properties** > **Chart series**.

## Echarts JSON

Apart from the built-in charts and graphs, you can also plot your data with [Apache ECharts](https://echarts.apache.org/en/index.html), an open-source JS visualization library. You only need to complete the **Configuration** > **Option** field in JSON format. For detailed information, see [ECharts docs](https://echarts.apache.org/en/option.html#title) and [ECharts examples](https://echarts.apache.org/examples/en/index.html).

For an instance of using Echarts, see [Stacked Area Chart](https://github.com/Flowerappeng-org/openflower/blob/main/docs/build-applications/app-editor/visual-components/todo/README.md).

To style your charts, the [Theme Editor of ECharts](https://echarts.apache.org/en/theme-builder.html) is a good tool. It can export a JSON, which you also can use in the Workspace Themes section to style both Chart types. The native ones and the ECharts.


# Image

Use the **Image** component to present information in your apps in a vivic way. OpenFlower supports you adding an image via URL and base64 encoded data.

## URL

Drag the **Image** component onto the canvas or into a **Container**. Click the **Image** component, and the **Properties** tab is displayed on the right. You can set the URL to display an image.

## Base64 encoded data

You can also insert an image via base64 encoded data. Ensure that the encoded image source starts with `data:image/PICTURE_FORMAT;base64,` or simply`data:image;base64,`.

{% hint style="warning" %}
Don't miss the comma at the end of the prefix:

<mark style="background-color:yellow;">`data:image/PICTURE_FORMAT;base64`</mark><mark style="background-color:yellow;">**`,`**</mark><mark style="background-color:yellow;">`YOUR_BASE64_ENCODED_IMAGE_DATA`</mark>

<mark style="background-color:yellow;">`data:image;base64`</mark><mark style="background-color:yellow;">**`,`**</mark><mark style="background-color:yellow;">`YOUR_BASE64_ENCODED_IMAGE_DATA`</mark>
{% endhint %}

### Demo: upload and display an image

One common use case for base64 image display would be combining a **File Upload** component with an **Image** component.

Here is JS code that concatenates the prefix with the encoded base64 data of the uploaded file via `file1.value[0]`.

```javascript
{{"data:image/jpeg;base64," + file1.value[0]}} 
```

## Set click preview for an image

Toggle **Support click preview** in the **Properties** tab to allow users to preview the image in its full size. Hover your mouse over the image, and you see **👁 Preview**. Then, you can click to preview.

## Set the style of an image

In **Properties** > **Style**, change border color and set **Border radius** in pixels or by percentage.


# Option lists

A drop menu ensures easy and error-free user input by offering a list of possible options. OpenFlower supports implementing option lists using components such as **Select** and **Multiselect**. You can configure an option list either manually or by mapping data from your data sources.

## Properties

Each option list has four properties.

* **Label**: the displayed text of the option to users
* **Value**: the unique identifier of the option
* **Disabled**: disable the option (the default value is `false`)
* **Hidden**: hide the option (the default value is `false`)

{% hint style="warning" %}
When more than one option in one list uses the same value, only the first option is valid and thus displayed.
{% endhint %}

## Manual mode

Under **Basic** > **Manual**, click **+ Add** to create a new option. Select an option to manually configure it. You can click `···` to **Duplicate** or **Delete** an option and drag `⋮⋮` to arrange its position.

Check the value of the selected option under **Components** in the data browser. For example, when **New York** is selected, you can find the string value `"1"` for **locationSelect**.

Manual mode is recommended for these scenarios:

* Manually manageable and enumerable.
* Used by only one component.
* Data doesn't come from data sources.

## Mapped mode

When you have option list data that comes from data sources, transformers or temporary state, you can use **Mapped Mode**. Under **Basic** > **Mapped**, input your data source array in **Data** box using JavaScript (JS) code. A list of options will be automatically mapped from this array. For example, access university information via `{{university.data}}`. Each item in the result array contains `country`, `web_pages`, `alpha_two_code`, and `name`.

You can reference the value of any filed of an option through `item` and its index `i`, starting from 0. In the following example, the **Label** of each option is the `name` of the university, the **Value** is the `web_page`, and those containing an opening parenthesis `(` in their names are disabled. The default value is set to the `web_pages` of the first item using `{{university.data[0].web_pages}}`. Note that the default value of an option list should be an element from the **Value** array but not the **Label** array.

Check the value of the selected option under **Components** in the data browser. For example, when "Ahi Evran University" is selected, its string value `[\"http://www.ahievran.edu.tr/\"]` is displayed.


# List View

The component **List View** displays rows of data. Similar to Form, Modal, and Drawer, it is also a container-like component that can hold other components or modules. For list viewing data, you first bind the data to a **List View** component and configure the naming rules for the items. Then, you design the display of the first row by dragging and dropping components, and this layout will be applied to all items within this component.

The following is a demo of **List View**, displaying part of BEST BOOKS OF 2021 from [Goodreads](https://www.goodreads.com/choiceawards/best-books-2021):

Drag and drop **List View** onto the canvas. Each item contains an **Image**, a **Text** and a **Rating** component. You can update the exhibition of the whole **List View** component by configuring the layout of the first entry.

Click the **Image** component, you see the defaulted image source.

The **List View** component retrieves data from a JSON array of objects and displays an **Image**, a **Text** and a **Rating** component for each entry. You can also pass query results to a **List View**.

## Bind data

The data of a **List View** component accepts two types of values: number and array.

* Number: Numbers are processed as row count and no valid data is passed into the **List View** component.
* Array: The length of an array is passed as row count and each entry in the array is processed as a JS object in JSON format, corresponding to a row in the **List View** component. For example, the following data contains two rows of data, each containing four fields: `rate`, `title`, `url`, and `cover`.

```json
[
    {
        "rate": "9.2",
        "title": "The Shawshank Redemption",
        "url": "https://www.imdb.com/title/tt0111161/",
        "cover": "https://m.media-amazon.com/images/M/MV5BMDFkYTc0MGEtZmNhMC00ZDIzLWFmNTEtODM1ZmRlYWMwMWFmXkEyXkFqcGdeQXVyMTMxODk2OTU@._V1_UY67_CR0,0,45,67_AL_.jpg"
    },
    {
        "rate": "9.2",
        "title": "The Godfather",
        "url": "https://www.imdb.com/title/tt0068646/",
        "cover": "https://m.media-amazon.com/images/M/MV5BM2MyNjYxNmUtYTAwNi00MTYxLWJmNWYtYzZlODY3ZTk3OTFlXkEyXkFqcGdeQXVyNzkwMjQ5NzM@._V1_UY67_CR1,0,45,67_AL_.jpg"
    }
]
```

You can view the detailed data about the **List View** component, its items, the properties, etc., in the data browser.

## Display data in inside components

After binding valid data to **List View** component and designing its inside components, you can add data to these components. **List View** supports local variables `currentItem` and `i`. Notice that you only need to change data of the first row, and the same settings are applied to the other rows automatically.

{% hint style="info" %}
You can bind a static URL address to the **Image** component for image display; and because that URL address is static, the same image shows up in all rows.
{% endhint %}

## Name items

You can set the item index names and item data names. This is useful when embedding a list in another list. For example, you can set the index of one list as `i`, and the inner nested `j` to avoid naming conflict.

### Item index name

By default, item index is named as`i`, referring to the index of list data and starting from zero. Besides using `i` as numbers, you can also use `i` to dynamically access data from query results.

For example, to access the `book_name` field from table `fiction` in the **Text** component, write the following code.

```javascript
{{getAllFictions.data[i].book_name}}
```

Then you can see the fiction book names displayed in **List View** by index order.

### Item data name

By default, you can reference the value of each item within a list using variable `currentItem`. For example, to display a serial number plus book name, write the following code in `text1` value.

```javascript
{{i+1}}. {{currentItem.book_name}}
```

{% hint style="info" %}
Circular dependency errors occur when you reference the data of a row by `listView.items[i]` from a component inside a **List View** component. It is recommended to use such reference only outside the list.
{% endhint %}

## Pagination

### Quick jumper

Toggle "Show quick jumper" or set its value to `true` to allow your users quickly jump to the specified page.

### Page size

You can configure whether to allow your users to customize how many items to be displayed on a single page by enabling or disabling the "Show size changer button" or set its value in JS. When disabled, you can set the default page size.

If enabled, you can configure several page sizes for your users to select.

### Total row count

By default, the total row count of a **List View** component is the number of current data items. You can also insert a value from a query. For example, `{{query1.data[0].count}}`.

## Reference List View items outside

**List View** supports exposing data of inside components with **Items** field. You can inspect the **Data Browser** in the left pane.

For example, drag an **Input** component into **List View**, and then you can reference the value of the **Rating** component in components outside the **List View** by the following code.

```javascript
{{listView1.items[0].rating1.value}}
```


# Drawer

In OpenFlower, **Drawer** is an overlay sidebar to display information or perform operations, without interrupting the workflow on the main window.

The following example builds a shopping cart using a drawer.

## Layout

When inserted by drag-and-drop operation, a **Drawer** floats on the right side (by default) of the main window. Then, you can add components onto the **Drawer** according to your needs. In the editing mode of a **Drawer**, other components on the canvas are not editable.

{% hint style="info" %}
When a drawer is closed, you can re-open it by clicking the corresponding label in the **Modals** tab in the left pane.
{% endhint %}

### Position

In the **Properties** tab, you can set the position of the drawer. The default position is the right side of the main window.

### Resize

In **Properties** tab, you can set the width of the drawer by pixels or percentage. Note that the height of a drawer is the same as the main window and is not customizable.

## Events

You can reference a drawer in JS code anywhere in your app or call it through a "control component" action in **Properties** > **Interaction** > **Event handlers**.

The following sections guide you through how to open and close a drawer by clicking a button in an online shopping app.

### Trigger openDrawer

Usually, in an app, you trigger opening a drawer by an event such as clicking a button. For example, in the [Online Shopping demo](https://github.com/Flowerappeng-org/openflower/blob/main/docs/build-applications/app-editor/visual-components/todo/README.md), opening a drawer to display the shopping cart is implemented in the following steps.

1. Add a button and rename it as `gotoCart`.
2. Set the event handler of the button. Select "Control component" as the **Action** and select the component "cart" and method "openDrawer". All these settings are auto-saved.
3. Click the button `gotoCart` and the binded drawer "cart" is open.

### Trigger closeDrawer

Triggerring "closeDrawer" is similar to triggering "openDrawer". When setting up the event handler, select the method "closeDrawer". For example, in the \[Online Shopping demo]todo), closing a drawer that displays the shopping cart is implemented in the following steps.

1. Add a button and rename it as `gobackShopping`.
2. Set the event handler of the button. Select "Control component" as the **Action** and select the component "cart" and method "closeDrawer". All these settings are auto-saved.
3. Click the "Continue Shopping" button and the binded drawer "cart" is closed.


# Google Maps

OpenFlower offers an innovative approach to integrating Geomaps from Google with an advanced geo-data-based overlay of eCharts, providing a dynamic and interactive mapping solution. This integration is particularly powerful for applications requiring real-time data visualization on geographical maps.

The core feature of this integration is the seamless combination of Google Geomaps with eCharts overlays. Google Geomaps provides a robust and familiar mapping interface, known for its detailed and accurate geographical data. By overlaying eCharts, OpenFlower enables the addition of rich, interactive data visualizations directly onto these maps. This overlay capability is not just about static data representation; it supports dynamic, real-time data updates, making it ideal for applications that require up-to-the-minute information, such as traffic monitoring, weather updates, or tracking movements in logistics.

<figure><img src="/files/xJobhl6SRDG093H7VKYz" alt=""><figcaption><p>Distribution of OpenFlower Users worldwide (10.2023)</p></figcaption></figure>

OpenFlower ensures that the data displayed in the eCharts overlay is not only current but can also be updated in real-time with the [Stream Query](/connect-your-data/data-sources-in-openflower/websocket-datasource). This is crucial for scenarios where timely data is essential for decision-making. Users can see changes as they happen, providing an interactive and engaging experience.

The eCharts overlay on Google Geomaps in OpenFlower also offers a high degree of customization and interactivity. Users can zoom in and out, pan across different regions, and interact with the data points on the map. This interactivity is enhanced with tooltips, clickable elements, and various chart types like heatmaps, scatter plots, or line graphs, all geo-referenced and layered over the map.

### Using Geomap in OpenFlower

As the first step, place a new Chart from the right Components panel on the canvas.

<figure><img src="/files/QdMQ8KuDL4FBFS271Osu" alt=""><figcaption></figcaption></figure>

Now you can select in the Component Properties the type "Map"

<figure><img src="/files/zWSzfwXwWvl9EjduObBe" alt=""><figcaption></figcaption></figure>

You can use the Properties "Zoomlevel, Longitude, and Latitude" to define the first impression of the GeoMap when the App is displayed.

In Version OpenFlower 2.1.x the eCharts Map is configurable like other eCharts by the "Options JSON"

{% hint style="info" %}
For more and deeper information check the eCharts documentation. <https://echarts.apache.org/en/api.html#echartsInstance.setOption>
{% endhint %}

As eCharts is already integrated into OpenFlower, you only need to prepare and set the Options-JSON Data. The configuration follows the standard eCharts notation.

In the data section, you have nevertheless the possibility not only to set chart data but also the geo-point (lat, long), where the chart data should be displayed. Each Object in the data array will be one chart element on the map.

The section "encode" helps to tell the eCharts Map, which entry (array index number) of the data-array-element represents latitude, longitude, and the value to display.

```json
{
  "tooltip": {
    "trigger": "item"
  },
  "animation": true,
  "series": [
    {
      "name": "Population",
      "type": "scatter",
      "coordinateSystem": "gmap",
      "itemStyle": {
        "color": "#00c1de"
      },
      "data": [
        {
          "name": "Azerbaijan",
          "value": [
            47.395,
            40.43,
            8352021
          ]
        },
        {
          "name": "Albania",
          "value": [
            20.068,
            41.143,
            3153731
          ]
        }, <...>
      ],
      "encode": {
        "value": 2,
        "lng": 0,
        "lat": 1
      }
    }
  ]
}
```

### Dynamic Data & Function binding

As in other components of OpenFlower, you can bind data and functions dynamically to the map.

```javascript
{
  "tooltip": {
    "trigger": "item"
  },
  "animation": true,
  "series": [
    {
      "name": "Company Size",
      "type": "scatter",
      "coordinateSystem": "gmap",
      "itemStyle": {
        "color": "#9d4edd"
      },
      "data": {{mapData.value}},
      "symbolSize" : {{function (val) {return window.mapValueToSize(val[2])}}},
      "encode": {
        "value": 2,
        "lng": 0,
        "lat": 1
      }
    }
  ]
}
```

In this case, the App-global function "mapValueToSize" is bound to the map settings, so the size of the Scatter Plot dots can be dynamically adjusted based on the "value" - of the mapped data.

### Setting Geo-Markers & JavaScript Access

In Version 2.1.x the possibility to place Geo-Markers on the map is possible via Javascript by accessing the maps object.

Here you can see the possibility to access the "gmap" object by the new function of the eChart Component "getMapInstance()". The gmap object is the well-known Google Maps Javascript Object, on which you can execute all typical operations.

```javascript
const renderMarker = async function () {
  const gmap = await chart1.getMapInstance ();
  var london = {lat: 51.5074, lng: -0.1278};
  var marker = new google.maps.Marker({
      position: london,
      map: gmap,
      title: 'Welcome to London!'
  });
}
renderMarker ();
```

### Google Maps API Key

When you publish your app, an Google Maps API Key must be in place to display the App accordingly. Use <https://console.cloud.google.com/apis/dashboard> to create your API Key.

<figure><img src="/files/zdy7miyqcTd1j6bsMwos" alt=""><figcaption></figcaption></figure>

To display only the Map and use the JavaScript API, "Map Embed API" and "Maps JavaScript API" need to be activated.

<figure><img src="/files/58JhtrtLRnwlMfB99L58" alt=""><figcaption></figcaption></figure>

Now you can create and see your Google Maps API Key. It is suggested that you limit the Traffic by a Domain or IP Range.

<figure><img src="/files/eBJZl3SQy2bo3UiGB8nJ" alt=""><figcaption></figcaption></figure>


# Table

The Standard Table is a highly functional and feature-rich component. It is recommended for Server-Side paging and filtering when data display for tables needs many individual settings.

The OpenFlower Table supports auto-size as a component as well as fixed size, where the Header and Toolbar can be fixed when the Table-Body is scrollable.

{% embed url="<https://app.supademo.com/demo/NEYc5eNZNchrXkpjo2GJs>" %}
Header & Toolbar can be fixed
{% endembed %}

The Borders of Tables can be set individually for the whole table till down to the individual cell.

{% embed url="<https://app.supademo.com/demo/9SG70olbmEvAIdCnYJxWm>" %}
Individual Settings for Borders in Tables
{% endembed %}

{% embed url="<https://app.supademo.com/demo/CJMoAPTKk3mZiSJeEYqDr>" %}
Individual Grid Settings in the Table
{% endembed %}

Also, the Text Styles can be individualized for every column.

{% embed url="<https://app.supademo.com/demo/BQZNtDeTNXX9gVKD292sq>" %}

### Component Playground

todo

### Component Auto-Docs

{% embed url="<https://prod-us1.openflower.org/components/input>" %}

### Properties of the Table

These properties are accessible in {{ }} notations, as well as in JavaScript Queries. \\

{% hint style="warning" %}
Properties are read-only
{% endhint %}

| Property Name   | Type    | Description                                                                                                                                    |
| --------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| selectedRow     | Object  | Provides Data for the Currently Selected Row, Indicating the Row That Triggers a Click Event If the User Clicks a Button/Link on the Row       |
| selectedRows    | Array   | Useful in Multiple Selection Mode, Same as SelectedRow                                                                                         |
| selectedIndex   |         | Selected Index in Display Data                                                                                                                 |
| selectedIndexes | Array   | Selected Index in Display Data                                                                                                                 |
| changeSet       | Object  | An Object Representing Changes to an Editable Table, Only Contains the Changed Cell. Rows Go First and Columns Go Second.                      |
| toUpdateRows    | Array   | An Array of Objects for Rows to Be Updated in Editable Tables.                                                                                 |
| pageNo          | Number  | Current Display Page, Starting from 1                                                                                                          |
| pageSize        | Number  | How Many Rows per Page                                                                                                                         |
| sortColumn      |         | The Name of the Currently Selected Sorted Column                                                                                               |
| sortDesc        | Boolean | Whether the Current Row Is in Descending Order                                                                                                 |
| pageOffset      | Number  | The Current Start of Paging, Used for Paging to Get Data. Example: Select \* from Users Limit {{table1.pageSize}} Offset {{table1.pageOffset}} |
| displayData     | Array   | Data Displayed in the Current Table                                                                                                            |
| filter          | Object  | Table Filtering Parameters                                                                                                                     |
| data            | Array   | The JSON Data for the Table                                                                                                                    |

### Events

Events give you the ability to trigger further actions (with Event-Handlers)

| Event Name        | Description                            |
| ----------------- | -------------------------------------- |
| Save Changes      | When a user clicks "Save Changes"      |
| Cancel Changes    | When a user decide to skip Changes     |
| Row Select Change | When the selected Row Change           |
| Row Click         | When a Row is Clicked                  |
| Row Expand        | When a Row gets Expanded               |
| Row Shrink        | When a expanded Row shrink             |
| Column Edited     | When a Column gets Edited              |
| Search            | When a user make a global Search       |
| Download          | When a user Download Table Data        |
| Filter Change     | When a Filter Change                   |
| Sort Change       | When a Sorting Change                  |
| Page Change       | When a user changes the displayed Page |
| Refresh           | When a user Refresh the Table View     |

### Methods

You have the capability to engage with components via their respective methods, which can be accessed by their designated names within any segment where JavaScript is utilized. Additionally, these components can be activated through the 'Control Component' action, which is triggered in response to specific events

| Method Name     | Description                               |
| --------------- | ----------------------------------------- |
| setFilter       | Set the Value of Property filter          |
| setPage         | Set the Value of Property page            |
| setSort         | Set the Value of Property sort            |
| resetSelections | Set the Value of Property resetselections |

\\


# Messages / Toast

The Toast component is a versatile tool for displaying brief notifications ("toasts") within an application. It supports various configurations to customize the appearance, behavior, and placement of toasts. Below is the documentation for the Toast component and its methods.

#### Syntax

```javascript
toast.open(title, options);
// toast.open( title: string, options?: { message?: string, duration?: number = 3, id?: string, placement?: "top" | "topLeft" | "topRight" | "bottom" | "bottomRight", "bottomRight" = "bottomRight", dismissible?: boolean = true } )
```

#### Parameters

* **`title`** (string): The title of the toast notification. This is a required parameter and will be displayed prominently on the toast.
* **`options`** (Object): An optional configuration object that allows you to customize the toast notification. The properties available in this object include:
  * **`message`** (string): Optional. The message to be displayed on the toast. Provides additional information about the toast notification.
  * **`duration`** (number): Optional. The duration for which the toast should remain on the screen, specified in seconds. The default value is `3` seconds.
  * **`id`** (string): Optional. A unique identifier for the toast. This can be used for targeting specific toast notifications if needed.
  * **`placement`** (string): Optional. Defines where on the screen the toast will appear. Possible values are `"top"`, `"topLeft"`, `"topRight"`, `"bottom"`, `"bottomRight"`. The default placement is `"bottomRight"`.
  * **`dismissible`** (boolean): Optional. Determines whether the toast can be dismissed by the user before the duration expires. The default value is `true`, making the toast dismissible.

#### Return Value

The `toast.open` method does not return a value.

#### Examples

**Basic Usage**

To display a simple toast with just a title:

```javascript
toast.open('Hello World!');
```

**With Message and Duration**

To display a toast with a title, message, and custom duration:

```javascript
toast.success("Query runs successfully", {duration: 10});
```

**Custom Placement and Dismissibility**

To display a toast at the top of the screen, which is not dismissible:

```javascript
toast.warn('Alert!', { message: 'System will undergo maintenance tonight.', placement: 'top', dismissible: false });
```

#### Customization

<figure><img src="/files/PGafCU5Txu7JUnUTDvzz" alt=""><figcaption></figcaption></figure>

The Toast component also provides additional methods for displaying toasts with predefined styles and icons corresponding to different notification types: `info`, `success`, `warn`, and `error`. These methods have the same signature and options as `toast.open`, but they display toasts with colors and icons that are appropriate for their respective notification types.

* The toast will automatically disappear after the duration has elapsed unless `dismissible` is set to `false`. In that case, the user must manually close the toast.
* If multiple toasts are triggered with the same `id`, they will be treated as separate instances unless custom logic is implemented to handle such cases.
* The placement of the toast might need to be adjusted based on the overall layout and responsiveness of the application to ensure optimal visibility on different devices.

Each of these methods accepts the same parameters as `toast.open`, allowing for customization of the message, duration, ID, placement, and dismissibility.

* **`toast.info(title, options?)`**: Displays an informational toast with a blue icon.
* **`toast.success(title, options?)`**: Displays a success toast with a green icon.
* **`toast.warn(title, options?)`**: Displays a warning toast with a yellow icon.
* **`toast.error(title, options?)`**: Displays an error toast with a red icon.

**Additional Methods**

**`toast.destroy(id?)`**

Destroys an open toast. If no ID is provided, all toasts will be closed.

* **Parameters**:
  * `id` (string, optional): The unique identifier of the toast to destroy. If not specified, all toasts will be destroyed.


# Calendar

The Calendar Component is a feature-rich way to organize and edit date and calendar entries in OpenFlower. To offer the best experience for Calendars, we integrated the [fullcalendar.io](https://fullcalendar.io/docs) Project.

You can refer to the documentation like this: <https://fullcalendar.io/docs>

<figure><img src="/files/GOA7kPyGdJ1TqUM5Q2oS" alt=""><figcaption></figcaption></figure>

### Dynamic Data for Events

In the "Basic" section, you can bind your Datasource to feed the calendar with your existing Events.

The JSON structure is an array of objects, where each object represents a single event on the calendar. Here's a breakdown of what each key in the object means:

* **`id`**: A unique identifier for the event. This is a string that helps distinguish each event from others.
* **`title`**: The name or title of the event. This is a brief description that will be displayed on the calendar to represent the event.
* **`start`**: The start date and time of the event. This should be in the format "YYYY-MM-DD HH:MM:SS". For all-day events, you can omit the time part and only provide the date.
* **`end`**: The end date and time of the event, following the same format as the start date. For all-day events that last multiple days, this indicates the end date.
* **`color`**: (Optional) A string representing the color code (in hexadecimal format) to be used for the event's display on the calendar. This allows for visual differentiation between different types of events.
* **`allDay`**: (Optional) A boolean value (`true` or `false`) indicating whether the event lasts all day. If `true`, the event will be shown as an all-day event, possibly in a different section of the calendar or styled differently. If this key is omitted, the event is treated as having specific start and end times.

```
[
    {
        "id": "1",
        "title": "Coding",
        "start": "2024-03-04 09:00:00",
        "end": "2024-03-04 23:30:00",
        "color": "#079968",
        "allDay": true
    },...
]
```

As soon as a user edits or adds a new Event, the data object is updated so that you can persist this change on your chosen Datasource.

```
// Access all current calendar event data
return calendar1.events;
```

### Editable Calendar

Based on the Setting "Editable" you can allow your Application Users to add or edit own Calendar Events. A small modal opens and allows the entry of Event information.

### Premium Views

To display the Premium Views (Resource Timeline and Resource Grid), you must purchase a License at <https://fullcalendar.io/purchase> per Developer Seat. This License is then valid for all App Users in the Role "Member / Viewer".

You can enter the License Key in the Property Settings Menu. As soon as you enter the License Key, the Premium Views will be available at the Dropdown "Default View".


# Date handling

Day.js is a lightweight JavaScript library for parsing, validating, manipulating, and formatting dates and times, designed to be a simpler and smaller alternative to Moment.js.

Day.js is already included in OpenFlower, so you can directly begin using it to work with dates and times by creating Day.js objects using `dayjs()`. This function accepts various formats, including strings, Date objects, and UNIX timestamps, allowing for flexible date and time manipulation such as adding or subtracting time, formatting dates, and comparing dates.

{% hint style="info" %}
You can read how to use Day.js in their excellent Documentation here: <https://day.js.org/docs/en/get-set/get>
{% endhint %}

### Day.js Plugins

To enhance Day.js's functionality, developers can utilize its plugin system, which allows for the inclusion of additional features not available in the core library.

Since OpenFlower Version 2.3.1, we load all DayJS plugins automatically

{% hint style="info" %}
An overview of Day.js Plugins: <https://day.js.org/docs/en/plugin/plugin>
{% endhint %}

You can use Day.js Plugins at all places in OpenFlower that support {{ }} Handlebar notation.

```javascript
Quarter: {{dayjs().format('Q')}}

Day of Month with ordinal: {{dayjs().format('Do')}}

Week of year: {{dayjs().format('w')}}

{{dayjs('2013-11-18 11:55').tz('Asia/Taipei')}}

```


# Bulk Editing

Settings & Properties for components can sometimes be repetitive. That means you would like, for example, to change the Background Color for some components. Till OpenFlower Version 2.3.1 you needed to set up this for each Component individually, which means a lot of Clicks.

With Version 2.3.1 we introduce the ability to edit Properties for many selected Components at once.

{% embed url="<https://app.supademo.com/demo/pnnnuSs7sVrcnqDuv9UI6>" %}

The "Apply Action" tries to set the Property Value at all selected Components. It depends then on the Component if this Property is supported. If not, the Action for the individual Component is simply ignored. For all other Components, it takes effect immediately.


# Layers

OpenFlower offers the fantastic feature of enabling a mode where components can freely overlap on the canvas, combined with precise control over the layering of these components. This advanced functionality diverges from traditional grid-based placement constraints, offering developers and designers unparalleled UI/UX creation flexibility.

The control over component layering empowers you to build interfaces that are not only highly functional but also aesthetically pleasing. Layers are an essential step towards the Vision of OpenFlower to merge the ability to create "Apps, Websites, Presentations, and Collaboration Tools" inside a single Lowcode Platform.

From Version 2.3.1 on we introduce this feature in several steps and develop it further by the feedback of the OpenFlower Community.

### Step 1 - Simple Layers

In the App Editor, we introduce a setting to switch on the "Layer Feature" within the App Editor.\
Each App that is edited during this feature is switched on will become capable of organizing components in layers. Existing Apps will look exactly the same till you start to place components differently. However, by the nature of "Layers", the positioning of components is "fixed" in a vertical & horizontal manner.

That means that you need to plan the layout differently, as the automatic "pushing down" of visual components with "auto-height" will not be effective in the Layers Mode. Each component is defined at the same time as a single layer. In other words, the app has as many layers as you place Components to the Canvas.

### Step 2 - Layer Groups & deep Layers

In Version 2.4.x we will activate Layer Groups to help you to organize components in a layer group together. A layer group will behave like a single Layer.

Deep Layers are the possibility to organize Layer Positions for Components inside of Containers & Lists. (Child Components). The Layer & Selection List shows already the full Hierarchy, but limits in Step 1 the possibility to the root-level of Components.

### Step 3 - Layers & automatic vertical scaling control

When we activate Step 3, you will be able to control the vertical auto-scaling of components in connection to the current layer group and other elements on the Canvas.

## Using Layers

In the new "Layers" Settings Menu on the left Sidebar you can open the Settings regarding Layers, Bulk edit, and Layout.

{% embed url="<https://app.supademo.com/demo/5GvlYeyGG5WMV_ilagjhG>" %}

### Organize Layer Position

To change the Layer Position and move Components in front or back of each other, simply use the drag n drop List on the left Layers Settings section.

{% embed url="<https://app.supademo.com/demo/ZCxHoz84UavOMrnGWHo0H>" %}


# Data selection & Javascript

When building apps in OpenFlower, you can use JavaScript (JS) to access and transform data from objects, including components, queries, and global parameters. When writing JavaScript inside SQL editor, component property's input box, table column settings, etc., always remember to enclose all your JS code inside double curly braces, such as\
`{{'hello, ' + currentUser.name}}`.

## Access data

Objects have globally unique names, such as `input1`, `query1`, and `table1`. You can refer to the properties of the objects in your app by JS code.

### Access data in an object

OpenFlower supports you accessing the data in an object using dot notation (`objectName.keyName`). For example,`{{userInfo.selectedRow.userName}}` accesses the `userName` value in the currently selected row of **Table** `userInfo`.

When writing JS in `{{ }}` to access values in an object, add a `.` after the object name to trigger an autosuggest menu in case you are not sure about objects' built-in properties or methods.

**Example**

This GIF shows how the dot notation triggers an autosuggest menu and displays the properties of `table1`.

### Access data in an array

You can access the values in an array by index. The index always starts at 0, so you can use `array[0]` to access the first element of the array.

**Example**

The **Data** property of **Table** component is an array of objects. This GIF shows how to access the value of `first_name` in the first element of the **data** array in `table1`.

## Transform data

You can leverage built-in JS functions and third-party libraries in `{{ }}` to transform data, such as `filter()`, `map()` and `reduce()` operations.

**Examples**

Lowercase a string.

```javascript
{{input1.value.toLowerCase()}}
```

Change date format.

```javascript
{{moment(table1.selectedRow.date_column).format('YYYY-MM-DD')}}
```

Return name from query results.

```javascript
{{query1.data.map(i => i.name)}}. 
```

## Restrictions

The JS code in `{{ }}` should be a single-line code, such as `.map()` or `.reduce()` combined with an arrow function or a ternary operator.

**Examples**

```javascript
{{query1.data.id.length}} // ✅ to reference a value
{{query1.data.map(row => row.id)}} // ✅ .map() + arrow function
{{ num1 > num2 ? num1 : num2 }} // ✅ ternary
```

The following JS code examples are illegal in `{{ }}`.

```javascript
{{ 
    // ❌ you can't write code like this in {{ }}
    const array = query1.data;
    const filterArray = array.filter(it => it.value > 10);
    return filterArray; 
}}
```

If you wish to orchestrate multiple lines of JavaScript, OpenFlower supports you writing such code in transformers.

```javascript
// codes inside a transformer
if (select.value === "1") {  
    return "Option 1";
}
if (select.value === "2") {
    return "Option 2";
}
return "Option 3"; 
```

## View data

Data from queries can be complicated and nested in real cases. Viewing data provides you with the detailed structure of data in objects and helps you understand your data better. Before accessing or transforming data, you may need to view the data and its structure first. OpenFlower offers three ways to view data.

### View query result

After running a query inside the query editor by clicking the **Run** button\*\*.\*\* Query result is displayed in the format shown below.

### View data in Data Browser

Data browser located in the left pane displays all of the data inside your app. You can click on the node to expand and view the data structure.

### Real-time view

When setting up properties or writing JS code inside an editor, you can view the evaluated result in real-time in a box below your editor.


# Use Markdown

OpenFlower supports you using the lightweight markup language Markdown to format texts in your app, such as changing the font size, inserting code, and adding a list. You can also embed HTML-formatted text in Markdown for more styling options.

Use Markdown in **Text** and **Table** components.

* **Text**: Use Markdown for the input text.
* **Table**: Use Markdown for column type.

## Markdown basics

All Markdown syntax is valid in OpenFlower.

* **Headings**: Use hashtag (#) before heading. More hashtags, lower heading level.
* **Paragraphs**: Use a blank line to separate texts into paragraphs.
* **Line breaks**: Add at least two whitespaces at the end of a line and press **Enter** to start a new line.
* **Emphasis**: Use single asterisk (∗) before and after text to convert it to *italic* text, double asterisks (∗∗) for **bold** text, and triple asterisks (∗∗∗) for ***italic bold*** text.
* **Blockquotes**: Use the closing angle bracket (>) to create a blockquote.
* **Lists**: Use hyphen (-), asterisk (∗), or plus sign (+) followed by a whitespace to create an unordered list, and use numbers followed by a period (.) to create an ordered list.
* **Code**: Use backticks (\`) before and after code to denote `` `code` `` in plain text, and three backticks (\`\`\`) to present ` ```code block``` `.
* **Horizontal rules**: Use at least three asterisks (∗∗∗), hyphens (---), or underscores (\_ \_ \_) at the beginning of a line to create a horizontal rule.
* **Links**: Use square brackets (\[]) for the display text and parenthesis (()) for the link address. Note that no space should be inbetween. For example, `[OpenFlower](https://docs.openflower.org)`.
* **Images**: Use an exclamation mark (!) to start an image shiinsertion, and then use square bracket (\[]) for the alternative text and parenthesis (()) for the link address or source path. The caption can follow the parentheses or start at a new line.
* **Escaping characters**: Use backslash (\\) to display the special symbols that Markdown uses. For instance, to print ` `` ` in plain text, you should use `` \`\` `` in Markdown.

For more information, see [Markdown guide](https://www.markdownguide.org/basic-syntax/).

## GFM extension

You can also use GitHub Flavored Markdown (GFM) extensions for additional syntax.

* **Footnotes**: Use caret (^) and number in square brackets (\[]) to insert footnotes. For more information, see [Footnotes](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#footnotes).
* **Strikethrough lines**: Use double tilde (\~\~) before and after text to add strikethrough lines. For more information, see [Styling text](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#styling-text).
* **Tables**: Use hyphens (-) separated by the vertical bar (|) to create the header row of a table, and continue using the vertical bar to separate content in cells. For more information, see [Tables (extension)](https://github.github.com/gfm/#tables-extension-).
* **Task lists**: Use a whitespace in square brackets (\[ ]) within the list format to create task lists. To mark a task completed, replace the whitespace with the letter *x*. For more information, see [Task lists](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#task-lists).

## HTML support

In OpenFlower, you can use most HTML tags and properties. For more information, see [Embed HTML in Markdown](https://www.markdownguide.org/basic-syntax/#html).

{% hint style="warning" %}
For security reasons, a few HTML tags including **iframe** and **script** are not permitted in OpenFlower.
{% endhint %}

### Appendix: Supported HTML tags

```html
<h1>
<h2>
<h3>
<h4>
<h5>
<h6>
<br>
<b>
<i>
<strong>
<em>
<a>
<pre>
<code>
<img>
<tt>
<div>
<ins>
<del>
<sup>
<sub>
<p>
<ol>
<ul>
<table>
<thead>
<tbody>
<tfoot>
<blockquote>
<dl>
<dt>
<dd>
<kbd>
<q>
<samp>
<var>
<hr>
<ruby>
<rt>
<rp>
<li>
<tr>
<td>
<th>
<s>
<strike>
<summary>
<details>
<caption>
<figure>
<figcaption>
<abbr>
<bdo>
<cite>
<dfn>
<mark>
<small>
<span>
<time>
<wbr>
<input>
```


# Keyboard shortcuts

## Built-in keyboard shortcuts

Keyboard shortcuts are a powerful tool that can greatly improve productivity and efficiency when working on a computer. With the ability to assign custom keystrokes to frequently used commands, tasks can be completed quickly and with minimal effort. This is particularly useful for individuals who rely heavily on keyboard input, such as programmers, writers, or designers.

Keyboard shortcuts have been around for decades, and most software applications come with a set of default shortcuts. OpenFlower offers built-in keyboard shortcuts to help you work on the interface faster and more efficiently with improved controlling precision.

### Global

Global keyboard shortcuts across OpenFlower interfaces.

| Shortcut | Description               |
| -------- | ------------------------- |
| `?`      | Toggle keyboard shortcuts |

### App editor

Keyboard shortcuts in the App editor.

| Shortcut      | Description        |
| ------------- | ------------------ |
| `⌘` + `B`     | Toggle left pane   |
| `⌘` + `J`     | Toggle bottom pane |
| `⌘` + `U`     | Toggle right pane  |
| `⌘` + `.`     | Toggle all panes   |
| `⌘` + ⇧ + `P` | Preview            |
| `⌘` + `Z`     | Undo               |
| `⌘` + ⇧ + `Z` | Redo               |
| `⌘`           | Show grid          |

### Component

Keyboard shortcuts to control components.

| Shortcut              | Description     |
| --------------------- | --------------- |
| `⌘` + Click           | Select multiple |
| `⌘` + `A`             | Select all      |
| `⌘` + `C`             | Copy            |
| `⌘` + `X`             | Cut             |
| `⌘` + `V`             | Paste           |
| `←` `↑` `→` `↓`       | Move            |
| `⌘` + `←` `↑` `→` `↓` | Resize          |
| `DEL`/`Backspace`     | Delete          |
| `ESC`                 | Deselect        |

### Query editor

Keyboard shortcuts in the Query editor.

| Shortcut  | Description       |
| --------- | ----------------- |
| `⌘` + `↵` | Run current query |

### Text editor

Keyboard shortcuts in the Text editor.

| Shortcut    | Description       |
| ----------- | ----------------- |
| `⌘` + `L`   | Format            |
| `⌘` + Click | Open in left pane |

## Custom keyboard shortcuts

You can define custom keyboard shortcuts at app-level. Go to ⚙️ on the left side-bar > **App settings** > **Custom shortcuts**. Click **+ Add** to add new shortcuts for specific actions. Then, you can click a custom shortcut to midify it or click the three dots `···` to delete it.

{% hint style="info" %}
OpenFlower keyboard shortcuts are better set to avoid conflicts with the browser's keyboard shortcuts, in case they can not be successfully triggered.
{% endhint %}


# App Navigation

**Navigation** aggregates your apps onto the left sidebar and guides you to quickly find the needed app.

On OpenFlower homepage, click **New** and select **Navigation** to create a navigation page.

## Menu items

In the **Navigation** editor, click **+ New** to add menu items.

Select a menu item, choose the app to navigate to, and change the label and icon if needed. The app is displayed on the canvas in real time.

### Add submenu items

To add submenu items, click `···` > **Add submenu item**, or drag `⋮⋮` to make one menu item subordinated to another.

{% hint style="warning" %}
A parent menu item can only expand or fold the submenu items. You cannot link an app to it.

When you delete a parent menu item, all submenu items under it are deleted at the same time.
{% endhint %}

### Add URL parameters

Add URL query parameters or hash parameters to a menu item with `key` and `value`.

Then click the menu item on the canvas, and the parameters are appended to the URL of the app to be opened. You can check in **Globals** tab in the left pane.

{% hint style="info" %}
To reference the url parameters, use `{{url.query.key1}}` or `{{url.hash.key1}}` in your app.
{% endhint %}

For more information of URL parameters, see Go to app.

### Visability of menu items

By default, a menu item is hidden for users without app permission. When you toggle this option off, the users can see the menu item but can not view the app it references to.

## Permissions

The permissions for navigations are the same as those for modules and apps. See Permissions for resources.


# App Interaction

How to use Events inside of Apps

There are two ways to extend the standard components with interaction. If you are fluent in Javascript, you can develop many interaction control by classic javascript with many helper functions. If you are not "the hacker", you will find the existing GUI elements and controls for interaction helpful.

Any interaction starts with "Event handlers", which you will find at almost each component. Based on possible user interactions, you can take events as triggers to realize a wise set of actions. In most of the cases you can combine multiple event triggers by adding Event handlers with different Actions configurations


# Event handlers

In OpenFlower, event handlers are responsible for collecting and processing events from components and queries, and executing subsequent actions. For example, for a **Button** component, you can add an event handler to trigger the **Run query** action \*\*\*\* in response to the button **Click** event.

Set event handlers wisely to provide a reactive and responsive user experience (UX). For example, triggering a **get-all** query after **insert-new-data** query finishes enables table automatically refresh.

{% hint style="info" %}
You can add as many event handlers as you want for a single component or query, and these event handlers run concurrently.
{% endhint %}

## Events

### Component events

Different components have different events. For example, **Button** components only have a **Click** event.

**Input** components have **Change**, **Focus**, **Blur**, and **Submit** events.

### Query events

Running a query can result in success or failure, so queries have two events: **Success** or **Failure**. You can add event handlers to queries in Query editor.

## Actions

There are a number of event handler actions available in OpenFlower for handling different scenarios. Set them in the **Action** dropdown list in an event handler.

{% hint style="info" %}
See advanced on this page to know advanced settings.
{% endhint %}

### Run query

Trigger the selected query.

### Control components

To control a component, select a component in the **Component** dropdown list and call one of its methods in the **Method** dropdown list.

### Set temporary state

Store data in a temporary state.

### Go to app

Navigate to an OpenFlower app with optional query or hash parameters.

| Parameter            | Function                                                                                        |
| -------------------- | ----------------------------------------------------------------------------------------------- |
| <p>URL Query<br></p> | <p>Append <code>?key1=value1\&key2=value2...</code> to the URL of the app to be opened.<br></p> |
| URL Hash             | Append `#key1=value1&key2=value2...` to the URL of the app to be opened.                        |

#### **Use case**

In the **User list** app, click **See info** to navigate to the **User info** app, and the detailed information of the user in the selected row will be displayed.

1. The **User list** app passes `{{currentRow.id}}` in the table row as a URL Query parameter to the **User info** app.
2. The URL field of the **User info** app global variable will automatically resolve the URL requested by the app. In this case, the value of `url.query` is `{"id":3}`, so the query will retrieve the corresponding user info via `{{url.query.id}}`. See the figure below.

### Go to URL

Navigate to an external URL.

### Show notification

Show a floating notification of informational, success, warning, or error message.

### Copy to clipboard

Copy a value to the clipboard.

### Export data

Export data in a certain file type (TXT, JSON, CSV, and Excel).

## Advanced

### Only run when

The action can be triggered only under a specified condition. Configure the running condition of an event handler in **Only run when** option, and then the event handler runs only when this condition evaluates to `true`.

### Debounce and throttle

**Debounce** and **Throttle** reduce the frequency of triggering the action.

#### **Debounce**

Debounce delays an action. Once a debounce time is set, the action will not be executed immediately following the event until the debounce time passes. If the event occurs again before the time ends, the debounce time will be retimed. For example, an input component triggers a query once the user changes the text. If the query is expensive to run and you don't want to run it after every single character is typed, you can delay the execution of the query by setting the debounce time. Then the query will only run after the user finishes typing.

#### **Throttle**

Throttle lets an action happen only once during a specified period of time. By default, every single event triggers an action, but sometimes running an action can be costly. For example, you set an event handle to trigger **Run query** action \*\*\*\* to update data in response to the **Click** event, but you find that query too expensive to run, then you can set a throttle time to let the query run only once within a given time.


# Themes & Styling

Good user interface (UI) improves user experience (UX) and increases user involvement. You can customize the style of each component in OpenFlower and use the theme feature to design the interface on a per-app or workspace basis. Features such as icon configuration and hint messages make the app interaction more user-friendly.

## Component styles

You can modify the style of all components in the **Properties** tab.

Click the color picker to select a color or write CSS color code in the text box.

You can also write JavaScript in the text box to conditionally control the style setting of the component.

## Themes

The [theme feature](https://prod-us1.openflower.org/setting/theme) helps you quickly set the styles of all your apps within a workspace, such as the primary color of the apps and the default background color of containers. Created Themes are available and in each app you would need to apply a Theme.

### Create a theme

Workspace admins have access to theme settings. On OpenFlower homepage, go to **Settings** > **Themes**, and click **+ Create theme**. Enter the theme name, and select one of the preset default themes as the starting point.

Preview the real-time theme effect on the right.

For charts you can insert or modify the ECharts style JSON. Find a good way to create and preview these styles with the [ECharts style Editor.](https://echarts.apache.org/en/theme-builder.html)

### Apply a theme

In the app editor, switch the theme by clicking ⚙️ on the left side-bar. Select a theme from **Theme setting**.

You can also set the default theme for all your apps within a workspace in **Settings** > **Themes** on OpenFlower homepage.

### Switching themes dynamically

You can access the global variable `theme` and call the method `theme.switchTo()` to allow the end users to switch the theme of the apps on their side using JavaScript.The global variable `theme` has three fields. You can view them in the data browser.

* `id` and `name` are strings, indicating the ID and name of the current theme. When their values are empty, then the default theme is applied.
* `allThemes` is an array, including all information of available themes in the current workspace.

`theme.switchTo()` method switches the theme at the end user's side, and requires only a theme ID. When the passed value is an empty string `""`, then the default theme is applied.Once the end user switches the theme, it will be saved to the user browser's local storage. And this theme will override the default theme and apply to all apps that are used in the same browser.

#### Demo

Combining Option lists and Events, end users can switch the theme within the app. For details, see [Change theme by code demo](https://github.com/Flowerappeng-org/openflower/blob/main/docs/build-applications/themes-and-styling/todo/README.md).

Follow the steps below to include this function in your app.

1. Drag and drop a **Select** component onto your canvas. Set the data value as follows.

<pre class="language-Plain"><code class="lang-Plain"><strong>{{[{ id: "", name: "Default" }, ...theme.allThemes]}}
</strong></code></pre>

2. Set the labels and values as `{{item.name}}` and `{{item.id}}` respectively. Then, you can view the default theme and all other available themes in the current workspace.
3. Insert a **Button** component onto your canvas to switch theme. Add an event to the button, select "Run JavaScript" as the action, and run `theme.switchTo()` method which takes the value of the **Select** component.

## Custom CSS

OpenFlower provides a custom CSS feature for more flexible and customized UI styling.

### App-level CSS

In the app editor, click ⚙️ on the left side-bar, select **Scripts and style** > **CSS**, and then write CSS code for the current app.

For example, insert text component `text1`. Then use `.text1` as the element name and modify its CSS style.

{% hint style="info" %}
It is recommended to modify the component styles in **Properties** > **Style** because the DOM of an adjusted CSS style may change as the system iterates.
{% endhint %}

### Preload CSS

In OpenFlower, workspace admins can also set pre-loaded CSS styles for all apps within the workspace. Open the **Settings**, and click **Advanced** > **Preload CSS**.

It is highly recommended to use CSS selectors as follows:

| Class name     | Description               |
| -------------- | ------------------------- |
| top-header     | Top navigation bar        |
| root-container | Root container of the app |

The name of each component functions as the class name. For example, for the `text1` component, you can use `.text1` as its class name and write CSS code for it. And the class names share the same form: `ui-comp-{COMP_TYPE}`—for example, you can use `.ui-comp-select` to define CSS style of all select components. All the components' class names are listed as follows.

```Plain
input
textArea
password
richTextEditor
numberInput
slider
rangeSlider
rating
switch
select
multiSelect
cascader
checkbox
radio
segmentedControl
file
date
dateRange
time
timeRange
button
link
dropdown
toggleButton
text
table
image
progress
progressCircle
fileViewer
divider
qrCode
form
jsonSchemaForm
container
tabbedContainer
modal
listView
navigation
iframe
custom
module
jsonExplorer
jsonEditor
tree
treeSelect
audio
video
drawer
carousel
collapsibleContainer
chart
imageEditor
scanner
```

Avoid using class names that may change with iterations, such as `sc-dkiQaF bfTYCO`.OpenFlower supports [CSS pre-processor](https://stylis.js.org/), you can use CSS nesting to improve efficiency, for example:

```css
.text1 {
    span {
        color: red;
        font-weight: bold;
    }
}
```

All the custom CSS for apps is saved into the space named `#app-{APP_ID}`, and the CSS for modules is saved into the space named `#module-{MODULE_ID}`.If your preload CSS does not work properly, it might be overridden by the theme or component styles with higher priority. Open the browser **Inspect** to check.

### Demo 1: Line break in table header

To allow line break in table header, insert the following code in **Script and style** > **CSS**.

```css
.table1 {
  th div {
    white-space: pre-wrap;
    word-break: break-word;
    max-height: unset;
  } 
}
```

### Demo 2: Custom font family

To use custom font family, you need to define it first and then apply it. Insert the following code in **Script and style** > **CSS** to apply the font "Fredoka One" to all text components using Markdown mode within the app.

```css
@font-face {
  font-family: 'Fredoka One';
  font-style: normal;
  font-weight: 400;
  src: url(https://fonts.gstatic.com/s/fredokaone/v13/k3kUo8kEI-tA1RRcTZGmTlHGCaen8wf-.woff2) format('woff2');
}

.ui-comp-text .markdown-body {
  font-family: 'Fredoka One';
  font-size: 30px;
}
```

## User-friendly interaction

OpenFlower always lives up to efficiency, security, and easy-to-use design.

### Hide UI components

Set the hidden properties of components when necessary to avoid information overload. For example, when creating a suggestion collection form, you can set the input box as visible or hidden depending on the user's selection.

To achieve this effect, set the hidden property of the component `textArea1` with the code:

```JavaScript
{{Number(radio1.value) === 1 ? 'false' : 'true'}} 
```

When the value of the component `radio1` is "1", the value of the hidden property is "false"; otherwise, the value is "true". The component layout is automatically adjusted.

### Icon configuration

Icons are intuitive, and can be alternatives to text in some cases. The proper use of icons gives users a better visual experience, and helps them use the app more easily.

Prefix and suffix icons are available for some components, such as **Button**. Add icons in **Properties** > **Layout**.

You can select from preset icons or write JS code to insert icons, for example, `{{ "/icon:solid/Users" }}`.

### Placeholder and tooltip

Tips improve app usability–for example, showing the tips for the input helps users better interact with the app.

* Placeholder: It displays in the empty input field to prompt the user what to type.
* Tooltip: It adds an underline to the label. Users can see the tooltip via a mouse hover.

### Notifications

Notifications are messages directly sent to your users to remind them of the status of their operations, confirm their success, or help them to proceed.

#### Global notifications

Global notifications for certain user interactions give users timely feedback. OpenFlower offers four types of global notificaitons: **Information**, **Success**, **Warning** and **Error**.

You can set global notifications in three ways:

1. Set in **Event handlers** > **Action** > **Show notification**. See Show notification (Event handlers).
2. Set in **JavaScript queries** with built-in functions.
3. Set in **Notification** tab in query settings. See Notification tab.

### Loading effect

When a query takes time to run, you can set the loading effect to inform your users that the query is running and avoid them from performing frequent operations.

For example, the loading effect of the Submit button is `{{form1SubmitToHrmsEn1.isFetching}}`. Clicking the button triggers query `form1SubmitToHrmsEn1` to run, and during this process, the button is displayed with the loading effect.

#### Confirmation modal

You can set a confirmation modal for a double check for your users when they perform operations such as adding, modifying or deleting data. In the **Advanced** tab of the query, toggle **Show a confirmation modal before running**, and enter a confirmation message.

#### Form design

Forms are frequently used to collect information. For more details on building easy-to-follow and productive forms, see Design an efficient and user-friendly form.


# Design an efficient and user-friendly form

### Select proper input components

The UX design of input components in a form is crucial to the form's efficiency. Properly chosen components can save users effort and lead to better results. OpenFlower offers a variety of input components, including text input, number input, option list, and radio buttons, each of which works for different scenarios.

For example, when the input is generated from data, use the option list to let users quickly fill in the input.

Other input components, such as radio, are really helpful when the users need to directly choose between two options, for example, to indicate whether they would like to sign up for a particular activity, like a conference.

Number input is also a commonly used component type—for example, in an order management system.

### Keep layout clear and consistent

Keep all input field lengths the same to make the form visually neat, and put all input fields into a single column to achieve better readability.

### Group content with OpenFlower divider

Use the OpenFlower **Divider** component to group relevant input fields to help users process information in an organized way.

### Add user feedback

Form component is quite often used for CRUD operations, which sometimes can be risky due to human errors. To avoid harmful CRUD operations, you can add a confirmation modal before the users submit the form.

Similarly, a notification after users' submission gives users timely feedback. You can add a global notication to the Submit button. In OpenFlower, you can add global notifications in three ways. See global notification for details.

### Clear input fields on submission

Clearing input fields in a form upon submission helps users proceed with the subsequent submissions. You can set in **Properties** tab, and toggle **Reset after successful submit**.

However, sometimes it is better to leave the form uncleared—for example, when much of the values stay the same in subsequent submissions, or when the user continuously edits data. In such cases, you can insert a button with an event handler to clear each input on click.


# Customize Styles

## Using Webfonts

Based on the @import statement you can use external hosted Webfonts with OpenFlower.\
\
In the Workspace CSS or App CSS editor, you can add Font Family Import Statements

```
@import url('https://fonts.googleapis.com/css2?family=Abel&display=swap');
```

Based on the Text-Property "Font Family" you can then activate this Font Family in your Apps at many components.

You can use the interactive Demo to see the Steps based on the Component "Divider"

{% embed url="<https://app.supademo.com/demo/cWzrhtxHJfJq9ZpgDOq4K>" %}

## Customize Component Styles with CSS

With CSS you can further customize the Design of Components. You can use the same CSS Editor for every app or on the Workspace for all apps.

{% hint style="info" %}
To set CSS Styles for all apps in a Workspace, use the Editors in the Advanced Settings.
{% endhint %}

OpenFlower Allows you to define Styles at "global Level". That means, you also can customize the style of the Admin Area and the Editor.

To limit the effect of a customized Styling to your Apps only, you can use the prefix class "root-container" in your selectors.

<pre class="language-css"><code class="lang-css">// Use the prefix-class in your Selectors to limit the customized CSS to your Apps only.

// will have an effect for all Buttons of OpenFlower (Editor + Apps)
.button1 {
<strong>    border-radius: 50px;
</strong>}

// will have an effect for all Buttons of your OpenFlower Apps only
.root-container .button1 {
    border-radius: 50px;
}

// from version v2.3.2 on this class get a new name
// will have an effect for all Buttons of your OpenFlower Apps only
.lowcoder-app-canvas .button {
    border-radius: 50px;
}
</code></pre>

Each OpenFlower Component has a root element with the CSS class name "lowcoder-\<component-type>. This identifies the class, not the individual item. In styles that you develop for components, you can use this global class identifier to make sure your styles apply only to all children of a component type.

<figure><img src="/files/EFyaK5u1iDtlNqDCHycN" alt=""><figcaption><p>CSS Class names for Types of Components</p></figcaption></figure>

To address a specific instance of a OpenFlower Component, you can use the CSS Class which is available in the further tree.

<figure><img src="/files/GqGeWlBazWimn5sMJ84h" alt=""><figcaption><p>Address specific instances of a OpenFlower Component</p></figcaption></figure>

```css
// will apply to all Buttons in your App
.lowcoder-app-canvas .button {
    border-radius: 50px;
}

// will apply to the specific Button with the name button1 in your App
.lowcoder-app-canvas .button1 {
    border-radius: 50px;
}
```


# Component Styling Possibilities

OpenFlower allows you to style your components in many ways and so enables you to build beautiful and customized applications.

Since OpenFlower 2.3.0 we support more style attributes.

* Text Size
* Font Weight
* Font Family
* Background Image
* Background Gradient
* Border-Size
* Border-Color
* Border-Radius

Here are some interactive Demos as examples.

{% embed url="<https://app.supademo.com/demo/FWO1CpYQeBSYStJjvi_DM>" %}
Text Styling for Standard Texts (Markdown-Type has own styling support)
{% endembed %}

{% embed url="<https://app.supademo.com/demo/C0xlbDTUZeRDX0EYhv77s>" %}
Container Styling is detailed for Header, Body and Footer
{% endembed %}

{% embed url="<https://app.supademo.com/demo/oqryqCpi0mAG2oLGj5ypd>" %}
Better Styling Support for Modals
{% endembed %}


# Video Calls in OpenFlower

Collaboration features, especially interactive video calls, in customer-facing and internal employee apps are crucial for modern Applications.

For customer-facing apps, this enhances engagement, provides immediate support, and personalizes the user experience, fostering customer satisfaction and loyalty.

For internal apps, it streamlines communication, enables real-time problem-solving, and promotes team cohesion, boosting productivity and innovation. Combining these collaboration tools ensures seamless interaction, reduces response times, and creates a more connected and efficient workflow, which is essential for maintaining a competitive edge and achieving business success.

We wanted to give you, as an application developer, the maximum freedom at the smallest cost possible. After a research phase, we decided to integrate Agora SDK in OpenFlower so that you can build Video-Meeting Experiences inside your Apps.

{% embed url="<https://agora.io/>" %}

{% hint style="info" %}
To make the docs readable, we will name the user who is in a Video Meeting "**You**" and other users who are in the same Meeting Room "**Attendees**".
{% endhint %}

## Preparation

To use Video Meetings based on Agora SDKs in OpenFlower, you need to create an Account with Agora.

{% embed url="<https://console.agora.io>" %}

#### Step 1: Create an Agora Account

1. **Sign Up**: Go to [Agora Console](https://console.agora.io/) and sign up for a new account if you don't already have one.
2. **Log In**: After creating your account, log in to the Agora Console.

<figure><img src="/files/WLP55P1PhT8hTQrDGkvU" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
The first steps and even some meeting hours are free. Only if you have a higher volume of meeting hours than your app users spend do you need to purchase a plan. This allows you to get to know and test intensely - free of charge.
{% endhint %}

#### Step 2: Create a New Project

1. **Navigate to Projects**: Once logged in, go to the "Projects" section on the Agora Console.
2. **Create a Project**:
   * Click on "Create" to start a new project.
   * Enter a name for your project (e.g., "MeetingProject").
   * Select "App ID without Certificate" or "App ID with Certificate" based on your security needs. For production environments, it is recommended to use "App ID with Certificate".

<figure><img src="/files/KjqNj791Jh8pnL92ckLM" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
If you use App ID with Certificate, you will need the additional component to install, which is the Agora Token Server.
{% endhint %}

#### Step 3: Obtain App ID

1. **App ID**: After creating the project, you will see your newly created project listed. Click on the project name to view its details.
2. **Copy the App ID**: In the project details, you will find your App ID. Copy this App ID as you will need it for integrating Agora SDKs.

<figure><img src="/files/2EtOoifea3YVRkHDzut7" alt=""><figcaption></figcaption></figure>

#### Step 4: Enable Required Services

1. **RTM Service**: Ensure that the Real-Time Messaging (RTM) service is enabled for your project. This can be done from the "Projects" settings.
2. **RTC Service**: Ensure that the Real-Time Communication (RTC) service is enabled for your project.

#### Step 5: Set Up Token Generation (Optional but Recommended)

1. **Enable App Certificate**: If you opted for "App ID with Certificate," you need to enable the App Certificate.
   * Go to the "Security" tab in your project settings.
   * Enable the App Certificate.
   * Copy the App Certificate as it will be used to generate tokens.\\

To use the App Certificate (a token-based additional security layer for your meetings and real-time communication), you will need to operate your own Token Server. This is easy, based on the prepared standard Docker image.

{% embed url="<https://github.com/AgoraIO-Community/agora-token-service>" %}

You can read more details about Agora [Token Management here](https://docs.agora.io/en/video-calling/get-started/authentication-workflow?platform=web). However, the public docker image and the ready integration in OpenFlower make it truly easy.

The public Standard Docker Image of the Token Server needs 3 ENV Variables:

```
APP_CERTIFICATE // the key, copied from the Agora Project Console
APP_ID // your Agora Ap Project ID, copied from the Agora Project Console
CORS_ALLOW_ORIGIN // control which domains can have CORS access
```

As soon as the Token Server is up and running, you can create a Rest Data Query like this:

```
HTTP GET
https://<tokenServerAddress>/rte/{{meetingController.meetingName}}/publisher/userAccount/{{meetingController.localUserID}}/
```

As a Response, you will get back a JSON, which contains 2 Tokens.

```json
{
  "rtcToken": "007eJxSYC ... wACAAD//+loLdY=",
  "rtmToken": "007eJxSYH ... MPCAAA//+1IzWK"
}
```

{% hint style="warning" %}
If you have activated "App Certificates" in the Agora Project Settings and so use the Agora Token Server, then you will need both Tokens to Start a Meeting at the Agora Meeting Controller Component.
{% endhint %}

{% hint style="success" %}
We strongly recommend to use Agora Meeting Tokens to ensure a secure Meeting and Realtime Messages Experience for your Users.
{% endhint %}

### Agora Token Handling

The flow of the Agora Tokens for Meetings is like this:

1. **User clicks 'Start Meeting' button**:
   * The user initiates the process by clicking the "Start Meeting" button in the application.
2. **Token Server is Requested**:
   * The application sends a request to the Token Server to obtain the necessary tokens for the session.
3. **Token Server responds with rtcToken and rtmToken**:
   * The Token Server responds with the required `rtcToken` and `rtmToken` for the session.
4. **App Developer binds Data Query Response using Handlebars**:
   * The response data containing the tokens is processed and bound to the application using Handlebars templating.
5. **Tokens are assigned to Agora Meeting Controller Component**:
   * The tokens are assigned to the appropriate fields in the Agora Meeting Controller Component, enabling the meeting session to start.
6. Call the function startMeeting by JavaScript or Event Handler
   * By the help of an Event Handler or by JavaScript the App Creator can now call the yourMeetingController.startMeeting() Function of the Meeting Controller. *(before that, a single Agora Meeting Controller Component must be placed in the App)*

<figure><img src="/files/VX1QvN6jJlK9iWbtjX08" alt=""><figcaption></figcaption></figure>

## Video Meeting Components

<figure><img src="/files/XirmK0dgIZnJDxsmQvla" alt=""><figcaption><p>3 Dedicated Components for Video Meetings</p></figcaption></figure>

There are 3 components to build a Video-Call Meeting experience.

* Agora Meeting Controller - the central controlling component
* Camera Stream - show the camera stream of you or the attendees
* Screen Share Stream - shows the screen share stream of you

### Agora Meeting Controller

The Agora Meeting Controller is the central Component that controls all aspects of the meeting and provides the necessary data objects and functions.

We built the Agora Meeting Controller around the "Drawer" Component, so you can have a visual space where the App Creator can display Meeting Configuration Settings.

<figure><img src="/files/8juW001zs4wAycD1Wrud" alt=""><figcaption></figcaption></figure>

The following settings are mandatory:

* Agora Application ID (copied from the Agora Project Console. Must be identical to the one used at the Agora Token Server)
* Meeting Name: This is the Meeting Room Name. Everyone who knows this name can attend the Meeting. It should be a dynamic value, and you need to manage in your own Backend / Database the management of the Meeting Room Names
* Host User ID: This is the Unique Identifier for a User in Agora Meetings. It *could* be the UserID of OpenFlower Users - if your scenario offers Meetings only for OpenFlower Users. It should be a dynamic value, and you need to manage it in your own Backend / Database the management of the User IDs.
* RTM and RTC Token are used when an Agore App Certificate and a Token Server is used. For each meeting instance, you need a fresh Token Pair.

{% hint style="warning" %}
A Meeting Room can be used for multiple Meetings. The Room is "open" as soon as created. Users who specify the same *channel name* (In OpenFlower: "Meeting Name") join a common channel and interact with each other. "A channel is created when the first user joins. It ceases to exist when the last user leaves." The [Agora Docs](https://docs.agora.io/en/video-calling/overview/core-concepts?platform=web) can give more & deeper information to it.
{% endhint %}

{% hint style="warning" %}
Tokens have to be generated at each start of a meeting for a meeting attendee. When the Tokens are fetched and bound to the Meeting Controller, the Meeting Controller Function startMeeting() can be called.
{% endhint %}

{% hint style="warning" %}
Meeting Name (Meeting Room) & Host User ID have to be managed in your own Backend / Database. In regards to the OpenFlower App, they must be dynamic - as multiple Users may want to join the meeting - which is based on the same single OpenFlower App.
{% endhint %}

### Meeting Controller Data & Functions

#### Main Data Objects

1. **localUser**: An object representing the local user, including properties like `user`, `audiostatus`, `streamingVideo`, `speaking`, etc.
2. **participants**: An array of objects representing the "remote" participants in the meeting, each with properties like `user`, `audiostatus`, `streamingVideo`, etc.
3. **messages**: An array of messages exchanged in the RTM channel.
4. **meetingActive**: A boolean indicating whether the meeting is currently active.
5. **meetingName**: The name of the meeting.

#### Functions

1. **openDrawer()**: Opens the meeting drawer (UI component).
2. **startSharing()**: Toggles screen sharing on and off.
3. **audioControl()**: Toggles the microphone on and off.
4. **videoControl()**: Toggles the local camera on and off.
5. **startMeeting()**: Initiates the meeting, joins the RTC and RTM channels, and sets up the local user.
6. **startSharing()**: Initiates the Screen Sharing at the local User
7. **broadCast()**: Sends a message to the RTM channel or to specific peers.
8. **setMeetingName()**: Sets the name of the meeting.
9. **setUserName()**: Sets the name of the local user.
10. **setRTCToken()**: Sets the RTC token for the session.
11. **setRTMToken()**: Sets the RTM token for the session.
12. **endMeeting()**: Ends the meeting, leaves the RTC and RTM channels, and updates the local user's state.

### Camera Stream

The Camera Stream allows you to show your own Camera Stream or the Video Stream of the Cameras of the other Attendees. You can place this component like any other on the canvas.

<figure><img src="/files/bKBXBef8wknq8UyiUJdB" alt="" width="563"><figcaption></figcaption></figure>

The Camera Stream Component has 2 special features to enable good styling options.

* Vide Aspect Ratio - a CSS property that, in combination with Auto-Height, makes sure that you have perfect squares/circles of the Camera Streams.
* Profile Image URL - Here you can set an actual user Profile Image - or a Profile Image Generator like in our Example [https://www.dicebear.com/playground](https://www.dicebear.com/playground/). The Profile image is displayed as long as the Camera Video Stream is not active.

The Video Stream ID is the Meeting User ID.

* To set the video Stream of the local user (you), you can bind the value of **localUser.** `{{meetingController.localUser}}`
* To set the video Stream of the Attendee user, you can make use of the **participants** Array. You would need a repeater component like the List Component for example to keep the meeting attendee display dynamic. In the listItem (**currentItem**) you would set then for example: `{{meetingController.participants[currentItem]}}`

{% hint style="info" %}
The Video Stream get's automatic visible, as soon as the respective Camera is ready and active.
{% endhint %}

{% hint style="danger" %}
The incoming Sharing Screen Stream, unfortunately, is currently in Agora Meeting tight connected to the Camera Stream (in fact, there is only a single stream per attendee). That means, that the control of the displayed Stream (Camera or Screen Sharing) has to be managed by the App Creator
{% endhint %}

### Screen Share Stream

The Screen Share Stream Component is almost identical to the Camera Stream component. It is meant to enable you to choose a place where to display Shareing Stream. Often this will be in a bigger content area.

To set (display) the Sharing Stream of the local user (you), you can bind the value of **localUser.** `{{meetingController.localUser}}.`

## Realtime Messages

In a Meeting Room, you can send and receive messages, either to all Attendees or to selected Users (selected Attendees).

You can send messages as soon as the meeting has started. To do so, you can use the JavaScript Query and use, for example, a function call like this:

```javascript
const message = {
  text: "Hello everyone!",
  sender: userId // you can send this to understand the Sender.
  data: {...} // // you also can send a complex nested JSON Object. 
};
meetingController.broadCast(message);
```

Messages sent in the Meeting Room (Channel) are collected for each Meeting Attendee at the local Data Object **messages** (`meetingController.messages`f.e.) as an Array.

{% hint style="danger" %}
the Data Object **messages** will contain only the latest 100 Messages. As App Creator you are responsible for any further storage of these Messages.
{% endhint %}

## Why Video Meetings in OpenFlower?

<figure><img src="/files/fFJAPOzICPQKe5B5sD2Q" alt=""><figcaption></figcaption></figure>

A picture says more than 100 words. Or?

Despite all the amazing Developments in IT, the bridge between Meeting and "Working" or "Shopping," "Learning," or any other online Activity is not made.

We encourage you to use OpenFlower and the first fully Lowcode Meeting App Generator in it, to create completely new and useful experiences for your App Users and Customers.

* Face-to-face online Support
* Social Shopping - but together
* Meetings, where people can actually work together within - not only talk about
* Customer Relationship Management with real-time alignment
* A new style of eLearning - interactive content + face-to-face with the Tutors
* Business Apps with enabled Collaboration to speed up business processes

MIX IT! Shake the World and enable the Future for your Users!


# Data source basics

Data source is where you manage credentials and make connections to various popular databases such as PostgreSQL, MongoDB, Elasticsearch and all kinds of internal or third-party APIs.

OpenFlower stores your credentials securely and does not store any data from your requests or responses. It only serves as a proxy between client side and your data sources.

## Connect to a new data source

{% hint style="info" %}
Check out IP allowlist and add OpenFlower IP addresses to your data source allowlist when needed.
{% endhint %}

Follow the steps below:

1. Click **Data Sources** on OpenFlower homepage.
2. Click **New data source** on the upper right. This permission is restricted to workspace admins and developers.
3. Select the database or API type you need to connect.
4. Fill out data source connection fields. See docs in Connect to databases and Connect to APIs for detailed tutorials.
5. Click **Test connection** to check whether you can make a successful connection to data source based on current settings.
6. Click **Save**.

You can also connect to a new data source when creating or editing queries in query library or query editor.

## Permissions

Only workspace admins and developers can view the **Data Sources** page and create new data sources.

For each data source, workspace admins and the creator can change its **Access Control**. Go to OpenFlower homepage > **Data Sources**, and click `···` > **Access Control**.

For more information about permissions for data sources, see Data sources.


# Configure IP allowlists

If your data source has firewall rules, it is vital to add IP addresses of OpenFlower to the allowlist before connection. Then you can connect to databases or APIs and create queries.

## Allow IP addresses of OpenFlower

Add the following IP addresses to your data source allowlist when needed:

```
(US) !! IMPORTANT From 03.03.2024 we do not use US infrastructure !! 
185.122.165.50
185.122.165.51
185.122.165.52
185.122.165.56

(GB)
185.85.242.35
185.85.242.36
185.85.242.37

(DE)
185.44.64.41
185.44.64.42
185.44.64.43
185.44.64.44
185.44.64.47
```


# Data sources in OpenFlower

Data sources offer a wide range of capabilities to empower your OpenFlower applications and enable various use cases, from data storage and analytics to communication and automation.

As of 2023, you can use the following included Data sources:

**PostgreSQL** is an open-source relational database management system. It's highly versatile and can be used for various applications. With its ACID compliance and extensibility, it's a great choice for complex data structures and queries. Use cases include transactional and analytical applications across different industries.

**Microsoft SQL Server** is a relational database system developed by Microsoft. It's commonly employed in enterprise environments for mission-critical applications and business intelligence. What makes it exceptional is its robust security features, scalability, and seamless integration with Microsoft products.

**Oracle** is a powerful, enterprise-grade relational database. It's the go-to choice for large-scale applications that require high performance, reliability, and scalability. Some of the advanced features it offers include partitioning, clustering, and real application clusters.

**MariaDB** is an open-source relational database derived from MySQL. It's a versatile option, suitable for web applications, e-commerce, and general data storage. It combines MySQL compatibility with performance improvements and enhanced security.

**MySQL** is another open-source relational database management system known for its speed, reliability, and ease of use. It's widely used for web applications and data-driven websites.

**Elasticsearch** is a distributed, RESTful search and analytics engine. It's an exceptional choice for applications requiring full-text search, log and event data analysis, and real-time analytics. What makes it amazing is its ability to provide near-instant search results and scalability for large datasets.

**Redis** is an open-source, in-memory data structure store. It's ideal for tasks like caching, real-time analytics, and session management for web applications. It stands out for its high performance and low latency data access.

**MongoDB** is a NoSQL database designed for handling unstructured or semi-structured data. It's an excellent fit for use cases like content management systems, IoT applications, and mobile apps. MongoDB's flexible schema, scalability, and efficient handling of JSON data make it amazing.

**DynamoDB** is a managed NoSQL database service provided by AWS. It's particularly well-suited for serverless applications, gaming, and mobile apps. What sets it apart is its automatic scaling and high availability, all with minimal operational overhead.

**CouchDB** is a NoSQL database known for its simplicity and ease of use. It's great for document storage and other use cases where flexibility and straightforward data access are essential.

**ClickHouse** is an open-source columnar database management system designed for analytical processing. It's ideal for applications with large datasets that require fast analytics and reporting capabilities.

**Snowflake** is a cloud-based data warehousing platform that offers instant scaling and diverse data integration options. It's amazing for organizations that need a high-performance data warehousing solution without the hassles of traditional setups.

**Google Big Query** is a fully managed, serverless, and highly scalable multi-cloud data warehouse. It excels in fast data analytics, ad hoc queries, and real-time data analysis across large datasets.

**REST API** allows you to interact with web services using standard HTTP methods. It's widely used for integrating web applications with external services, retrieving data, and updating information.

**Open API Specification APIs** enable a GUI interface to access REST APIs. It's excellent for ensuring consistency and ease of use when working with APIs.

**SMTP** is used for sending and receiving email. It's essential for email communication in various applications, including transactional messages and notifications.

**Google Sheets** is a cloud-based spreadsheet software. It's amazing for collaborative data analysis, reporting, and sharing information in real-time.

**GraphQL** is the query language for APIs that provides more flexibility and efficiency compared to REST. It's great for fetching precisely the data you need and avoiding over-fetching, which is common in REST APIs.

**S3** Amazon S3 (Simple Storage Service) is a scalable object storage service that's perfect for storing and retrieving data, including backups, media files, and static website content.

**Google Cloud Storage** is a versatile and highly available object storage service. It's amazing for data storage and backups in Google Cloud Platform projects.

**n8n** the open-source workflow automation tool. It's excellent for building custom workflows and automating tasks across different applications and services.

**Firebase** is a platform for building mobile and web applications with a wide range of features, including real-time databases, authentication, and cloud functions. It's amazing for rapidly developing and deploying apps.

**WooCommerce** is a popular e-commerce plugin for WordPress. It's ideal for building online stores and managing product listings, customer orders, and payments.

**Open AI** provides APIs for various AI services, including natural language processing and machine learning. It's amazing for integrating advanced AI capabilities into your applications and services.

**Athena** Amazon Athena is an interactive query service that allows you to analyze data stored in Amazon S3 using standard SQL. It's excellent for ad-hoc data exploration and analysis.

**Asana** is a work management platform that helps teams organize and track tasks and projects. It's amazing for improving collaboration and productivity within organizations.

**Stripe** is a payment processing platform that simplifies online transactions and billing. It's great for e-commerce businesses and applications that require secure payment processing.

**CircleCI** is a continuous integration and continuous deployment (CI/CD) platform. It's amazing for automating software testing and deployment pipelines, ensuring code quality and reliability.

**Lambda** AWS Lambda is a serverless compute service. It's great for executing code in response to events and automating tasks without the need to manage server infrastructure.

**Front** is a customer communication platform that centralizes email, chat, and other customer interactions. It's amazing for enhancing customer support and collaboration within teams.

**GitHub** is a web-based platform for version control and collaborative software development. It's excellent for source code management and team collaboration on software projects.

**Gitlab** is a web-based DevOps lifecycle tool that provides version control, continuous integration, and more. It's amazing for managing the entire software development process.

**Hugging Face Endpoints:** Hugging Face provides endpoints for accessing state-of-the-art natural language processing models and tools. It's great for language understanding and generation tasks.

**Hugging Face Inference** provides APIs for running NLP models. It's amazing for integrating cutting-edge NLP capabilities into your applications.

**Jira** is a project and issue tracking tool by Atlassian. It's excellent for managing software development projects and workflows.

**OneSignal** is a push notification and in-app messaging platform. It's great for engaging and retaining users through timely notifications.

**SendGrid** is an email marketing and transactional email service. It's amazing for sending bulk emails and transactional messages.

**Shopify** is an e-commerce platform for building online stores. It's ideal for businesses looking to sell products online.

**Slack** is a messaging and collaboration platform for teams. It's great for real-time communication, file sharing, and project coordination.

**Supabase** is an open-source alternative to Firebase, offering a real-time database, authentication, and more. It's amazing for building web and mobile applications with dynamic, real-time features.

**Cloudinary** is a cloud-based media management platform. It's excellent for storing, optimizing, and delivering images and videos for web applications.

**Notion** is an all-in-one workspace for notes, tasks, wikis, and databases. It's amazing for knowledge management, project planning, and collaborative documentation.

**Datadog** is a monitoring and analytics platform for cloud-scale applications. It's great for gaining insights into the performance and health of your applications.

**Twilio** is a cloud communications platform that allows you to send messages, make voice calls, and more. It's amazing for adding communication features to your applications.

**Fauna** is a globally distributed, serverless database that's great for building modern applications with low-latency, real-time data access.

**D-ID** provides solutions for protecting privacy by anonymizing faces in images and videos. It's amazing for safeguarding sensitive data and ensuring GDPR compliance.


# APIs as Datasource


# REST API

## Connect REST API to OpenFlower

Follow these steps to connect to REST APIs.

1. Click **Data Sources** on OpenFlower homepage.
2. Click **New Data Source** on the upper right. This permission is restricted to workspace admins and developers.
3. Select **APIs** > **REST API**.
4. Configure general settings. At a minimum, input **Name** and the base **URL** of the API to connect to. **Headers** and **Parameters** are optional. These are all common parameters that consist of URL query prefixes.
5. Configure **Authentication Type**.
6. Click **Save**.

You can also connect to a new REST API data source when creating or editing queries in query library or query editor.

## Authentication type

The default authentication type is **None**. Other options are **Basic** and **Digest**, which require configuration of **Username** and **Password**.

If you need other customized authentication methods, contact our customer service on the [homepage](https://prod-us1.openflower.org).

## SSL Cert Verification

In the advanced settings tab, you can configure SSL Cert Verification for your data sources. Three options are available: CA (Certificate Authority) Certificate, Self Signed Certificate, or Disabled. For security reasons, OpenFlower accepts APIs with CA certificates by default. For APIs without CA certificates, you can either select "Verify Self Signed Cert" and enter the self-signed certificate or disable the SSL Cert verification.

Note that for self-signed certificate, the information should be provided in .[PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) (Privacy-Enhanced Mail) format. This certificate will be encrypted and stored securely in OpenFlower.

## Forward cookies

If you are self-hosting OpenFlower in your own domain, you can make REST API requests with cookies to existing services under that domain. You can either toggle the option "Forward all cookies" or specify certain cookies to forward in the text box. This feature can be helpful in cases where you use cookies to share common context (like identity info in JWT format) across different services.

## Create a REST API query

You can create REST API queries in two ways:

* add a new data source and then create a query
* create a query directly

Note that without creating a new data source, the REST API data source is not stored in your **Data Sources** and thus cannot be accessed to build other queries.

### Create a query with a new data source

Follow the steps to add a new REST API data source first and then create a query.

1. Connect REST API to access to.
2. In the query editor, click **+ New**.
3. Select the added API in **Data source**.
4. Enter URL, Headers, Parameters, and fill in other fields if needed.
5. Click ▶ **Run**.

### Create a REST API query with predefined data source

Follow the steps to create a REST API query directly.

1. In the query editor, click **+ New**.
2. Select **REST Query** in Data Sources. OpenFlower offers this built-in plain data source so that you can quickly create REST API queries without adding a new data source first.
3. Enter URL, Query, Variables, Parameters, and fill in other fields if needed.
4. Click ▶ **Run**.


# GraphQL

GraphQL query language allows the client to specify the structure of the requested data, and returns data from the server using the same structure. Such design ensures that only required data is retrieved, thus improving query efficiency.

## Connect GraphQL API to OpenFlower

Follow these steps to connect to GraphQL APIs.

1. Click **Data Sources** on the OpenFlower homepage.
2. Click **New data source** on the upper right. This permission is restricted to workspace admins and developers.
3. Select **APIs** > **GraphQL**.
4. Configure general settings. At a minimum, input **Name** and the base **URL** of the API to connect to. **Headers** and **Parameters** are optional.
5. Configure **Authentication Type**.
6. Click **Save**.

You can also connect to a new GraphQL API data source when creating or editing queries in query library or query editor.

## Authentication type

By default, the authentication type is **None**. You can configure it as **Basic** or **Digest**, which require Username and Password. If you need other authentication methods, contact our customer service on the [homepage](https://prod-us1.openflower.org).

The following is an example of connecting to [GitHub GraphQL API](https://docs.github.com/en/graphql). Set the authentication type to **None**. Then, input the required information into **Headers** to complete the authentication. The key is "Authorization" and the value is `"Bearer" + white space + YOUR_ACCESS_TOKEN`. See [Creating a personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) for detailed information.

## SSL Cert Verification

For detailed information, see SSL Cert Verification.

## Forward cookies

For detailed information, see Forward cookies.

## Create a GraphQL query

You can create GraphQL queries in two ways:

* add a new data source and then create a query
* create a query directly

Note that without creating a new data source, the GraphQL data source is not stored in your **Data Sources** and thus cannot be accessed to build other queries.

### Create a query with a new data source

Follow the steps to add a new GraphQL data source first and then create a query.

1. Connect GraphQL API to access to.
2. In the query editor, click **+ New**.
3. Select the added API in **Data source**.
4. Write a query.
5. Click ▶ **Run**.

For details about using GraphQL, reference [Introduction to GraphQL](https://graphql.org/learn/).

The following GraphQL query searches the latest 20 closed issues in the repository `octocat/Hello-World`, and returns the title, URL, and the first 5 labels of each issue.

### Create a query with predefined data source

Follow the steps to create a GraphQL query directly.

1. In the query editor, click **+ New**.
2. Select **GraphQL Query** in Data sources. OpenFlower offers this built-in plain data source so that you can quickly create GraphQL queries without adding a new data source first.
3. Enter URL, Query, Variables, Parameters, and fill in other fields if needed.
4. Click ▶ **Run**.


# Google Sheets

## Authentication

Follow the steps below to create a service account for your Google Cloud project and share your Google sheet through the \*\*\*\* service account key.

### Create a service account key

Follow the steps below:

1. Enable the [Google Sheets API](https://console.cloud.google.com/apis/library/sheets.googleapis.com) of your Google Cloud project.
2. Select **APIs & Services** > **Credentials** from the left panel.
3. Click **+ CREAT CREDENTIALS** > **Service account** to create a new service account.
4. Get a service account key:
   1. Click the **Email** of the service account.
   2. Click **KEYS** > **ADD KEY** > **Create new key**.
   3. Select **JSON** as the key type.
   4. Click **CREATE** to download it.

{% hint style="success" %}
Now the service account key, in a JSON format, is downloaded to your device. Make sure to keep it somewhere safe.
{% endhint %}

### Share your Google sheet

Navigate back to to [Google Sheets](https://docs.google.com/spreadsheets), and follow the steps below to share your sheet through the \*\*\*\* service account key.

1. Open the JSON file of the key.
2. Copy the value of `client_email`, which is an identity used for access management of your sheet.
3. Click **Share** at the top right and paste the copied `client_email` value to add a member with access.
4. Select **Editor** as the role and click **Send**.

{% hint style="success" %}
When you see the notification **Access updated**, you have authorized this service account to edit your sheet.
{% endhint %}

## Connect Google Sheets to OpenFlower

Follow the steps below:

1. Click **Data Sources** on OpenFlower homepage.
2. Click **New Data Source** on the top right. Note that this permission is restricted to workspace admins and developers.
3. Select **Database** > **Google Sheets** as the type.
4. Set its name and paste the whole content in the JSON flie of the key to **Service Account**.
5. Click **Save**, and you can check it in your data source library later.
6. To access and manipulate data in your app, you can fill in the **Spreadsheet** id and **Sheet Name** in a query editor as follows.

   The available methods are **Read Data**, **Append Row**, **Update Row**, and **Clear Row**.


# SQL Databases


# MySQL

MySQL is a popular relational database management system (RDBMS) and is widely used in various industries due to its scalability, flexibility, and ease of use.

## Prerequisites

* Get MySQL database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to MySQL data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **MySQL** as the data source type.
3. Set its name and configure general settings, including host, port, and database name. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.

## Query a MySQL data source

Create `query1` and select the recently created data source. Insert a query and then click ▶ **Run**.


# MariaDB

MariaDB is a popular open-source relational database management system that offers high performance, scalability, and security features for businesses and organizations of all sizes.

## Prerequisites

* Get MariaDB database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to MariaDB data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **MariaDB** as the data source type.
3. Set its name and configure general settings, including host, port, and database name. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# PostgreSQL

PostgreSQL is a powerful open-source relational database management system (RDBMS), renowned for stability, scalability, and robustness. It is an ideal choice for applications that require data consistency and reliability.

## Prerequisites

* Get PostgreSQL database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to PostgreSQL data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **PostgreSQL** as the data source type.
3. Set its name and configure general settings, including host, port, and database name. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# Microsoft SQL Server

Microsoft SQL Server is a relational database management system (RDBMS), offering a comprehensive platform for storing, managing, and retrieving data from various sources.

## Prerequisites

* Get Microsoft SQL Server database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to Microsoft SQL Server data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **Microsoft SQL Server** as the data source type.
3. Set its name and configure general settings, including host, port, and database name. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# Oracle

Oracle is a leading provider of enterprise software and database management systems, offering a wide range of solutions for businesses of all sizes.

## Prerequisites

* Get Oracle database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to Oracle data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **Oracle** as the data source type.
3. Set its name and configure general settings, including host, port, and service name. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# NoSQL Databases


# MongoDB

MongoDB is a broadly used NoSQL database system that stores data in JSON-like documents with dynamic schemas and handles both unstructured and semi-structured data. By offering high performance, scalability, and ease of use, MongoDB is a versatile and robust solution for a wide range of use cases.

## Prerequisites

* Get MongoDB database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to MongoDB data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **MongoDB** as the data source type.
3. Set its name and configure the parameters according to your **Connection Type**:
   * **Regular**: Host, port, database name (required); user name, password (optional)
   * **URI**: URI
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# CouchDB

CouchDB is an open-source NoSQL document-oriented database management system that provides easy-to-use web-based interfaces for data storage, retrieval, and indexing, as well as built-in synchronization and replication capabilities.

## Prerequisites

* Get CouchDB database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to CouchDB data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **CouchDB** as the data source type.
3. Set its name and configure the server URL. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# DynamoDB

DynamoDB is a fully managed NoSQL database service offered by Amazon Web Services (AWS) that provides fast and predictable performance with seamless scalability, making it ideal for applications with high scalability requirements.

## Prerequisites

* Get DynamoDB database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to DynamoDB data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **DynamoDB** as the data source type.
3. Set its name and configure general settings, including access key ID, secret key. You can also set endpoint URL and region.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# InMemory Databases


# Redis

Redis (Remote Dictionary Server) is an open-source in-memory data structure store. Due to the nature of being fast, scalable, and highly available, Redis is a popular choice for applications that require real-time data processing, high throughput, and low latency.

## Prerequisites

* Get Redis database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to Redis data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **Redis** as the data source type.
3. Set its name and configure the parameters according to your **Connection Type**:
   * **Regular**: Host, port, database name (required); user name, password (optional)
   * **URI**: URI
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# File Storages

With OpenFlower you can connect to different File storage providers to upload and manage Your Files there.


# S3 File Storage

With the S3 Datasource Plugin, you can connect to your Amazon S3 Storage to upload and manage Files.

### Prepare Amazon S3 Buckets

<figure><img src="/files/s3k9ALOUf0t1gyl4ST9s" alt=""><figcaption><p>Create at least one Bucket in a Region.</p></figcaption></figure>

Next to Buckets, you need to create a User to configure Access Keys

<figure><img src="/files/jE7bES1kd6KFSa02z0AI" alt=""><figcaption><p>Create a Amazon AWS User, give with a Policy the desired Access for your Buckets</p></figcaption></figure>

Now you can create the Access Keys

<figure><img src="/files/7tbDsWKz57uejQjPpJRk" alt=""><figcaption><p>The Access Keys are needed to connect to your S3 Buckets</p></figcaption></figure>

### Create a Datasource to Connect to your Bucket

To use Amazon S3, you need to connect to your Buckets.

<figure><img src="/files/FJ82Ygt1MkAJA7TAM0g8" alt=""><figcaption><p>You need to have at least one Bucket as also your Access Key for S3</p></figcaption></figure>

The URL contains the Amazon AWS Region, in which you created the Buckets. This does not affect replica regions.

```
https://s3.<your_aws_region>.amazonaws.com
```

### Upload Files

With the help of the file upload component, you can now gather the data that you want to upload as Fle to your Bucket. There are two possible file types in general.

* Text based Files like JSON or CSV
* Binary Files

{% hint style="info" %}
Binary files are represented in the Browser as base64 encoding. However, after the transport to the S3 Bucket, they will be available as binary files.
{% endhint %}

From the possible actions of the S3 Datasource Plugin you can choose "Upload file" and fill in the settings.

<figure><img src="/files/NP8tlV6s0VNXX5PPAEf1" alt=""><figcaption></figcaption></figure>

### List Buckets and Files

Choose "List Buckets" to get a JSON List of your S3 Buckets.

<figure><img src="/files/PP0hGGBMf5x2vf33GPq7" alt=""><figcaption><p>You can get all your S3 Buckets as Data List.</p></figcaption></figure>

```json
// Response of "List buckets"
[
  {
    "name": "lowcoder-documentation-bucket"
  }
]
```

With the name of the Bucket you can now request a list of all Files.

<figure><img src="/files/d6TW8tf645xINS0tscVJ" alt=""><figcaption><p>Get a List of all Files in your Bucket as JSON</p></figcaption></figure>

```
// Response of "List files"
[
  {
    "name": "open_issues.json",
    "size": 247285,
    "lastModified": "2023-06-24T18:28:51.000Z",
    "signedUrl": "",
    "url": "https://s3.eu-central-1.amazonaws.com/lowcoder-documentation-bucket/open_issues.json"
  },
  {
    "name": "test.json",
    "size": 236646,
    "lastModified": "2023-06-24T18:17:13.000Z",
    "signedUrl": "",
    "url": "https://s3.eu-central-1.amazonaws.com/lowcoder-documentation-bucket/test.json"
  }
]
```

### Read / use Files

Based on Bucket & File name you can now request the Data of Files.

<figure><img src="/files/hEWTRvkSgzC9bYu3UgbG" alt=""><figcaption><p>Get the file as JSON / Text or base64 encoded.</p></figcaption></figure>

An alternative way to access the File is via the secure Link (Signed URL). To get the "signed Link URLs" for your files, just use the "List files" method including the "Return signed url" checkbox.

<figure><img src="/files/ihwDPX0wByndaNPQnUe0" alt=""><figcaption><p>Get direct accessible Links of your files</p></figcaption></figure>

You can now use the URL of each file and for example, display it in the File viewer or with the [Image Component](/build-applications/app-editor/visual-components/image).

### Delete Files

You can use the Method "Delete file" to ... delete a single file from your Bucket.

<figure><img src="/files/erbFfrxeNop9WxhLCbfQ" alt=""><figcaption><p>You can delete single files from your Bucket</p></figcaption></figure>


# BigData & OLAP


# Big Query

Big Query is a fully managed cloud-based data warehouse offered by Google Cloud Platform that provides high-speed querying and interactive analysis of large datasets using SQL-like queries.

## Prerequisites

* Get Big Query database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to Big Query data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **Big Query** as the data source type.
3. Set its name and service account.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# Snowflake

Snowflake is a cloud-based data warehousing platform that provides scalable and flexible solutions for storing, processing, and analyzing large volumes of data.

## Prerequisites

* Get Snowflake database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to Snowflake data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **Snowflake** as the data source type.
3. Set its name and configure general settings, including account identifier, and database. You can also set schema, warehouse, user name, password, and so on.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# ClickHouse

ClickHouse is an open-source column-oriented database management system designed for high-speed online analytical processing (OLAP) and data warehousing, capable of processing petabytes of data in real-time.

## Prerequisites

* Get ClickHouse database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to ClickHouse data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **ClickHouse** as the data source type.
3. Set its name and configure general settings, including host, port, and database name. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# Elasticsearch

Elasticsearch is a highly scalable and distributed search and analytics engine designed to perform fast and complex searches on large volumes of data.

## Prerequisites

* Get Microsoft SQL Server database connection parameters from the database owner.
* Refer to IP allowlist to add IP addresses of OpenFlower to your allowlist (if needed).

## Connect to Elasticsearch data source

Follow the steps below:

1. Create a new data source in two ways. Note that this permission is restricted to workspace admins and developers.
   * Navigate to the **Data Sources** tab on [OpenFlower Homepage](https://prod-us1.openflower.org) and click **New data source**.
   * When creating a new query in the app editor, click **+ New** > **+ New data source**.
2. Select **Database** > **Elasticsearch** as the data source type.
3. Set its name and configure general settings, including host, port, and database name. You can also set the user name and a password.
4. (Optional) Click **Test connection** to check whether the new data source is successfully connected.
5. Click **Save**, and it will be saved to your data source library.


# Websocket Datasource

Realtime Data Updates in OpenFlower

In the digital age, speed and immediacy are a key success factor. That's why we believe our **WebSocket Datasource** is a gread addition. But why is this such a game-changer?

* **Instant Updates:** Unlike traditional methods where you'd need to refresh or poll for new data via APIs, WebSockets push updates to the Browser of the Application Users the moment they happen. Imagine viewing a dashboard and watching data points, charts, and metrics update in real-time without additional API Call logic.
* **Enhanced Collaboration:** WebSockets don't just update data; they transform collaboration. When multiple users are viewing the same content, any change made by one user can be instantly seen by others. This means teams can work together seamlessly, making decisions based on real-time insights. This is possible thanks to the broadcast function we introduced.
* **Reduced Latency:** With WebSockets, the lag between sending a request and receiving a response is drastically reduced. This ensures that your apps feel snappier and more responsive, enhancing user experience.
* **Endless Possibilities:** From live chat applications to real-time gaming, tracking, and monitoring systems, the applications of WebSockets are vast. With our WebSocket Datasource, you're not just staying updated; you're unlocking a world of real-time possibilities for your apps.

### Using a WebSocket as Datasource.

First, select "Stream Query" as a new Query from the available Datasources. Stream Query is available from OpenFlower v2.1.0.

<figure><img src="/files/8SG3yi7FzwxPeXh7ZicI" alt=""><figcaption></figcaption></figure>

#### Connect to a WebSocket Server

As URL enter the WebSocket Server address ws\:// (without) and wss\:// (with SSL secured connection). From the moment you run this query, OpenFlower tries to establish a connection to the WebSocket Server. If successful, the specialty of WebSocket connections is, that they remain active and open till you disconnect the Dataquery / OpenFlower App.

{% hint style="info" %}
[https://www.piesocket.com](https://www.piesocket.com/) is a service that you can use to establish and use WebSocket Servers as a Service.
{% endhint %}

{% hint style="warning" %}
Each time a message arrives in the open connection, the OpenFlower Query will fire the event "success", so you can bind an Event-Handler to process these messages.
{% endhint %}

<figure><img src="/files/2eWyv2A92ZGaqkq0Zlph" alt=""><figcaption></figcaption></figure>

```
// for example wss://ws.kraken.com
```

#### Receive and Broadcast messages

Now the connection is made and depending on the channel you may already receive data. Quite often it is however the case, that a Client needs to subscribe to a certain topic or room. To do so, but also simply to broadcast a message into the connection, you can use the new function broadcast(); This is a function of the WebSocket Datasource / Stream Query.

Here is an example as JavaScript, which is called as soon as the Stream Query is connected successfully to the WebSocket Server and listens in the channel.

Messages arrive and are broadcasted as JSON.

<figure><img src="/files/Hz929d699YKDzW7ZZxF5" alt=""><figcaption></figcaption></figure>

```javascript
// for example:

krakenStockTicker.broadcast({
  "event": "subscribe",
  "pair": [
    "EUR/USD"
  ],
  "subscription": {
    "name": "ohlc"
  }
});
```

#### Processing incoming Messages

We can now bind the messages that are incoming and trigger the "onSuccess" of the Stream Query to a Temporary state. This is exemplary and you may want to integrate it differently.

<figure><img src="/files/boGVL7CIMJEsrIG2U4X1" alt=""><figcaption></figcaption></figure>

We use the "Success" trigger of the Stream Query and set a Temporary State with the value. In the special case of Kraken Websocket, we want to skip all "heartbeat messages", so we only set Temporary State, if there are payload data in the current message.

#### Collecting Messages

It may be useful in your application scenario to collect Messages, for example, to show a sliding Window Chart like this OHLC diagram of our example. To do so, we create for example an Array, which is available for the whole app.

```javascript
window.krakenStockTickerData = [];
```

<figure><img src="/files/V7nm6GkNGNnSQLOcEdwA" alt=""><figcaption></figcaption></figure>

Now we can bind a Javascript processing for each incoming message to the "Success" Trigger of the Stream Query and bind our chart to this data-array.

```javascript
// we take the string value of the Websocket Message and build an Array
const currentRawTickerData = currentTickerData.value.split(',');
// based on the array we can extract now the data we want and formulate an JSON Object
const currentCleanTickerData = {
  "time" : new Date(currentRawTickerData[0].substr(0,10) * 1000).toISOString().substr(11, 8),
  "duration" : new Date(currentRawTickerData[1].substr(0,10) * 1000).toISOString().substr(11, 8),
  "open" : currentRawTickerData[2],
  "high" : currentRawTickerData[3],
  "low" : currentRawTickerData[4],
  "close" : currentRawTickerData[5],
  "weighted_volume" : currentRawTickerData[6],
  "accumulated_volume" : currentRawTickerData[7],
  "trades" : currentRawTickerData[8]
}
// we push the new Object into the array, krakenStockTickerData
window.krakenStockTickerData.push(currentCleanTickerData);
// and we make sure that old data is deleted, so the array won't get too big
while (window.krakenStockTickerData.length > 30) {
  window.krakenStockTickerData.shift();
}
// Here we set a value in a temporary state, so the eCharts would update their visualization
// (the binding to the array on the window-object does not release a trigger for eCharts to re-render)
clockTickerData.setValue(currentCleanTickerData.time);
```

<figure><img src="/files/ymiFFgcGDIhB7r7YmtAj" alt=""><figcaption></figcaption></figure>


# Query basics

If there is a special case handling, you can specify it additionally for the individual Event HandlerQueries support reading data from or writing data to your data sources. You can use Data Queries for all kinds of Data Sources, like Databases, Stream Data Sources, or classic Restful APIs. You can create queries while editing an app or in the query library.

All Data Queries in OpenFlower will always return JSON Data. When you call your Database, the result will get transformed into a JSON response for example. This is very useful to have ease of use for the data in all later places in OpenFlower without the need for a Data Serialization between Formats.

## Connect to a data source

You can connect to a data source that was already in your data source library or create a new one. For detailed information, see [Data sources in OpenFlower](/connect-your-data/data-sources-in-openflower).

## Create a query

The UI of query editor varies when you choose different types of data sources. Below is an example of a connection to a PostgreSQL database and a corresponding statement.

<figure><img src="/files/9hTXfJXXs9Ro5TZnkSnR" alt=""><figcaption><p>A Data Query to a Postgres Database</p></figcaption></figure>

The SQL Mode is suitable for everyone who queries the Database and for proficient Developers who are very fluent in SQL Language and their used Database specialties.

<figure><img src="/files/VjWUhKnBKFq0Vp5Rfjtz" alt=""><figcaption><p>For many Databases there is a visual GUI Mode to execute prepared statements on the Database.</p></figcaption></figure>

OpenFlower also offers a GUI Mode for Write-Operations on the Database, such as INSERT, UPDATE, and DELETE. This helps you to ease the Binding of user-made changes of Data in your App, back to the Databases.

### Metadata

You can view the metadata of databases such as PostgreSQL, MongoDB, and MySQL. Click Metadata, and the database tables and their fields are displayed in a tree structure. Metadata is useful for writing queries, and it enables auto-suggestion while you type. It can also help you understand the Structure of the Database and its tables for a connected Database.

### Dynamic Queries and Data Binding

OpenFlower evaluates your query statement with JavaScript code inside `{{ }}` in real-time, and the result is displayed below in a floating box, so you can use it to check the correctness of the query statement.

## Trigger a query when:...

OpenFlower triggers your queries in two modes: it runs automatically when "inputs change or on page load" or manually (and so invoked in other event handlers for example).

### Inputs change or on page load

Queries set to this mode automatically run when dependent inputs change or on page load. For example, the query result of `select * from users where customer_id = {{input.value}}` updates immediately when `input.value` changes. \*\*\*\* This mode to run a query is recommended for queries reading data from data sources.

{% hint style="warning" %}
Consider carefully when to trigger a query. Data Queries that may take longer to respond may block the application visuals from loading properly and displaying your data.
{% endhint %}

### Manually invoked

For this mode of queries, they run only with your manual trigger, such as a button or link click. You need to specify an event handler to trigger the query. This mode is recommended for queries writing data to data sources, because you may need to confirm that the input is complete and error-free before running it.

{% hint style="info" %}
**How to choose a query's trigger mode?**

In most cases, queries reading data, like **select** operations, can be set to the former mode, and those writing data, like **create**/**delete**/**update** operations, run the latter way.

When OpenFlower detects your query statement switches from reading to writing data, the trigger mode switches to **Manually invoked** automatically, but not the other way around.
{% endhint %}

{% hint style="info" %}
Sometimes, you can also have the need to call a Data Query as an outcome or after the response of another Data Query to merge data responses, for example. You can use [Event-Handlers](/build-applications/app-interaction/event-handlers) of Data Queries or the so called [Data Responder](/business-logic-in-apps/write-javascript/data-responder) then. The first query may be triggered "on Inputs change or on page load" and following queries will then use the trigger "manually invoked".
{% endhint %}

## Response-Event Handler

As soon as the Response processing (and parsing) of a Data Query is done, you can use Event Handlers as a trigger for further actions. There are onSuccess and onError Triggers. You also can combine multiple Event Handlers.

### onSuccess Handler

The `onSuccess` handler is designed to execute specific actions after a data query has successfully completed. This mechanism is essential for initiating subsequent operations, such as running JavaScript queries or other subsequent queries that may need the Response of a initial Data Query.

**Implementation**

To utilize the `onSuccess` handler, simply click on "Event Handlers" in the lower section of the Data Query Editor in the "General" Tab. Then you choose "on Success" and next your desired action.

<figure><img src="/files/sPbpsrmRRsJjOXp5jMAI" alt="Data Query Editor Event Handler"><figcaption></figcaption></figure>

#### onError Handler

The `onError` handler serves as the counterpart to the `onSuccess` handler, designed to manage scenarios where a data query fails. This could be due to various reasons, such as network issues, API errors, or data inconsistencies.

<figure><img src="/files/1M0jv5uz9XyhnxSbA3kf" alt=""><figcaption></figcaption></figure>

To build an event handling for a data query error case, simply choose "Failure" on the Event Selection for the Event Handler. You can specify additionally for the individual Event Handler if there is a special case handling. For example, you would only show a notification on a 4xx or 5xx Response Code for a Restful Data Query.

<figure><img src="/files/NCMfNX9Trw7VurQlui9Z" alt=""><figcaption><p>Create a rule when to use the Event Handler</p></figcaption></figure>

## Notification tab and Advanced tab

In notification and advanced tabs, you can configure settings regarding notifications, timeout, periodic run, and more. Settings in **Notification** tab and **Advanced** vary based on trigger mode of your query.

| Tab              | Settings                                    | Inputs change or on page load | Manually invoked |
| ---------------- | ------------------------------------------- | :---------------------------: | :--------------: |
| **Notification** | Display a Success message after running     |               🚫              |         ✅        |
|                  | Display a Failure message after running     |               ✅               |         ✅        |
| **Advanced**     | Display a confirmation modal before running |         <p>🚫<br></p>         |         ✅        |
|                  | Set timeout for query running               |               ✅               |         ✅        |
|                  | Perform query periodically                  |               ✅               |        🚫        |


# Bind Query Data to Components

When building apps in OpenFlower, you can use JavaScript (JS) to access and transform data from objects, including components, queries, and global parameters. When writing JavaScript inside SQL editor, component property's input box, table column settings, etc., always remember to enclose all your JS code inside double curly braces, such as\
`{{'hello, ' + currentUser.name}}`.

## Access data

Objects have globally unique names, such as `input1`, `query1`, and `table1`. You can refer to the properties of the objects in your app by JS code.

### Access data in an object

OpenFlower supports you accessing the data in an object using dot notation (`objectName.keyName`). For example,`{{userInfo.selectedRow.userName}}` accesses the `userName` value in the currently selected row of **Table** `userInfo`.

When writing JS in `{{ }}` to access values in an object, add a `.` after the object name to trigger an autosuggest menu in case you are not sure about objects' built-in properties or methods.

**Example**

This GIF shows how the dot notation triggers an autosuggest menu and displays the properties of `table1`.

### Access data in an array

You can access the values in an array by index. The index always starts at 0, so you can use `array[0]` to access the first element of the array.

**Example**

The **Data** property of **Table** component is an array of objects. This GIF shows how to access the value of `first_name` in the first element of the **data** array in `table1`.

## Transform data

You can leverage built-in JS functions and third-party libraries in `{{ }}` to transform data, such as `filter()`, `map()` and `reduce()` operations.

**Examples**

Lowercase a string.

```javascript
{{input1.value.toLowerCase()}}
```

Change date format.

```javascript
{{moment(table1.selectedRow.date_column).format('YYYY-MM-DD')}}
```

Return name from query results.

```javascript
{{query1.data.map(i => i.name)}}. 
```

## Restrictions

The JS code in `{{ }}` should be a single-line code, such as `.map()` or `.reduce()` combined with an arrow function or a ternary operator.

**Examples**

```javascript
{{query1.data.id.length}} // ✅ to reference a value
{{query1.data.map(row => row.id)}} // ✅ .map() + arrow function
{{ num1 > num2 ? num1 : num2 }} // ✅ ternary
```

The following JS code examples are illegal in `{{ }}`.

```javascript
{{ 
    // ❌ you can't write code like this in {{ }}
    const array = query1.data;
    const filterArray = array.filter(it => it.value > 10);
    return filterArray; 
}}
```

If you wish to orchestrate multiple lines of JavaScript, OpenFlower supports you writing such code in transformers.

```javascript
// codes inside a transformer
if (select.value === "1") {  
    return "Option 1";
}
if (select.value === "2") {
    return "Option 2";
}
return "Option 3"; 
```

## View data

Data from queries can be complicated and nested in real cases. Viewing data provides you with the detailed structure of data in objects and helps you understand your data better. Before accessing or transforming data, you may need to view the data and its structure first. OpenFlower offers three ways to view data.

### View query result

After running a query inside the query editor by clicking the **Run** button\*\*.\*\* Query result is displayed in the format shown below.

### View data in Data Browser

Data browser located in the left pane displays all of the data inside your app. You can click on the node to expand and view the data structure.

### Real-time view

When setting up properties or writing JS code inside an editor, you can view the evaluated result in real-time in a box below your editor.


# Query library

**Query Library** lets you store, reuse and share data queries for data sourced across your workspace. Create and run queries in query library, and call them from any OpenFlower app.

## Features

The main function of the query \*\*\*\* library is to create and manage reusable queries:

* Writing queries based on data sources, with the ability to define **Inputs** variables.\\
* Importing and triggering queries from query library in apps and modules.\\
* Referencing different versions of queries.\\

## Create a new query

1. Click **+ New** and select a data source.
2. Rename the query on demand.
3. Write code and click ▶ **Run** to get results. The results will display in the bottom pane of the window.

### Input parameters

There are cases where you want to pass dynamic parameters to a query. OpenFlower supports that by introducing input configuration of a query.

Click **Add** in the **Inputs** tab in the right pane to add input parameters. Reference them using `{{}}` in the query, then you can input test values to test execution of the query.

{% hint style="warning" %}
You can only reference variables instead of writing JavaScript code in `{{}}`.
{% endhint %}

To edit the name and description of the input variables, click `...` and select **Edit** or directly click the title of the variable.

## Version management

Queries may be updated on demand. You can make an adjustment to a query to a newer version and at the same time maintain the older version. Such version management is achievable in query library. Click `···` in the top right corner to publish different versions of queries, and you can view and restore history versions.

### Publish

When you finish writing a version of the query, you can publish it. Click `···` > **Publish**. Use [semantic versioning](https://semver.org/) (also known as SemVer) to name your version.

### History version

To view and restore the history versions, go to `···` > **History version**.

You can restore your current draft to a history version by selecting the target version from the history version list and clicking **Restore this version**. Click **Exit** to return to edit mode.

## Import a query

1. In an app or module, Click **+ New** in the **Query editor** in the bottom pane, then select **Import from Query Library**.
2. Select a query from the library from the dropdown list, choose your desired version and pass in corresponding input parameters.

For other settings, see .

## Permissions

Workspace **Admin** and members of **Developers** group have access to the **Query Library** and have read and write permissions to all queries in the library.


# Workflows

The integration of OpenFlower with workflow tools opens up a world of possibilities for app creators, making it truly amazing in its capabilities. These dynamic duos allow developers to build not just single application screens but entire workflows that are interconnected and intelligent. The synergy between OpenFlower and workflow tools empowers app creators to create applications that are not only visually appealing but also highly functional.

What truly sets this combination apart is the ability to access data from various data sources. With the extensive array of connectors in existing workflow platforms, OpenFlower users can seamlessly integrate with databases, APIs, and other data repositories far beyond the integrated data sources. This means that your applications can pull in real-time information, providing users with up-to-the-minute data, and resulting in a more engaging and informative user experience.

In 11.2023 we offer now the first integration with n8n which also enables OpenFlower Applications to react dynamically to user inputs. They can process and act upon these inputs, making them responsive and adaptable. Decision-making within your workflows becomes more intelligent, as your applications can now evaluate user actions and provide tailored responses.

Imagine a scenario where a user interacts with your application. With OpenFlower and n8n, you can design workflows that not only collect user input but also trigger a sequence of actions based on that input. This could include sending notifications, updating databases, or performing complex calculations. The possibilities are endless, and the power to create intelligent, responsive applications is now at your fingertips.

In 2024 we will integrate OpenFlower with more different workflow platforms, to offer you the extending the capabilities of our platform.


# n8n Integration

The combination of OpenFlower and n8n is just amazing. It empowers you as App creators to build not just individual screens, but entire ecosystems of interconnected, data-driven workflows. The ability to access data from various sources and react to user inputs elevates the user experience to new heights, making it a game-changer in the world of application development. With this partnership, your applications can truly become dynamic, intelligent, and tailored to the needs of your users.

The overall path is as follows:

* [Install](https://docs.openflower.org/setup-and-run/self-hosting) OpenFlower or use [prod-us1.openflower.org](https://prod-us1.openflower.org)
* [Install n8n](https://docs.n8n.io/hosting/installation/) (selfhosted to access Community Nodes)
* Install the [OpenFlower Community Node](https://www.npmjs.com/package/n8n-nodes-lowcoder)
* Create OpenFlower Credentials in n8n
* Place OpenFlower n8n Nodes in your workflow to represent individual app screens
* Access n8n via Datasource to gather Workflow and Execution Data
* Automate Application loading and data handover based on workflows

### Install the n8n OpenFlower Node

The OpenFlower Community Node for n8n is public in npmjs.com\
<https://www.npmjs.com/package/n8n-nodes-lowcoder>

In your self-host installation of n8n you can install Community nodes easily with the UI.

```
n8n-nodes-lowcoder
```

<figure><img src="https://github.com/Flowerappeng-org/openflower/blob/main/docs/.gitbook/assets/n8n%20%20Install%20OpenFlower%20Node.png" alt=""><figcaption><p>UI to install Community Nodes for n8n</p></figcaption></figure>

You can also install the node based on the shell command in the local n8n root directory.

```
npm install n8n-nodes-lowcoder
```

If you run n8n in a docker, use the mounted /custom folder for the installation

```
mkdir ~/.n8n/custom
cd ~/.n8n/custom
npm install n8n-nodes-lowcoder
```

{% hint style="info" %}
If you operate n8n in the queue mode and use multiple workers, remember to install the OpenFlower Community Node on each worker too.
{% endhint %}

### Connect OpenFlower in n8n

After proper installation, the OpenFlower Community Node will be available in the list of actions.

<figure><img src="https://github.com/Flowerappeng-org/openflower/blob/main/docs/.gitbook/assets/n8n%20%20OpenFlower%20Node.png" alt=""><figcaption><p>use the OpenFlower Node in your n8n Workflows</p></figcaption></figure>

You can use one or many OpenFlower Nodes in your workflow. As any other n8n node, just place the OpenFlower Node in your workflow and connect it.

### Enter Credentials to connect to your OpenFlower

With the help of the credentials dialogue, you can now connect to OpenFlower (API).

As API Base URL you can use TODO for the public community edition or the URL of your self-hosted api-service instance of OpenFlower.

<figure><img src="https://github.com/Flowerappeng-org/openflower/blob/main/docs/.gitbook/assets/n8n%20%20OpenFlower%20Credencials.png" alt=""><figcaption><p>Connect to OpenFlower with your credencials</p></figcaption></figure>

### Set an app as a Reference

As we use Username and Password - and internally work with the OpenFlower Access-Token, your current workspace is the used Workspace for the OpenFlower Node. Based on that you can now search your app or module to set this app as reference in the OpenFlower Community Node.

<figure><img src="/files/LhJCVNkvBCcsMOYVqxm5" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
Remember, the displayed App List is connected to your current Workspace in OpenFlower. When you change the workspace in OpenFlower, you will get an adapted List of apps of the newly selected Workspace.
{% endhint %}

If you know the ID of your OpenFlower App, you can enter it manually instead "by ID" of searching from the Application List.

<figure><img src="/files/U1TYU7hN607zN1JSoC5D" alt=""><figcaption><p>Select "By ID" to enter an OpenFlower App ID manually</p></figcaption></figure>

### n8n OpenFlower App Reference & State

As soon as a workflow of n8n reaches a OpenFlower Node, the execution in n8n changes in the state of "waiting". The expectation behind this is, that a OpenFlower Node represents an App, in which most likely a "User-Action" will happen. The Workflow therefore needs to wait, till the User-Action is finalized.

By itself, the OpenFlower n8n Node will wait infinitely. To resume the workflow and continue to the next nodes, the OpenFlower n8n Node offers a Webhook. This is a Rest API call that is connected to the OpenFlower n8n node. The structure of this webhook is also described in the UI of the OpenFlower Node in n8n.

{% hint style="info" %}
Resume the workflow by calling this Webhook:\
http(s)://{n8n-url}/webhook-waiting/{Workflow-Execution-ID}/{OpenFlower-App-ID}
{% endhint %}

This Webhook has 2 functions. First, it resumes the Workflow, so n8n will exit the "wait" state and continue the execution of the workflow. The second function is truly important tough...

By the Webhook in POST Method, you can send Data from the OpenFlower App to the OpenFlower n8n node. In this way, you can easily integrate User-feedback / User-action-data in n8n workflows. Imagine for example Form Data that is later in the n8n workflow processed.

### Access n8n information in OpenFlower Apps

OpenFlower offers REST API calls (to use for Webhooks) and a native n8n API integration as Datasource. These instruments you can use to work with n8n on the OpenFlower side.

To connect to the n8n API you need to create and use an API Key of n8n.

<figure><img src="/files/vxsduzwmVOJFfCrSPv30" alt=""><figcaption><p>Create and Manage API Keys in n8n</p></figcaption></figure>

Now you can connect the native n8n Data source in OpenFlower.

<figure><img src="/files/Jj8uyoQURD32uxGvzAQB" alt=""><figcaption><p>accessing n8n as a Data source by API</p></figcaption></figure>

### The magic sauce - n8n Workflows & OpenFlower

There are two important IDs you need from n8n to deal with workflows in OpenFlower.\
The "Workflow-ID" and the "Workflow-Execution-ID".

The Workflow-Execution-ID is built, as soon as the first node of a workflow (trigger) is successfully executed. In our example, we trigger the workflow from a OpenFlower App by the Webhook. To get the "own Execution-ID" of this Workflow Execution as a result (to work with it further on till the workflow is finalized), we need this ID in OpenFlower. The trick here is to use a "Respond to Webhook" Node of n8n, which does not do much more than sending back the Execution-ID as soon as a Workflow Execution is triggered.

To achieve this, we use the expression for a JSON Data Response.

```
{"execution_id" : "{{ $execution.id }}"}
```

<figure><img src="/files/LUPAdOA491KRtTsMCJXL" alt=""><figcaption><p>using the Response to Webhook to gather the Workflow-Execution-ID</p></figcaption></figure>

<figure><img src="/files/9ZBVE67gg9xaqZ1BLJpu" alt=""><figcaption><p>an example n8n Workflow which can be triggered by OpenFlower</p></figcaption></figure>

We can now use REST API Calls to trigger n8n to start or continue Workflows.

<figure><img src="/files/LIy2tJtoSnJkEtnq0B6u" alt=""><figcaption><p>use a POST Method Webhook to resume a n8n execution after a OpenFlower Node and send Data from OpenFlower to n8n</p></figcaption></figure>

Next to the Webhooks to trigger actions in n8n, we can use the n8n native Datasource in OpenFlower to retrieve Data about the Workflow and the current workflow execution.

<figure><img src="/files/EvhblxEj7AEStwHEZZYJ" alt=""><figcaption><p>access the current workflow execution and data, which was build and collected in the n8n workflow so far.</p></figcaption></figure>

### How to use

The possibilities of this integration are pretty endless. However, a few major concept ideas may help you to start with the first steps.

* You can create a "Master-App" in OpenFlower, which contains several hidden modules, which are screens of a larger application. Based on the current workflow execution node, you can hide or show these modules (Apps) based on the current situation in the n8n workflow.

```
// A n8n datasource query with the name "get_workflow_execution_data" retrieves 
// the current state of an execution, so you can access the current (OpenFlower) node
// in the workflow with this notation:
{{ get_workflow_execution_data.data.data.resultData.lastNodeExecuted }}
```

<figure><img src="https://github.com/Flowerappeng-org/openflower/blob/main/docs/.gitbook/assets/n8n%20%20OpenFlower%20Master%20App.png" alt=""><figcaption><p>a Master-App can contain multiple "hidden" Modules (apps).<br>The display of these Modules is then bound to the current n8n workflow execution</p></figcaption></figure>

* You could create independent OpenFlower apps, connect user-action buttons and the "Go to App" Event Handler, and open "next" apps in the workflow based on the current node information after a resumed workflow (when n8n did execute further steps and waits at the next OpenFlower Node)
* You could use OpenFlower Apps like Forms or Interactive Charts to send Data into Workflows with the OpenFlower Node and the POST Method resume-Webhook.
* You can use n8n to collect data from various data sources and pre-process it before it reaches a OpenFlower App. This could save you from writing complex data transformation steps based on Javascript in OpenFlower Transformers.
* Lowering the Datasizes which hit OpenFlower Applications and the Browser of the Users. Smaller Data, which fits the needs of components and the desired display of data will speed up the Development of apps as well as the displaying and execution of Apps.
* You could use n8n as your Middleware, and store in the backend sensitive Data like Credentials of APIs or OAuth Tokens (JWT) and allow only access to backend APIs via n8n. A similar principle is used in OpenFlower too.


# Write JavaScript


# JavaScript query

There are cases where you want to orchestrate operations, for instance, after triggering two queries, you want to combine and store their results to a temporary state, and then open a modal. This process can be complicated when chaining several event handlers, and certainly cannot be done in one line of code in `{{ }}`. That's where JavaScript (JS) query comes into play. It unleashes the ability to interact with components and queries by writing complex JS queries to achieve the following operations:

* Interact with UI components
* Trigger queries
* Access third-party JS libraries
* Customize functions

The following example is for you to quickly understand what JS query is and how it works.

## Use JS query to join query results

SQL query `query1` reads `id`, `first_name`, `last_name` and `tid` fields from table `players` in a PostgreSQL database.

```sql
select id, first_name, last_name, tid from players
```

SQL query `query2` reads `tid`, `city` and `name` fields from table `teams` in a PostgreSQL database.

```sql
select tid, city, name from teams
```

Use a JS query to left join `query1` and `query2` on the same `tid` in the following steps.

1. Create `query3`, and choose **Run** **JavaScript Code**.
2. Insert the following code.

   ```javascript
   return Promise.all([query1.run(), query2.run()]).then(
     data => join(data[0], data[1]),
     error => {}
   );

   function join(players, teams) {
     return players.map(player => {
       const team = teams.find(t => player.tid === t.tid);
       return { ...player, ...team };
     });
   }
   ```

   \
   In this code snippet, the `Promise.all()` method receives the results of `query1` and `query2`, and the `join()` method joins their results after a successful run based on the values of `tid` field.\\

## Return data

Use `return` syntax to return result. For example, the following code returns `3`.

```javascript
return Math.floor(3.4)
```

The result returned can also be a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) object. For example, `query2.run()` returns a Promise object.

```javascript
return query2.run()
```

{% hint style="info" %}
The `return` statement is not necessary for scenarios where you want to omit results.
{% endhint %}

## Access data

Use JS queries to access data in your app. Notice that there's no need to use `{{ }}` notation.

```javascript
var data = [input1.value, query1.data, fileUpload.files[0].name];
```

## Control component

In JS queries, you can use methods exposed by components to interact with UI components in your app. Such operation is not supported by the inline JS code in `{{}}`.

```javascript
// set the value of input1 to "Hello"
input1.setValue("Hello");
```

{% hint style="warning" %}
The `input1.setValue()` method (or other component methods) is asynchronous and returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) object. Accessing `input1.value` immediately after setting the value of `input1` does not return the updated value.
{% endhint %}

## Run query

### `run()` method and callbacks

Call `run()` method to run other queries, for example:

```javascript
return queryByName.run(); // run a query and it returns a Promise
```

The return value of `query.run()` is a Promise, so you can attach callbacks to handle the successful result or error.

```javascript
return queryByName.run().then(
  data => { // after query runs successfully
      return "hello, " + data.user_fullname; 
  },
  error => { // after query runs in failure
    // use built-in message function to pop up an error message
    message.error("An error occured when fetching user: " + error.message); 
  }
);
```

### Pass in parameters

You can pass parametes in the `run()` method to decouple query implementation from its parameters.

```javascript
query.run({
    param1: value1,
    param2: value2
    ...
});
```

For example, in SQL query `query1`, you can define `name` and `status` as parameters that need to be passed in for its execution.

```sql
select * from users 
   where 
     user_name = {{ name }} 
   and
    user_status = {{ status }}
```

Then you can pass in corresponding parameters to `query1`.

```javascript
query1.run({
  name: "Bob",
  status: 0 
}).then(
  data => { // after query1 runs successfully
    console.log("The result is" + JSON.stringify(data)); 
  },
  error => { // after query1 runs failed
    console.log("An error occured," + error.message);
  }
);
```

**Demo 1**

When you have several inputs in an app triggering the same query, passing parameters to this query allows you to reuse it anywhere.

```sql
-- query1: 
select id, name, gender, address from users where id={{numberInput1.value}}
-- query2: 
select id, name, gender, address from users where id={{table1.selectedRow.id}}
-- query3: 
select id, name, gender, address from users where id={{select1.value}}
...
```

Things might get fuzzy when you want to update SQL implementations, because you have to carefully check and update all duplicated queries. Now you can be relieved of this repeated SQL by introducing query parameters.

```sql
-- just write the SQL once, and extract its parameter {{id}}: 
select id, name, gender, address from users where id= {{id}}
```

Then trigger this query in **Run JavaScript** of event handlers in each of the inputs.

## Declare a function

You can declare functions inside a JS query for better readability.

```javascript
// Whether the first number is a multiple of the second number
function isMultiple(num1, num2) {
      return num1 % num2 === 0;
   }
   
// Call the moment library to return the current date
function getCurrentDate() {
      return moment().format("YYYY-MM-DD");
}
```

## Add preloaded scripts

OpenFlower supports importing third-party JS libraries and adding predefined JS code, such as adding global methods or variables for reuse either at **app-level** or **workspace-level**. You can find the app-level settings in ⚙️ > **Other** > **Scripts and style**.

For workspace-level, go to ⚙️ **Settings** > **Advanced**.

In **JavaScript** tab, you can add preloaded JavaScript code to define global methods and variables and then reuse them in your app. For importing libraries, see .

## Restrictions

For security reasons, several global variables and functions of **window** are disabled in OpenFlower. Please report to our [GitHub](https://github.com/Flowerappeng-org/openflower/issues) if you encounter any issues.


# Temporary state

Temporary states in OpenFlower are a powerful feature used to manage complex variables that dynamically update the state of components in your application. These states act as intermediary or transient storage for data that can change over time due to user interactions or other processes.

In OpenFlower, temporary states are particularly useful when dealing with scenarios where the state of a component needs to be updated based on user input or other dynamic conditions. By binding these states to components using Handlebars syntax, you can create highly interactive and responsive user interfaces.

Here's a brief overview of how temporary states work in OpenFlower:

1. **Definition and Initialization**: Temporary states are defined within the OpenFlower environment. They are initialized with a default value, which can be a simple data type like a string or number, or more complex objects and arrays.
2. **Binding to Components**: These states are then bound to UI components using Handlebars syntax. For example, `{{tempState}}` would bind the value of `tempState` to a component. This binding ensures that any changes to the temporary state are immediately reflected in the component.
3. **Dynamic Updates**: As users interact with the application, actions such as clicking a button, entering text, or selecting an option from a dropdown can trigger updates to these temporary states. This could be as simple as updating a text value or as complex as altering an array or object structure.
4. **Reactivity**: The key advantage of using temporary states is their reactivity. When a temporary state changes, all components bound to that state automatically update to reflect the new value. This reactivity is crucial for creating dynamic and fluid user experiences.
5. **Use Cases**: Common use cases for temporary states include form input handling, visibility toggling of UI elements, temporary storage of user selections, and managing the state of interactive elements like accordions, tabs, and modals.
6. **Lifecycle**: Temporary states in OpenFlower are typically short-lived. They exist for the duration of a specific task or user interaction and do not persist across different sessions or page reloads, unlike more permanent state management solutions.

By leveraging temporary states, OpenFlower allows developers to build complex, state-driven applications with ease, ensuring that the UI stays consistent with the underlying application state and providing a seamless experience for the end-user.

## Use case scenarios

Temporary states may help in the following scenarios:

* To track the temporary values of a variable when the user interacts with your app.
* To store your data only in operation without persisting to a database.
* To function as a temporary property when built-in properties in OpenFlower (such as `{{table.selectedRow}}` and `{{select.value}}`) do not support your use case.

{% hint style="info" %}
To store and access data across apps in your workspace, use localStorage instead.
{% endhint %}

## Create a temporary state

Click **+ New** and select **Temporary state** in query editor.

You can rename the temporary state and set an initial value.

## Set state values

Temporary state offers `setValue()` and `setIn()` methods to set or change its value, which can be called in JavaScript queries.

Use `setValue()` to change the value directly.

```javascript
//state.setValue(value: any)
state.setValue(3)
state.setValue(input1.value)
```

When the initial value of a temporary state is an object, use `setIn()` to change the value in a specified path.

```javascript
// initial value of state2 as follows：
{
    girl: {
        name: "Lucy",
        age: 18,
        city: {
            name: "New York"
        }
     }
     boy: {
         name: "Bob",
         age: 21,
         city: {
             name: "Los Angeles"
         }
     }
}
//To change the value in a specified path
//state.setIn(path, any value)
//path: an array of keys or indexes. Only the last item in the path will be changed.
state2.setIn(['girl','city'],{name:'Seatle'})
state2.setIn(['boy','age'],18)


// To set value array value, you can use 
// init value = ["hello", "world"]
state2.setIn([1],"foo") // this will result to ["hello", "foo"]
```

You can also call these two methods in event handlers. Select **Set temporary state** as the action and choose method on demand.

## Example: Increment counter

In this example, the counter tracks the number of button clicks. Every time the user clicks the button, the number in the text component increases by one.

Build an increment counter in following steps:

1. Add a button component `button1` and a text component `text1`.
2. Create a temporary state `state1`, set its initial value as `0`. Bind `{{state1.value}}` as the display text of `text1`.
3. Add an event handler for `button1`. Select the action **Set temporary state** and the method **setValue**, and then set `{{state1.value+1}}` as the value.
4. Click the button, you can see the value of `text1` increases by one each time you click.

You can also achieve the same result using JavaScript queries:

1. Add a new query, select **Run JavaScript code**.
2. Write JavaScript query with this code, and set it to be manually invoked:\
   `state1.setValue(state1.value + 1)`
3. Add an event handler of `button1` to run `query1`.

Now click the **Increment counter** button, you should see the same result as above.


# Transformers

Transformers are designed for data transformation and reuse of your multi-line JavaScript code. Data from queries or components might not meet your needs in business scenarios. Also, you may use the same code block several times within an app. In such cases, a transformer is what you need.

Compared with inline code in `{{ }}`, transformer supports multi-line code blocks. And unlike JavaScript query, transformer is designed to do read-only operations, which means that you cannot trigger a query or update a temporary state inside a transformer.

## Quickstart

Click **+ New > Transfromer** in a query editor to create a transformer.

Then write your JS code in the transformer. You can click **Preview** to get the return value and access it by `transformerName.value` in your app.

In the following example, `transformer1` uses the data of star rating in `rating1` to calculate a score.

{% hint style="warning" %}
`{{ }}` is disallowed inside a transformer or JS query. `{{ }}` is only used for the purpose of single-line JS expression, whereas a transformer or JS query is for multiple lines of JS code.
{% endhint %}

## Use cases

### Transform timestamp

Use the `moment().format()` method to transform timestamp formats. The following example converts the timestamp value of `start_time` returned by `query1` to `YYYY-MM-DD` format.

```javascript
return query1.data.map(it => {
     return {
         ...it,
         start_time: moment(it.start_time).format('YYYY-MM-DD')
     };
})
```

### Sort query data

Use the `_.orderBy()` method provided by [lodash](https://lodash.com/) to sort data. The following example returns `query1.data` sorted by `amount` column in ascending order.

```javascript
return _.orderBy(query1.data, 'amount', 'asc')
```

### Join two queries

The example code below shows how to join query results of `getUsers` and `getOrders` on user id.

```javascript
const users = getUsers.data;
const userOrders = getOrders.data;
return users.map(user => ({
  ...user,
  orders : userOrders.find(order => order.customer_id === user.id),
}));
```

## Read-only operations

Only read-only operations are allowed inside a transformer. It means that you cannot set values of components or temporary states, or trigger queries. For those operations, use JavaScript queries instead.

For example, you cannot call the method `setText()` of a text component in a transformer.

Instead, calling the method `setText()` in a JavaScript query reports no error.

In another example, transformer`sort1` aims at sorting the data of `getUsers` by `first_name`, but the `sort()` method may change the original data, so an error occurs.

In this case, use the method `_.orderBy()` provided by [lodash](https://lodash.com/) instead.


# Data responder

When building an app, you can set events for the components in order to listen to the changes of certain data. For example, for a **Table** component, the built-in events for the change of the `selectedRow` property include "Row select change", "Filter change", "Sort change", and "Page change".

However, there lacks similar events for some data changes, such as the changes of temporary states, transformers, or query results. Data responders are designed for these cases and allow you to listen and respond to any data change.

{% hint style="info" %}
Events for data responders are more general than the events that listen to data changes such as content change, row select change, etc.
{% endhint %}

## Listen to data changes

In query editor, click **+ New**, and then select **Data responder** to create a new data responder.

You can set the data that data responder listens to. It supports all kinds of data formats, including number, string, array, and JS object. In the following example, any value change in the **Text** component triggers a global notification.

If the data is in array or JS object format, then data change from any sub-element will trigger the configured event. For example, the data of `dataResponder2` is a JS object, which listens to two **Input** components in the app. Data change of any component triggers the same global notification.

## Respond actions

For detailed information, go to Event handlers > Actions.


# Built-in JS functions

## utils - utility functions

### utils.openUrl()

Open a URL.

```javascript
// Syntax
utils.openUrl( url: string, options?: { newTab:  boolean = true } )
```

| Parameter      | Description                                                                                                                |
| -------------- | -------------------------------------------------------------------------------------------------------------------------- |
| <p>url<br></p> | Required. A **String** value that specifies the URL to open. It must start with *http\://* or *https\://*.                 |
| newTab         | Optional. **Boolean** value that, when **True**, specifies the url is to open in a new tab. The default value is **True**. |

```javascript
// Example: Open google.com in a new tab.
utils.openUrl("https://www.google.com", { newTab: true })
```

### utils.openApp()

Open an OpenFlower app.

```javascript
// Syntax
utils.openApp( applicationId: string, options?: { queryParams?: {"key":"value"}, hashParams?: {"key":"value"}, newTab: true } )
```

| Parameter                                                 | Description                                                                                                                                                                |
| --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p>appId<br></p>                                          | Required. A **String** value that specifies the ID of the app to open.                                                                                                     |
| queryParams: {'key1':'value1',key2:'value2',...}          | Optional. An **Object** that specifies query parameters to pass into the app. The query parameters are added to the app URL in the form of ?*key1=value1\&key2=value2&...* |
| <p>hashParams：{'key1':'value1',key2:'value2',...}<br></p> | Optional. An **Object** that specifies hash parameters to pass into the app. The hash parameters are added to the app URL in the form of *#key1=value1\&key2=value2&...*   |
| newTab                                                    | Optional. A **Boolean** value that, when **True**, specifies the url is to open in a new tab. The default value is **True**.                                               |

```javascript
// Example: Open an OpenFlower app in a new tab.
utils.openApp("632bddc33bb9722fb888f6c0", { newTab: true })

// Example: Open an OpenFlower app and pass in "id" parameter.
utils.openApp("632bddc33bb9722fb888f6c0", {
  queryParams: { "id": table1.selectedRow.id },
} )
```

### utils.downloadFile()

Download a file containing the specified data.

```javascript
// Syntax
utils.downloadFile(data: any, fileName: string, options?: {
  fileType?: string, 
  dataType?: "url" | "base64"
} )
```

| Parameter | Description                                                                                                                                                                                             |
| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| data      | Required. A **String** or **Object** that specifies the data to download from queries, components, transformers, etc.                                                                                   |
| fileName  | Required. A **String** value that specifies the name of the file to download.                                                                                                                           |
| fileType  | Optional. A **String** value that specifies the type of the file to download. All [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types) are supported. |
| dataType  | Optional. A **String** value that specifies the type of the data: "url" or "base64".                                                                                                                    |

```javascript
// Example: Download the base64 data from a file component as a PNG file named users-data.
utils.downloadFile(file1.value[0], "users-data", {
  fileType: "png",
  dataType: "base64",
})


// Example: Download the results of query1 as a XLXS file named users-data.
utils.downloadFile(query1.data, "users-data", { fileType: "xlsx" })
// or in this way:
utils.downloadFile(query1.data, "users-data.xlsx")

// Example: Download the results of query1 as a XLXS file named users-data.
utils.downloadFile(restApiQuery.data, "users-data", {
  fileType: "pdf",
  dataType: "base64",
})
```

### utils.copyToClipboard()

Copy a string to clipboard.

```javascript
// Syntax
utils.copyToClipboard( text: string )
```

| Parameter | Description                                                      |
| --------- | ---------------------------------------------------------------- |
| text      | Required. A **String** value that specifies the content to copy. |

```javascript
// Example: Copy the content of input component to clipboard.
utils.copyToClipboard( input1.value )
```

## message - global notification

Use `message` methods to send a global alert notification, which displays at the top of the screen and lasts for 3 seconds by default. Each of the following four methods supports a unique display style.

```javascript
// message.info( text: string, options?: {duration: number = 3 } )
message.info("Please confirm your information", { duration: 10 })
// message.success( text: string, options?: {duration: number = 3 } )
message.success("Query runs successfully", { duration: 10 })
// message.warn( text: string, options?: {duration: number = 3 } )
message.warn("Warning", { duration: 10 })
// message.error( text: string, options?: {duration: number = 3 } )
message.error("Query runs with error", { duration: 10 })
```

## localStorage

Use `localStorage` methods to store and manage key-value pair data locally, which is not reset when the app refreshes, and can be accessed in any app within the workspace using `localStorage.values`.

| Method                             | Description                     |
| ---------------------------------- | ------------------------------- |
| setItem(*key: string, value: any*) | Store a key-value pair.         |
| removeItem(*key: string*)          | Delete a key-value pair.        |
| clear()                            | Clear all data in localStorage. |

### localStorage.values

You can access any key-value pair in local storage using `localStorage.values.` in JavaScript queries.

Inspect the data in localStorage in **Globals** in the data browser.

### localStorage.setItem()

Store a key-value pair.

```javascript
// Syntax
localStorage.setItem(key: string, value: any)

// Example
localStorage.setItem("order", select1.value)
```

### localStorage.removeItem()

Delete a key-value pair.

```javascript
// Syntax
localStorage.removeItem(key: string)

// Example
localStorage.removeItem("order")
```

### localStorage.clear()

Clear all data in localStorage.

## Responsiveness / Screen information

To enable responsive Layouts, you need to know which device type your app is currently viewed.\
This helper gives you information about the screen sizes. The values automatically update on Screen size changes.

### screenInfo.deviceType

You can use deviceType to get the Type of the Device based on the current screen width of the Lowoder app (or the website where it is embedded). This value automatically updates on Screen size changes.

```javascript
screenInfo.deviceType
=> returns String: Desktop | Tablet | Mobile
```

### screenInfo.height

```javascript
screenInfo.height
=> returns Number: height of the screen (browser window)
```

### screenInfo.width

```
screenInfo.width
=> returns Number: width of the screen (browser window)
```

### screenInfo.isDesktop

```
screenInfo.isDesktop
=> returns Boolean: if the current width is considered as Desktop size
```

### screenInfo.isTablet

```
screenInfo.isTablet
=> returns Boolean: if the current width is considered as Tablet size
```

### screenInfo.isMobile

```
screenInfo.isMobile
=> returns Boolean: if the current width is considered as Mobile size
```


# Share an App

Application Sharing in OpenFlower

## Overview

Application sharing in OpenFlower allows Application Creators to share their applications with specific users, user groups, or the public. This functionality is accessible via the sharing dialogue and supports various sharing states and roles.

## Sharing States

An application can be shared in two states:

1. **In Editing**: The app is still under development and not yet published. That means, the App can get *edited* and shown as *preview* by all allowed users.
2. **Published**: The app is completed and released to users. That means, the App can get additionally *viewed* by all allowed users.

<figure><img src="/files/Qh67MsPx4fJiGmYEJnI7" alt=""><figcaption><p>The Share button is on the App Editor in the upper right corner</p></figcaption></figure>

## Roles

When sharing an application with a User-Group or individual User, you can apply a role to define the level of access:

* **Viewer**: Can view the application but cannot make any changes.
* **Editor**: Can view and edit the application.
* **Owner**: Has full control over the application, including editing and managing sharing settings.

<figure><img src="/files/urlkuLV0u1YyDm2zWhBU" alt="" width="563"><figcaption></figcaption></figure>

## Sharing Methods

There are three primary methods for sharing an application:

1. **Direct Sharing**: Share the app directly with specific users or user groups.
2. **Public Sharing**: Share the app with the "Anonymous" user role, making it accessible to anyone.
3. **Public to Marketplace**: Share the app on the marketplace, making it accessible to all users within the marketplace scope.

<figure><img src="/files/JNyTE5YBNKD9Nk10MfaT" alt="" width="563"><figcaption></figcaption></figure>

### Public to Marketplace

"Public to Marketplace" is a special sharing case designed to showcase applications in a OpenFlower marketplace.

<figure><img src="/files/8pCvToFaIZYYZxhFYJGr" alt="" width="563"><figcaption></figcaption></figure>

This feature supports two types of marketplaces:

1. **Global Marketplace**: Accessible at <https://prod-us1.openflower.org/>, where apps are available to everyone.
2. **Local Marketplace**: Individual OpenFlower installations can host their own marketplaces, accessible only to their users.

The `LOWCODER_MARKETPLACE_PRIVATE_MODE` environment variable controls the visibility of apps on local marketplaces:

* **True**: Only logged-in users (Viewer, Editor, Owner) can see apps published on the local marketplace.
* **False**: Apps published on the local marketplace can be seen by everyone, including Anonymous users.

{% hint style="warning" %}
Remember, you need to publish an App, so the **Public Sharing** or the **Public to Marketplace** Sharing has an effect.
{% endhint %}


# Publish an App

Publishing an app in OpenFlower transitions it from a Editing State to a live (published) state. This allows users to access and interact with the app based on the sharing and publishing settings defined by the App Creator.

## Publishing States

1. **Editing State**:
   * Apps in this state can only be accessed via the `/preview` endpoint by allowed users and user-groups.
   * Changes made to the app are reflected in real-time during editing but are not visible to end-users until published.
2. **Published State**:
   * The most recent version of the app at the time of publishing becomes the official published version.
   * The published version remains unchanged until a new publish action is performed, even if further edits are made to the app in the meantime.
   * Published apps are accessible via the `/view` endpoint.

<figure><img src="/files/n0iCQYRAONg0FHe8ZKNR" alt=""><figcaption></figcaption></figure>

## Publishing Scope

An app can be published with different scopes, determining who can access the app:

1. **Selected User Groups or Users**:
   * The app can be published ([shared](/publish-apps/share-an-app)) to specific user groups or individual users within a workspace.
   * Only those with explicit access will be able to view the app. This option allows the app to be viewed by allowed users via the `/view` endpoint.
2. **Public to All**:
   * The app can be published as "public to all", making it accessible to anyone, including non-logged-in users and users from different workspaces.
   * This option allows the app to be viewed by anyone via the `/view` endpoint.

<figure><img src="/files/JNyTE5YBNKD9Nk10MfaT" alt="" width="563"><figcaption></figcaption></figure>

## Access Control

* If an app is not shared *publicly* and a user without the appropriate permissions try to access it, an error message will be displayed.
* For anonymous users (not logged in), attempting to access a non-public app will redirect them to the `/user/auth` endpoint for sign-in or sign-up.

## Marketplace Publishing

Apps can also be published to OpenFlower marketplaces:

<figure><img src="/files/AjVaHKrhasWYHSnvE7Pg" alt="" width="563"><figcaption></figcaption></figure>

1. **Public to Marketplace**:
   * After *sharing* the app as "public to all", it can be further published to the marketplace.
   * This makes the app available in the marketplace listings, either globally at <https://prod-us1.openflower.org/> or within individual OpenFlower installations.

## Workflow for Publishing an App

1. **Development and Preview**:
   * Develop the app in the editing state.
   * Use the `/preview` endpoint to test and view changes during development.
2. **Publish the App**:
   * Select the appropriate user groups, users, or choose to make the app "public to all". in the "Share" dialogue.
   * When ready, publish the app to create the official live version.
   * The published app is now accessible via the `/view` endpoint.
3. **Marketplace Publication** (Optional):
   * If desired, mark the app as "public to All" in the sharing dialogue. Now you can see the second option switch "Public to marketplace"
   * Then, publish the app to the marketplace as you publish other apps too.
   * The app will be listed in the *global* or local OpenFlower marketplace.

{% hint style="info" %}
Global Marketplace means, if you published the app for the Marketplace on <https://prod-us1.openflower.org/>
{% endhint %}

## Adding Title, Description, Category and Icon

In the left App-Settings Menu, you can add Title, Description, Category, and an Icon to your App, so the display is informative.

{% embed url="<https://app.supademo.com/demo/KsbwqBCpWK6OZascdMI-E>" %}
Adding Application Meta Information help to make the app display informative.
{% endembed %}


# Embedd an App

OpenFlower's ability to run apps natively within other web apps addresses many of the challenges businesses face in today's digital landscape. It offers a holistic solution that caters to diverse needs, from enhancing a company's online presence to providing specialized functionalities and ensuring a consistent user experience across platforms.

By allowing apps to run both in the browser and natively within other web apps, OpenFlower offers unparalleled flexibility. Developers and businesses can choose the best deployment method based on their specific needs, whether it's a standalone web app, an integrated module within a larger system, or a feature within a mobile app.

Content Management Systems (CMS) are widely used to manage and publish content on the web. With OpenFlower's SDK, businesses can directly integrate custom apps into their CMS platforms. This means they can enhance their websites with specialized functionalities without having to rely on third-party plugins or extensive custom development.

Integrating apps directly into websites or web apps ensures a seamless user experience. Users won't need to switch between different platforms or interfaces; everything they need is available within a single unified environment.

By leveraging OpenFlower's capabilities, businesses can reduce the costs associated with developing separate apps for different platforms. They can build once and deploy everywhere, whether it's on their website, CMS, or as a native mobile app.

The OpenFlower platform likely simplifies the app development process, enabling rapid prototyping and deployment. This speed is further enhanced when integrating apps directly into existing systems, as there's no need for extensive integration work.

One of the challenges businesses face is maintaining consistency across various platforms. With OpenFlower's approach, the same app can be deployed in multiple places, ensuring a consistent look, feel, and functionality.

By allowing apps to be published across various platforms, businesses can reach a broader audience. Whether users interact with the app on a company's website, within a web app, or on their mobile device, they get the same functionality and experience.

As technology evolves, the lines between web apps, websites, and native mobile apps are blurring. By adopting a platform like OpenFlower that bridges these gaps, businesses are better positioned to adapt to future technological shifts and user behavior changes.

After the blue section and before the FAQs you see (or not to see so obviously) a native embedded OpenFlower APP in a website. This is not an IFrame - but a native embedding, which creates the HTML elements of the OpenFlower App in the HTML hierarchy of the Website.

<figure><img src="/files/ymcfBAnvTw9WOZoO5fAY" alt=""><figcaption></figcaption></figure>


# Embed Apps in React

### Native embedding in your React-based Web App

First, install the Lowcoder SDK. <https://www.npmjs.com/package/lowcoder-sdk>

yarn:

```
yarn add lowcoder-sdk
```

npm:

```
npm install lowcoder-sdk
```

### Integrate an OpenFlower App or Module into your existing app <a href="#user-content-integrate-openblocks-appmodule-into-existing-app-page" id="user-content-integrate-openblocks-appmodule-into-existing-app-page"></a>

1. Publish your app/module in OpenFlower.
2. Set the app/module's access privilege as public.
3. Add code in your existing app as below.

#### Import CSS styles <a href="#user-content-import-style" id="user-content-import-style"></a>

```
import "lowcoder-sdk/dist/style.css";
```

#### For React app: <a href="#user-content-for-react-app" id="user-content-for-react-app"></a>

```
import { LowcoderAppView } from "lowcoder-sdk";

<LowcoderAppView appId="{YOUR_APPLICATION_ID}" baseUrl="https://prod-us1.openflower.org" />;
```

**Properties**

<table><thead><tr><th width="171">Name</th><th width="149">Type</th><th>Description</th><th>Default value</th></tr></thead><tbody><tr><td>appId</td><td>string</td><td>The app's id is required!</td><td>--</td></tr><tr><td>baseUrl</td><td>string</td><td>The api base url of the OpenFlower Instance.</td><td>Free playground account - https://prod-us1.openflower.org or Cloud Hosted account - https://flower-us1.bitwebservices.com</td></tr><tr><td>onModuleEventTriggered</td><td>(eventName: string) => void</td><td>(Only for Modules) Triggered when module's custom event is triggered. Works only when the app is a module.</td><td>--</td></tr><tr><td>onModuleOutputChange</td><td>(output: any) => void</td><td>(Only for Modules) Triggered when module's outputs change. Works only when the app is a module.</td><td>--</td></tr></tbody></table>

Modules are special Apps, which make bidirectional communication between your app and the OpenFlower Module possible. You can send data to Module-Inputs and receive Data back via Module Outputs. Also, you can trigger Methods and listen to Events.

**Invoke module methods**

```
import { useRef } from "ref";
import { LowcoderAppView } from "lowcoder-sdk";

function MyExistingAppPage() {
  const appRef = useRef();
  return (
    <div>
      <LowcoderAppView appId={YOUR_APPLICATION_ID} ref={appRef} />;
      <button onClick={() => appRef.current?.invokeMethod("some-method-name")}>
        Invoke method
      </button>
    </div>
  );
}
```

#### For vanilla js: <a href="#user-content-for-vanilla-js" id="user-content-for-vanilla-js"></a>

```
import { bootstrapAppAt } from "lowcoder-sdk";

const node = document.querySelector("#my-app");

async function bootstrap() {
  const instance = await bootstrapAppAt(YOUR_APPLICATION_ID, node);

  // set module inputs
  instance.setModuleInputs({ input1: "xxx", input2: "xxx" });

  // invoke module methods
  instance.setModuleInputs({ input1: "xxx", input2: "xxx" });

  // listen module event trigger
  instance.on("moduleEventTriggered", (eventName) => {
    console.info("event triggered:", eventName);
  });

  // listen module output change
  instance.on("moduleOutputChange", (data) => {
    console.info("output data:", data);
  });
}
```


# Native embed SDK

The key advantage for React developers using OpenFlower is the ease of embedding OpenFlower apps directly into the React component hierarchy. This native integration means that OpenFlower apps can be treated as part of the React tree, ensuring a smooth and cohesive user experience. Developers can place OpenFlower apps within their existing React components, maintaining the structural and stylistic consistency of their applications.

Moreover, the interaction between React components and OpenFlower apps is exceptionally fluid. React developers can directly manipulate the state of OpenFlower apps, invoke methods, and handle events, creating a highly interactive and responsive environment. This direct interaction is a game-changer, as it allows for real-time data exchange and dynamic behavior within the application.

For instance, a React developer can easily update the state of a OpenFlower app based on user interactions or external data changes in their React components. This two-way communication ensures that the embedded OpenFlower apps are not just static elements but integral, interactive parts of the overall application.

The ability to interact with OpenFlower apps through state management, method calls, and event handling in React also simplifies the development process. Developers can leverage their existing knowledge of React's ecosystem to control and customize OpenFlower apps, reducing the learning curve and accelerating development timelines.

## OpenFlower SDK usable imports

This is an overview list of exports available for developers using the OpenFlower SDK.

{% hint style="warning" %}
It is important to keep in mind, that this List was build during a reverse engineering attempt. We cannot guarantee the completeness. Also you will find at many Items that the description was guessed (by ChatGPT).\
\
We try step by step to add missing Items and optimize the documentation.
{% endhint %}

### External Libraries

```plaintext
numbro - A library for formatting and manipulating numbers.
Papa - A robust CSV parsing library for in-browser use.
uuid - A library for generating unique identifiers.
```

### Redux Store

```plaintext
redux/store/store - Exports from Redux store for state management.
```

### Utilities

Utilities provide a wide range of functionality, from application utilities to hooks and performance utilities.

* **util/appUtils** - General application utilities.
* **util/bottomResUtils** - Utilities for bottom resolution management.
* **util/cacheUtils** - Caching mechanisms for performance optimization.
* **util/commonUtils** - Commonly used utilities across applications.
* **util/convertUtils** - Data type conversion utilities.
* **util/dateTimeUtils** - Utilities for handling dates and times.
* **util/editoryHistory** - Utilities for managing editor history.
* **util/envUtils** - Environment interaction utilities.
* **util/fileUtils** - File operation utilities.
* **util/history** - Navigation history management utilities.
* **util/historyManager** - Utilities for managing history instances.
* **util/hooks** - Custom React hooks.
* **util/hotkeys** - Keyboard shortcut utilities.
* **util/jsonTypes** - JSON data type handling utilities.
* **util/keyUtils** - Key management and operation utilities.
* **util/localStorageUtil** - Local storage interaction utilities.
* **util/memoize** - Memoization utilities for performance optimization.
* **util/objectUtils** - Object manipulation and operation utilities.
* **util/perfUtils** - Performance monitoring and optimization utilities.
* **util/permissionUtils** - User permission management utilities.
* **util/reducerUtils** - Redux reducer utilities.
* **util/scheduleUtils** - Scheduled task management utilities.
* **util/stringUtils** - String manipulation and operation utilities.
* **util/tutorialUtils** - Tutorial creation and management utilities.
* **util/uaUtils** - User agent detection utilities.
* **util/urlUtils** - URL manipulation and parsing utilities.
* **util/context/ExternalEditorContext** - Context for external editor integration. With the help of the EditorContext Object you can access also EditorState and understand the current usage of the Editor / Canvas.

### Constants

```plaintext
constants/domLocators/CanvasContainerID - Identifier for the canvas container element.
```

### Components and Component Utilities

Components and utilities for building and managing UI components.

```plaintext

comps/comps/rootComp - Root component structure.
comps/utils/useCompInstance - Utilities for component instance management.
comps/utils/idGenerator - Unique identifier generator for components.
comps/utils/propertyUtils - Component property management utilities.
comps/utils/globalSettings - Global settings management utilities.
comps/utils/themeContext - Theme management context.
```

### Design and Core

```plaintext
lowcoder-design - Core design elements for building applications.
lowcoder-core - Core functionalities for application development.
```

### Wrappers

Wrappers play crucial roles in structuring the UI and UX of the SDK's components, ensuring that complex functionalities are manageable and that the interface remains user-friendly.

* **ControlPropertyViewWrapper** - A wrapper for property views within control components, likely enhancing layout or providing additional UI context for control settings.
* **ModalFooterWrapper** - A wrapper specifically for the footer section of modal dialogs, potentially standardizing layout or spacing for action buttons.
* **SecondStepPropertyViewWrapper** - A wrapper used for the second step in a multi-step property configuration process, possibly organizing or grouping properties for clarity.
* **GlobalShortcutsWrapper** - A wrapper for global shortcut configurations, ensuring shortcut keys are managed or displayed in a consistent format across the application.
* **QueryConfigItemWrapper** - A wrapper for individual items within query configuration settings, providing a consistent UI structure for each query parameter or option.
* **QueryConfigWrapper** - A more general wrapper for the entire set of query configuration settings, encapsulating all query config items for cohesive presentation.
* **QueryPropertyViewWrapper** - A wrapper for property views related to queries, likely organizing and displaying query-related settings in a user-friendly manner.
* **QuerySectionWrapper** - A wrapper for sections within a query configuration or editor, segmenting the query UI into logical parts for better user navigation.
* **ShortcutsWrapper** - A wrapper for shortcut configurations, similar to GlobalShortcutsWrapper, but potentially for a more specific context or component.
* **SuspensionBoxWrapper** - A wrapper that might be used to suspend or temporarily hold components, possibly for lazy loading or conditional rendering scenarios.
* **SwitchWrapper** - A wrapper for switch components, providing additional styling or context for toggle switches within the UI.
* **ValueAndMsgWrapper** - A wrapper for components or UI elements that display both a value and a message, ensuring consistent formatting and presentation.
* **WrapContextNodeV2Wrapper** - A wrapper for context nodes in a version 2 format, possibly providing a new or enhanced way to manage context in component trees.
* **WrapNodeWrapper** - A general wrapper for nodes, likely used to encapsulate components or elements within additional logic or styling context.
* **WrapperToControlItem** - A wrapper that converts generic components or elements into control items, likely for use in a control panel or settings menu.

### Controls

Controls for managing Properties for UI elements and interactions.

* **ActionSelectorControl** - Enables selection from a list of predefined actions.
* **ArrayControl** - Manages arrays, allowing users to add, remove, and edit array items.
* **ArrayNumberControl** - Specifically for arrays of numbers, facilitating their management.
* **ArrayOrJSONObjectControl** - Allows input to be either an array or a JSON object, supporting flexible data structures.
* **ArrayStringControl** - Designed for arrays of strings, enabling easy manipulation of string lists.
* **ArrayStringOrNumberControl** - Accepts arrays composed of either strings or numbers, offering versatility in data handling.
* **BooleanStateControl** - Toggles a boolean state, useful for settings that can be switched on or off.
* **ButtonEventHandlerControl** - Configures event handlers for button components, customizing their behavior.
* **ColorCodeControl** - Inputs color codes (e.g., HEX, RGB), providing a precise method for color selection.
* **ColorControl** - Offers a color picker for intuitive color selection.
* **ColumnOptionControl** - Selects options for table columns, such as visibility and sorting.
* **DropdownOptionControl** - Manages options within a dropdown, allowing for the organization of selectable items.
* **FunctionControl** - Defines functions, enabling custom logic or calculations.
* **JSONObjectArrayControl** - Manages arrays of JSON objects, facilitating complex data structures.
* **JSONObjectControl** - Inputs JSON objects, allowing users to define key-value pairs.
* **JSONValueControl** - Inputs values in JSON format, supporting various data types.
* **NumberControl** - Inputs numeric values, often with validation for specific ranges.
* **NumberOrJSONObjectArrayControl** - Allows either a single number or an array of JSON objects as input.
* **NumberStateControl** - Manages numeric state properties, useful for numeric configurations.
* **ObjectControl** - Inputs generic objects, providing a flexible interface for defining properties.
* **ParamsArrayStringControl** - Manages string arrays as parameters, useful for multiple text inputs.
* **ParamsBooleanCodeControl** - Inputs boolean parameters with code support, allowing dynamic logic.
* **ParamsBooleanControl** - Inputs boolean parameters, typically for toggling settings.
* **ParamsJsonControl** - Inputs parameters in JSON format, supporting complex configurations.
* **ParamsNumberControl** - Inputs numeric parameters, with support for range constraints.
* **ParamsPositiveNumberControl** - Specifically for positive numeric parameters, ensuring positive inputs.
* **ParamsStringControl** - Inputs string parameters, allowing text input for configurations.
* **ParamsStringJsonControl** - Inputs string parameters with JSON support, offering flexible text inputs.
* **SelectEventHandlerControl** - Selects event handlers, customizing interactive component responses.
* **SelectInputOptionControl** - Configures options within select components, enhancing dropdown functionality.
* **SelectOptionControl** - Manages options in select components, facilitating choice organization.
* **TransformerCodeControl** - Inputs transformation code, used for data modification.
* **IconControl** - Enables the selection of icons from a predefined set, useful for visually representing actions or statuses.
* **InputEventHandlerControl** - Configures event handlers for input fields, enabling custom reactions to user inputs.
* **KeyValueControl** - Allows users to input key-value pairs, facilitating the management of object properties or configurations.
* **LabelControl** - A control for inputting and editing text labels, often used for form fields, buttons, or other interactive elements.
* **OptionsControl** - Manages selectable options across various components, enhancing configurability.
* **PositionControl** - Allows users to specify the position of elements, useful for layout and design settings.
* **QuerySelectControl** - Enables the selection of queries from a list, often used in data-binding or API-related configurations.
* **RadiusControl** - Inputs radius values, controlling the curvature of borders or other circular elements.
* **RangeControl** - Provides a UI for selecting a range of values, typically represented by a slider with two handles.
* **RefControl** - Manages references to other components or data sources, facilitating complex data interactions.
* **RegexControl** - Inputs regular expressions, allowing for pattern matching and validation settings.

### Generators

Generators for creating components and enhancing them with additional functionalities.

* **changeDataType** - Generator for changing data types.
* **hookToComp** - Generator for converting hooks to components.
* **index** - Base generator exports.
* **list** - Generator for list components.
* **multi** - Generator for multi-component structures.
* **sameTypeMap** - Generator for mapping same type components.
* **simpleGenerators** - Simple component generators.
* **uiCompBuilder** - UI component builder generator.
* **withContext** - Generator for adding context to components.
* **withDispatchHook** - Generator for adding dispatch hooks.
* **withErrorBoundary** - Generator for adding error boundaries.
* **withExposing** - Generator for exposing component functionalities.
* **withIsLoading** - Generator for loading state management.
* **withMethodExposing** - Generator to expose methods on a component.
* **withType** - Generator for type management.

### Components

Components cover a wide range of functionalities, from input and selection to display and multimedia, facilitating the development of interactive and dynamic web applications.

* **AbstractComp** - Represents an abstract component, likely serving as a base or common interface for other components.
* **AudioComp** - Component for audio playback functionality.
* **AutoCompleteComp** - Component for autocomplete functionality, allowing users to quickly find and select from a list of values as they type.
* **CalendarComp** - Component for displaying and interacting with a calendar, typically used for date selection.
* **CascaderComp** - Component that provides a cascading select interface, where the selection in one level determines the options available in the next.
* **CheckboxComp** - Component for checkbox functionality, allowing users to select one or more options from a set.
* **CollapsibleContainerComp** - Component that can expand and collapse to show or hide its content.
* **ContainerComp** - Generic container component, used to group other components together.
* **DateComp** - Component for date input, allowing users to select a date.
* **DateRangeComp** - Component for selecting a range of dates, from a start date to an end date.
* **DropdownComp** - Component for dropdown selection functionality, allowing users to select an option from a dropdown menu.
* **FileViewerComp** - Component for viewing files, potentially supporting various file types.
* **FormComp** - Component for form functionality, allowing users to input and submit data.
* **HookComp** - Likely a component designed to utilize React hooks for state management or side effects.
* **IFrameComp** - Component for embedding an IFrame, allowing for the inclusion of another HTML page within the current page.
* **InputComp** - Component for text input, allowing users to enter and edit text.
* **JsonEditorComp** - Component for editing JSON data, providing a user-friendly interface for modifying JSON structures.
* **JsonFormComp** - Component for generating forms based on JSON data, allowing dynamic form creation and manipulation.
* **ListViewComp** - Component for displaying a list of items, potentially supporting scrolling, selection, and other list interactions.
* **ModalComp** - Component for modal dialog functionality, allowing for pop-up dialogs that overlay the main content.
* **NumberInputComp** - Component for number input, allowing users to enter numeric values.
* **PasswordComp** - Component for password input, typically hiding the entered characters for security.
* **ProcessCircleComp** - Component likely representing a process or progress indicator in the form of a circle.
* **ProgressComp** - Component for displaying progress, such as a progress bar.
* **QRCodeComp** - Component for displaying QR codes.
* **RadioComp** - Component for radio button functionality, allowing users to select a single option from a set.
* **RatingComp** - Component for rating functionality, allowing users to provide a rating, typically represented by stars or similar indicators.
* **RichTextEditorComp** - Component for rich text editing, providing a user interface for formatting text with options like bold, italic, lists, etc.
* **SliderComp** - Component for slider functionality, allowing users to select a value from a range by sliding a handle.
* **SwitchComp** - Component for switch functionality, allowing users to toggle between two states, such as on/off.
* **TableComp** - Component for table functionality, displaying data in rows and columns.
* **TextAreaComp** - Component for multi-line text input, allowing users to enter text over multiple lines.
* **TextComp** - Component for displaying text.
* **ToggleButtonComp** - Component for a toggle button that can switch between two states.
* **VideoComp** - Component for video playback functionality.

### Styles

```
styled - An object combining default and named Styles 
so you can use it like "const YourDiv = styled.div`... {your CSS}`;
```

* **BigButtonStyle** - Style definition for large buttons, likely specifying size, padding, and possibly color.
* **BlockGrayLabelStyle** - Style for a label component with a block display and gray background, typically used for categorization or emphasis.
* **BlockLabelStyle** - General style for label components that are displayed as blocks, focusing on background color, margin, and padding.
* **BlueButtonStyle** - Style specifically for blue buttons, defining the color scheme and possibly hover states for buttons.
* **ButtonStyle** - General style for button components, including padding, font size, and color.
* **CalendarStyle** - Style for calendar components, affecting the layout, colors, and typography of the calendar UI.
* **CarouselStyle** - Style for carousel components, which may include settings for transitions, navigation buttons, and pagination indicators.
* **CascaderStyle** - Style for cascader components, affecting how nested options are displayed and interacted with.
* **CheckboxStyle** - Style for checkbox components, including the box size, checkmark appearance, and spacing.
* **CommentStyle** - Style for comment sections or components, focusing on typography, spacing, and background color.
* **ContainerBodyStyle** - Style specifically for the body of container components, affecting padding, margin, and background color.
* **ContainerFooterStyle** - Style for the footer area of container components, which may include padding, border, and background color settings.
* **ContainerHeaderStyle** - Style for the header part of container components, typically defining background color, typography, and padding.
* **ContainerStyle** - General style for container components, affecting overall layout, padding, and border properties.
* **DividerStyle** - Style for divider components, focusing on line color, thickness, and margin.
* **DropdownContainerStyle** - Style for the container part of dropdown components, affecting the dropdown list's appearance and positioning.
* **DropdownMenuStyle** - Style for dropdown menu components, including item spacing, background color, and typography.
* **FormCheckboxItemStyle** - Style for checkbox items within forms, specifying the layout and appearance of checkboxes in form contexts.
* **FormInputItemStyle** - Style for input items within forms, focusing on border, padding, and typography for form inputs.
* **FormSectionStyle** - Style for sections within forms, used to group related form items and visually separate different parts of a form.
* **GridIconStyle** - Likely a style for icons within a grid layout, focusing on size and spacing of icons.
* **IconWrapperStyle** - Style for a wrapper around icons, potentially affecting size, padding, or background color to enhance icon presentation.
* **IframeStyle** - Style for iframe components, affecting border, size, and possibly responsiveness.
* **ImageStyle** - General style for image components, including size, border-radius, and object-fit properties.
* **InputLikeStyle** - Style for components that resemble input fields, focusing on border, background color, and typography to match the appearance of standard inputs.
* **JsonEditorStyle** - Style for JSON editor components, affecting the layout, color scheme, and typography of the editor interface.
* **JsonFormCompStyle** - Style for components that generate forms based on JSON data, likely affecting the layout and appearance of dynamically created forms.
* **ListViewStyle** - Style for list view components, focusing on item spacing, scroll behavior, and possibly item hover states.
* **ModalStyle** - Style for modal dialog components, including background overlay, modal positioning, and content padding.
* **MultiSelectStyle** - Style for multi-select components, affecting how selected items are displayed and how the selection list is presented.
* **NavLayoutItemStyle** - Style for items within a navigation layout, specifying the appearance of navigation links or buttons.
* **ProgressStyle** - Style for progress bar components, including bar color, height, and border-radius.
* **RatingStyle** - Style for rating components, such as star ratings, including size, color, and spacing of rating icons.
* **ResponsiveLayoutColStyle** - Style for columns in responsive layouts, affecting width, padding, and breakpoint behaviors.
* **ResponsiveLayoutRowStyle** - Style for rows in responsive layouts, focusing on spacing between columns and alignment within the row.
* **RichTextEditorStyle** - Style for rich text editor components, affecting the toolbar, typography, and editor area appearance.
* **SegmentStyle** - Style for segment controls or segmented buttons, including border, background color, and selected state appearance.
* **SelectStyle** - Style for select dropdown components, focusing on dropdown appearance, item spacing, and hover states.
* **SignatureStyle** - Style for signature components, likely affecting the canvas area where signatures are drawn.
* **SliderStyle** - Style for slider components, including track and handle appearance, as well as responsive behavior.
* **SwitchStyle** - Style for switch components, focusing on the toggle appearance, color, and size.
* **TableStyle** - General style for table components, affecting cell padding, border, and header styling.
* **TextWrapperStyle** - Style for text wrapper components, potentially affecting typography, line-height, and text alignment.
* **TimeLineStyle** - Style for timeline components, focusing on line color, event marker appearance, and spacing.
* **ToggleButtonStyle** - Style for toggle button components, including the appearance of the toggle state and transition effects.
* **TreeStyle** - Style for tree view components, affecting node spacing, indentation, and expand/collapse icons.
* **VideoStyle** - Style for video components, including controls appearance, aspect ratio, and border.

### Labels

* **BlockGrayLabel** - A style or component for displaying labels with a gray background, typically used for subtle emphasis.
* **BlockLabel** - A component or style for block-level labels, used for headings or important textual information.
* **CommonGrayLabel** - A predefined style for labels with a common gray theme, likely used for neutral or secondary information.
* **DropdownOptionLabelWithDesc** - A component for dropdown options that includes both a label and a description for more detailed selections.
* **CollapseLabel** - Likely a label used within a collapsible component to indicate the section or content that can be expanded or collapsed.
* **IconWrapperLabel** - A label associated with an icon wrapper, possibly used for tooltips or descriptions of icons.
* **QueryConfigLabel** - A label used within query configuration settings, possibly to identify different query parameters or options.
* **TooltipLabel** - A label specifically designed for use within tooltips, providing brief descriptions or additional information on hover.
* **ShowBorderLabel** - A label that might be used to toggle or indicate the visibility of borders in a UI component or layout section.
* **TextLabel** - A general-purpose label for text, used across various components and contexts to display information.
* **FormSectionLabel** - A label used to title or describe sections within a form, helping to organize and categorize form fields.
* **DropdownOptionLabel** - Similar to DropdownOptionLabelWithDesc but possibly without the description, used for simpler dropdown selections.
* **InputLabel** - A label typically used for input fields, indicating the type of information expected from the user.
* **MenuItemLabel** - A label used within menu items, providing clear identification of the menu option.
* **OptionLabel** - A label used for options in select dropdowns, checkboxes, or radio buttons, providing clear identification of each choice.

### Options

* **ActionOptions** - Likely refers to configurations or settings related to actions that can be performed within the application, such as button actions or event triggers.
* **AlignOptions** - Configurations for alignment properties, possibly including options for text or element alignment within a UI component (e.g., left, right, center).

### Checkers

* **CheckEmailValid** - A utility or function designed to validate email addresses, ensuring they meet a specific format or criteria.
* **CheckIsMobile** - A function or method to determine if the user's device is a mobile device, based on screen size, user agent, or other factors.
* **CheckOtpValid** - A validation function for one-time passwords (OTPs), ensuring that the inputted OTP is correct and valid within a given context or timeframe.
* **CheckPhoneValid** - A utility designed to validate phone numbers, verifying their format and possibly their authenticity or region-specific characteristics.

### Constants / Patterns

* **CACHE\_PREFIX** - Likely a constant used as a prefix in caching mechanisms to differentiate or namespace cached items.
* **COLOR\_PALETTE** - A predefined set of colors available for use throughout the application, ensuring consistency in the UI's color scheme.
* **DATE\_FORMAT** - A constant representing the default format for displaying dates within the application.
* **DATE\_FORMAT\_EN** - Specifies the date format specifically for English locales, ensuring dates are presented in a region-appropriate manner.
* **DATE\_TIME\_12\_FORMAT** - Represents a date and time format that includes a 12-hour clock, possibly with AM/PM indicators.
* **DATE\_TIME\_FORMAT** - A general constant for the format used to display both date and time information.
* **DEFAULT\_IMG\_URL** - A fallback URL for images, used when a specific image is not available or cannot be loaded.
* **DEP\_TYPE** - Likely denotes the type of dependency, possibly used in the context of module imports or external library dependencies.
* **EMAIL\_PATTERN** - A regular expression pattern used for validating email addresses in form inputs or data processing.
* **SURFACE\_COLOR** - A constant that might define a default or primary surface color used in the UI, part of a theme or design system.
* **TIME\_FORMAT** - Specifies the format for displaying time within the application, potentially in a 24-hour clock format.
* **TIME\_FORMAT\_MINUTES** - A more specific time format constant, possibly focusing on displaying time with minute precision.
* **TIMESTAMP\_FORMAT** - Defines the format for displaying timestamps, which include both date and time information.
* **TIME\_12\_FORMAT** - Similar to **DATE\_TIME\_12\_FORMAT**, representing a time format using a 12-hour clock.
* **URL\_PATTERN** - A regular expression pattern for validating URLs, ensuring that input or linked URLs are in a correct format.

### Application View and IDE

```plaintext
appView/bootstrapAt - Bootstrap utility for application initialization.
appView/OpenFlowerAppView - Main application view component.
appView/AppViewInstance - Application view instance management.
ide/CompIde - Component IDE for development.
ide/CompPlayground - Component playground for testing.
```

### Ant Design

```plaintext
antd - Exports from Ant Design for comprehensive UI design utilities and components.
```

## Event Handler & Trigger

Event Handlers enable Application Creators to use visually guided Handler Creation for Events that may occur in Visual Components. Use these Event handlers to trigger Events and ensure a good UI.

The event handler itself does not contain any application logic. It manages only the display of the Event handler. For you as Developer, you can use them to prepare a good visual user interface for Application Creators.

<figure><img src="/files/tNrZpwSkMV131Rxk3Ah1" alt=""><figcaption><p>Meeting related Event Handlers. Developers can bind logical triggers in their component code to this visual Event Handlers.</p></figcaption></figure>

### Importing Event Handlers

To utilize an event handler, first import it from the designated module:

```javascript
import { InputEventHandlerControl } from "path/to/eventHandlerControl";
```

You can then attach this handler directly to component events or use intermediary functions for specific user actions.

### Using Event Handlers

A typical use of event handlers is in combination with the childrenmap of a component.

<pre class="language-typescript"><code class="lang-typescript">const childrenMap = {
    ... 
    onEvent: InputEventHandlerControl,
    ...
}

// other code

return new UICompBuilder(childrenMap, (props: { 
    ... 
    onFocus: () => props.onEvent("focus"),
    onBlur: () => props.onEvent("blur"),
<strong>    onPressEnter: () => props.onEvent("submit"),
</strong>    ...
}) => {
... 
</code></pre>

{% hint style="info" %}
The further code examples are just abstract examples to list the event handlers.
{% endhint %}

### Available Event Handlers

#### **Input Events**

Handles text-input related events, including changes, focus, blur, and submit actions.

**Module Import:**

```javascript
import { InputEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<Component
  onEvent={InputEventHandlerControl}
  onFocus={() => InputEventHandlerControl("focus")}
  onBlur={() => InputEventHandlerControl("blur")}
  onPressEnter={() => InputEventHandlerControl("submit")}
/>
```

**Exposed Events:**

* `change`
* `focus`
* `blur`
* `submit`

#### **Button Events**

Manages click events for button components.

**Module Import:**

```javascript
import { ButtonEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<Button
  onClick={() => ButtonEventHandlerControl("click")}
/>
```

**Exposed Events:**

* `click`

#### **Change Events**

Dedicated to handling change events primarily from user-inputs.

**Module Import:**

```javascript
import { ChangeEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<Input
  onChange={() => ChangeEventHandlerControl("change")}
/>
```

**Exposed Events:**

* `change`

#### **Click Events**

Handles single click, double click, and right click events.

**Module Import:**

```javascript
import { ClickEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<Component
  onClick={() => ClickEventHandlerControl("click")}
  onDoubleClick={() => ClickEventHandlerControl("doubleClick")}
  onContextMenu={() => ClickEventHandlerControl("rightClick")}
/>
```

**Exposed Events:**

* `click`
* `doubleClick`
* `rightClick`

#### **Checkbox Events**

Controls checkbox interactions, including check and uncheck actions.

**Module Import:**

```javascript
import { CheckboxEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<Checkbox
  onChange={(e) => CheckboxEventHandlerControl(e.target.checked ? "checked" : "unchecked")}
/>
```

**Exposed Events:**

* `checked`
* `unchecked`

#### **Drag and Drop Events**

Facilitates drag-and-drop operations across draggable components.

**Module Import:**

```javascript
import { DragEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<DraggableComponent
  onDragStart={() => DragEventHandlerControl("drag")}
  onDrop={() => DragEventHandlerControl("drop")}
/>
```

**Exposed Events:**

* `drag`
* `drop`

#### **Media Control Events**

Handles media controls like play, pause, and end events for media components.

**Module Import:**

```javascript
import { MediaEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<MediaComponent
  onPlay={() => MediaEventHandlerControl("play")}
  onPause={() => MediaEventHandlerControl("pause")}
  onNext={() => MediaEventHandlerControl("next")}
  onEnded={() => MediaEventHandlerControl("ended")}
/>
```

**Exposed Events:**

* `play`
* `pause`
* `next`
* `ended`

#### **Collaboration Events**

Manages events related to screen- and content-sharing features.

**Module Import:**

```javascript
import { CollaborationEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<CollaborationComponent
  onStartShare={() => CollaborationEventHandlerControl("shareScreen")}
  onStopShare={() => CollaborationEventHandlerControl("shareScreenEnd")}
/>
```

**Exposed Events:**

* `shareScreen`
* `shareScreenEnd`
* `shareControl`
* `shareControlEnd`
* `shareContent`
* `shareContentEnd`

#### **Element Events**

It handles events related to general element interactions, such as open, edit, close, save, and delete actions.

**Module Import:**

```javascript
import { ElementEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

<pre class="language-javascript"><code class="lang-javascript"><strong>&#x3C;ElementComponent
</strong>  onOpen={() => ElementEventHandlerControl("open")}
  onEdit={() => ElementEventHandlerControl("edited")}
  onClose={() => ElementEventHandlerControl("close")}
  onSave={() => ElementEventHandlerControl("saved")}
  onDelete={() => ElementEventHandlerControl("delete")}
/>
</code></pre>

**Exposed Events:**

* `open`
* `edited`
* `close`
* `saved`
* `delete`

#### **GeoMap Events**

Manages events related to geographical map interactions such as move, zoom, and select.

**Module Import:**

```javascript
import { GeoMapEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
javascriptCopy code<GeoMapComponent
  onMove={() => GeoMapEventHandlerControl("geoMapMove")}
  onZoom={() => GeoMapEventHandlerControl("geoMapZoom")}
  onSelect={() => GeoMapEventHandlerControl("geoMapSelect")}
/>
```

**Exposed Events:**

* `geoMapMove`
* `geoMapZoom`
* `geoMapSelect`

#### **Chart Events**

Controls events related to chart interactions including zoom, hover, select, and deselect.

**Module Import:**

```javascript
import { ChartEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<ChartComponent
  onZoom={() => ChartEventHandlerControl("chartZoom")}
  onHover={() => ChartEventHandlerControl("chartHover")}
  onSelect={() => ChartEventHandlerControl("chartSelect")}
  onDeselect={() => ChartEventHandlerControl("chartDeselect")}
/>
```

**Exposed Events:**

* `chartZoom`
* `chartHover`
* `chartSelect`
* `chartDeselect`

#### **Meeting Events**

Handles events specific to meeting interactions such as start, end, join, leave, and camera control.

**Module Import:**

```javascript
import { MeetingEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<MeetingComponent
  onStart={() => MeetingEventHandlerControl("meetingStart")}
  onEnd={() => MeetingEventHandlerControl("meetingEnd")}
  onJoin={() => MeetingEventHandlerControl("meetingJoin")}
  onLeave={() => MeetingEventHandlerControl("meetingLeave")}
  onCameraOn={() => MeetingEventHandlerControl("showCamera")}
  onCameraOff={() => MeetingEventHandlerControl("hideCamera")}
/>
```

**Exposed Events:**

* `meetingStart`
* `meetingEnd`
* `meetingJoin`
* `meetingLeave`
* `showCamera`
* `hideCamera`

#### **Scanner Events**

Manages scanner interaction events such as success, error, and related operational controls.

**Module Import:**

```javascript
import { ScannerEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<ScannerComponent
  onSuccess={() => ScannerEventHandlerControl("scannerSuccess")}
  onError={() => ScannerEventHandlerControl("scannerError")}
/>
```

**Exposed Events:**

* `scannerSuccess`
* `scannerError`

#### **Step Events**

Controls step-based interactions, suitable for workflows or guided processes.

**Module Import:**

```javascript
import { StepEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<StepComponent
  onNext={() => StepEventHandlerControl("next")}
  onStep={() => StepEventHandlerControl("step")}
  onFinish={() => StepEventHandlerControl("finished")}
/>
```

**Exposed Events:**

* `step`
* `next`
* `finished`

#### **Share Content Events**

Handles events related to content sharing within collaborative environments.

**Module Import:**

```javascript
import { ShareContentEventHandlerControl } from "path/to/eventHandlerControl";
```

**Usage:**

```javascript
<ContentSharingComponent
  onStartShare={() => ShareContentEventHandlerControl("shareContent")}
  onEndShare={() => ShareContentEventHandlerControl("shareContentEnd")}
/>
```

**Exposed Events:**

* `shareContent`
* `shareContentEnd`


# Build the SDK from Source

1. Clone the Repository <https://github.com/Flowerappeng-org/openflower>
2. Make sure the repo is synced with the latest updates of the actual OpenFlower repo. If not sync the repo and take the newest pull.
3. Move to “client/packages/lowcoder-sdk“ directory
4. run `yarn install` and `yarn build` command.
5. Increase the package version in “client/packages/lowcoder-sdk/package.json“
6. Publish the package with `npm publish`


# Opensource Contribution

By expanding the capabilities of Lowcode platforms through open source contributions, we not only broaden the spectrum of use cases but also exponentially increase their value to the community. We commit to supporting this growth in every way possible.

Here is a small guide on where to start and which style of development we prefer.

## Core System

OpenFlower has 3 main services, which are developed by the Community and us - the OpenFlower Team.

* [Frontend App](https://github.com/flowerappeng-org/openflower/tree/main/client) - JavaScript, TypeScript, React, ANTd
* [API-Service](https://github.com/flowerappeng-org/openflower/tree/main/server/api-service) - Java, Spring, Spring WebFlux - using MongoDB and Redis
* [Node-Service](https://github.com/flowerappeng-org/openflower/tree/main/server/node-service) - Node.js, TypeScript

These 3 services are the main deliverables and the codebase of OpenFlower. We are happy to work with you on your contribution and express that Frontend App and API-Service are fairly complex systems. You would need to reserve a bit of time to get to know it and understand the details.

## Plugins and Extensions

Extension of OpenFlower for and with the Community happens mainly by the Plugins and Extensions at defined Interfaces. OpenFlower has the following Plugin Systems:

* [Visual Component Plugins](https://github.com/flowerappeng-org/openflower-create-component-plugin)
* Plugin API of API Service
* [Data-Source Plugins in the Node-Service](https://github.com/flowerappeng-org/openflower/tree/main/server/node-service/src/plugins)

{% hint style="success" %}
We suggest looking first into the development of these plugins, as they offer a good abstraction that speeds up development and offers a good and fast value for the community.
{% endhint %}

### Visual Component Plugin Builder

The main steps are:

* Fork of [this Repository](https://github.com/flowerappeng-org/openflower-create-component-plugin)
* Local installation & preparation
* Developing & preview the Components
* Publish the Components to NPM

1. Forking of the Repository

To ensure you can develop your Component Plugin including as your repository, please fork (update) our Flowerappeng-org/openflower-create-component-plugin repository first.

Find here more information: <https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo>

2. Cloning of the new repository to work local

Now you can clone your new repository to develop local.

```
https://github.com/<your org>/openflower-create-component-plugin.git

or 

git@github.com:<your org>/openflower-create-component-plugin.git
```

3. Local Development preparation

Navigate your terminal or bash to your /root folder of the cloned repository to install general dependencies and the OpenFlower SDK

```bash
yarn install
```

Execute the Plugin Builder Script. Please name your plugin with the prefix **"lowcoder-comp-"** to make it easy for other users to find OpenFlower Component Pluins on NPM

```bash
npm create lowcoder-plugin lowcoder-comp-my-plugin
```

Navigate your terminal or bash to the newly created Plugin folder

```bash
cd lowcoder-comp-my-plugin
```

Install all dependencies:

```bash
yarn install
```

Start the Playground (Components Preview): Now you can start your Plugin in the playground, so during development you have a realtime preview.

```bash
yarn start
```

This will start the local development server and open a browser on [http://localhost:9000](http://localhost:9000/)

#### Start developing

After the preparation, a skeleton project for OpenFlower Component Plugin development was created and the SDK prepared. A new browser window should open at [http://localhost:9000](http://localhost:9000/) This is the Components Preview, which allows you to see your new component in action, as it would work in the OpenFlower Editor.

Data, methods, and properties are visible and interactive, so you can test your Component during development. The view will automatically refresh.

Find the /src folder in the new created project. Here are some demonstration files prepared. The OpenFlower Component Builder makes the development & publishing of multiple individual components as bundle possible. In the left navigation of the Components Preview you can switch between your components.

Before you publish, please cleanup all demonstration files like the "HelloWorldComp.tsx" and the references to HelloWorldComp.

Folder Structure:

**lowcoder-comp-my-plugin/**

* ├ icons/
* ├ locales/
* └ src/
  * └ index.ts

In "icons" you will place an SVG, which will later displayed to drag the component to the OpenFlower Editor Canvas. In "locales" you place translation files for all displayed texts of your components And in the "src" folder you place all code. Make sure, your Copmonent is referenced right in the index.ts file.

#### Publish a Component Plugin

With the following command you can publish the script to the NPM repository:

```bash
yarn build --publish
```

## OpenFlower Marketplace

Next to this direct code development contribution, we also encourage you to contribute smart solutions and reusable Apps and Modules on the [OpenFlower Marketplace](https://prod-us1.openflower.org/marketplace) so other users can see solution patterns and Application Building Blocks.

You can follow the [Guide for Apps & Modules to publish on the Marketplace](/workspaces-and-teamwork/lowcoder-marketplace)

## Code Contribution to Core System

We feel honored to work with you together on OpenFlower as a Platform! A good start and procedure that allows a smooth development process is like this:

1. [Fork the Repository](https://github.com/flowerappeng-org/openflower/fork)
2. Clone it into your local environment / IDE
3. Create a workable local Development Environment
4. Create your Feature-Branch from **/main** branch to get the latest stable Environment
5. **Develop your magic and enjoy the ride!**
6. Raise a PR / Merge Request to [**/dev branch**](https://github.com/flowerappeng-org/openflower/tree/dev) of the OpenFlower Main Repository
7. Follow up if / when we have questions at your Merge Request

Please ask us directly for any related questions so we can help you the fastest way.

### Frontend App

#### Start a local backend server

Simply run the below command to start a local backend server. This is the fasted way. The Backend typically changes less frequent, so you can just run the latest version

```bash
docker run -d --name openflower -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks" flowerappengorg/openflower
```

For more information, view our [docs](/setup-and-run/self-hosting)

**Build a Docker image from the source**

You also can build the image from the latest Source code or a special branch. However, for pure frontend development there are less reasons to go this way.

1. Check out the source code and change to source dir.
2. Use the command below to build a Docker image :

```bash
docker build -f ./deploy/docker/Dockerfile -t openflower-dev .
```

3. Start the fresh built Docker image

```bash
docker run -d --name lowcoder-dev -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks" openflower-dev
```

#### Start developing

As soon as the development server is ready you can access OpenFlower by <http://localhost:3000>. Now, you can start to develop locally.

1. Check out the source code.

```bash
git@github.com:your-org/openflower.git
```

1. Change to **/client** dir in the source dir.

```bash
cd client
```

3. Run yarn to install dependencies.

```bash
yarn install
```

4. Start dev server:

```bash
LOWCODER_API_SERVICE_URL=http://localhost:3000 yarn start
```

After the dev server starts successfully, it will be automatically opened in the default browser. The local Frontend App is served by [Vite](https://vitejs.dev/). It chooses an available port automatically. Typically, it will open at <http://localhost:8000>

Vite keeps the Browser for all changes current. That means you can see the effect of your development in most cases instantly. Sometimes, Vite rebuilds briefly and reloads the App in the Browser. In most cases, however, the changes are directly rendered. If you are not sure that your changes are already active in the Browser, you can stop and restart Vite at any time.

### API-Service

#### Preparation

To develop with us in the API-Service, you need to have Java - OpenJDK 17 Maven - Version 3+ (preferably 3.8+) installed. Also, it is helpful if you have knowledge of [Spring Webflux](https://docs.spring.io/spring-framework/reference/web/webflux.html)

You would need to have a MongoDB and a RedisDB ready and accessible.

If you don't have an available MongoDB, you can start a local MongoDB service with docker:

```bash
docker run -d  --name lowcoder-mongodb -p 27017:27017 -e MONGO_INITDB_DATABASE=lowcoder mongo
```

If you don't have an available Redis, you can start a local Redis service with docker:

```bash
docker run -d --name lowcoder-redis -p 6379:6379 redis
```

Both, you will need to register in the application-lowcoder.yml file.

```yaml
spring:
  data:
    mongodb:
      authentication-database: admin
      auto-index-creation: false
      uri: mongodb://localhost:27017/lowcoder?authSource=admin
    redis:
      url: redis://localhost:6379
```

{% hint style="info" %}
Configure the local runtime:\
./api-service/lowcoder-server/src/main/resources/application-lowcoder.yml
{% endhint %}

{% hint style="warning" %}
Add the VM Options:\
-Dpf4j.mode=development -Dpf4j.pluginsDir=lowcoder-plugins -Dspring.profiles.active=lowcoder -XX:+AllowRedefinitionToAddDeleteMethods --add-opens java.base/java.nio=ALL-UNNAMED
{% endhint %}

#### Using VS Code <a href="#unsing-vs-code" id="unsing-vs-code"></a>

Create a launch.json file in the .vscode folder of your newly opened workspace. The contents should look like this:

```json
{
    "version": "0.0.1",
    "configurations": [
        {
            "type": "java",
            "name": "ServerApplication",
            "request": "launch",
            "mainClass": "org.lowcoder.api.ServerApplication",
            "projectName": "OpenFlower API Service",
            "vmArgs": "-Dpf4j.mode=development -Dpf4j.pluginsDir=./server/api-service/lowcoder-plugins -Dspring.profiles.active=lowcoder -XX:+AllowRedefinitionToAddDeleteMethods --add-opens java.base/java.nio=ALL-UNNAMED"
        }
    ],
}
```

Important is here the command -Dspring.profiles.active= - as it is responsible for the selection of the right apllication settings file too.

### Start the debug locally <a href="#start-the-debug-locally" id="start-the-debug-locally"></a>

Make sure that the apllication settings file contains the full local configuration you need.

The apllication settings file is named application-\<profile>.yaml and reside in server/api-service/lowcoder-server/src/main/resources.

The profile relates to your setting in the launch file. For example: -Dspring.profiles.active=lowcoder would make sure, lowcoder seeks the right config at application-lowcoder.yaml

Navigate to the file:\
server/api-service/lowcoder-server/src/main/java/org/lowcoder/api/ServerApplication.java\
\
This is the main class. Now you can use the IDE to "run" it or "debug it".

### Unsing IntelliJ IDEA <a href="#unsing-intellij-idea" id="unsing-intellij-idea"></a>

Configure the Run/Debug configuration as shown below.

| JDK version | Java 17                                                                                                                                                                          |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| -cp         | lowcoder-server                                                                                                                                                                  |
| VM options  | -Dpf4j.mode=development -Dpf4j.pluginsDir=lowcoder-plugins -Dspring.profiles.active=lowcoder -XX:+AllowRedefinitionToAddDeleteMethods --add-opens java.base/java.nio=ALL-UNNAMED |
| Main class  | com.lowcoder.api.ServerApplication                                                                                                                                               |

Next, execute the following commands in sequence

```shell
cd server
mvn clean package
```

After Maven package runs successfully, you can start the OpenFlower server with IntelliJ IDEA.

1. Check out the source code and change to source dir.
2. Use the Terminal of your IDE to execute the following commands. First, change to the server directory.

```bash
cd server/api-service
```

3. Now, you can build the sources.

```bash
mvn clean package -DskipTests

// or to include all Tests

mvn clean package
```

4. And run the Server

```bash
java -Dpf4j.mode=development -Dspring.profiles.active=lowcoder -Dpf4j.pluginsDir=lowcoder-plugins -jar lowcoder-server/target/lowcoder-server-1.0-SNAPSHOT.jar
```

{% hint style="info" %}
The main class is: com.lowcoder.api.ServerApplication
{% endhint %}

Now, you can check the status of the service by visiting <http://localhost:8080> through your browser. By default, you should see an HTTP 404 error. (which, we know, is not the best welcome message ever).

{% hint style="warning" %}
If you run the Api-Service locally on Port 8080, remember the URL for the Frontend App would change to: LOWCODER\_API\_SERVICE\_URL=<http://localhost:8080> yarn start
{% endhint %}

### Node Service

Please read more information in the following guides:

1. [How to develop a DataSouce Plugin](/openflower-extension/opensource-contribution/develop-data-source-plugins)
2. \[Data Source Plugin Skeleton] - todo fix this.()

#### Preparation

To develop and test Datasource Plugins locally in the Node-Service, you should have Node.js installed in Version from v14.18.0 or from v16.0.0.

#### Start of Development

1. Check out the source code and change to source dir.
2. Use the Terminal of your IDE to execute the following commands. First, change to the server directory.

```bash
cd server/node-service
```

3. Install dependencies

```bash
yarn install
```

4. Now you can start the local development Server. We use Nodemon.

```bash
yarn dev
```

#### Bundle it for Production

```bash
yarn build 
yarn start
```

#### Plugin Skeleton Helper for OpenAPI Services

We have a helper script that enables you, based on an OpenAPI Specification, to bootstrap the development of a Datasource Plugin.

If the data source you're going to develop as a plugin provides an [Open API Spec](https://en.wikipedia.org/wiki/OpenAPI_Specification) definition file, then its plugin code can be quickly generated. Below is an example of generating Jira plugin code.

```
yarn genOpenApiPlugin --name Jira --url https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json
```

Sometimes, due to network issues, the spec file cannot be downloaded correctly. In this case, it can be manually downloaded to the file `src/plugins/<plugin id>/<plugin id>.spec.yaml(json)`.




---

[Next Page](/llms-full.txt/1)

