AEM 6530 - Core Components 280 - Touch UI RTE (Rich Text Editor) Dialog Color Font Plugin

Goal


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

In Core Components 2.8.0 uiSettings/cui/dialogFullScreen was removed and the plugin settings were moved to Template (Component) Policies, this post is for making the Font & Color plugin work with Core Components 2.8.0

For a similar extension on 65 and older Core Components check this post 64 check this post 63 check this post

For demo purposes, design dialog of core text component v2 was modified to add the font picker configuration - /apps/core/wcm/components/text/v2/text/cq:design_dialog/content/items/tabs/items/plugins/items/experience-aem-fonts

Demo | Package Install | Github


Plugin Configuration in Design Dialog





Plugin Config in Template Policy





Plugin Picker



Font and Color








Solution


1) Login to CRXDE Lite, add nt:folder /apps/eaem-fonts-plugin

2) To show the color picker in a dialog create cq:Page /apps/eaem-fonts-plugin/font-selector and add  eaem.rte.font.plugin categories in /apps/eaem-fonts-plugin/font-selector/jcr:content/head/clientlibs

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/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="cq:Page">
    <jcr:content
        jcr:mixinTypes="[sling:VanityPath]"
        jcr:primaryType="nt:unstructured"
        jcr:title="EAEM Font Selector"
        sling:resourceType="granite/ui/components/coral/foundation/page">
        <head jcr:primaryType="nt:unstructured">
            <favicon
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/page/favicon"/>
            <viewport
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/admin/page/viewport"/>
            <clientlibs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/includeclientlibs"
                categories="[coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral, eaem.rte.font.plugin]"/>
        </head>
        <body
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/page/body">
            <items jcr:primaryType="nt:unstructured">
                <form
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/form"
                    class="foundation-form content-container"
                    maximized="{Boolean}true"
                    style="vertical">
                    <items jcr:primaryType="nt:unstructured">
                        <wizard
                            jcr:primaryType="nt:unstructured"
                            jcr:title="Select Text Font Color..."
                            sling:resourceType="granite/ui/components/coral/foundation/wizard">
                            <items jcr:primaryType="nt:unstructured">
                                <text
                                    jcr:primaryType="nt:unstructured"
                                    jcr:title="Select Text Font Color..."
                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                    <items jcr:primaryType="nt:unstructured">
                                        <fixedColumns
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
                                            margin="{Boolean}true">
                                            <items jcr:primaryType="nt:unstructured">
                                                <column
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/container">
                                                    <items jcr:primaryType="nt:unstructured">
                                                        <fontSize
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                                            fieldDescription="Select text size"
                                                            fieldLabel="Size"
                                                            name="./size">
                                                            <items jcr:primaryType="nt:unstructured">
                                                                <def
                                                                    jcr:primaryType="nt:unstructured"
                                                                    text="Select Size"
                                                                    value=""/>
                                                                <small
                                                                    jcr:primaryType="nt:unstructured"
                                                                    text="Small (15px)"
                                                                    value="15px"/>
                                                                <medium
                                                                    jcr:primaryType="nt:unstructured"
                                                                    text="Medium (30px)"
                                                                    value="30px"/>
                                                                <large
                                                                    jcr:primaryType="nt:unstructured"
                                                                    text="Large (40px)"
                                                                    value="40px"/>
                                                            </items>
                                                        </fontSize>
                                                        <color
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
                                                            fieldDescription="Select text color"
                                                            fieldLabel="Text Color"
                                                            name="./color"
                                                            showProperties="{Boolean}true"/>
                                                        <bgColor
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
                                                            fieldDescription="Select background color"
                                                            fieldLabel="Background Color"
                                                            name="./bgColor"
                                                            showProperties="{Boolean}true"/>
                                                    </items>
                                                </column>
                                            </items>
                                        </fixedColumns>
                                    </items>
                                    <parentConfig jcr:primaryType="nt:unstructured">
                                        <prev
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/anchorbutton"
                                            text="Cancel">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="cancel"/>
                                        </prev>
                                        <next
                                            granite:class="foundation-wizard-control"
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/coral/foundation/button"
                                            disabled="{Boolean}true"
                                            text="Apply"
                                            type="submit"
                                            variant="primary">
                                            <granite:data
                                                jcr:primaryType="nt:unstructured"
                                                foundation-wizard-control-action="next"/>
                                        </next>
                                    </parentConfig>
                                </text>
                            </items>
                        </wizard>
                    </items>
                </form>
            </items>
        </body>
    </jcr:content>
