AEM 6 SP1 - TouchUI Richtext Editor Color Picker Plugin

Goal


For a better implementation check this post

Create a Color Picker Plugin (CUI.Colorpicker) for the Touch UI Rich Text Editor (RTE). A Color Picker plugin for Classic UI RTE is available here

Demo shows dialog of foundation text component (/libs/foundation/components/text/dialog/items/tab1/items/text/rtePlugins) modified to add the color picker config. This is just for demonstration only (on Geometrixx pages), ideally the foundation components should never be altered...

Please leave a comment if you find bug / fix...

Demo | Package Install


Picker Configuration

Configure the palette colors in rtePlugins touchuicolorpicker node property colors as String[]


Inline View

Full Screen View



Color applied to text wrapped in span tag



Text in CRX node



Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/rte-touch-ui-color-picker

2) Create clientlib (type cq:ClientLibraryFolder/apps/rte-touch-ui-color-picker/clientlib and set property categories of String type to rte.coralui2

3) Create file ( type nt:file ) /apps/rte-touch-ui-color-picker/clientlib/css.txt, add the following

                         color-picker.css

4) Create file (type nt:file) /apps/rte-touch-ui-color-picker/clientlib/color-picker.css, add the following code. This is palette button (available in font-family: font-family: AdobeIcons)

.eaem-touchui-color-picker::before {
    content: "\f156";
}

5) Create file ( type nt:file ) /apps/rte-touch-ui-color-picker/clientlib/js.txt, add the following

                         color-picker.js

6) Create file (type nt:file) /apps/rte-touch-ui-color-picker/clientlib/color-picker.js, add the following code

