11 KiB
title | description |
---|---|
Widget Guide | How to create a custom widget for Homepage. |
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:
{ "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:
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;
- We import the
genericProxyHandler
from theutils/proxy/handlers/generic
module. ThegenericProxyHandler
is a generic handler that can be used to fetch data from an API. We then assign thegenericProxyHandler
to theproxyHandler
property of thewidget
object. There are other handlers available that you can use depending on your requirements. You can also create custom handlers if needed. - We define a
widget
object that contains the metadata for the widget. - 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. - An object that contains mappings for different endpoints. Each mapping should have an
endpoint
property that specifies the endpoint to fetch data from. - A mapping named
info
that specifies thev1/info
endpoint to fetch data from. This would be called from the component as such:#!js useWidgetAPI(widget, "info");
- The
endpoint
property of theinfo
mapping specifies the endpoint to fetch data from. There are other properties you can pass to the mapping, such asmethod
,headers
, andbody
.
!!! warning "Important"
All widgets that fetch data from dynamic endpoints should have either `mappings` or an `allowedEndpoints` property.
Set up translation strings
Homepage uses translated and localized strings for all text and numerical content in widgets. English is the default language, and other languages can be added via Crowdin. To add the English translations for your widget, follow these steps:
Open the public/locales/en/common.js
file.
Add a new object for your widget to the bottom of the list, like this:
"yourwidget": {
"key1": "Value 1",
"key2": "Value 2",
"key3": "Value 3"
}
!!! note
Even if you nativly speak another language, you should only add English translations. You can then add translations in your native language via [Crowdin](https://crowdin.com/project/gethomepage), once your widget is merged.
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:
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)!
#!js useTranslation()
is a hook provided bynext-i18next
that allows us to access the translation strings#!jsx <Container>
and#!jsx <Block>
are custom components that we'll use to structure our widget.#!jsx <Container>
and#!jsx <Block>
are custom components that we'll use to structure our widget.#!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.
export default function Component({ service }) {}
We grab the helper functions from the useTranslation
hook.
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.
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.
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.
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
.
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.
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:
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.
// ...
import yourwidget from "./yourwidget/widget";
// ...
Add yourwidget
to the widgets
object. Please keep the alphabetical order.
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.
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.
- 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!