AEM 65 - Touch UI RTE (Rich Text Editor) Dialog Color Picker Plugin

Goal


Touch UI Color Picker Plugin for RTE (Rich Text Editor) Dialog - /libs/cq/gui/components/authoring/dialog/richtext

For a similar extension on 64 check this post 63 check this post, 62 check this post, 61 check this post

In Core Components 2.8.0 uiSettings/cui/dialogFullScreen was removed and the plugin settings were moved to Template Policies, for making the RTE Color plugin work with Core Components 2.8.0 check this post

For demo purposes, dialog of core text component v2 was modified to add the color picker configuration - /apps/core/wcm/components/text/v2/text/cq:dialog/content/items/tabs/items/properties/items/columns/items/column/items/text/rtePlugins

Demo | Package Install | Github


Plugin Configuration

                                         Add node experience-aem under rtePlugins and set features=* 



                                         Add experience-aem#colorPicker to uiSettings > cui > dialogFullScreen for showing the color palette toolbar icon in full screen




RTE Toolbar Icon




Color Picker in Full Screen


Color applied to text




Text with Color in CRX



Solution


1) Login to CRXDE Lite, add nt:folder /apps/eaem-touchui-dialog-rte-color-picker

2) To show the color picker in a dialog create /apps/eaem-touchui-dialog-rte-color-picker/color-picker-popover of type sling:Folder and /apps/eaem-touchui-dialog-rte-color-picker/color-picker-popover/cq:dialog of type nt:unstructured

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" 
             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"
    jcr:title="Color Picker"
    sling:resourceType="cq/gui/components/authoring/dialog">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/foundation/container">
        <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">
                    <picker
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
                        name="./color">
                        <items jcr:primaryType="nt:unstructured">
                            <red
                                jcr:primaryType="nt:unstructured"
                                name="Red"
                                value="#FF0000"/>
                            <green
                                jcr:primaryType="nt:unstructured"
                                name="Green"
                                value="#00FF00"/>
                            <blue
                                jcr:primaryType="nt:unstructured"
                                name="Blue"
                                value="#0000FF"/>
                            <black
                                jcr:primaryType="nt:unstructured"
                                name="Black"
                                value="#000000"/>
                        </items>
                    </picker>
                    <add
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/foundation/button"
                        class="coral-Button--primary"
                        id="EAEM_CP_ADD_COLOR"
                        text="Add Color"/>
                    <remove
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/foundation/button"
                        class="coral-Button--warning"
                        id="EAEM_CP_REMOVE_COLOR"
                        text="Remove Color"/>
                </items>
            </column>
        </items>
    </content>
</jcr:root>


                           
3) Create clientlib (cq:ClientLibraryFolder) /apps/eaem-touchui-dialog-rte-color-picker/clientlib set property categories to cq.authoring.dialog.all and dependencies to [underscore]

4) Create file (nt:file) /apps/eaem-touchui-dialog-rte-color-picker/clientlib/js.txt, add the following content

                                    color-picker.js


5) Create file (nt:file) /apps/eaem-touchui-dialog-rte-color-picker/clientlib/color-picker.js, add the following code

