You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
10 KiB
274 lines
10 KiB
---
|
|
title: Widget Tutorial
|
|
description: Follow along with this guide to learn how to create a custom widget for Homepage. We'll cover the basic structure of a widget, how to use translations, and how to fetch data from an API.
|
|
---
|
|
|
|
In this guide, we'll walk through the process of creating a custom widget for Homepage. We'll cover the basic structure of a widget, how to use translations, and how to fetch data from an API. By the end of this guide, you'll have a solid understanding of how to build your own custom widget.
|
|
|
|
**Prerequisites:**
|
|
|
|
- Basic knowledge of React and JavaScript
|
|
- Familiarity with the Homepage platform
|
|
- Understanding of JSON and API interactions
|
|
|
|
Throughout this guide, we'll use `yourwidget` as a placeholder for the unique name of your custom widget. Replace `yourwidget` with the actual name of your widget. It should contain only lowercase letters and no spaces.
|
|
|
|
This guide makes use of a fake API, which would return a JSON response as such, when called with the `v1/info` endpoint:
|
|
|
|
```json
|
|
{ "key1": 123, "key2": 456, "key3": 789 }
|
|
```
|
|
|
|
## Set up the widget definition
|
|
|
|
Create a new folder for your widget in the `src/widgets` directory. Name the folder `yourwidget`.
|
|
|
|
Inside the `yourwidget` folder, create a new file named `widget.js`. This file will contain the metadata for your widget.
|
|
|
|
Open the `widget.js` file and add the following code:
|
|
|
|
```js title="src/widgets/yourwidget/widget.js"
|
|
import genericProxyHandler from "utils/proxy/handlers/generic"; // (1)!
|
|
|
|
const widget = /* (2)! */ {
|
|
api: "{url}/{endpoint}" /* (3)! */,
|
|
proxyHandler: genericProxyHandler /* (1)! */,
|
|
|
|
mappings: /* (4)! */ {
|
|
info: /* (5)! */ {
|
|
endpoint: "v1/info" /* (6)! */,
|
|
},
|
|
},
|
|
};
|
|
|
|
export default widget;
|
|
```
|
|
|
|
1. We import the `genericProxyHandler` from the `utils/proxy/handlers/generic` module. The `genericProxyHandler` is a generic handler that can be used to fetch data from an API. We then assign the `genericProxyHandler` to the `proxyHandler` property of the `widget` object. There are other handlers available that you can use depending on your requirements. You can also create custom handlers if needed.
|
|
2. We define a `widget` object that contains the metadata for the widget.
|
|
3. The API endpoint to fetch data from. You can use placeholders like `{url}` and `{endpoint}` to dynamically generate the API endpoint based on the widget configuration.
|
|
4. An object that contains mappings for different endpoints. Each mapping should have an `endpoint` property that specifies the endpoint to fetch data from.
|
|
5. A mapping named `info` that specifies the `v1/info` endpoint to fetch data from. This would be called from the component as such: `#!js useWidgetAPI(widget, "info");`
|
|
6. The `endpoint` property of the `info` mapping specifies the endpoint to fetch data from. There are other properties you can pass to the mapping, such as `method`, `headers`, and `body`.
|
|
|
|
!!! warning "Important"
|
|
|
|
All widgets that fetch data from dynamic endpoints should have either `mappings` or an `allowedEndpoints` property.
|
|
|
|
## Including translation strings in your widget
|
|
|
|
Refer to the [translations guide](translations.md) for more details. The Homepage community prides itself on being multilingual, and we strongly encourage you to add translations for your widgets.
|
|
|
|
## Create the widget component
|
|
|
|
Create a new file for your widgets component, named `component.jsx`, in the `src/widgets/yourwidget` directory. We'll build the contents of the `component.jsx` file step by step.
|
|
|
|
First, we'll import the necessary dependencies:
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="1"
|
|
import { useTranslation } from "next-i18next"; // (1)!
|
|
|
|
import Container from "components/services/widget/container"; // (2)!
|
|
import Block from "components/services/widget/block"; // (3)!
|
|
import useWidgetAPI from "utils/proxy/use-widget-api"; // (4)!
|
|
```
|
|
|
|
1. `#!js useTranslation()` is a hook provided by `next-i18next` that allows us to access the translation strings
|
|
2. `#!jsx <Container>` and `#!jsx <Block>` are custom components that we'll use to structure our widget.
|
|
3. `#!jsx <Container>` and `#!jsx <Block>` are custom components that we'll use to structure our widget.
|
|
4. `#!js useWidgetAPI(widget, endpoint)` is a custom hook that we'll use to fetch data from an API.
|
|
|
|
---
|
|
|
|
Next, we'll define a functional component named `Component` that takes a `service` prop.
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="7"
|
|
export default function Component({ service }) {}
|
|
```
|
|
|
|
---
|
|
|
|
We grab the helper functions from the `useTranslation` hook.
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="8"
|
|
const { t } = useTranslation();
|
|
```
|
|
|
|
---
|
|
|
|
We destructure the `widget` object from the `service` prop. The `widget` object contains the metadata for the widget, such as the API endpoint to fetch data from.
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="9"
|
|
const { widget } = service;
|
|
```
|
|
|
|
---
|
|
|
|
Now, the fun part! We use the `useWidgetAPI` hook to fetch data from an API. The `useWidgetAPI` hook takes two arguments: the `widget` object and the API endpoint to fetch data from. The `useWidgetAPI` hook returns an object with `data` and `error` properties.
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="10"
|
|
const { data, error } = useWidgetAPI(widget, "info");
|
|
```
|
|
|
|
!!! tip "API Tips"
|
|
|
|
You'll see here how part of the API url is built using the `url` and `endpoint` properties from the widget definition.
|
|
|
|
In this case, we're fetching data from the `info` endpoint. The `info` endpoint is defined in the `mappings` object. So the full API endpoint will be `"{url}/v1/info"`.
|
|
|
|
The mapping and endpoint are often the same, but must be defined regardless.
|
|
|
|
---
|
|
|
|
Next, we check if there's an error or no data.
|
|
|
|
If there's an error, we return a `Container` and pass it the `service` and `error` as props. The `Container` component will handle displaying the error message.
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="12"
|
|
if (error) {
|
|
return <Container service={service} error={error} />;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
If there's no data, we return a `Container` component with three `Block` components, each with a `label`.
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="16"
|
|
if (!data) {
|
|
return (
|
|
<Container service={service}>
|
|
<Block label="yourwidget.key1" />
|
|
<Block label="yourwidget.key2" />
|
|
<Block label="yourwidget.key3" />
|
|
</Container>
|
|
);
|
|
}
|
|
```
|
|
|
|
This will render the widget with placeholders for the data, i.e., a skeleton view.
|
|
|
|
!!! tip "Translation Tips"
|
|
|
|
The `label` prop in the `Block` component corresponds to the translation key we defined earlier in the `common.js` file. All text and numerical content should be translated.
|
|
|
|
---
|
|
|
|
If there is data, we return a `Container` component with three `Block` components, each with a `label` and a `value`.
|
|
|
|
Here we use the `t` function from the `useTranslation` hook to translate the data values. The `t` function takes the translation key and an object with variables to interpolate into the translation string.
|
|
|
|
We're using the `common.number` translation key to format the data values as numbers. This allows for easy localization of numbers, such as using commas or periods as decimal separators.
|
|
|
|
There are a large number of `common` numerical translation keys available, which you can learn more about in the [Translation Guide](translations.md).
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="26"
|
|
return (
|
|
<Container service={service}>
|
|
<Block label="yourwidget.key1" value={t("common.number", { value: data.key1 })} />
|
|
<Block label="yourwidget.key2" value={t("common.number", { value: data.key2 })} />
|
|
<Block label="yourwidget.key3" value={t("common.number", { value: data.key3 })} />
|
|
</Container>
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
Here's the complete `component.jsx` file:
|
|
|
|
```js title="src/widgets/yourwidget/component.jsx" linenums="1"
|
|
import { useTranslation } from "next-i18next";
|
|
|
|
import Container from "components/services/widget/container";
|
|
import Block from "components/services/widget/block";
|
|
import useWidgetAPI from "utils/proxy/use-widget-api";
|
|
|
|
export default function Component({ service }) {
|
|
const { t } = useTranslation();
|
|
const { widget } = service;
|
|
const { data, error } = useWidgetAPI(widget, "info");
|
|
|
|
if (error) {
|
|
return <Container service={service} error={error} />;
|
|
}
|
|
|
|
if (!data) {
|
|
return (
|
|
<Container service={service}>
|
|
<Block label="yourwidget.key1" />
|
|
<Block label="yourwidget.key2" />
|
|
<Block label="yourwidget.key3" />
|
|
</Container>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Container service={service}>
|
|
<Block label="yourwidget.key1" value={t("common.number", { value: data.key1 })} />
|
|
<Block label="yourwidget.key2" value={t("common.number", { value: data.key2 })} />
|
|
<Block label="yourwidget.key3" value={t("common.number", { value: data.key3 })} />
|
|
</Container>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Add the widget to the Homepage
|
|
|
|
To add your widget to the Homepage, you need to register it in the `src/widgets/widgets.js` file.
|
|
|
|
Open the `src/widgets/widgets.js` file and import the `Component` from your widget's `component.jsx` file. Please keep the alphabetical order.
|
|
|
|
```js
|
|
// ...
|
|
import yourwidget from "./yourwidget/widget";
|
|
// ...
|
|
```
|
|
|
|
Add `yourwidget` to the `widgets` object. Please keep the alphabetical order.
|
|
|
|
```js
|
|
const widgets = {
|
|
// ...
|
|
yourwidget: yourwidget,
|
|
// ...
|
|
};
|
|
```
|
|
|
|
You also need to add the widget to the `components` object in the `src/widgets/components.js` file.
|
|
|
|
Open the `src/widgets/components.js` file and import the `Component` from your widget's `component.jsx` file.
|
|
|
|
Please keep the alphabetical order.
|
|
|
|
```js
|
|
const components = {
|
|
// ...
|
|
yourwidget: dynamic(() => import("./yourwidget/component")),
|
|
// ...
|
|
};
|
|
```
|
|
|
|
## Using the widget
|
|
|
|
You can now use your custom widget in your Homepage. Open your `services.yaml` file and add a new service with the `yourwidget` widget.
|
|
|
|
```yaml
|
|
- Services:
|
|
- Your Widget:
|
|
icon: yourwidget.svg
|
|
href: https://example.com/
|
|
widget:
|
|
type: yourwidget
|
|
url: http://127.0.0.1:1337
|
|
```
|
|
|
|
!!! tip "API Tips"
|
|
|
|
You'll see here how part of the API url is built using the `url` and `endpoint` properties from the widget definition.
|
|
|
|
We defined the api endpoint as `"{url}/{endpoint}"`. This is where the `url` is defined. So the full API endpoint will be `http://127.0.0.1:1337/{endpoint}`.
|
|
|
|
---
|
|
|
|
That's it! You've successfully created a custom widget for Homepage. If you have any questions or need help, feel free to reach out to the Homepage community for assistance. Happy coding!
|