December 31, 2020
Estimated Post Reading Time ~

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

Goal: Touch UI Rich Text Editor (RTE) Plugin to open a Dialog, Select 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


Solution
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 the user clicks on image plugin icon; create a 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="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="sling:Folder"
    jcr:title="Pick an Image"
    sling:resourceType="cq/gui/components/authoring/dialog"/>

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="http://sling.apache.org/jcr/sling/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"
    sling:resourceType="granite/ui/components/foundation/container"
    eaem-rte-iframe-content="">
    <layout
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
        margin="{Boolean}false"/>
    <items jcr:primaryType="nt:unstructured">
        <column
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/foundation/container">
            <items jcr:primaryType="nt:unstructured">
                <alt
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/foundation/form/textfield"
                    fieldLabel="Alt Text"
                    name="./alt"/>
                <image
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
                    fieldLabel="Image"
                    name="./image"
                    rootPath="/content/dam"/>
            </items>
        </column>
    </items>
</jcr:root>

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
    image-insert.js
    popover.js

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 a 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 this.superClass.create.call(this, 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);
 
            dm.prepareShow(this.dialog);
 
            dm.show(this.dialog);
 
            var $popover = this.dialog.$dialog.find(".coral-Popover-content");
 
            loadPopoverUI($popover);
 
            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
                registerReceiveDataListener(receiveMessage);
            }
 
            function receiveMessage(event) {
                if (_.isEmpty(event.data)) {
                    return;
                }
 
                var message = JSON.parse(event.data);
 
                if(!message || message.sender != ExperienceAEM.EAEM_RTE_IFRAME_CONTENT){
                    return;
                }
 
                var action = message.action;
 
                if(action == "submit"){
                    var data = message.data;
 
                    if(!_.isEmpty(data) && !_.isEmpty(data.imagePath)){
                        ek.relayCmd(id, message.data);
                    }
                }
 
                dialog.hide();
 
                removeReceiveDataListener(receiveMessage);
            }
 
            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) {
                this.pickerUI.setSelected(hasUC);
            }
        }
    });
 
    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>');
    }
 
    cpTemplate();
})(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 + "]");
 
        if(_.isEmpty($iframeContent)){
            return
        }
 
        var $form = $iframeContent.closest("form"),
            $cancel = $form.find(CANCEL_BUTTON_SEL),
            $submit = $form.find(SUBMIT_BUTTON_SEL);
 
        $form.css("border", "solid 2px");
        $form.find(HELP_BUTTON_SEL).hide();
 
        $document.off("click", CANCEL_BUTTON_SEL);
        $document.off("click", SUBMIT_BUTTON_SEL);
        $document.off("submit");
 
        $cancel.click(sendCloseMessage);
        $submit.click(sendDataMessage);
    }
 
    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",
            data:{
                altText: $("[name='" + ALT_TEXT_NAME + "']").val(),
                imagePath: $("[name='" + IMAGE_NAME + "']").val()
            }
        };
 
        parent.postMessage(JSON.stringify(message), "*");
    }
})(jQuery, jQuery(document));

Source:
http://experience-aem.blogspot.com/2015/09/aem-61-touch-ui-rich-text-editor-rte-browse-insert-image.html


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.