This commit is contained in:
2025-11-17 18:45:35 +01:00
parent 0f58e3bdff
commit 14d6f9aa73
7607 changed files with 1969407 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
# Write JavaScript

View File

@@ -0,0 +1,213 @@
# 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 Lowcoder 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 Lowcoder app in a new tab.
utils.openApp("632bddc33bb9722fb888f6c0", { newTab: true })
// Example: Open an Lowcoder 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
```

View File

@@ -0,0 +1,21 @@
# 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.

View File

@@ -0,0 +1,202 @@
# 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.
**Demo 2**
You can find another demo for using passed-in paramter queries [here](https://cloud.lowcoder.dev/apps/637f38daa899fe1ffcb17f0b/view).
## 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
Lowcoder 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 Lowcoder. Please report to our [GitHub](https://github.com/lowcoder-org/lowcoder/issues) or [Discord](https://discord.gg/3JKuvhWzTx) if you encounter any issues.

View File

@@ -0,0 +1,100 @@
# Temporary state
Temporary states in Lowcoder 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 Lowcoder, 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 Lowcoder:
1. **Definition and Initialization**: Temporary states are defined within the Lowcoder 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 Lowcoder 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, Lowcoder 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 Lowcoder (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.

View File

@@ -0,0 +1,65 @@
# 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.