Adobe

How to Architect a RTE Solution for AEM Touch UI Dialog

Rich Text Editor (RTE) is one of the most common and widely used widgets in a component in Adobe Experience Manager (AEM). Although Adobe provides out of the box (OOTB) RTE functionality, most projects I’ve been on require custom development or configurations. Hence, knowing more about RTE should help you better design a rich text solution for clients.
Here, we’ll focus on touch-enabled UI. Adobe introduced touch UI in AEM 5.6 and has become the standard UI throughout the product. Classic UI will be deprecated in AEM 6.4 and will be removed in the 2019 release.
You can reference to a complete code sample of RTE in title text component in my GitHub project.

Get Started with OOTB RTE

In touch UI dialog, you define the sling:resourceType property in _cq_dialog/.content.xml for each field, as touch UI dialog is rendered on server-side as Granite UI components. It leverages Sling and sling resource type to render the component.
In order to have RTE in touch UI dialog, you can call the OOTB RTE script. The OOTB sling:resourceType for RTE is cq/gui/components/authoring/dialog/richtext. You can reference to the OOTB text component under /libs/wcm/foundation/components/text and /libs/foundation/components/text, where the former uses HTML Template Language (HTL) and the latter uses JSP. There’s also text component from AEM WCM Core components.

Touch UI Dialog Mode and In-place Editing Mode

There are two modes author can use RTE in a touch-enabled UI AEM page. One is touch UI dialog mode, which is the wrench when you click on the component; the other is in-place editing mode, the pencil.

Adobe recommends to use in-place editing for RTE. Both in-place editing and touch UI dialog have two views. One is the compact inline view, the other is full screen view. They can be configured by the corresponding uiSettings node. It controls the plugins and popovers in RTE toolbar for each view. A default uiSettings will be used if you don’t have a uiSettings node for your field.
Table plugins and the source-edit feature are not available for in-place editing in the touch-enabled UI. Moreover, the Image plugin is only supported partially (drag-and-drop functionality is not available). Also, the drag-and-drop does not apply to the full-screen mode.

Multiple In-place Editors

In-place editors are defined in cq editconfig node. Multiple in-place editors, no matter if they are all text or different types, require editorType to be hybrid. Cq:childEditors node holds in place editor nodes. And config node holds configurations for in place editors. Detail instructions can be found here.

Use Same RTE Plugins and UI Settings in Touch UI Dialog and In-place Editing

The Digital Essentials, Part 3
The Digital Essentials, Part 3

Developing a robust digital strategy is both a challenge and an opportunity. Part 3 of the Digital Essentials series explores five of the essential technology-driven experiences customers expect, which you may be missing or not fully utilizing.

Get the Guide

Same RTE plugins and UI settings provide author the consistent authoring experience in both modes. To achieve that, for touch UI dialog mode, you can use Sling Resource Merger to call path to the RTE plugins and UI settings. Sling Resource Merger is introduced since AEM 6.1. It overwrites the default component dialog for touch-enabled UI, using the resource type hierarchy (by means of sling:resourceSuperType property).
For in-place editing, it’s a little bit difficult. Sling Resource Merger doesn’t work in edit config. You can set configpath property in cq:inplaceEditing node, especially when you have only one type of in place editors. The OOTB RTE script works charmingly and will not display the control buttons (like the fullscreen, cancel and save) in touch UI dialog inline view. So you can have one shared set of rtePlugins and uiSettings nodes for both touch UI dialog and in-place editing. However, the configpath doesn’t work quite well if you have different types of in-place editors and need to pass multiple custom configurations, since you can only point to one path in the property. Plus, the configpath property is for backward compatibility only. It’s not recommended by Adobe.
Optionally, you can write a script to encapsulate all your RTE plugins and UI settings and then load them into RTE config programmatically. Here’s some pointers for you.
/libs/cq/gui/components/authoring/editors/clientlibs/core/inlineediting/js/HybridEditor.js /libs/cq/gui/components/authoring/editors/clientlibs/core/inlineediting/rte/coralui3/js/InlineTextEditor.js
/libs/clientlibs/granite/richtext/js/rte/ConfigUtils.js
/libs/cq/gui/components/authoring/editors/clientlibs/core/inlineediting/rte/coralui3/js/RTE.js
The drawback of this approach is that Adobe doesn’t make the RTE js apis public, so you will have to dig into all the OOTB RTE scripts. And you may fix any discrepancies when you do an upgrade.
The recommended way is to use config node in cq:inplaceEditing node. But then you will need to configure rtePlugins and uiSettings in each component.
An example of RTE node for touch UI dialog mode:

<text
    jcr:primaryType="nt:unstructured"
    sling:resourceType="cq/gui/components/authoring/dialog/richtext"
    fieldLabel="Text"
    name="./text">
    <rtePlugins
        jcr:primaryType="nt:unstructured"
        sling:resourceSuperType="/apps/blog/dialogs/standardRTE/rtePlugins"/>
    <uiSettings
        jcr:primaryType="nt:unstructured"
        sling:resourceSuperType="/apps/blog/dialogs/standardRTE/uiSettings"/>
