Updates
This commit is contained in:
181
lowcoder/docs/lowcoder-extension/custom-component.md
Normal file
181
lowcoder/docs/lowcoder-extension/custom-component.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Custom component
|
||||
|
||||
In Lowcoder, you can design custom components using React.js library to satisfy specific needs when building your app. The custom component can be static or dynamic, but either requires coding.
|
||||
|
||||
{% hint style="info" %}
|
||||
If you consider the custom component you are crafting suits general use cases, contact us and we are happy to do coding.
|
||||
{% endhint %}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* Good understanding of how to build an app in Lowcoder.
|
||||
* Familiar with HTML/CSS/JS and the React.js library.
|
||||
|
||||
## Basics
|
||||
|
||||
Drag a **Custom component** onto the canvas. By default, Lowcoder adds a title box, a text box, and two buttons into it, as shown below. You can modify **Data** and **Code** in the **Properties** pane to tailor it according to your requirements.
|
||||
|
||||
{% hint style="info" %}
|
||||
Click the border instead of the inside area to select a **Custom component** and display its property settings.
|
||||
{% endhint %}
|
||||
|
||||
### Data
|
||||
|
||||
**Data** stores information in key-value pairs, providing an interface for the **Custom component** to interact with data outside it. For instance, you can reference data of the **Custom component** in other components in your app via `customComponentName.model`, or pass data from other components to the **Custom component**.
|
||||
|
||||
### Code
|
||||
|
||||
By default, Lowcoder defines the object `model`, and two functions `runQuery` and `updateModel`.
|
||||
|
||||
* `runQuery` is a function that accepts a query name in string format. For example, `runQuery(model.query)`.
|
||||
* `updateModel` is a function that accepts a single argument of object type. The argument passed to `updateModel` will be merged with data of the **Custom component**.
|
||||
|
||||
## Implementation
|
||||
|
||||
All code of your **Custom component**, including HTML, CSS, and JavaScript, stores in the **Code** box in the **Properties** pane. When your app runs, the custom component will be embedded into an [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) element.To facilitate the interaction between the **Custom component** and other components in your app, Lowcoder offers an API for you through global objects. The type definition and description of the objects are as follows.
|
||||
|
||||
```javascript
|
||||
interface Lowcoder {
|
||||
// Subscribe to data change
|
||||
// When data changes, handler will be triggered
|
||||
// The returned value is the unsubscribe function
|
||||
subscribe(handler: SubscribeHandler): () => void;
|
||||
// React HOC component function that accepts a React component
|
||||
// Return a new component that contains properties: runQuery, model, updateModel
|
||||
connect(Component: ComponentType<any>): ComponentType;
|
||||
// Run the specified query
|
||||
runQuery(queryName: string): Promise<void>;
|
||||
// Update data
|
||||
updateModel(patch: any): Promise<any>;
|
||||
}
|
||||
|
||||
interface SubscribeHandler {
|
||||
(data: IDataPayload): void;
|
||||
}
|
||||
|
||||
interface IDataPayload {
|
||||
model: any;
|
||||
}
|
||||
```
|
||||
|
||||
The following example is the least code that a custom component requires to work.
|
||||
|
||||
```javascript
|
||||
<div id="react"></div>
|
||||
<script type="text/babel">
|
||||
const MyCustomComponent = ({ runQuery, model, updateModel }) => (
|
||||
<p>Hello, world!</p>
|
||||
);
|
||||
const ConnectedComponent = Lowcoder.connect(MyCustomComponent);
|
||||
ReactDOM.render(<ConnectedComponent />,
|
||||
document.getElementById("react"));
|
||||
</script>
|
||||
```
|
||||
|
||||
## Data interaction
|
||||
|
||||
### Pass data from app to custom component
|
||||
|
||||
For instance, to pass the text in an input box to a custom component, you can use the `{{}}` syntax to reference data from this **Text** component. Note that you can also reference data from queries in the same way.
|
||||
|
||||
Below is the code for this example.
|
||||
|
||||
```javascript
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="text/babel">
|
||||
|
||||
const { Button, Card, Space } = antd;
|
||||
|
||||
const MyCustomComponent = ({ runQuery, model, updateModel}) => (
|
||||
<Card title={"Hello, " + model.name}>
|
||||
<p>{model.text}</p>
|
||||
<Space>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => runQuery(model.query)}
|
||||
>
|
||||
Trigger query
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => updateModel({ text: "I'm also in a good mood!" })}
|
||||
>
|
||||
Update data
|
||||
</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const ConnectedComponent = Lowcoder.connect(MyCustomComponent);
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(<ConnectedComponent />);
|
||||
|
||||
</script>
|
||||
```
|
||||
|
||||
### Pass data from custom component to app
|
||||
|
||||
For instance, to display certain text from the **Custom component** in an **Input** component in the app, you can set the value of `custom1.model.name` as the default value of `input1`. The dot notation `custom1.model.name` accesses the name of the **Custom component**.
|
||||
|
||||
### Trigger query from custom component
|
||||
|
||||
For instance, given table `users` which displays information of all users, you want to filter data based on the inputted text in a **Custom component**. Besides, the filter operation is triggered by clicking a button inside the same **Custom component**.
|
||||
|
||||
According to the requirement, the **Custom component** contains an **Input** component and a **Button** component. You can also add a **Text** component to provide context to the users of your app. When a user inputs into the text box, for example "gov", and then clicks the search button, the table only presents the entries in which the "email" field contains "gov".
|
||||
|
||||
To implement such a **Custom component**, first you create query `filterUser` to access data from the custom component and set it to run by manual invoke.
|
||||
|
||||
```SQL
|
||||
select * from users where email like '%{{custom1.model.search}}%';
|
||||
```
|
||||
|
||||
Then, you import the "antd" library and use the components **Button**, **Input**, **Card**, and **Space**. Finally, one more setting for each component inside the **Custom component**:
|
||||
|
||||
* Configure the `updateModel` method to run and update the data of the **Custom component** when the text in the **Input** component changes.
|
||||
* Trigger the query `filterUser` by the `runQuery` method when the **Search** button is clicked.
|
||||
|
||||
```javascript
|
||||
<style type="text/css">
|
||||
body {
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/antd@4.21.4/dist/antd.min.css"/>
|
||||
|
||||
<script type="text/javascript" src="https://unpkg.com/antd@4.21.4/dist/antd.min.js" ></script>
|
||||
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="text/babel">
|
||||
|
||||
const { Button, Card, Input, Space } = antd;
|
||||
|
||||
const MyCustomComponent = ({ runQuery, model, updateModel}) => (
|
||||
<Card title={"Hello, " + model.name + " filters data for you!"}>
|
||||
|
||||
<Space>
|
||||
<Input
|
||||
value={model.search}
|
||||
onChange={e => updateModel({ search: e.target.value})}
|
||||
placeholder="Input a name"
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => runQuery(filterUser)}
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
|
||||
</Space>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const ConnectedComponent = Lowcoder.connect(MyCustomComponent);
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||
root.render(<ConnectedComponent />);
|
||||
|
||||
</script>
|
||||
```
|
||||
176
lowcoder/docs/lowcoder-extension/lowcoder-open-rest-api.md
Normal file
176
lowcoder/docs/lowcoder-extension/lowcoder-open-rest-api.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Lowcoder Open REST API
|
||||
|
||||
Lowcoder comes with a feature-rich REST API, so you can use it in Lowcoder Apps or extend Lowcoder with new functionality.
|
||||
|
||||
On [api-service.lowcoder.cloud](https://api-service.lowcoder.cloud/api/docs/webjars/swagger-ui/index.html#/) you can access this API as well, just some endpoints are not available.
|
||||
|
||||
## Authentication
|
||||
|
||||
### Session Cookie
|
||||
|
||||
In application properties of the API-Service - or as ENV Variable in Docker setups, you can set a name for the Cookie. In our Examples`LOWCODER_CE_SELFHOST_TOKEN`
|
||||
|
||||
With this value, you can then authenticate API Calls.
|
||||
|
||||
If no user is logged In, API Calls will get executed in the name of "Anonymous User" and for most of the API Calls, this user has no desired rights.
|
||||
|
||||
If you are logged in, the Cookie of the currently logged-in user will be used to make API Calls in the name of the current user. This means, that Access Rights to different Functions are automatically applied by the Role of the User. (Admin, Member, Visitor)
|
||||
|
||||
If you want to use the API from outside of Lowcoder, you need to authenticate first and use the Cookie as the `LOWCODER_CE_SELFHOST_TOKEN` API key in every API Call.
|
||||
|
||||
```bash
|
||||
// Login a User on Lowcoder by eMail
|
||||
|
||||
curl --location '//api/auth/form/login' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--header 'Accept: */*' \
|
||||
--data '{
|
||||
"loginId": "<your_user>",
|
||||
"password": "<your_password>",
|
||||
"register": "false",
|
||||
"source": "EMAIL",
|
||||
"authId": "EMAIL"
|
||||
}'
|
||||
```
|
||||
|
||||
When successfully logged in, you will get the following Response:
|
||||
|
||||
```json
|
||||
// Login Method Response
|
||||
|
||||
{
|
||||
"code": 1,
|
||||
"message": "",
|
||||
"data": true,
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
In particular, you will get back the Cookie to authorize the next API Calls.
|
||||
|
||||
```
|
||||
// Cookie in Response
|
||||
LOWCODER_CE_SELFHOST_TOKEN=<generatedCookieValue>; Path=/; Max-Age=2592000; Expires=Tue, 25 Jul 2023 13:51:31 GMT; HttpOnly; SameSite=Lax
|
||||
```
|
||||
|
||||
For all the next API Calls you need to set the Cookie
|
||||
|
||||
```
|
||||
// API Requests authorized
|
||||
curl --location 'http://localhost:3000//api/users/currentUser' \
|
||||
--header 'Accept: */*' \
|
||||
--header 'Cookie: LOWCODER_CE_SELFHOST_TOKEN=<generatedCookieValue>'
|
||||
```
|
||||
|
||||
### API Key
|
||||
|
||||
Since Lowcoder v2.1.3 you can create and use alternatively also a JWT-based API Key.
|
||||
|
||||
As a logged-in user, you can use the API based on the Cookie to generate an API Key.
|
||||
|
||||
```bash
|
||||
// use the Lowcoder API to generate the JWT based API Key
|
||||
curl --location '<your lowcoder location>/api/auth/api-key' \
|
||||
--header 'cookie: LOWCODER_CE_SELFHOST_TOKEN=<generatedCookieValue>;' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{
|
||||
"name":"<your api key name>",
|
||||
"description": "A wonderful API Key"
|
||||
}'
|
||||
```
|
||||
|
||||
In return, you will get a JSON response containing the API key
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"message": "",
|
||||
"data": {
|
||||
"id": "<the generated API Key Id",
|
||||
"token": "ey...<the JSON Web Token>"
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
For all further API Calls, you can then use the API Key, which impersonates the logged-in user, that created the API Key.
|
||||
|
||||
{% hint style="warning" %}
|
||||
As the API Key impersonates the user, who created the API Key (based on the Cookie), all rights of that impersonated User are also active via API Key. 
|
||||
{% endhint %}
|
||||
|
||||
## OpenAPI Specification & Postman Collection
|
||||
|
||||
You can find more information of the specification & documentation here:\
|
||||
[https://docs.lowcoder.cloud/lowcoder-api-specification/api-reference-lowcoder](https://docs.lowcoder.cloud/lowcoder-api-specification/api-reference-lowcoder)\
|
||||
|
||||
|
||||
The Base URL of the Lowcoder API depends on your installation.
|
||||
|
||||
### Single Docker Deployment
|
||||
|
||||
The Base URL of the API is the same as for the Frontend. In local installations for example:
|
||||
|
||||
```
|
||||
http://localhost:3000/
|
||||
https://<yourdomain>:3000/
|
||||
https://<yourdomain>/
|
||||
```
|
||||
|
||||
### Multi-Docker Deployment
|
||||
|
||||
In a Multi-Docker Deployment, you will have an individual IP address or Domain for the API-Service Container. This is then the Base URL for the Lowcoder API.
|
||||
|
||||
```
|
||||
https://<your-api-service-domain>:8080/
|
||||
https://<your-api-service-domain>/
|
||||
```
|
||||
|
||||
When you run Multi-Docker Deployment on Localhost, you will need to look for the [Bridge-Network Settings](https://www.baeldung.com/ops/docker-communicating-with-containers-on-same-machine) that apply to your setup.
|
||||
|
||||
### app.lowcoder.cloud
|
||||
|
||||
To use the API of the Cloud Version, the API is to reach via the separate API Service.
|
||||
|
||||
```
|
||||
https://api-service.lowcoder.cloud/
|
||||
```
|
||||
|
||||
Since Lowcoder v2.1.6 we publish the OpenAPI Specification and the Swagger Documentation automatically.
|
||||
|
||||
```
|
||||
Swagger Documentation: <Lowcoder-Location>/api/docs/webjars/swagger-ui/index.html#
|
||||
OpenAPI Specification: <Lowcoder-Location>/api/docs/api-docs
|
||||
```
|
||||
|
||||
You can find the current API Documentation for example here: \
|
||||
[https://api-service.lowcoder.cloud/api/docs/webjars/swagger-ui/index.html#/](https://api-service.lowcoder.cloud/api/docs/webjars/swagger-ui/index.html#/)
|
||||
|
||||
## Using Lowcoder API - inside Lowcoder Apps
|
||||
|
||||
Since Lowcoder v2.0.0, it is possible to use the Lowcoder REST API inside of Apps in Lowcoder itself. To do so, create an OpenAPI specification-based Data Source.
|
||||
|
||||
<figure><img src="../.gitbook/assets/Lowcoder API Create Datasource.png" alt=""><figcaption><p>Connect the Lowcoder API as OpenAPI Datasource</p></figcaption></figure>
|
||||
|
||||
Use your defined `LOWCODER_CE_SELFHOST_TOKEN` as API Key Auth. It will be automatically replaced by the adapted Cookie if a User is logged in.
|
||||
|
||||
Also, you can use the API Key to interact with the Lowcoder API as an impersonated user.
|
||||
|
||||
The OpenAPI specification Document is automatically generated. The Server URL is your API-Service URL. [Please read more about it here](https://docs.lowcoder.cloud/lowcoder-api-specification/api-reference)
|
||||
|
||||
```
|
||||
http://localhost:3000/api/docs/api-docs
|
||||
https://<yourdomain>/api/docs/api-docs
|
||||
```
|
||||
|
||||
As soon as connected and the OpenAPI specification is found and processed, the API Controllers are accessible in the Datasource.
|
||||
|
||||
<figure><img src="../.gitbook/assets/Lowcoder API Chose Controller.png" alt=""><figcaption><p>Select a Controller to see it's Operations</p></figcaption></figure>
|
||||
|
||||
For each Controller, you can see then the possible Operations.
|
||||
|
||||
<figure><img src="../.gitbook/assets/Lowcoder API Choose Operation.png" alt=""><figcaption><p>Find the list of possible Operations for the selected Controller</p></figcaption></figure>
|
||||
|
||||
Now you can execute the API Call based on its settings.
|
||||
|
||||
<figure><img src="../.gitbook/assets/Lowcoder API Get User Profile.png" alt=""><figcaption></figcaption></figure>
|
||||
@@ -0,0 +1,379 @@
|
||||
# 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
|
||||
|
||||
Lowcoder has 3 main services, which are developed by the Community and us - the Lowcoder Team.
|
||||
|
||||
* [Frontend App](https://github.com/lowcoder-org/lowcoder/tree/main/client) - JavaScript, TypeScript, React, ANTd
|
||||
* [API-Service](https://github.com/lowcoder-org/lowcoder/tree/main/server/api-service) - Java, Spring, Spring WebFlux - using MongoDB and Redis
|
||||
* [Node-Service](https://github.com/lowcoder-org/lowcoder/tree/main/server/node-service) - Node.js, TypeScript
|
||||
|
||||
These 3 services are the main deliverables and the codebase of Lowcoder. 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 Lowcoder for and with the Community happens mainly by the Plugins and Extensions at defined Interfaces. Lowcoder has the following Plugin Systems:
|
||||
|
||||
* [Visual Component Plugins](https://github.com/lowcoder-org/lowcoder-create-component-plugin)
|
||||
* Plugin API of API Service
|
||||
* [Data-Source Plugins in the Node-Service](https://github.com/lowcoder-org/lowcoder/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/lowcoder-org/lowcoder-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 lowcoder-org/lowcoder-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](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>/lowcoder-create-component-plugin.git
|
||||
|
||||
or
|
||||
|
||||
git@github.com:<your org>/lowcoder-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 Lowcoder 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 Lowcoder 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 Lowcoder 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 Lowcoder 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 Lowcoder 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 Lowcoder 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
|
||||
```
|
||||
|
||||
## Lowcoder Marketplace
|
||||
|
||||
Next to this direct code development contribution, we also encourage you to contribute smart solutions and reusable Apps and Modules on the [Lowcoder Marketplace](https://app.lowcoder.cloud/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.md)
|
||||
|
||||
## Code Contribution to Core System
|
||||
|
||||
We feel honored to work with you together on Lowcoder as a Platform! A good start and procedure that allows a smooth development process is like this:
|
||||
|
||||
1. [Fork the Repository](https://github.com/lowcoder-org/lowcoder/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/lowcoder-org/lowcoder/tree/dev) of the Lowcoder 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. We kindly ask you to use our [Discord Server](https://discord.gg/An6PgWpegg) for these questions. Here, especially the [#contribute channel](https://discord.gg/An6PgWpegg).
|
||||
|
||||
### 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 lowcoder -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks" lowcoderorg/lowcoder-ce
|
||||
```
|
||||
|
||||
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 lowcoder-dev .
|
||||
```
|
||||
|
||||
3. Start the fresh built Docker image
|
||||
|
||||
```bash
|
||||
docker run -d --name lowcoder-dev -p 3000:3000 -v "$PWD/stacks:/lowcoder-stacks" lowcoder-dev
|
||||
```
|
||||
|
||||
#### Start developing
|
||||
|
||||
As soon as the development server is ready you can access Lowcoder by [http://localhost:3000](http://localhost:3000). Now, you can start to develop locally.
|
||||
|
||||
1. Check out the source code. 
|
||||
|
||||
```bash
|
||||
git@github.com:your-org/lowcoder.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](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": "Lowcoder 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 Lowcoder 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](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](develop-data-source-plugins.md)
|
||||
2. [Data Source Plugin Skeleton](https://github.com/lowcoder-org/lowcoder-datasource-plugin-skeleton)
|
||||
|
||||
#### 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)`.
|
||||
@@ -0,0 +1,295 @@
|
||||
# Develop Data Source Plugins
|
||||
|
||||
This document provides basic information and guides for developing data source plugins. Developers are highly welcomed to make contributions to [Lowcoder](https://github.com/lowcoder-org/lowcoder)--the open source project.
|
||||
|
||||
## Basics
|
||||
|
||||
A data source plugin is described by a **JavaScript Object** which mainly consists of following parts:
|
||||
|
||||
* Definition of the basic information of the plugin such as **name**, **icon**, **description**, etc.
|
||||
* Definition of the **configuration form** of the data source.
|
||||
* Definition of the **validation logic** for configuration.
|
||||
* Definition of the **Action list** for data source queries and the configuration form for each Action.
|
||||
* Definition of the **execution logic** for Actions.
|
||||
|
||||
Currently, all data source plugins are maintained in the `src/plugins` directory of the `node-service` project. Click to view [the project](https://github.com/lowcoder-org/lowcoder/tree/main/server/node-service), and you might take a quick look at the [S3 plugin](https://github.com/lowcoder-org/lowcoder/tree/main/server/node-service/src/plugins/s3).
|
||||
|
||||
## Overall definition of a plugin
|
||||
|
||||
The type `DataSourcePlugin` is defined as follows:
|
||||
|
||||
```typescript
|
||||
interface DataSourcePlugin {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
category: string;
|
||||
dataSourceConfig: DataSourceConfig;
|
||||
queryConfig: QueryConfig | () => Promise<QueryConfig>;
|
||||
|
||||
validateDataSourceConfig?: (
|
||||
dataSourceConfig: DataSourceConfigType,
|
||||
context: PluginContext
|
||||
) => Promise<ValidateDataSourceConfigResult>;
|
||||
|
||||
run: (
|
||||
actionData: ActionDataType,
|
||||
dataSourceConfig: DataSourceConfigType,
|
||||
context: PluginContext
|
||||
) => Promise<any>;
|
||||
}
|
||||
```
|
||||
|
||||
* `id`: the unique identifier of the data source plugin, globally unique.
|
||||
* `name`: the display name of the data source plugin.
|
||||
* `icon`: the file name of the icon that represents the data source plugin. It must be stored in the `src/static/plugin-icons` directory.
|
||||
* `category`: the category of the data source. Currently there are two categories: `database` and `api`.
|
||||
* `dataSourceConfig`: the configuration form of the data source, see Data source configuration form.
|
||||
* `queryConfig`: the query configuration, see Data source queries.
|
||||
* `validateDataSourceConfig` defines the validation logic for the data source configuration. See Validate data source configuration.
|
||||
* `run` defines the execution logic for data source queries.
|
||||
|
||||
## Data source configuration form
|
||||
|
||||
The configurations of a data source will be securely saved on the server-side. A configuration form can include the **connection information** of the data source as well as other **common configurations**.
|
||||
|
||||
The type `DataSourceConfig` is defined as follows:
|
||||
|
||||
```typescript
|
||||
interface DataSourceConfig {
|
||||
type: "dataSource";
|
||||
params: readonly DataSourceParamConfig[];
|
||||
extra?: (data: DSC) => Promise<DataSourceExtraConfig>;
|
||||
}
|
||||
```
|
||||
|
||||
1. `type`: a fixed String "dataSource".
|
||||
2. `params`: various fields of a data source configuration form.\\
|
||||
|
||||
The type `CommonParamConfig` is defined as follows:
|
||||
|
||||
```typescript
|
||||
interface CommonParamConfig {
|
||||
type: string;
|
||||
defaultValue: V;
|
||||
options?: ParamOption[];
|
||||
rules?: ParamRule[];
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
```
|
||||
|
||||
* `type`: the type of the configuration field which determines the form control used for inputting the field and its data type. See Supported field types.
|
||||
* `options`: For the "select" type, it defines the option list. The type `ParamOption` is defined as follows:
|
||||
|
||||
```typescript
|
||||
interface ParamOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
```
|
||||
* `label`: the label of the field.
|
||||
* `tooltip`: the hint text of the field, with Markdown syntax supported.
|
||||
* `placeholder`: the placeholder text of the form component of the field.
|
||||
* `defaultValue`: the default value.
|
||||
* `rules`: the validation rules of the field.\\
|
||||
3. `extra`: additional data source data or parameter configurations. By using this parameter, more data source configuration fields and more data source data can be dynamically obtained based on the data source configurations that users fill out.\\
|
||||
|
||||
The result of executing `extra` functions is defined as follows:
|
||||
|
||||
```typescript
|
||||
interface DataSourceExtraConfig {
|
||||
data?: any;
|
||||
extraParams?: DataSourceParamConfig[];
|
||||
}
|
||||
```
|
||||
|
||||
* `data`: additional data source data that will be saved in the backend together with data source configurations that users fill out. Can be obtained when executing a query using the `extra` field of the data source data.
|
||||
* `extraParams`: additional configuration fields. For example, after a user inputing an OpenAPI spec URL, authentication-related configuration fields can be dynamically obtained for the user to fill in.
|
||||
|
||||
### Supported field types
|
||||
|
||||
| Field type | Form component | Data type | Description |
|
||||
| ----------- | -------------------------- | --------- | --------------------------------------------------------- |
|
||||
| textInput | single-line text input box | String | <p><br></p> |
|
||||
| password | password input box | String | Encrypted and stored on the server-side. |
|
||||
| numberInput | numeric input box | Number | <p><br></p> |
|
||||
| select | drop-down selection box | String | <p><br></p> |
|
||||
| checkbox | checkbox | Boolean | <p><br></p> |
|
||||
| groupTitle | group title | -- | Non-data fields. Displayed as the title of a field group. |
|
||||
|
||||
### Validate data source configuration
|
||||
|
||||
When the `validateDataSourceConfig` field is defined in the data source, a "**Test Connection**" button will appear at the bottom of the data source creation interface. When the user clicks it, this method is called to test whether the data source configuration filled by a user is correct or not.
|
||||
|
||||
The function defined in `validateDataSourceConfig` needs to return a **Promise** with a resolve value of a structured object as follows:
|
||||
|
||||
```typescript
|
||||
interface ValidateDataSourceConfigResult {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
```
|
||||
|
||||
* When `success` is "True", it indicates that the validation has passed. When `success` is "False" or an Exception is thrown, it indicates that the validation has failed.
|
||||
* `message`: the error message shown when the validation fails.
|
||||
|
||||
## Data source queries
|
||||
|
||||
The type `QueryConfig` is defined as follows:
|
||||
|
||||
```typescript
|
||||
interface QueryConfig {
|
||||
type: "query";
|
||||
categories?: {
|
||||
label?: string;
|
||||
items?: ActionCategory[];
|
||||
};
|
||||
actions: {
|
||||
actionName: string;
|
||||
label: string;
|
||||
params: ActionParamConfig[];
|
||||
}[];
|
||||
}
|
||||
```
|
||||
|
||||
A data source query consists of multiple **Actions**. In actual use, after a user creates a query for the data source, they need to select a specific action to execute.
|
||||
|
||||
The `actions` field defines the list of query actions supported by the current data source. Each field is described as follows:
|
||||
|
||||
* `actionName`: the identifier of the action. Must be **unique** within the current data source definition scope.
|
||||
* `label`: the name of the action displayed in Lowcoder interface.
|
||||
* `params`: The field list of the action configuration form, and the type `CommonParamConfig` is defined as follows:
|
||||
|
||||
```typescript
|
||||
interface CommonParamConfig {
|
||||
type: string;
|
||||
options?: ParamOption[];
|
||||
defaultValue?: V;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
```
|
||||
|
||||
The utility of each field is the same as the data source parameters. About all supported field types of the configuration form of a query action, see Supported field types below.
|
||||
|
||||
### Supported field types
|
||||
|
||||
| Field type | Form component | Data type | Description |
|
||||
| ------------ | -------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| textInput | Single-line code input box | String | <p><br></p> |
|
||||
| numberInput | Numeric code input box | Number | <p><br></p> |
|
||||
| booleanInput | Boolean code input box | Boolean | <p><br></p> |
|
||||
| select | Drop-down selection box | String | Code input is not supported. |
|
||||
| switch | Switch | Boolean | <p><br></p> |
|
||||
| file | File | <p>String, or</p><p>{</p><p>data: string;</p><p>name?: string;</p><p>type?: string;</p><p>}</p> | <p>When it is a <strong>String</strong>, it represents the content of the file. When it is an <strong>Object</strong>, each field has the following meaning:</p><ul><li>data: File content</li><li>name: File name</li><li>type: MIME type of the file, such as image/png</li></ul> |
|
||||
| jsonInput | JSON data input box | JSONValue | <p><br></p> |
|
||||
| sqlInput | SQL statement input box | String | <p><br></p> |
|
||||
|
||||
### Execute query action
|
||||
|
||||
The necessary `run` method of a data source query defines the execution logic of a query action. The type of `run` method is defined as follows:
|
||||
|
||||
```typescript
|
||||
(
|
||||
actionData: ActionDataType,
|
||||
dataSourceConfig: DataSourceConfigType,
|
||||
context: PluginContext
|
||||
) => Promise<any>;
|
||||
```
|
||||
|
||||
The meaning of each parameter is as follows:
|
||||
|
||||
* `actionData`: The values of each configuration field, set by the user when executing current action. The type definition is as follows:
|
||||
|
||||
```typescript
|
||||
interface ActionDataType {
|
||||
actionName: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
```
|
||||
|
||||
1. `actionName`: identifier of the current action being executed
|
||||
2. `[key: string]`: other action configuration parameters
|
||||
|
||||
The `run` method should return a **Promise**, with the resolved result being the current query execution result.
|
||||
|
||||
## I18n support
|
||||
|
||||
When a plugin needs internationalization (i18n) support, that is, to support multiple languages, the plugin definition object should be generated as a function, as defined below:
|
||||
|
||||
```typescript
|
||||
interface PluginContext {
|
||||
languages: string[];
|
||||
}
|
||||
|
||||
type DataSourcePluginFactory = (context: PluginContext) => DataSourcePlugin;
|
||||
```
|
||||
|
||||
The `languages` field in the `context` parameter can be used to obtain information about the language of the current request, and can be directly used to initialize an `I18n` Object:
|
||||
|
||||
```typescript
|
||||
const i18n = new I18n({ zh, en }, languages);
|
||||
|
||||
// It can then be used directly where multilingual support is needed, such as:
|
||||
|
||||
{
|
||||
name: i18n.trans('name')
|
||||
}
|
||||
```
|
||||
|
||||
## Auto-generate plugin code based on Open API
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
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)`.
|
||||
|
||||
### Optimize plugin code
|
||||
|
||||
Due to various reasons, the generated plugin code needs to be correctly validated, mainly in the following aspects:
|
||||
|
||||
1. Check whether the data source has reasonable API authentication configuration.
|
||||
* If not, it is often because it is not defined in the current spec, and the spec file needs to be manually rewritten and then regenerated.
|
||||
2. Check whether the current data source's API URL is correct.
|
||||
* If incorrect, it can be handled in two ways:
|
||||
* If the API URL of current data source is not fixed, such as for data sources that support self-hosting, a field similar to `API URL` needs to be added to the data source configuration. The specific field label depends on the different data sources.
|
||||
* If the API URL of the current data source is fixed, the value of the `Server URL` field can be hardcoded in the generated data source plugin code.
|
||||
3. Check whether the generated **Category** and **Actions** are reasonably displayed.
|
||||
* If unreasonable, adjust the `parseOptions` configuration in the generated code.
|
||||
|
||||
## Testing
|
||||
|
||||
Necessary testing should be done before publishing the plugin. Testing a data source plugin requires a backend environment. You can start a local environment by following the documentation [Start a local backend server](https://github.com/lowcoder-org/lowcoder/tree/main/server/api-service#readme) and test the data source plugin in following aspects:
|
||||
|
||||
1. Make sure the data source plugin has been added to the plugin list in the file `src/plugins/index.ts`.
|
||||
2. Start the node-service server in the `node-service` directory by executing `yarn dev`.
|
||||
3. Execute the following command to enter the image command-line tool and modify the backend configuration.
|
||||
|
||||
{% code overflow="wrap" %}
|
||||
```bash
|
||||
# Enter the image command-line tool
|
||||
docker exec -it Lowcoder bash
|
||||
|
||||
# Enter the mongo command-line tool
|
||||
mongo
|
||||
use Lowcoder;
|
||||
|
||||
# Insert a service configuration
|
||||
db.serverConfig.insert({key: "deployment.js-executor.host", value: "http://<IP-ADDR>:6060/"})
|
||||
```
|
||||
{% endcode %}
|
||||
|
||||
You can then use the data source plugin just developed in this environment.
|
||||
|
||||
## What's next
|
||||
|
||||
Congrats! After testing the data source plugin, you can submit a [Pull Request](https://github.com/lowcoder-org/lowcoder/pulls) now.
|
||||
@@ -0,0 +1,95 @@
|
||||
# Develop UI components for Apps
|
||||
|
||||
With Lowcoder plugins, you can develop customized components that are consistent with native components for your specific scenarios.
|
||||
|
||||
## Initialization
|
||||
|
||||
Execute the following `yarn start` file:
|
||||
|
||||
```bash
|
||||
# Project initiation
|
||||
yarn create lowcoder-plugin my-plugin
|
||||
|
||||
# Go to the project root
|
||||
cd my-plugin
|
||||
|
||||
# Start the development environment
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Component development environment
|
||||
|
||||
After executing `yarn start`, the browser is automatically opened and you enter the component development environment.
|
||||
|
||||
## Plugin configurations
|
||||
|
||||
In `Lowcoder` field in `package.json` file, you need to define the component properties. For example, the following is the explanation of several fields:
|
||||
|
||||
* `comps` defines UI components contained in the plugin. For each component, the key name of the object is the unique identity, and the value is metadata.
|
||||
* `comps[someCompKey].name` defines the component name shown in the **Insert** tab.
|
||||
* `comps[someCompKey].icon` defines the component icon shown on the canvas. Use a relative path to where `package.json` file is located.
|
||||
* `comps[someCompKey].layoutInfo` defines the component layout:
|
||||
* w: width of the component. Counted by the number of grid cells (range: 1 - 24).
|
||||
* h: height of the component. Counted by the number of grid cells (range: >= 1).
|
||||
|
||||
```bash
|
||||
"Lowcoder": {
|
||||
"description": "",
|
||||
"comps": {
|
||||
"hello_world": {
|
||||
"name": "Hello World",
|
||||
"icon": "./icons/hello_world.png",
|
||||
"layoutInfo": {
|
||||
"w": 12,
|
||||
"h": 5
|
||||
}
|
||||
},
|
||||
"counter": {
|
||||
"name": "Counter",
|
||||
"icon": "./icons/hello_world.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Export components
|
||||
|
||||
To export all the components, use `src/index.ts`, for example:
|
||||
|
||||
```bash
|
||||
import HelloWorldComp from "./HelloWorldComp";
|
||||
|
||||
export default {
|
||||
hello_world: HelloWorldComp,
|
||||
};
|
||||
```
|
||||
|
||||
The default exported object `key` needs to be consistent with the `key` configured in `comps` in `package.json` file.
|
||||
|
||||
## Publish plugins
|
||||
|
||||
When you finish developing and testing the plugin, you can publish it into the npm registry. Login in to the npm registry locally, and then execute the following command:
|
||||
|
||||
```
|
||||
yarn build --publish
|
||||
```
|
||||
|
||||
If you do not specify the parameter `--publish`, the `tar` file will be saved in the root folder.
|
||||
|
||||
## Import plugins
|
||||
|
||||
In the Lowcoder app, click **Insert** > **Extensions** > **Add npm plugin** in the right pane. 
|
||||
|
||||
Input your npm package's URL or name, and then you can use your customized components.
|
||||
|
||||
```bash
|
||||
my-plugin
|
||||
|
||||
# or
|
||||
|
||||
https://www.npmjs.com/package/my-plugin
|
||||
```
|
||||
|
||||
## Code demo
|
||||
|
||||
For code demo, refer to Lowcoder Github.
|
||||
@@ -0,0 +1,145 @@
|
||||
# Use third-party libraries in Apps
|
||||
|
||||
Every developer learns one of the most important principles of software engineering early in their career: DRY (Don’t Repeat Yourself). Using third-party libraries can save you time as you do not need to develop the functionality that the library provides. Lowcoder provides some built-in third-party libraries for common uses, and you can manually import other libraries on demand.
|
||||
|
||||
## Built-in libraries
|
||||
|
||||
Lowcoder provides some JavaScript built-in libraries for use.
|
||||
|
||||
| Library | What for | Docs | Version |
|
||||
| --------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -------------------------- |
|
||||
| lodash | Powerful **utility** functions for _Arrays, Collections, JS Functions, JS Objects, Strings, Numbers, Math, Sequences_ | [https://lodash.com/docs/](https://lodash.com/docs/) | 4.17.21 |
|
||||
| uuid | Create, parse and validate **UUIDs** of [version 1 to 5](https://www.uuidtools.com/uuid-versions-explained) | [https://github.com/uuidjs/uuid](https://github.com/uuidjs/uuid) | 9.0.0(Support v1/v3/v4/v5) |
|
||||
| numbro | Powerful **Number formatting** helper | [https://numbrojs.com/format.html](https://numbrojs.com/format.html) | 2.3.6 |
|
||||
| papaparse | Parser for CSV to JSON | [https://www.papaparse.com/docs](https://www.papaparse.com/docs) | 5.3.2 |
|
||||
|
||||
Built-in Libraries can be used directly everywhere where you can use JavaScript.
|
||||
|
||||
```
|
||||
// loadash
|
||||
return _.chunk(['a', 'b', 'c', 'd'], 2);
|
||||
// => [['a', 'b'], ['c', 'd']]
|
||||
|
||||
// uuid
|
||||
return uuid.v4();
|
||||
// => 3965f3d4-8dd8-4785-9756-0ee98d91238d
|
||||
|
||||
// numbro
|
||||
return numbro(1000).format({thousandSeparated: true});
|
||||
// => 1,000
|
||||
|
||||
// Papaparse
|
||||
return Papa.parse("name|age\nJohn Doe|30\nJane Doe|25", {delimiter: "|"})
|
||||
// => {"data":[["name","age"],["John Doe","30"],["Jane Doe","25"]],"errors":[],"meta":{"delimiter":"|","linebreak":"\n","aborted":false,"truncated":false,"cursor":32}}
|
||||
```
|
||||
|
||||
## Import third-party libraries
|
||||
|
||||
Lowcoder supports setting up preloaded JavaScript and libraries, which can either be imported for individual apps or the whole workspace.
|
||||
|
||||
{% hint style="danger" %}
|
||||
Only libraries using the [UMD (Universal Module Definition)](https://github.com/umdjs/umd) approach are supported. 
|
||||
{% endhint %}
|
||||
|
||||
{% hint style="success" %}
|
||||
As soon as imported/bound to an App or workspace, Lowcoder manages the pre-loading of these libraries automatically in the editor and app view.
|
||||
{% endhint %}
|
||||
|
||||
* **App-level** libraries get loaded only in the app where they were defined. This means a library imported to app A is not loaded for app B. The reverse is also true. A library imported for app B is not available to app A unless it is explicitly imported to app A as well.
|
||||
* **Workspace-level** libraries will be loaded when you open any application in your workspace. All apps can access (and will load automatically) those libraries.
|
||||
|
||||
{% hint style="warning" %}
|
||||
importing third-party libraries can impact app performance, especially when you have complex JavaScript functions. Decide carefully to import on the App-level or on Workspace-level
|
||||
{% endhint %}
|
||||
|
||||
{% hint style="info" %}
|
||||
**Tips you should know before setting up libraries:**
|
||||
|
||||
* External libraries are loaded and run in the browser.
|
||||
* NodeJS-only libraries are not supported now.
|
||||
* URLs of external libraries need to support cross-domain.
|
||||
* The export of the library must be set directly on the window object, global variables like `var xxx = xxx` do not take effect.
|
||||
* The external libraries run in a restricted sandbox environment and the following global variables are not available:
|
||||
|
||||
<mark style="background-color:yellow;">`parent`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`document`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`location`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`chrome`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`setTimeout`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`fetch`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`setInterval`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`clearInterval`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`setImmediate`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`XMLHttpRequest`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`importScripts`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`Navigator`</mark>
|
||||
|
||||
<mark style="background-color:yellow;">`MutationObserver`</mark>
|
||||
{% endhint %}
|
||||
|
||||
Now let's take **cowsay** as an example and import it at the app level and the workspace level.
|
||||
|
||||
* GitHub page: [https://github.com/piuccio/cowsay](https://github.com/piuccio/cowsay)
|
||||
* Unpkg link: [https://unpkg.com/cowsay@1.5.0/build/cowsay.umd.js](https://unpkg.com/cowsay@1.5.0/build/cowsay.umd.js)
|
||||
* JSDeliver link: [https://cdn.jsdelivr.net/npm/cowsay@1.6.0/build/cowsay.umd.min.js](https://cdn.jsdelivr.net/npm/cowsay@1.6.0/build/cowsay.umd.min.js)
|
||||
|
||||
{% hint style="info" %}
|
||||
You can check popular CDNs if they host your desired library as a minified package.\
|
||||
|
||||
|
||||
[https://jsdelivr.com](https://www.jsdelivr.com/)
|
||||
|
||||
[https://cdnjs.com](https://cdnjs.com/)
|
||||
|
||||
[https://unpkg.com](https://unpkg.com/)
|
||||
{% endhint %}
|
||||
|
||||
### Import / bind at the app level
|
||||
|
||||
Navigate to the settings page and then click the plus sign **+** under the **JavaScript library** tab. Paste the **library** link and click **Add New**. Lowcoder will now check, if the external library will be compatible and securely usable.
|
||||
|
||||
You can also click the download icon to quickly download any recommended JS library.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/App Editor External Libraries.png" alt=""><figcaption><p>Bind an external JS Library to an individual App</p></figcaption></figure>
|
||||
|
||||
Now, you can create a JS query and insert code.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/App Edtor External Library usage.png" alt=""><figcaption><p>You can start using the external Library</p></figcaption></figure>
|
||||
|
||||
```
|
||||
|
||||
return window.cowsay.say({
|
||||
text : "Lowcoder is cool", e : "oO", T : "U "
|
||||
})
|
||||
```
|
||||
|
||||
Note that the cowsay library is imported in our example at app-level and you can not use it in any other app within your workspace.
|
||||
|
||||
You should see something similar to the image below after successfully importing the cowsay library. Note that you see what global variable you can use to access the library.
|
||||
|
||||

|
||||
|
||||
{% hint style="danger" %}
|
||||
Imported external libraries are bound to the window object. \
|
||||
\
|
||||
cowsay.say(...) will not work\
|
||||
\
|
||||
window.cowsay.say(...) - does the job.
|
||||
{% endhint %}
|
||||
|
||||
### Import/bind at the workspace level
|
||||
|
||||
Go to [Lowcoder's homepage](https://www.lowcoder.cloud/), select **Settings** > **Advanced**, and click **Add** under the **JavaScript library** tab. Paste the link of the third-party JS library and click **Add New** to add it to your workspace. You can also click the download icon to add any recommended JS library quickly. The installed libraries are accessible from any app within your workspace.
|
||||
|
||||
<figure><img src="../../.gitbook/assets/Admin external Libraries.png" alt=""><figcaption><p>Bind an external JS Library to all Apps of a Workspace</p></figcaption></figure>
|
||||
@@ -0,0 +1,52 @@
|
||||
# Day.js 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 Lowcoder, 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](https://day.js.org/docs/en/get-set/get)
|
||||
{% endhint %}
|
||||
|
||||
### Day.js Plugins
|
||||
|
||||
To enhance the functionality of Day.js, developers can utilize its plugin system, which allows for the inclusion of additional features not available in the core library. Plugins can be added by including their scripts in the project and then registering them with Day.js using `dayjs.extend()`. For instance, if a developer wants to use the `advancedFormat` plugin, they would include the plugin script and then call `window.dayjs.extend(window.dayjs_plugin_advancedFormat)` to make the advanced formatting options available. 
|
||||
|
||||
```javascript
|
||||
// in your JavaScript for Page or Workspace Level first require the plugin
|
||||
var advancedFormat = require('dayjs/plugin/advancedFormat');
|
||||
|
||||
// then you can extend Day.js by it.
|
||||
window.dayjs.extend(window.dayjs_plugin_advancedFormat);
|
||||
```
|
||||
|
||||
This modular approach allows you to keep your Apps lightweight by only including the needed features. 
|
||||
|
||||
{% hint style="info" %}
|
||||
An overview of Day.js Plugins: [https://day.js.org/docs/en/plugin/plugin](https://day.js.org/docs/en/plugin/plugin)
|
||||
{% endhint %}
|
||||
|
||||
When using plugins, it's important to ensure plugins are loaded and extended after the Day.js library is initialized. We have seen cases when this is not automatically the case and so it can mean making use of an additional **JavaScript Query** to make sure the Plugin is loaded and instantiated.
|
||||
|
||||
{% embed url="https://app.supademo.com/demo/T6c6mf3uyXdWj7_w-J4o7" %}
|
||||
|
||||
{% hint style="info" %}
|
||||
Day.js plugins on CDNjs: [https://cdnjs.com/libraries/dayjs](https://cdnjs.com/libraries/dayjs)
|
||||
{% endhint %}
|
||||
|
||||
Now you can use Day.js Plugins at all places in Lowcoder 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')}}
|
||||
|
||||
```
|
||||
|
||||
### A demo App with Day.js and Plugin usage.
|
||||
|
||||
{% file src="../../.gitbook/assets/DayJs with Plugin.json" %}
|
||||
@@ -0,0 +1,75 @@
|
||||
# Import your own JavaScript Library
|
||||
|
||||
The custom JavaScript library needs to be imported using the UMD approach.
|
||||
|
||||
That means the following approach will fail:
|
||||
|
||||
```javascript
|
||||
function ULID() {
|
||||
...
|
||||
return function() {
|
||||
...
|
||||
};
|
||||
}
|
||||
exports.ULID = ULID
|
||||
```
|
||||
|
||||
UMD uses a single wrapper function to check for the presence of `module.exports` (indicating a CommonJS environment) and `define.amd` (indicating an AMD environment). If neither is found, it assumes it is running in a browser and attaches the module to the global `window` object.
|
||||
|
||||
Here is a simplified example of a UMD module:
|
||||
|
||||
```javascript
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], factory);
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory();
|
||||
} else {
|
||||
// Browser globals (root is window)
|
||||
root.myModule = factory();
|
||||
}
|
||||
}(typeof self !== 'undefined' ? self : this, function () {
|
||||
// Your module code goes here
|
||||
|
||||
var myModule = {};
|
||||
myModule.hello = function () {
|
||||
return 'Hello, world!';
|
||||
};
|
||||
|
||||
return myModule;
|
||||
}));
|
||||
```
|
||||
|
||||
In the example case, the new code structure would be:
|
||||
|
||||
```javascript
|
||||
(function (global, factory) {
|
||||
if (typeof define === "function" && define.amd) {
|
||||
define(["exports"], factory);
|
||||
} else if (typeof exports !== "undefined") {
|
||||
factory(exports);
|
||||
} else {
|
||||
var mod = {
|
||||
exports: {}
|
||||
};
|
||||
factory(mod.exports);
|
||||
global.ULID = mod.exports;
|
||||
}
|
||||
})(this, function (exports) {
|
||||
function ULID() {
|
||||
...
|
||||
}
|
||||
|
||||
function generator(){
|
||||
var ulid = new ULID()
|
||||
return ulid()
|
||||
}
|
||||
exports.generator = generator;
|
||||
});
|
||||
```
|
||||
|
||||
Find the discussion to it in our Github: [https://github.com/lowcoder-org/lowcoder/discussions/524](https://github.com/lowcoder-org/lowcoder/discussions/524)
|
||||
Reference in New Issue
Block a user