(function($, CUI){
    var GROUP = "experience-aem",
        COLOR_PICKER_FEATURE = "colorPicker",
        TCP_DIALOG = "eaemTouchUIColorPickerDialog",
        PICKER_NAME_IN_POPOVER = "color",
        REQUESTER = "requester",
        PICKER_URL = "/apps/eaem-touchui-dialog-rte-color-picker/color-picker-popover/cq:dialog.html",
        url = document.location.pathname;

    if( url.indexOf(PICKER_URL) !== 0 ){
        addPluginToDefaultUISettings();
        addDialogTemplate();
    }

    var EAEMColorPickerDialog = new Class({
        extend: CUI.rte.ui.cui.AbstractDialog,

        toString: "EAEMColorPickerDialog",

        initialize: function(config) {
            this.exec = config.execute;
        },

        getDataType: function() {
            return TCP_DIALOG;
        }
    });

    var TouchUIColorPickerPlugin = new Class({
        toString: "TouchUIColorPickerPlugin",

        extend: CUI.rte.plugins.Plugin,

        pickerUI: null,

        getFeatures: function() {
            return [ COLOR_PICKER_FEATURE ];
        },

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

            if (!this.isFeatureEnabled(COLOR_PICKER_FEATURE)) {
                return;
            }

            this.pickerUI = tbGenerator.createElement(COLOR_PICKER_FEATURE, this, false, { title: "Color Picker" });
            tbGenerator.addElement(GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 10);

            var groupFeature = GROUP + "#" + COLOR_PICKER_FEATURE;
            tbGenerator.registerIcon(groupFeature, "textColor");
        },

        execute: function (id, value, envOptions) {
            if(!isValidSelection()){
                return;
            }

            var context = envOptions.editContext,
                selection = CUI.rte.Selection.createProcessingSelection(context),
                ek = this.editorKernel,
                startNode = selection.startNode;

            if ( (selection.startOffset === startNode.length) && (startNode != selection.endNode)) {
                startNode = startNode.nextSibling;
            }

            var tag = CUI.rte.Common.getTagInPath(context, startNode, "span"), plugin = this, dialog,
                color = $(tag).css("color"),
                dm = ek.getDialogManager(),
                $container = CUI.rte.UIUtils.getUIContainer($(context.root)),
                propConfig = {
                    'parameters': {
                        'command': this.pluginId + '#' + COLOR_PICKER_FEATURE
                    }
                };

            if(this.eaemColorPickerDialog){
                dialog = this.eaemColorPickerDialog;
            }else{
                dialog = new EAEMColorPickerDialog();

                dialog.attach(propConfig, $container, this.editorKernel);

                dialog.$dialog.css("-webkit-transform", "scale(0.9)").css("-webkit-transform-origin", "0 0")
                    .css("-moz-transform", "scale(0.9)").css("-moz-transform-origin", "0px 0px");

                dialog.$dialog.find("iframe").attr("src", getPickerIFrameUrl(color));

                this.eaemColorPickerDialog = dialog;
            }

            dm.show(dialog);

            registerReceiveDataListener(receiveMessage);

            function isValidSelection(){
                var winSel = window.getSelection();
                return winSel && winSel.rangeCount == 1 && winSel.getRangeAt(0).toString().length > 0;
            }

            function getPickerIFrameUrl(color){
                var url = PICKER_URL + "?" + REQUESTER + "=" + GROUP;

                if(!_.isEmpty(color)){
                    url = url + "&" + PICKER_NAME_IN_POPOVER + "=" + color;
                }

                return url;
            }

            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);
                }
            }

            function receiveMessage(event) {
                if (_.isEmpty(event.data)) {
                    return;
                }

                var message = JSON.parse(event.data),
                    action;

                if (!message || message.sender !== GROUP) {
                    return;
                }

                action = message.action;

                if (action === "submit") {
                    if (!_.isEmpty(message.data)) {
                        ek.relayCmd(id, message.data);
                    }
                }else if(action === "remove"){
                    ek.relayCmd(id);
                    plugin.eaemColorPickerDialog = null;
                }else if(action === "cancel"){
                    plugin.eaemColorPickerDialog = null;
                }

                dialog.hide();

                removeReceiveDataListener(receiveMessage);
            }
        },

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

            if (this.pickerUI != null) {
                this.pickerUI.setSelected(hasUC);
            }
        }
    });

    CUI.rte.plugins.PluginRegistry.register(GROUP,TouchUIColorPickerPlugin);

    var TouchUIColorPickerCmd = new Class({
        toString: "TouchUIColorPickerCmd",

        extend: CUI.rte.commands.Command,

        isCommand: function(cmdStr) {
            return (cmdStr.toLowerCase() == COLOR_PICKER_FEATURE);
        },

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

        _getTagObject: function(color) {
            return {
                "tag": "span",
                "attributes": {
                    "style" : "color: " + color
                }
            };
        },

        execute: function (execDef) {
            var color = execDef.value ? execDef.value[PICKER_NAME_IN_POPOVER] : undefined,
                selection = execDef.selection,
                nodeList = execDef.nodeList;

            if (!selection || !nodeList) {
                return;
            }

            var common = CUI.rte.Common,
                context = execDef.editContext,
                tagObj = this._getTagObject(color);

            //if no color value passed, assume delete and remove color
            if(_.isEmpty(color)){
                nodeList.removeNodesByTag(execDef.editContext, tagObj.tag, undefined, true);
                return;
            }

            var tags = common.getTagInPath(context, selection.startNode, tagObj.tag);

            //remove existing color before adding new color
            if (tags != null) {
                nodeList.removeNodesByTag(execDef.editContext, tagObj.tag, undefined, true);
                nodeList.commonAncestor = nodeList.nodes[0].dom.parentNode;
            }

            nodeList.surround(execDef.editContext, tagObj.tag, tagObj.attributes);
        }
    });

    CUI.rte.commands.CommandRegistry.register(COLOR_PICKER_FEATURE, TouchUIColorPickerCmd);

    function addPluginToDefaultUISettings(){
        var toolbar = CUI.rte.ui.cui.DEFAULT_UI_SETTINGS.inline.toolbar;
        toolbar.splice(3, 0, GROUP + "#" + COLOR_PICKER_FEATURE);

        toolbar = CUI.rte.ui.cui.DEFAULT_UI_SETTINGS.fullscreen.toolbar;
        toolbar.splice(3, 0, GROUP + "#" + COLOR_PICKER_FEATURE);
    }

    function addDialogTemplate(){
        var url = PICKER_URL + "?" + REQUESTER + "=" + GROUP;

        var html = "<iframe width='600px' height='500px' frameBorder='0' src='" + url + "'></iframe>";

        if(_.isUndefined(CUI.rte.Templates)){
            CUI.rte.Templates = {};
        }

        if(_.isUndefined(CUI.rte.templates)){
            CUI.rte.templates = {};
        }

        CUI.rte.templates['dlg-' + TCP_DIALOG] = CUI.rte.Templates['dlg-' + TCP_DIALOG] = Handlebars.compile(html);
    }
}(jQuery, window.CUI,jQuery(document)));

