Skip to main content
Version: 4.12.0

Custom widgets (React)

AWE lets you extend the UI it provides with your own JavaScript components — including third-party libraries and their styles — by embedding them in a screen through the <widget> element. On the React client, widgets are resolved by their type against a client-side widget registry, so you can plug in whatever you need at the application level without modifying the framework.

This guide walks through a complete, working example: an event calendar (built on the third-party FullCalendar library) added to a React AWE project.

React event calendar widget

How it works

When you place a widget in a screen:

<widget type="event-calendar" id="eventCalendar" style="expand"/>

the React client looks up the widget type in its registry and renders the matching component:

  • The awe-react-client package exposes registerWidget(type, component) and getWidget(type). The client resolves each <widget> node against the registry by its type.
  • The built-in widgets (file-manager, log-viewer, help-viewer, pdf-viewer, carousel) register through this same mechanism, so an application widget is not a special case.
  • All attributes of the widget node (id, style, ...) are passed to your component as props.
  • If no component matches type, the client renders a graceful placeholder (The widget <type> has not been created yet.) instead of failing.

To register your own component, import registerWidget from the client's main entry point and call it once at application startup.

Steps

1. Add the library dependency

Install the component library in your project (here, FullCalendar and its React adapter):

npm install --save @fullcalendar/core @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/list @fullcalendar/interaction @fullcalendar/react

2. Write the widget component

Create the component under src/js/widgets/, for example src/js/widgets/EventCalendar.jsx. Key points:

  • It is a plain React component. AWE passes the widget node attributes as props, so destructure id and style from them.
  • Render your third-party component (here, <FullCalendar>) inside your own container.
  • Follow the AWE locale through react-i18next: useTranslation() gives you t for labels and i18n.language for the active AWE language code (e.g. en-GB, es-ES).
import React, { useState } from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from "@fullcalendar/list";
import interactionPlugin from "@fullcalendar/interaction";
import esLocale from "@fullcalendar/core/locales/es";
import enGbLocale from "@fullcalendar/core/locales/en-gb";

// AWE language codes mapped to FullCalendar locales
const CALENDAR_LOCALES = {
"es-ES": esLocale,
"en-GB": enGbLocale
};

// Receives the widget node attributes (id, style, ...) as props
function EventCalendar({ id, style = "" }) {
const { t, i18n } = useTranslation();
const [events, setEvents] = useState([/* ... in-memory demo events ... */]);

return (
<div id={id} className={`awe-ec ${style}`}>
<div className="awe-ec-mount">
<FullCalendar
plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin]}
initialView="dayGridMonth"
height="100%"
locale={CALENDAR_LOCALES[i18n.language] ?? enGbLocale}
events={events}
/>
</div>
{/* Composer to create/edit events (not a native <form> — see caution below) */}
</div>
);
}

EventCalendar.propTypes = {
id: PropTypes.string.isRequired,
style: PropTypes.string
};

export default EventCalendar;
Follow the AWE locale

Read the active language with react-i18next: const { t, i18n } = useTranslation(). Use i18n.language (codes like en-GB, es-ES) to pick the third-party library's locale, and t("KEY") for your own labels so they change with the AWE language. Do not rely on the browser language.

No native <form>, no native dialogs

The AWE screen already wraps its content in a <form>, and HTML forms cannot nest — so do not add another <form> inside your widget. Drive create/edit/delete with buttons and onClick handlers instead. Likewise, keep interactions inline rather than using alert() / confirm() / prompt(), and persist data through the widget server-action rather than in memory (the in-memory events above are only for the demo).

3. Register the component

Register the widget once at application startup in src/js/main.js, using registerWidget from the client main entry point:

const { registerWidget } = require('awe-react-client/js/main');
const { default: EventCalendar } = require('./widgets/EventCalendar');

// Custom widgets
registerWidget("event-calendar", EventCalendar);

The first argument ("event-calendar") is the type used in the screen XML; the second is your component.

4. Add the styles

Add your widget styles in src/css/main.css (imported by src/js/main.js). FullCalendar v6 injects its own base CSS at runtime, so you mainly override its CSS custom properties (scoped to your widget container) to blend with the active AWE theme:

.awe-ec {
display: flex;
flex-direction: column;
height: 100%;
}

.awe-ec-mount {
/* FullCalendar v6 custom properties */
--fc-border-color: #e2e8ec;
--fc-today-bg-color: rgba(86, 158, 27, 0.08);
--fc-event-bg-color: #569e1b;
--fc-event-border-color: #569e1b;
}

5. Add the screen

Create a screen that embeds the widget. The type matches the key you registered:

<screen xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://aweframework.gitlab.io/awe/docs/schemas/screen.xsd"
template="window" label="MENU_TEST_EVENT_CALENDAR">
<tag source="center" type="div" expandible="vertical" style="expand">
<widget type="event-calendar" id="eventCalendar" style="expand"/>
</tag>
</screen>

6. Add the menu option and locales

Register a menu option pointing to the screen and add the locale keys used by the screen/widget:

<!-- menu/private.xml -->
<option name="event-calendar-test" label="MENU_TEST_EVENT_CALENDAR" screen="event-calendar-test" icon="calendar"/>
<!-- locale/Locale-en-GB.xml -->
<locale name="MENU_TEST_EVENT_CALENDAR" value="Event calendar"/>
<locale name="SCR_EVENT_CALENDAR_NEW" value="New event"/>
<locale name="SCR_EVENT_CALENDAR_TITLE" value="Event title"/>

Notes

  • type is the extension key — it is a free string, so you are not limited to a fixed set of components. Screen/profile restrictions remain the right place to control access.
  • Built-in and custom widgets share the same registryregisterWidget is exactly how the client registers file-manager, log-viewer and the rest, so there is no framework change needed to add your own.
  • Pass configuration to the component with <widget-parameter>; the widget node (including its parameters) reaches your component as props.
  • For the resolution model shared with the Angular client, see the Widgets API reference.