(function(){
    var ExperienceAEM = {
        TCP_UI_SETTING: "touchuicolorpicker#touchuicolorpicker",
        TCP_FEATURE: "touchuicolorpicker",
        TCP_DIALOG: "touchuicolorpickerdialog"
    };

    ExperienceAEM.CuiToolbarBuilder = new Class({
        toString: "EAEMCuiToolbarBuilder",

        extend: CUI.rte.ui.cui.CuiToolbarBuilder,

        _getUISettings: function(options) {
            var uiSettings = this.superClass._getUISettings(options);

            var items = uiSettings["inline"]["popovers"]["format"].items;

            if(items.indexOf(ExperienceAEM.TCP_UI_SETTING) == -1){
                items.push(ExperienceAEM.TCP_UI_SETTING);
            }

            items = uiSettings["fullscreen"]["toolbar"];

            if(items.indexOf(ExperienceAEM.TCP_UI_SETTING) == -1){
                items.splice(3, 0, ExperienceAEM.TCP_UI_SETTING);
            }

            //add the color picker css to ui settings of toolbar
            if(!this._getClassesForCommand(ExperienceAEM.TCP_UI_SETTING)){
                this.registerAdditionalClasses(ExperienceAEM.TCP_UI_SETTING, "coral-Icon eaem-touchui-color-picker");
            }

            return uiSettings;
        }
    });

    //the popover dialog
    ExperienceAEM.ColorPickerDialog = new Class({
        extend: CUI.rte.ui.cui.AbstractBaseDialog,

        toString: "EAEMColorPickerDialog",

        initialize: function(config) {
            //exec function passes the color value to plugin command
            this.exec = config.execute;
        },

        getDataType: function() {
            return ExperienceAEM.TCP_DIALOG;
        },

        attach: function(config, $container, editorKernel) {
            this.superClass.attach.call(this,config, $container, editorKernel);

            var self = this;

            //to removed previously selected color
            this.$dialog.on("click.rte-dialog", "button[data-type=\"delete\"]",
                function(e) {
                    self.colorPicker.$element.removeAttr("value");
                    self.apply();
                    e.stopPropagation();
                }
            );
        },

        apply: function() {
            this.hide();

            if(!this.colorPicker){
                return;
            }

            var $selection = this.colorPicker.$element.find(".colorpicker-holder").find(".selection");

            var hexCode = $selection.length > 0 ? $selection.find("span:last-child").html() : undefined;

            //pass the color value to command
            this.exec(hexCode);
        },

        cancel: function() {
            this.hide();
        }
    });

    //extend the CUI dialog manager to register color picker dialog
    ExperienceAEM.DialogManager = new Class({
        toString: "EAEMDialogManager",

        extend: CUI.rte.ui.cui.CuiDialogManager,

        create: function(dialogId, config) {
            if(dialogId !== ExperienceAEM.TCP_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.ColorPickerDialog();
            dialog.attach(config, $container, this.editorKernel);

            return dialog;
        }
    });

    //extend CUI toolkit impl to create instances of extended toolbar builder and dialog manager
    ExperienceAEM.ToolkitImpl = new Class({
        toString: "EAEMCuiToolbarBuilder",

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

    //the color picker plugin for touch ui
    ExperienceAEM.TouchUIColorPickerPlugin = new Class({
        toString: "TouchUIColorPickerPlugin",

        extend: CUI.rte.plugins.Plugin,

        pickerUI: null,

        getFeatures: function() {
            return [ ExperienceAEM.TCP_FEATURE ];
        },

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

            //add the color picker
            if (this.isFeatureEnabled(ExperienceAEM.TCP_FEATURE)) {
                this.pickerUI = tbGenerator.createElement(ExperienceAEM.TCP_FEATURE, this, true, "Color Picker");
                tbGenerator.addElement("format", plg.Plugin.SORT_FORMAT, this.pickerUI, 140);
            }
        },

        //executes when user clicks on color picker button to open the picker dialog
        execute: function(id, value, envOptions) {
            var ek = this.editorKernel;
            var dm = ek.getDialogManager();

            if (dm.isShown(this.dialog)) {
                dm.hide(this.dialog);
                return;
            }

            var dialogConfig = {
                execute: function(value) {
                    ek.relayCmd(id, value);
                },
                parameters : {
                    "command": ExperienceAEM.TCP_UI_SETTING
                }
            };

            //create or get existing dialog
            this.dialog = dm.create(ExperienceAEM.TCP_DIALOG, dialogConfig);

            dm.prepareShow(this.dialog);

            dm.show(this.dialog);

            if(!this.dialog.colorPicker){
                //default colors if the colors are not configured for plugin in crx
                var colors = { "White" : "FFFFFF", "Yellow" : "FFFF00" };

                if(this.config.colors){
                    colors = this.config.colors;
                }

                var options = {
                    element : $('[data-rte-dialog="' + ExperienceAEM.TCP_DIALOG + '"] .coral-ColorPicker'),
                    config : {
                        colors: colors,
                        displayModes : {
                            "freestylePalette" : true,
                            "edit" : false
                        }
                    }
                };

                //create the picker
                this.dialog.colorPicker = new CUI.Colorpicker(options);
            }

            var context = envOptions.editContext;

            var selection = CUI.rte.Selection.createProcessingSelection(context);
            var tag = CUI.rte.Common.getTagInPath(context, selection.startNode, "span" );

            //get existing color to initialize picker
            var color = $(tag).css("color");

            if(color){
                this.dialog.colorPicker._setColor(color);
            }
        },

        updateState: function(selDef) {
            var hasColorPicker = this.editorKernel.queryState(ExperienceAEM.TCP_FEATURE, selDef);

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

    CUI.rte.plugins.PluginRegistry.register(ExperienceAEM.TCP_FEATURE,
                                                ExperienceAEM.TouchUIColorPickerPlugin);

    //the command for making text colored
    ExperienceAEM.ColorPickerCmd = new Class({
        toString: "ColorPickerCmd",

        extend: CUI.rte.commands.Command,

        isCommand: function(cmdStr) {
            return (cmdStr.toLowerCase() == ExperienceAEM.TCP_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 selection = execDef.selection;

            if (!selection) {
                return;
            }

            var nodeList = execDef.nodeList;

            if (!nodeList) {
                return;
            }

            var common = CUI.rte.Common;
            var context = execDef.editContext;

            var tagObj = this._getTagObject(execDef.value);

            //if no color value passed, assume delete and remove color
            if(!execDef.value){
                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);
        },

        queryState: function(selectionDef) {
            var common = CUI.rte.Common;
            var context = selectionDef.editContext;

            var selection = selectionDef.selection;
            var tagObj = this._getTagObject();

            return (common.getTagInPath(context, selection.startNode, tagObj.tag, tagObj.attributes) != null);
        }
    });

    CUI.rte.commands.CommandRegistry.register(ExperienceAEM.TCP_FEATURE, ExperienceAEM.ColorPickerCmd);

    //returns the picker dialog html
    //Handlebars doesn't do anything useful here, but the framework expects a template
    var cpTemplate = function(){
        CUI.rte.Templates["dlg-" + ExperienceAEM.TCP_DIALOG] =
            Handlebars.compile('<div data-rte-dialog="' + ExperienceAEM.TCP_DIALOG + '" class="coral--dark coral-Popover coral-RichText-dialog">'
                + '<div class="coral-RichText-dialog-columnContainer">'
                    + '<div class="coral-RichText-dialog-column">'
                    +   '<label class="coral-Form-fieldlabel">Select color </label>'
                    + '</div>'
                    + '<div class="coral-RichText-dialog-column">'
                        + '<span  class="coral-Form-field coral-ColorPicker">'
                            + '<button class="coral-ColorPicker-button coral-MinimalButton" type="button"></button>'
                        + '</span>'
                    + '</div>'
                    + '<div class="coral-RichText-dialog-column">'
                        + '<button data-type="apply" class="coral-RichText-dialogButton coral-Icon coral-Icon--check coral-Icon--sizeS coral-RichText--white coral-Button--primary"></button>'
                    + '</div>'
                    + '<div class="coral-RichText-dialog-column">'
                        + '<button data-type="cancel" class="coral-RichText-dialogButton coral-Icon coral-Icon--close coral-Icon--sizeS coral-RichText--white"></button>'
                    + '</div>'
                    + '<div class="coral-RichText-dialog-column">'
                        + '<button data-type="delete" class="coral-RichText-dialogButton coral-Icon coral-Icon--delete coral-Icon--sizeS coral-RichText--white coral-Button--warning"></button>'
                    + '</div>'
                + '</div>'
            + '</div>');
    };

    cpTemplate();
})();


7) Add any text component with RichText editor and in the rtePlugins path of dialog add touchuicolorpicker node to enable color picker plugin

4 comments:

  1. if colors are more than one page, all pages show colors of the first page.

    ReplyDelete
    Replies
    1. James, can you elaborate... how many colors are there in your configuration?

      Delete
  2. Sreekanth, adding color to several texts without closing RTE is not possible as color-picker always shows tick mark (already selected).
    for example:
    1. open RTE
    2. select text
    3. add color using color-picker
    4. select another text
    5. try to add color

    ReplyDelete
  3. i am getting error as
    Uncaught TypeError: Cannot read property 'addClass' of null
    at el.child('a.color-'+color).addClass('x-color-palette-sel');

    ReplyDelete