</jcr:root>


3) Create clientlib (cq:ClientLibraryFolder) /apps/eaem-fonts-plugin/clientlib set property categories to [cq.authoring.dialog.all, eaem.rte.font.plugin] and dependencies to [lodash]

4) Create file (nt:file) /apps/eaem-fonts-plugin/clientlib/js.txt, add the following content

                                    eaem-fonts.js


5) Create file (nt:file) /apps/eaem-fonts-plugin/clientlib/eaem-fonts.js, add the following code

(function($, CUI, $document){
    var GROUP = "experience-aem-fonts",
        FONT_FEATURE = "applyFont",
        FONT_SIZE_FEATURE = "fontSize",
        TEXT_COLOR_FEATURE = "textColor",
        TEXT_BG_COLOR_FEATURE = "textBackgroundColor",
        EAEM_APPLY_FONT_DIALOG = "eaemTouchUIApplyFontDialog",
        SENDER = "experience-aem", REQUESTER = "requester", $eaemFontPicker,
        CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
        FONT_SELECTOR_URL = "/apps/eaem-fonts-plugin/font-selector.html",
        url = document.location.pathname;

    if( url.indexOf(FONT_SELECTOR_URL) == 0 ){
        handlePicker();
        return;
    }

    function handlePicker(){
        $document.on("foundation-contentloaded", fillDefaultValues);

        $document.on("click", CANCEL_CSS, sendCancelMessage);

        $document.submit(sentTextAttributes);
    }

    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 setWidgetValue(form, selector, value, enable){
        Coral.commons.ready(form.querySelector(selector), function (field) {
            field.value = _.isEmpty(value) ? "" : decodeURIComponent(value);

            if(enable){
                delete field.disabled;
            }else{
                field.disabled = "disabled";
            }
        });
    }

    function fillDefaultValues(){
        var queryParams = queryParameters(),
            $form = $("form");

        if(_.isEmpty(queryParams.features)){
            return;
        }

        var features = queryParams.features.split(",");

        setWidgetValue($form[0], "[name='./color']", queryParams.color, features.includes(TEXT_COLOR_FEATURE));

        setWidgetValue($form[0], "[name='./size']", queryParams.size, features.includes(FONT_SIZE_FEATURE));

        setWidgetValue($form[0], "[name='./bgColor']", queryParams.bgColor, features.includes(TEXT_BG_COLOR_FEATURE));

        $form.css("background-color", "#fff");
    }

    function sentTextAttributes(){
        var message = {
            sender: SENDER,
            action: "submit",
            data: {}
        }, $form = $("form"), $field;

        _.each($form.find("[name^='./']"), function(field){
            $field = $(field);
            message.data[$field.attr("name").substr(2)] = $field.val();
        });

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

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

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

    function getParent() {
        if (window.opener) {
            return window.opener;
        }

        return parent;
    }

    addPlugin();

    addPluginToDefaultUISettings();

    addDialogTemplate();

    function addDialogTemplate(){
        var url = Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;

        var html = "<iframe width='700px' 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-' + EAEM_APPLY_FONT_DIALOG] = CUI.rte.Templates['dlg-' + EAEM_APPLY_FONT_DIALOG] = Handlebars.compile(html);
    }

    function rgbToHex(color){
        if(_.isEmpty(color)){
            return color;
        }

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

        return color;
    }

    function addPluginToDefaultUISettings(){
        var groupFeature = GROUP + "#" + FONT_FEATURE,
            toolbar = CUI.rte.ui.cui.DEFAULT_UI_SETTINGS.dialogFullScreen.toolbar;

        if(toolbar.includes(groupFeature)){
            return;
        }

        toolbar.splice(3, 0, groupFeature);
    }

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

        toString: "EAEMApplyFontDialog",

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

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

    function addPlugin(){
        var EAEMTouchUIFontPlugin = new Class({
            toString: "EAEMTouchUIFontPlugin",

            extend: CUI.rte.plugins.Plugin,

            pickerUI: null,

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

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

                addPluginToDefaultUISettings();

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

                this.pickerUI = tbGenerator.createElement(FONT_FEATURE, this, false, { title: "Select Font..." });
                tbGenerator.addElement(GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 10);

                var groupFeature = GROUP + "#" + FONT_FEATURE;
                tbGenerator.registerIcon(groupFeature, "abc");
            },

            execute: function (pluginCommand, value, envOptions) {
                var context = envOptions.editContext,
                    ek = this.editorKernel;

                if (pluginCommand != FONT_FEATURE) {
                    return;
                }

                if(!isValidSelection()){
                    return;
                }

                var selection = CUI.rte.Selection.createProcessingSelection(context),
                    startNode = selection.startNode;

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

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

                var color = this.getColorAttributes($tag);

                if(this.eaemApplyFontDialog){
                    dialog = this.eaemApplyFontDialog;

                    dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl(this.config.features, size, color.color, color.bgColor));
                }else{
                    dialog = new EAEMApplyFontDialog();

                    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", this.getPickerIFrameUrl(this.config.features, size, color.color, color.bgColor));

                    this.eaemApplyFontDialog = dialog;
                }

                dm.show(dialog);

                $(window).off('message', receiveMessage).on('message', receiveMessage);

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

                function receiveMessage(event) {
                    event = event.originalEvent || {};

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

                    var message, action;

                    try{
                        message = JSON.parse(event.data);
                    }catch(err){
                        return;
                    }

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

                    action = message.action;

                    if(action === "submit"){
                        ek.relayCmd(pluginCommand, message.data);
                    }

                    dialog.hide();
                }
            },

            getColorAttributes: function($tag){
                var key, color = { color: "", bgColor : ""};

                if(!$tag.attr("style")){
                    return color;
                }

                //donot use .css("color"), it returns default font color, if color is not set
                var parts = $tag.attr("style").split(";");

                _.each(parts, function(value){
                    value = value.split(":");

                    key = value[0] ? value[0].trim() : "";
                    value = value[1] ? value[1].trim() : "";

                    if(key == "color"){
                        color.color = rgbToHex(value);
                    }else if(key == "background-color"){
                        color.bgColor = rgbToHex(value);
                    }
                });

                return color;
            },

            showFontModal: function(url){
                var self = this, $iframe = $('<iframe>'),
                    $modal = $('<div>').addClass('eaem-cfm-font-size coral-Modal');

                $iframe.attr('src', url).appendTo($modal);

                $modal.appendTo('body').modal({
                    type: 'default',
                    buttons: [],
                    visible: true
                });

                $eaemFontPicker = $modal;

                $eaemFontPicker.eaemFontPlugin = self;

                $modal.nextAll(".coral-Modal-backdrop").addClass("cfm-coral2-backdrop");
            },

            getPickerIFrameUrl: function(features, size, color, bgColor){
                var url = Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;

                url = url + "&features=" + features.join(",");

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

                if(!_.isEmpty(bgColor)){
                    url = url + "&bgColor=" + encodeURIComponent(bgColor);
                }

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

                return url;
            },

            updateState: function(selDef) {
                var hasUC = this.editorKernel.queryState(FONT_FEATURE, selDef);

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

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

            extend: CUI.rte.commands.Command,

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

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

            getTagObject: function(textData) {
                var style = "";

                if(!_.isEmpty(textData.color)){
                    style = "color: " + textData.color + ";";
                }

                if(!_.isEmpty(textData.size)){
                    style = style + "font-size: " + textData.size + ";";
                }

                if(!_.isEmpty(textData.bgColor)){
                    style = style + "background-color: " + textData.bgColor;
                }

                return {
                    "tag": "span",
                    "attributes": {
                        "style" : style
                    }
                };
            },

            execute: function (execDef) {
                var textData = execDef.value, selection = execDef.selection,
                    nodeList = execDef.nodeList;

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

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

                if(_.isEmpty(textData.size) && _.isEmpty(textData.color) && _.isEmpty(textData.bgColor)){
                    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, tags.attributes ? tags.attributes : undefined, true);
                }

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

            queryState: function(selectionDef, cmd) {
                return false;
            }
        });

        CUI.rte.commands.CommandRegistry.register(FONT_FEATURE, EAEMTouchUIFontCmd);

        CUI.rte.plugins.PluginRegistry.register(GROUP,EAEMTouchUIFontPlugin);
    }
}(jQuery, window.CUI,jQuery(document)));

