Add badges to Rich Text Editor (RTE)

Learn how to add badges to Rich Text Editor (RTE) in the AEM Content Fragment Editor.

Rich Text Editor badge are extensions that make text in the Rich Text Editor (RTE) non-editable. This means that a badge declared as such can only be completely removed and cannot be partially edited. These badges also support special coloring within the RTE, clearly indicating to content authors that the text is a badge and therefore not editable. Additionally, they provide visual cues regarding the meaning of the badge text.

The most common use case for RTE badges is to use them in conjunction with RTE widgets. This allows content injected into the RTE by the RTE widget to be non-editable.

Typically, the badges in association with the widgets are used to add the dynamic content that has an external system dependency but content authors cannot modify the inserted dynamic content to maintain the integrity. They can only be removed as a whole item.

The badges are added to the RTE in the Content Fragment Editor using the rte extension point. Using rte extension point’s getBadges() method one or many badges are added.

This example shows how to add a widget called Large Group Bookings Customer Service to find, select, and add the WKND adventure-specific customer service details like Representative Name and Phone Number within an RTE content. Using the badges functionality the Phone Number is made non-editable but WKND content authors can edit the Representative Name.

Also, the Phone Number is styled differently (blue) which is an extra use case of the badges functionality.

To keep things simple, this example uses the Adobe React Spectrum framework to develop the widget or dialog UI and hard-coded WKND Customer Service phone numbers. To control the non-editing and different style aspect of the content, the # character is used in the prefix and suffix attribute of the badges definition.

Extension points

This example extends to extension point rte to add a badge to the RTE in the Content Fragment Editor.

Example extension