(function($, $document){
    var SENDER = "experience-aem",
        REQUESTER = "requester",
        COLOR = "color",
        ADD_COLOR_BUT = "#EAEM_CP_ADD_COLOR",
        REMOVE_COLOR_BUT = "#EAEM_CP_REMOVE_COLOR";

    if(queryParameters()[REQUESTER] !== SENDER ){
        return;
    }

    $(function(){
        _.defer(stylePopoverIframe);
    });

   function queryParameters() {
        var result = {}, param,
            params = document.location.search.split(/\?|\&/);

        params.forEach( function(it) {
            if (_.isEmpty(it)) {
                return;
            }

            param = it.split("=");
            result[param[0]] = param[1];
        });

        return result;
    }

    function stylePopoverIframe(){
        var queryParams = queryParameters(),
            $dialog = $("coral-dialog");

        if(_.isEmpty($dialog)){
            return;
        }

        $dialog.css("overflow", "hidden").css("background-color", "#fff");

        $dialog[0].open = true;

        var $addColor = $dialog.find(ADD_COLOR_BUT),
            $removeColor = $dialog.find(REMOVE_COLOR_BUT),
            color = queryParameters()[COLOR],
            $colorPicker = $document.find("coral-colorinput");

        if(!_.isEmpty(color)){
            color = decodeURIComponent(color);

            if(color.indexOf("rgb") == 0){
                color = CUI.util.color.RGBAToHex(color);
            }

            $colorPicker[0].value = color;
        }

        adjustHeader($dialog);

        $colorPicker.css("margin-bottom", "285px");

        $(ADD_COLOR_BUT).css("margin-left", "220px");

        $addColor.click(sendDataMessage);

        $removeColor.click(sendRemoveMessage);
    }

    function adjustHeader($dialog){
        var $header = $dialog.css("background-color", "#fff").find(".coral3-Dialog-header");

        $header.find(".cq-dialog-submit").remove();

        $header.find(".cq-dialog-cancel").click(function(event){
            event.preventDefault();

            $dialog.remove();

            sendCancelMessage();
        });
    }

    function sendCancelMessage(){
        var message = {
            sender: SENDER,
            action: "cancel"
        };

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

    function sendRemoveMessage(){
        var message = {
            sender: SENDER,
            action: "remove"
        };

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

    function sendDataMessage(){
        var message = {
            sender: SENDER,
            action: "submit",
            data: {}
        }, $dialog, color;

        $dialog = $(".cq-dialog");

        color = $dialog.find("[name='./" + COLOR + "']").val();

        if(color && color.indexOf("rgb") >= 0){
            color = CUI.util.color.RGBAToHex(color);
        }

        message.data[COLOR] = color;

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


16 comments:

  1. We are having the an issue on 6.5 of Uncaught TypeError: Handlebars.compile is not a function. Is there an include or something we're missing?

    ReplyDelete
    Replies
    1. We had to add "handlebars" as a dependency in the clientlib, in addition to underscore.

      Delete
    2. Hi Metamort, did you change anything else? I am using 6.5 and tried to add Hadlebars and underscore but still not working for me.

      Delete
  2. Hi,
    Thanks for the document provided. it is working fine in aem 6.4 but the only problem is,
    It is storing as RGB(100,1,1) values if we change color. is it possible to change this.
    Thanks

    ReplyDelete
  3. Nice article. It's very helpful to me. Thank you. Please check my password generator.

    ReplyDelete
  4. Hi,

    This was working great. Core Components 2.8 removed uiSettings > cui > dialogFullScreen and moved the RTE plugin settings to Component Policies.
    https://github.com/adobe/aem-core-wcm-components/issues/158

    How do you update the settings so the icon shows up on the tool bar in full screen mode of the RTE with Core Components 2.8?

    ReplyDelete
    Replies
    1. hi - check https://experience-aem.blogspot.com/2020/01/aem-6530-core-components-280-touch-ui-rte-rich-text-editor-dialog-color-font-plugin.html

      Delete
  5. Great Article! I feel very thankful after reading this post on Rich Text Editor

    ReplyDelete
  6. Hi Sreekanth,

    Thank you for writing this article. Please can you advise me if the this works on a clean installation of AEM 6.5.2.0 with "We-Retail" sample content ?

    I have tried following the steps below and I cannot see the Color Picker icon in the Rich Text Editor ?

    (1) Navigate to http://localhost:4502/crx/de/index.jsp#/apps/core/wcm/components/text/v2/text/cq%3Adialog/content/items/tabs/items/properties/items/columns/items/column/items/text/rtePlugins
    (2) Create node "experience-aem"
    (3) Navigate to http://localhost:4502/crx/de/index.jsp#/apps/core/wcm/components/text/v2/text/cq%3Adialog/content/items/tabs/items/properties/items/columns/items/column/items/text/rtePlugins/experience-aem
    (4) Add property "features" with value "*"
    (5) Navigate to http://localhost:4502/crx/de/index.jsp#/apps/core/wcm/components/text/v2/text/cq%3Adialog/content/items/tabs/items/properties/items/columns/items/column/items/text/uiSettings/cui/dialogFullScreen
    (6) Add value "experience-aem#colorPicker" to existing property "toolbar"
    (7) Navigate to http://localhost:4502/crx/packmgr/index.jsp
    (8) Upload and install package "eaem-touchui-dialog-rte-color-picker-65.zip"
    (9) Navigate to http://localhost:4502/crx/de/index.jsp#/apps/eaem-touchui-dialog-rte-color-picker
    (10) Verify this contains folders "clientlib" and "color-picker-popover"
    (11) Navigate to http://localhost:4502/editor.html/content/we-retail/language-masters/en.html
    (12) Add a Text component
    (13) Select Text component , click "Edit" and "Full screen"

    Please can you advise me if there are any additional steps needed to get the Rich Text Editor Color Picker working on a clean installation of AEM 6.5.2.0 with "We-Retail" sample content ?

    Thank you for your time and advise,
    Best regards,
    James Collett

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Hi,
    we have implemented this color-picker plugin,
    Issue: Color picker option in Rich text editor disappears when any other components are opened first on the page. But once the page was refreshed and we open the rich text component we can see the color picker.
    We are seeing this issue post 6.5 upgrade can anyone please help us here.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Nicely described. I am happy after read your post and seen the opinion about Rich Text Editor is really helpful for me.

    Rich Text Editor

    ReplyDelete
  11. Hi Sreekanth

    Thanks for this plugin . We are using this in our project. The issue we are having is in the XF pages, the plugin is throwing Handlebars.compile is not a function error. I tried adding handlebars as additional dependencies value , but it didn't solve the issue. Any idea ?

    TIA
    Veena

    ReplyDelete
  12. Hello,

    I insterded this plugin in my project, it works fine in dialog and fullscreen editors, but it doesn't work on inline editor.
    I don't know if it is only my problem or the plugin itself. Is this plugin supposed to work even in the inline editor or only in fullscreen and dialog?

    Thanks in advance.

    ReplyDelete