AEM 61 - Touch UI Rich Text Editor (RTE) Browse and Insert Image


Touch UI Rich Text Editor (RTE) Plugin to open a DialogSelect Image and add it in RTE

Dialog for the plugin can be configured with any standard Touch UI Widgets. In this post, we configure a textfield - /libs/granite/ui/components/foundation/form/textfield for entering alt text and path browser - /libs/granite/ui/components/foundation/form/pathbrowser for selecting image

Demo | Package Install

Component Dialog RTE Config

Add the image insert plugin - touchuiinsertimage, available in group experience-aem in component dialog eg. /libs/foundation/components/text/dialog/items/tab1/items/text/rtePlugins (for demonstration only; never modify foundation components)

Plugin Dialog with Path Browser Config

Plugin Dialog

Plugin Dialog with Picker

Plugin Dialog with Image Selected

Image Shown in RTE

Image Source in RTE


1) Login to CRXDE Lite, create folder (nt:folder) /apps/touchui-rte-browse-insert-image

2)  Add the dialog configuration for RTE Plugin, shown in popover window when user clicks on image plugin icon; create node of type sling:Folder /apps/touchui-rte-browse-insert-image/popover with the following configuration

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="" xmlns:jcr=""
    jcr:title="Pick an Image"

3) Add dialog content /apps/touchui-rte-browse-insert-image/popover/content; #5 attribute eaem-rte-iframe-content marks this dialog RTE specific

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="" xmlns:jcr="" xmlns:nt=""
    <items jcr:primaryType="nt:unstructured">
            <items jcr:primaryType="nt:unstructured">
                    fieldLabel="Alt Text"

4) Dialog in CRXDE Lite

5) Create clientlib (type cq:ClientLibraryFolder/apps/touchui-rte-browse-insert-image/clientlib, set property categories of String type to rte.coralui2

6) Create file ( type nt:file ) /apps/touchui-rte-browse-insert-image/clientlib/js.txt, add the following


7) Create file ( type nt:file ) /apps/touchui-rte-browse-insert-image/clientlib/image-insert.js, add the following code. This file contains logic for image plugin; receiving selected image from popover iframe showing the dialog and adding it in RTE