</text>

An example of RTE config for in-place editing mode:

<cq:inplaceEditing
    jcr:primaryType="cq:InplaceEditingConfig"
    active="{Boolean}true"
    editorType="hybrid">
    <cq:childEditors jcr:primaryType="nt:unstructured">
        <title
            jcr:primaryType="cq:ChildEditorConfig"
            title="Title"
            type="text"/>
        <text
            jcr:primaryType="cq:ChildEditorConfig"
            title="Text"
            type="text"/>
    </cq:childEditors>
    <config jcr:primaryType="nt:unstructured">
        <rtePlugins jcr:primaryType="nt:unstructured">
            …
        </rtePlugins>
        <uiSettings jcr:primaryType="nt:unstructured">
            …
        </uiSettings>
    </config>
</cq:inplaceEditing>

An example of shared RTE plugins and UI settings file:

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured">
    <rtePlugins jcr:primaryType="nt:unstructured">
        <edit
            jcr:primaryType="nt:unstructured"
            features="*"/>
        ...
    </rtePlugins>
    <uiSettings jcr:primaryType="nt:unstructured">
        <cui jcr:primaryType="nt:unstructured">
            <inline
                jcr:primaryType="nt:unstructured"
                ...
            </inline>
            <dialogFullScreen
                jcr:primaryType="nt:unstructured"
                ...
            </dialogFullScreen>
            <tableEditOptions
                jcr:primaryType="nt:unstructured"
                ...
            </tableEditOptions>
        </cui>
    </uiSettings>
</jcr:root>

RTE Link href Issue and Resolution

With the out of the box richtext resource type defined for touch UI dialog, cq/gui/components/authoring/dialog/richtext, and when author adds a link from the touch UI dialog (the wrench), the href of anchor tag will be stripped off upon submitting the dialog.
Rich Text Editor Adobe Experience Manager
This issue happens only for RTE in touch UI dialog, not in-place editing. This is a known defect for AEM, also documented in the Adobe website. The root cause is that with the OOTB richtext resource type, an RTE instance is getting created each time the RTE is clicked or just simply scrolling the dialog. There’s a simple fix provided by Adobe for this. When you develop the touch UI dialog for the RTE, you can add a property of useFixedInlineToolbar="{Boolean}true". With this setting, you will see the toolbar is fixed above the text editing area, and if you open developer console in your browser, you will see only one instance for RTE is created. This setting will also fix the href issue in touch UI dialog, and other plugins should work as expected, too.
Moreover, if you want to apply this setting on an application level, you can use the sling resource merger and overlay the OOTB richtext script to your app location. I.e., create this structure in your code, apps/cq/gui/components/authoring/dialog/richtext/render.jsp. Now you can add your custom logic in the JSP to render RTE. And what you want to do is that when there’s no useFixedInlineToolbar setting in the dialog configuration, default it to be true.

/*
* Get useFixedInlineToolbar value from component configuration, if not found, default it to
* true to fix rte anchor link issue
*/
String useFixedInlineToolbar = cfg.get("useFixedInlineToolbar", String.class);
if (useFixedInlineToolbar == null) {
  useFixedInlineToolbar = "true";
}

And then in the markup you can pass the useFixedInlineToolbar variable in.
<div class="coral-RichText-editable coral-Form-field coral-Textfield coral-Textfield--multiline coral-RichText" data-config-path="<%=resource.getPath()%>.infinity.json" data-use-fixed-inline-toolbar="<%=useFixedInlineToolbar%>"></div>
In this case, if you do have a use case that requires to set the useFixedInlineToolbar to false, you can set it in the component dialog configuration and your custom script will be able to read that configuration value.

Custom RTE Plugin and Validation

Both RTE custom plugins and validation can be achieved by clientlibs and JavaScript. Even though OOTB RTE plugins are enough most of the time, I have been in couple projects that required to a custom RTE plugin. One example would be the font color picker. If you do need to develop a custom RTE plugin, you can start with creating a clientlib in your code with category rte.coralui3 (AEM 6.3) and dependencies underscore. This will load your JavaScript when AEM is calling RTE in a touch UI component. Then you will write JS code to build the plugin into toolbar and custom logic and implementation for the plugin. At the end, register your custom plugin with Coral UI RTE api, CUI.rte.commands.CommandRegistry.register(). After your plugin is registered, you can add that into the RTE plugins and author will be able to see it. You can reference to some OOTB plugins scripts under here:  /etc/clientlibs/granite/coralui2/optional/rte/js/components/rte/plugins(AEM 6.2)
/libs/clientlibs/granite/richtext/js/rte/plugins (AEM 6.3).
Validation for RTE touch UI dialog should follow the new best practice and use Granite UI foundation validation. I have written a separate blog for that if you are interested to know more. We also have a blog specifically for RTE validation.

About the Author

More from this Author

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to the Weekly Blog Digest:

Sign Up