17 comments:

  1. Hi, I am using AEM 6.5 and trying to integrate this feature to RTE. I am not able to integrate this. I have installed the package and added 'Plugin Configuration in Design Dialog' and also 'Plugin Config in Template Policy'. However, I don't see option 'AEM Experience Fonts & Colors' while updating the template policy. Am I missing anything?

    ReplyDelete
  2. Hi there, can someone please let me know how to get 'ABC' icon for the font color picker. I am not able to get the icon. I am using AEM 6.5

    ReplyDelete
    Replies
    1. Hi,Same issue i am facing..

      Are u still having same issue or resolved?

      Delete
  3. Hi, I am using AEM 6.5 and trying to integrate this feature to RTE. As per above given instructions I have installed the package and added Plugin configurations. However, I don't see option color picker icon in the RTE rich text field.

    ReplyDelete
  4. Hi Sreekanth,

    Thank you for writing this article. Please can you advise me if 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 Font & Colour icon in the Rich Text Editor ?

    (1) Navigate to http://localhost:4502/crx/packmgr/index.jsp
    (2) Upload and install package "eaem-fonts-plugin-6530.zip"
    (3) Navigate to http://localhost:4502/crx/de/index.jsp#/apps/eaem-fonts-plugin
    (4) Verify this contains folder "clientlib" and page "font-selector"
    (5) Navigate to http://localhost:4502/crx/de/index.jsp#/apps/core/wcm/components/text/v2/text/cq%3Adesign_dialog/content/items/tabs/items/plugins/items
    (6) Verify this contains "experience-aem-fonts" as well as Features , Formatting , Paraformat , Characters
    (7) Navigate to http://localhost:4502/editor.html/conf/we-retail/settings/wcm/templates/hero-page/structure.html
    (8) Observe error at top of page saying "TEMPLATE EDITORThis template is not a draft anymore and might already be referenced by one or more pages. Editing the structure will affect all the pages referencing it."
    (9) Click "Page Policy"
    (10) In Page Policy , the right-hand column (Properties) contains tabs Properties and Styles , and not Plugins and Styles as shown in https://1.bp.blogspot.com/-bnmdzZhBq9c/Xi8RhvyX9cI/AAAAAAAA3cI/nJLXjAoDC8o2LXTQ7F_V5pDoM2S406wcQCLcBGAsYHQ/s1600/360%2B-%2B6530%2B-%2Bpolicy.png
    (11) Navigate to http://localhost:4502/crx/de/index.jsp#/conf/we-retail/settings/wcm/policies/weretail/components/content
    (12) The "content" node does not contain child node "text" as shown in https://1.bp.blogspot.com/-W1EeSIA01lQ/Xi8RfoR7OrI/AAAAAAAA3b0/zhgwNQxIulgwgibtO9Ri9U7Vg7epnKoDACLcBGAsYHQ/s1600/360%2B-%2B6530%2B-%2Bcrx%2Bsave.png

    Please can you advise me if there are any additional steps needed to get the Rich Text Editor Font & Colour 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
  5. Nice post on Rich Text Editor. I was checking continuously this blog and I am impressed! Thanks once again.

    ReplyDelete
  6. Hi, I am using AEM 6.5 and trying to integrate this feature to RTE. As per above given instructions I have installed the package and added Plugin configurations. However, I don't see option color picker icon in the RTE rich text field.

    ReplyDelete
  7. Same problem here, followed all the instructions but it's not appering the "abc" icon

    ReplyDelete
  8. It is working fine
    Check if you have created the rtePlugins/experience-aem-fonts under the text/cq:dialog node.
    /apps/core/wcm/components/text/v2/text/cq:dialog/content/items/tabs/items/properties/items/columns/items/column/items/text/rtePlugins/experience-aem-fonts
    {"jcr:primaryType":"nt:unstructured","features":"textColor,applyFont,fontSize,textBackgroundColor"}

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

    ReplyDelete
  10. I can see the "abc" option but I have got an error 404: font-selector.html not found!

    I have checked this file is not part of the package. Where that is come from?

    ReplyDelete
  11. has anyone made this work? I am unable to

    ReplyDelete
  12. Hi, I have made all the required changes as suggested, but while trying to click on apply button I'm getting an error "Uncaught ReferenceError: _ is not defined", and not able to apply color. can anyone please help me here

    ReplyDelete
  13. I have found that this site is very informative, interesting and very well written. keep up the nice high quality writing. Developmental Disabilities Consultant

    ReplyDelete
  14. Can We put more than one custom plugin in RTE ?

    ReplyDelete