AEM 64 - 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 63 check this post, 62 check this post, 61 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









Color Picker



Color applied to text







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";

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



8 comments:

  1. Color Picker plugin has a bug. When I open a dialog of another component and save my modify the REFRESH_PAGE is triggered. At this point, if I open my rich text the color Picker plugin disappear, but when I refresh the page it is visibile again. Any solutions?

    ReplyDelete
  2. using the plugin in aem 6.5 getting Uncaught TypeError: Handlebars.compile is not a function. Can you provide any fix for this

    ReplyDelete
    Replies
    1. for 65 - https://experience-aem.blogspot.com/2019/04/aem-65-touch-ui-rte-rich-text-editor-dialog-color-picker-plugin.html

      Delete
  3. Hi all,
    Even we are getting the same error after the upgrade to AEM 6.4.8.2. Please help

    Uncaught ReferenceError: _ is not defined
    at addDialogTemplate (rteCustomPlugins.3b77f1c5eca0b443a7f2cec89fe4ec4f.js:247)
    at rteCustomPlugins.3b77f1c5eca0b443a7f2cec89fe4ec4f.js:11
    at rteCustomPlugins.3b77f1c5eca0b443a7f2cec89fe4ec4f.js:257

    ReplyDelete
    Replies
    1. So did you get the fix for this.

      Even i faced the same issue as i upgraded from 6.4.2 to 6.4.8 service pack.I think it is not able to find the "underscore dependency" in the AEM (Service pack 6.4.8) that 'y it throwing error. I tried to add same dependency directly in the style-picker.js and its working as expected but the behavior is not consistent, So whenever i am closing the style dialog it's throwing error apart from this it is working as expected.

      Delete
    2. I have fixed this issue. If anyone facing this kind of issue just let me know. I will tell the fix for the same.

      Delete
    3. Hi, I have the same issue, can you tell me how you have fixed?

      Delete
  4. Hi Keshav. I'm facing the same issue (_ is undefined). Can you please help me the fix?

    ReplyDelete