(function ($, $document, Handlebars) {
    var ExperienceAEM = {
        GROUP: "experience-aem",
        TIM_FEATURE: "touchuiinsertimage",
        TIM_DIALOG: "touchuiinsertimagedialog",
        CONTENT_URL: "/apps/touchui-rte-browse-insert-image/popover.html",
        EAEM_RTE_IFRAME_CONTENT: "eaem-rte-iframe-content"

    ExperienceAEM.TIM_UI_SETTING = ExperienceAEM.GROUP + "#" + ExperienceAEM.TIM_FEATURE;

    //extend toolbar builder to register insert image
    ExperienceAEM.CuiToolbarBuilder = new Class({
        toString: "EAEMCuiToolbarBuilder",

        extend: CUI.rte.ui.cui.CuiToolbarBuilder,

        _getUISettings: function (options) {
            var uiSettings = this.superClass._getUISettings(options);

            //inline toolbar
            var toolbar = uiSettings["inline"]["toolbar"],
                feature = ExperienceAEM.TIM_UI_SETTING;

            //uncomment this to make image insert available for inline toolbar
            /*if (toolbar.indexOf(feature) == -1) {
                var index = toolbar.indexOf("fullscreen#start");
                toolbar.splice(index, 0, feature);
                toolbar.splice(index + 1, 0, "-");

            //add image insert to fullscreen toolbar
            toolbar = uiSettings["fullscreen"]["toolbar"];

            if (toolbar.indexOf(feature) == -1) {
                toolbar.splice(3, 0, feature);

            if (!this._getClassesForCommand(feature)) {
                this.registerAdditionalClasses(feature, "coral-Icon coral-Icon--image");

            return uiSettings;

    //popover dialog thats hosts iframe
    ExperienceAEM.InsertImageDialog = new Class({
        extend: CUI.rte.ui.cui.AbstractBaseDialog,

        toString: "EAEMInsertImageDialog",

        getDataType: function () {
            return ExperienceAEM.TIM_DIALOG;

    //extend the CUI dialog manager to register popover dialog
    ExperienceAEM.DialogManager = new Class({
        toString: "EAEMDialogManager",

        extend: CUI.rte.ui.cui.CuiDialogManager,

        create: function (dialogId, config) {
            if (dialogId !== ExperienceAEM.TIM_DIALOG) {
                return, dialogId, config);

            var context = this.editorKernel.getEditContext();
            var $container = CUI.rte.UIUtils.getUIContainer($(context.root));

            var dialog = new ExperienceAEM.InsertImageDialog();
            dialog.attach(config, $container, this.editorKernel, true);

            return dialog;

    //extend the toolkit implementation for returning custom toolbar builder and dialog manager
    ExperienceAEM.ToolkitImpl = new Class({
        toString: "EAEMToolkitImpl",

        extend: CUI.rte.ui.cui.ToolkitImpl,

        createToolbarBuilder: function () {
            return new ExperienceAEM.CuiToolbarBuilder();

        createDialogManager: function (editorKernel) {
            return new ExperienceAEM.DialogManager(editorKernel);

    CUI.rte.ui.ToolkitRegistry.register("cui", ExperienceAEM.ToolkitImpl);

    ExperienceAEM.TouchUIInsertImagePlugin = new Class({
        toString: "TouchUIInsertImagePlugin",

        extend: CUI.rte.plugins.Plugin,

        pickerUI: null,

        getFeatures: function () {
            return [ ExperienceAEM.TIM_FEATURE ];

        initializeUI: function (tbGenerator) {
            var plg = CUI.rte.plugins;

            if (this.isFeatureEnabled(ExperienceAEM.TIM_FEATURE)) {
                this.pickerUI = tbGenerator.createElement(ExperienceAEM.TIM_FEATURE, this, true, "Insert Image");
                tbGenerator.addElement(ExperienceAEM.GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 120);

        execute: function (id) {
            var ek = this.editorKernel,
                dm = ek.getDialogManager();

            var dialogConfig = {
                parameters: {
                    "command": ExperienceAEM.TIM_UI_SETTING

            var dialog = this.dialog = dm.create(ExperienceAEM.TIM_DIALOG, dialogConfig);



            var $popover = this.dialog.$dialog.find(".coral-Popover-content");


            function loadPopoverUI($popover) {
                $popover.parent().css("width", ".1px").height(".1px").css("border", "none");
                $popover.css("width", ".1px").height(".1px");

                $popover.find("iframe").attr("src", ExperienceAEM.CONTENT_URL);

                //receive the dialog values from child window

            function receiveMessage(event) {
                if (_.isEmpty( {

                var message = JSON.parse(;

                if(!message || message.sender != ExperienceAEM.EAEM_RTE_IFRAME_CONTENT){

                var action = message.action;

                if(action == "submit"){
                    var data =;

                    if(!_.isEmpty(data) && !_.isEmpty(data.imagePath)){



            function removeReceiveDataListener(handler){
                if (window.removeEventListener) {
                    window.removeEventListener("message",  handler);
                } else if (window.detachEvent) {
                    window.detachEvent("onmessage", handler);

            function registerReceiveDataListener(handler) {
                if (window.addEventListener) {
                    window.addEventListener("message", handler, false);
                } else if (window.attachEvent) {
                    window.attachEvent("onmessage", handler);

        //to mark the icon selected/deselected
        updateState: function (selDef) {
            var hasUC = this.editorKernel.queryState(ExperienceAEM.TIM_FEATURE, selDef);

            if (this.pickerUI != null) {

    CUI.rte.plugins.PluginRegistry.register(ExperienceAEM.GROUP, ExperienceAEM.TouchUIInsertImagePlugin);

    ExperienceAEM.InsertImageCmd = new Class({
        toString: "InsertImageCmd",

        extend: CUI.rte.commands.Command,

        isCommand: function (cmdStr) {
            return (cmdStr.toLowerCase() == ExperienceAEM.TIM_FEATURE);

        getProcessingOptions: function () {
            var cmd = CUI.rte.commands.Command;
            return cmd.PO_BOOKMARK | cmd.PO_SELECTION;

        execute: function (execDef) {
            var data = execDef.value, path = data.imagePath, alt = data.altText || "",
                width = 100, height = 100,
                imageUrl = CUI.rte.Utils.processUrl(path, CUI.rte.Utils.URL_IMAGE),
                imgHtml = "";

            imgHtml += "<img src=\"" + imageUrl + "\" alt=\"" + alt + "\"";
            imgHtml += " " + CUI.rte.Common.SRC_ATTRIB + "=\"" + path + "\"";
            imgHtml += " width=\"" + width + "\"";
            imgHtml += " height=\"" + height + "\"";
            imgHtml += ">";

            execDef.editContext.doc.execCommand("insertHTML", false, imgHtml);

    CUI.rte.commands.CommandRegistry.register(ExperienceAEM.GROUP, ExperienceAEM.InsertImageCmd);

    //returns the picker dialog html
    //Handlebars doesn't do anything useful here, but the framework expects a template
    function cpTemplate() {
        CUI.rte.Templates["dlg-" + ExperienceAEM.TIM_DIALOG] =
            Handlebars.compile('<div data-rte-dialog="' + ExperienceAEM.TIM_DIALOG
                + '" class="coral--dark coral-Popover coral-RichText-dialog">'
                + '<iframe width="1100px" height="700px"></iframe>'
                + '</div>');

})(jQuery, jQuery(document), Handlebars);

8) Create file ( type nt:file ) /apps/touchui-rte-browse-insert-image/clientlib/popover.js, add the following code. This file contains logic for sending the dialog values like image selected to parent window RTE

(function($, $document){
    //dialogs marked with eaem-rte-iframe-content data attribute execute the below logic
    //to send dialog values to parent window RTE
    var EAEM_RTE_IFRAME_CONTENT = "eaem-rte-iframe-content",
        HELP_BUTTON_SEL = ".cq-dialog-help",
        CANCEL_BUTTON_SEL = ".cq-dialog-cancel",
        SUBMIT_BUTTON_SEL = ".cq-dialog-submit",
        ALT_TEXT_NAME = "./alt",
        IMAGE_NAME = "./image";

    $document.on("foundation-contentloaded", stylePopoverIframe);

    function stylePopoverIframe(){
        var $iframeContent = $("[" + 'data-' + EAEM_RTE_IFRAME_CONTENT + "]");


        var $form = $iframeContent.closest("form"),
            $cancel = $form.find(CANCEL_BUTTON_SEL),
            $submit = $form.find(SUBMIT_BUTTON_SEL);

        $form.css("border", "solid 2px");

        $"click", CANCEL_BUTTON_SEL);
        $"click", SUBMIT_BUTTON_SEL);


    function sendCloseMessage(){
        var message = {
            sender: EAEM_RTE_IFRAME_CONTENT,
            action: "close"

        parent.postMessage(JSON.stringify(message), "*");

    function sendDataMessage(){
        var message = {
            sender: EAEM_RTE_IFRAME_CONTENT,
            action: "submit",
                altText: $("[name='" + ALT_TEXT_NAME + "']").val(),
                imagePath: $("[name='" + IMAGE_NAME + "']").val()

        parent.postMessage(JSON.stringify(message), "*");
})(jQuery, jQuery(document));


  1. Good one sreekanth & thanks for sharing

  2. Thanks Sreekanth
    very usefull, a feature that is often requested

  3. This comment has been removed by the author.

  4. First off, thank you very much Sreekanth for this post! This is a feature that our users have wanted since we started on our project. I am currently trying to implement both this and the color picker but I'm having issues with that. I am only able to get this to work when I purposely break the color-picker-plugin.js. If I do that then the image browser icon shows up in the toolbar for the full screen rich text editor. Otherwise it does not show up at all. Were you able to get both of these to work together?

    1. Brendan, thanks for reporting; i think because of

      CUI.rte.ui.ToolkitRegistry.register("cui", ExperienceAEM.ToolkitImpl);

      the last one loading wins (overrrides previous register); ill work on a fix

    2. Hello Sreekanth,

      Just curious if you've had an opportunity to look at this at all? I did some digging of my own and found the ToolKitRegistry.js file but don't have enough understanding of it to correct this issue.

    3. Brendan,

      check the following demo (shows configuration too)

      the fix is not robust enough, not finding enough time... but if you'd like to try it out

    4. Thanks again Sreekanth,

      Just curious if you plan on revisiting this at all? If not could you please be more specific on what specifically is not robust enough about the fix?

    5. Thanks again Sreekanth,

      Just curious if you plan on revisiting this at all? If not could you please be more specific on what specifically is not robust enough about the fix?

  5. Sreekanth,

    I was able to get the colorpicker and image insert to work together by making the following changes:

    - Remove the custom EAEMCuiToolbarBuilder and EAEMDialogManager classes, instead extending CUI.rte.ui.cui.CuiToolbarBuilder and CUI.rte.ui.cui.CuiDialogManager directly - e.g. CUI.rte.ui.cui.CuiDialogManager = new Class({ extend: CUI.rte.ui.cui.CuiDialogManager, ... });

    - Remove the EAEMToolkitImpl class and the line `CUI.rte.ui.ToolkitRegistry.register("cui", EAEMToolkitImpl);` as it seems no longer necessary

    - Place the colorpicker and image insert plugins into different GROUP names, so that they dont collide when `CUI.rte.plugins.PluginRegistry.register(GROUP, EAEMColorPickerPlugin);` is called.

    1. Looks like I then also needed to change calls of `this.superClass._getUISettings(options)` and `, dialogId, config)` to `this.inherited(arguments)`.

  6. Sreekanth,

    In Chrome, the inserthtml command is adding garbage styling into the img tag for me, and also adding a span with font size and color around the text after the inserted image.

    To work around that, I replaced
    execDef.editContext.doc.execCommand("insertHTML", false, imgHtml);
    var range =;

    var el = execDef.editContext.doc.createElement("div");
    el.innerHTML = imgHtml;

  7. Also, one other bug fix.

    I added `dialog.restoreSelectionOnHide = false;` because otherwise after adding an image the cursor position would be saved in the old location, and if I tried using any of the other RTE tools it would move my cursor from the current position back to the position of the image.

    1. Brett / sreekanth,
      `dialog.restoreSelectionOnHide = false;` is not working as expected and still cursor is moving from the current position back to the position of the image.

    2. Hi Brett,

      Where in the code should 'dialog.restoreSelectionOnHide = false;' be added?

    3. This comment has been removed by the author.

    4. Hi everyone,
      just in case someone need an info to fix the cursor position issue, I've added "dialog.restoreSelectionOnHide = false;" just before the return statement of the "create" method of the class ExperienceAEM.DialogManager

    5. I am using this example to build a plugin that can create a span with some text inside. It is adding the span and text fine, but something is wrong with the cursor focus. For instance, after the span is saved in the RTE, if I hit space, it adds the space inside of the span instead of after it. Anyone know how to properly set the selection / context so once you've done insertHtml it places the cursor outside of the element you created?

  8. Hi Sreekanth,
    I have installed this package and did the node config as per the demo, but I could not see the image button on my RTE touch UI, could please let me know if I miss anything. Thanks in advance.

  9. This comment has been removed by the author.

  10. Hey Sreekanth, Thank you for the post. i have a question for you , is there a way that i could collect all the image paths and store them at the text node along with the "text" property, say another property containing the image paths?

  11. This comment has been removed by the author.

  12. Hi, Can anyone share me the popover.html file ,which is being used in the js file ?

    CONTENT_URL: "/apps/touchui-rte-browse-insert-image/popover.html


    1. Hi Febi,
      the content of "popover.html" is generated automatically from the ".content.xml" dialog in the "popover" directory. So in CONTENT_URL you have to specify the path where you have inserted the "popover" folder of the plugin

  13. Hi - we're using AEM 6.2 and I keep getting this error after selecting the image in the "pick an image" dialogue window.
    _is not defined.
    It appears to be coming from the popover.js file:

  14. Hi Sreekanth,
    Thanks for your solution which is the requirement of every client in almost all the projects for an RTE. As per the above comments I see there are few fixes made and few issues with 6.2, May I use this now directly on 6.2 version of AEM or any changes to be made for the above files and package you've given ?

    Thanks a lot for your work and sharing it with the community.

  15. It does not work on 6.3. Someone who has succeeded can help me

  16. Did anybody tried this on 6.3 SP1, if any, please share your findings.

  17. Hi Sreekanth,

    This plugin works well in AEM 6.1 and 6.2 but doesnot seems to work in AEM6.4. I tried placing the categories to rte.coralui3 also, but it didn't worked. Any suggestions?

  18. Hi Sreekanth, I am following your post but not able to do this image customization plugin under RTE in AEM 6.5 for Touch UI. Also I have posted my issue in the AEM forum