The following example creates a Large Group Bookings Customer Service widget. By pressing the { key within the RTE, the RTE widgets context menu is opened. By selecting the Large Group Bookings Customer Service option from the context menu the custom modal is opened.

Once the desired customer service number is added from the modal, the badges make the Phone Number non-editable and styles it in blue color.

Extension registration

ExtensionRegistration.js, mapped to the index.html route, is the entry point for the AEM extension and defines:

  • The badge’s definition is defined in getBadges() using the configuration attributes id, prefix, suffix, backgroundColor and textColor.
  • In this example, the # character is used to define this badge’s boundaries - meaning any string in the RTE that is surrounded by # is treated as an instance of this badge.

Also, see the key details of the RTE widget:

  • The widget definition in getWidgets() function with id, label and url attributes.
  • The url attribute value, a relative URL path (/index.html#/largeBookingsCustomerService) to load the modal.

src/aem-cf-editor-1/web-src/src/components/ExtensionRegistration.js

import React from "react";
import { Text } from "@adobe/react-spectrum";
import { register } from "@adobe/uix-guest";
import { extensionId } from "./Constants";

// This function is called when the extension is registered with the host and runs in an iframe in the Content Fragment Editor browser window.
function ExtensionRegistration() {
  const init = async () => {
    const guestConnection = await register({
      id: extensionId,
      methods: {
        rte: {
          // RTE Badges
          getBadges: () => [
            {
              id: "phoneNumber",
              prefix: "#",
              suffix: "#",
              backgroundColor: "",
              textColor: "#071DF8",
            },
          ],

          // RTE Widgets
          getWidgets: () => [
            {
              id: "largegroup-contact-list-widget",
              label: "Large Group Bookings Customer Service",
              url: "/index.html#/largeBookingsCustomerService",
            },
          ],
        },
      },
    });
  };

  init().catch(console.error);

  return <Text>IFrame for integration with Host (AEM)...</Text>;
}

export default ExtensionRegistration;

Add largeBookingsCustomerService route in App.js

In the main React component App.js, add the largeBookingsCustomerService route to render the UI for the above relative URL path.

src/aem-cf-editor-1/web-src/src/components/App.js


...

<Routes>
  <Route index element={<ExtensionRegistration />} />
  <Route
    exact path="index.html"
    element={<ExtensionRegistration />}
  />

  {/* Content Fragment RTE routes that support the Discount Codes Widget functionality*/}
  <Route path="/largeBookingsCustomerService" element={<LargeBookingsCustomerService />} />
</Routes>
...

Create LargeBookingsCustomerService React component create-widget-react-component

The widget or dialog UI is created using the Adobe React Spectrum framework.

The React component code when adding the customer service details, surround the phone number variable with the # registered badges character to convert it into badges, like #${phoneNumber}#, thus make it non-editable.

Here are key highlights of LargeBookingsCustomerService code:

  • The UI is rendered using React Spectrum components, like ComboBox, ButtonGroup, Button
  • The largeGroupCustomerServiceList array has hardcoded mapping of representative name and phone number. In real scenario, this data can be retrieved from Adobe AppBuilder action or external systems or home grown or cloud provider-based API gateway.
  • The guestConnection is initialized using the useEffect React hook and managed as component state. It is used to communicate with the AEM host.
  • The handleCustomerServiceChange function gets representative name and phone number and updates the component state variables.
  • The addCustomerServiceDetails function using guestConnection object provides RTE instruction to execute. In this case insertContent instruction and HTML code snippet.
  • To make the phone number non-editable using badges, the # special character is added before and after the phoneNumber variable, like ...<div><p>Phone Number: #${phoneNumber}#</strong></p></div>.

src/aem-cf-editor-1/web-src/src/components/LargeBookingsCustomerService.js

import {
  Button,
  ButtonGroup,
  Text,
  Divider,
  ComboBox,
  Content, Flex, Form,
  Item,
  Provider, defaultTheme
} from '@adobe/react-spectrum';
import { attach } from '@adobe/uix-guest';
import React, { useEffect, useState } from 'react';
import { extensionId } from './Constants';


const LargeBookingsCustomerService = () => {

  // The Large Group Bookings Customer Service
  // In this example its hard coded, however you can call an Adobe AppBuilder Action or even make an AJAX call to load it from 3rd party system
  const largeGroupCustomerServiceList = [
    { id: 1, repName: 'Max', phoneNumber: '1-800-235-1000' },
    { id: 2, repName: 'John', phoneNumber: '1-700-235-2000' },
    { id: 3, repName: 'Leah', phoneNumber: '1-600-235-3000' },
    { id: 4, repName: 'Leno', phoneNumber: '1-500-235-4000' }
  ];

  // Set up state used by the React component
  const [guestConnection, setGuestConnection] = useState();

  // State hooks to manage the component state
  const [repName, setRepName] = useState(null);
  const [phoneNumber, setPhoneNumber] = useState(null);

  // Asynchronously attach the extension to AEM, we must wait or the guestConnection to be set before doing anything in the modal
  useEffect(() => {
    (async () => {
      const myGuestConnection = await attach({ id: extensionId });

      setGuestConnection(myGuestConnection);
    })();
  }, []);

  // Handle the `customerService` Dropdown change
  const handleCustomerServiceChange = (id) => {

    if (id) {
      //Get Customer Service RepName and Phone Number values using selected id

      const rep = largeGroupCustomerServiceList.filter((r) => r.id === id)[0];

      //update the `repName` state
      setRepName(rep?.repName);

      //update the `phoneNumber` state
      setPhoneNumber(rep?.phoneNumber);
    }
  };

  // Add the selected Customer Service details into the RTE
  const addCustomerServiceDetails = () => {

    if (repName && phoneNumber) {
      // Use `guestConnection.host.rte.applyInstructions` method and provide RTE instruction to execute.
      // The instructions are passed as an array of object, that has `type` and `value` keys
      guestConnection.host.rte.applyInstructions([{ type: "insertContent", value: `<div><p>Representative Name: <strong>${repName}</strong></p></div><div><p>Phone Number: #${phoneNumber}#</strong></p></div>` }]);
    }
  };


  // Adobe React Spectrum (HTML code) that renders the Customer Service dropdown list, see https://react-spectrum.adobe.com/react-spectrum/index.html
  return (
    <Provider theme={defaultTheme}>
      <Content width="100%">
        <Flex width="100%">

          <Form width="50%">
            <Text>Representative Name: <strong>{repName}</strong></Text>
            <Text>Phone Number: <strong>{phoneNumber}</strong></Text>

            <p />

            <Divider size="M" />


            <ComboBox
              name="customerService"
              label="Type or Select Phone Number"
              defaultItems={largeGroupCustomerServiceList}
              onSelectionChange={handleCustomerServiceChange}>
              {item => <Item>{item.phoneNumber}</Item>}
            </ComboBox>

            <p />

            <ButtonGroup align="right">
              <Button variant="accent" onPress={addCustomerServiceDetails}>Add</Button>
              <Button variant="secondary" onPress={() => {setPhoneNumber(null); setRepName(null);}}>Clear</Button>
            </ButtonGroup>

          </Form>
        </Flex>
      </Content>
    </Provider>
  );

};

export default LargeBookingsCustomerService;
recommendation-more-help
4859a77c-7971-4ac9-8f5c-4260823c6f69