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.

  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


  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:

    1. This comment has been removed by the author.