Goal
Touch UI Color Picker Plugin for Dialog RTE (Rich Text Editor) - /libs/cq/gui/components/authoring/dialog/richtext
For a similar extension (Inplace editing) on 63 check this post 61 check this post
For demo purposes, dialog of foundation text component was modified to add the color picker configuration - /libs/foundation/components/text/cq:dialog/content/items/tabs/items/text/items/column/items/text/rtePlugins
Thank you Brett Birschbach for the firefox bug fix
Demo on Chrome | Demo on Firefox | Package Install
Plugin Configuration
Add Plugin to RTE Toolbar
Picker with Free Style Palette - Inline Dialog
Picker with Free Style Palette - Full Screen Dialog
Picker with Palette Shades - Classic
Palette Edit Mode
RTE text with Color
Solution
1) Login to CRXDE Lite, add nt:folder /apps/touchui-dialog-mini-rte-color-picker
2) To show the color picker in a dialog create /apps/touchui-dialog-mini-rte-color-picker/color-picker-popover of type sling:Folder and /apps/touchui-dialog-mini-rte-color-picker/color-picker-popover/cq:dialog of type nt:unstructured
3) XML representation of /apps/touchui-dialog-mini-rte-color-picker/color-picker-popover/cq:dialog
<?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="/apps/touchui-dialog-mini-rte-color-picker/color-picker" editType="{Boolean}true" freestylePaletteType="{Boolean}true" name="./color"> <colors 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"/> <brown jcr:primaryType="nt:unstructured" name="Brown" value="#996633"/> <orange jcr:primaryType="nt:unstructured" name="Orange" value="#FF7F00"/> <purple jcr:primaryType="nt:unstructured" name="Purple" value="#7F007F"/> <yellow jcr:primaryType="nt:unstructured" name="Yellow" value="#FFFF00"/> </colors> </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>
4) Colors shown in picker dialog are added in /apps/touchui-dialog-mini-rte-color-picker/color-picker-popover/cq:dialog/content/items/column/items/picker/colors
5) To workaround the pickerModes bug register a new color picker widget /apps/touchui-dialog-mini-rte-color-picker/color-picker/render.jsp extending ootb color picker widget /libs/granite/ui/components/foundation/form/colorpicker, add the following code
<%@ page import="com.adobe.granite.ui.components.Config" %> <%@include file="/libs/granite/ui/global.jsp" %> <% Config mCfg = cmp.getConfig(); String COLOR_PICKER_WRAPPER_ID = "eaem-color-picker-wrapper-" + mCfg.get("name", String.class).substring(2); %> <div id="<%=COLOR_PICKER_WRAPPER_ID%>"> <%--include ootb color picker--%> <sling:include resourceType="/libs/granite/ui/components/foundation/form/colorpicker"/> </div> <script> (function($){ var wrapper = $("#<%=COLOR_PICKER_WRAPPER_ID%>"), colorPicker = wrapper.find("[data-init='colorpicker']"); if(_.isEmpty(colorPicker)){ console.log("EAEM - color picker wrapper not found"); return; } //extend otb Colorpicker to workaround the pickerModes bug //in granite/ui/components/foundation/form/colorpicker/render.jsp //colorpickerJson.put("modes", pickerModes); should have been //colorpickerJson.put("pickerModes", pickerModes); var config = colorPicker.data("config"); config.pickerModes = config.modes; delete config.modes; colorPicker.attr("data-config", config); }(jQuery)); </script>
6) Create clientlib (cq:ClientLibraryFolder) /apps/touchui-dialog-mini-rte-color-picker/clientlib set property categories to rte.coralui2 and dependencies to [underscore]
7) Create file (nt:file) /apps/touchui-dialog-mini-rte-color-picker/clientlib/js.txt, add the following content
color-picker.js
8) Create file (nt:file) /apps/touchui-dialog-mini-rte-color-picker/clientlib/color-picker.js, add the following code
(function($, CUI){ var GROUP = "experience-aem", COLOR_PICKER_FEATURE = "colorPicker", COLOR_PICKER_MODAL_DIV = "eaem-color-picker", PICKER_NAME_IN_POPOVER = "color", REQUESTER = "requester", PICKER_URL = "/apps/touchui-dialog-mini-rte-color-picker/color-picker-popover/cq:dialog.html"; 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, true, "Color Picker"); tbGenerator.addElement(GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 120); var groupFeature = GROUP + "#" + COLOR_PICKER_FEATURE; tbGenerator.registerIcon(groupFeature, "coral-Icon coral-Icon--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"), content = this.getPickerIFrameContent($(tag).css("color")); this.addModalDiv(); var modal = new CUI.Modal({ element : '#' + COLOR_PICKER_MODAL_DIV, heading : "Pick a Color", content: content }); registerReceiveDataListener(receiveMessage); function isValidSelection(){ var winSel = window.getSelection(); return winSel && winSel.rangeCount == 1 && winSel.getRangeAt(0).toString().length > 0; } 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); } modal.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); } }, getModalHtml: function(){ return "<div class=\"coral-Modal-header\">" + "<h2 class=\"coral-Modal-title coral-Heading coral-Heading--2\"></h2>" + "<i class=\"coral-Modal-typeIcon coral-Icon coral-Icon--sizeS\"></i>" + "<button type=\"button\" " + "class=\"coral-MinimalButton coral-Modal-closeButton\" " + "data-dismiss=\"modal\">" + "<i class=\"coral-Icon coral-Icon--sizeXS coral-Icon--close " + "coral-MinimalButton-icon\"></i>" + "</button>" + "</div>" + "<div class=\"coral-Modal-body legacy-margins\"></div>"; }, addModalDiv: function(){ var $modalDiv = $("#" + COLOR_PICKER_MODAL_DIV); if (!_.isEmpty($modalDiv) ) { return; } var tag = { class: "coral-Modal", id: COLOR_PICKER_MODAL_DIV }; var insertModal = $("<div>", tag).hide().html(this.getModalHtml()); $(document.body).append(insertModal); }, getPickerIFrameContent: function(color){ var url = PICKER_URL + "?" + REQUESTER + "=" + GROUP; if(!_.isEmpty(color)){ url = url + "&" + PICKER_NAME_IN_POPOVER + "=" + color; } return "<iframe width='520px' height='405px' frameBorder='0' src='" + url + "'></iframe>"; } }); 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); }(jQuery, window.CUI)); (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; } $document.on("foundation-contentloaded", 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(); var $dialog = $(".cq-dialog").css("background", "white"), $addColor = $dialog.find(ADD_COLOR_BUT), $removeColor = $dialog.find(REMOVE_COLOR_BUT), $colorPicker = $document.find(".coral-ColorPicker"), pickerInstance = $colorPicker.data("colorpicker"); if(!_.isEmpty(queryParameters()[COLOR])){ pickerInstance._setColor(decodeURIComponent(queryParams[COLOR])); } $dialog.find(".cq-dialog-header").hide(); $dialog.find(".cq-dialog-content").css("top", ".1rem"); $colorPicker.closest(".coral-Form-fieldwrapper").css("margin-bottom", "285px"); $(ADD_COLOR_BUT).css("margin-left", "250px"); $addColor.click(sendDataMessage); $removeColor.click(sendRemoveMessage); } 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));
i have followed the above configuration but still i am not able to see the color picker icon.
ReplyDeletewe has the same issue within our project and found out that the JS is not minify compliant. you can either refactor the js yourself or disable minify for the authoring instance.
DeleteWhen i try to select the text and open the Plugin, the text selection disappears. Because of this, the span with the color gets inserted after the text. Any help?
ReplyDeleteMy JS is not minified. I have followed the instructions properly. But I couldn't still make it work. I'm seeing the browser console throws TypeError: CUI.rte is undefined. Please help
ReplyDeleteThank Sreekanth Choudry Nalabotu for this nice plugin.
ReplyDeleteRavi, please state the steps you took to install it. It should work just fine with OOT text component if you follow the steps at the beginning of the page.
Let me know.
Regards
Daniel.
Thanks for the plugin, its very helpful! I'm trying to add custom colors under the "colors" node but the new colors are not being updated. It seems like the plugin is pulling the color from the granite default colors. Is there any work around for this?
ReplyDeleteText coloring doesn't work on IE 11, although working fine with Edge and chrome. Is this the known issue with this plugin ?
ReplyDeleteI finally managed to make it working on IE. You just need to modify color-picker.js for this. Here is the piece of code ::
ReplyDeleteIn ColorPickerPlugin.execute() put the following code:
if(com.ua.isIE) {
CUI.rte.currentEditorKernel = ek;
}
In ColorPickerCmd.execute() put the following code:
var com = CUI.rte.Common;
var dpr = CUI.rte.DomProcessor;
var cmd = CUI.rte.commands.Command;
if(com.ua.isIE && CUI.rte.currentEditorKernel) {
var editKernel = CUI.rte.currentEditorKernel;
CUI.rte.Selection.selectBookmark(editKernel.editContext, editKernel.lastKnownBookmark);
selection = editKernel.createQualifiedSelection(editKernel.editContext);
nodeList = dpr.createNodeList(editKernel.editContext, selection);
CUI.rte.currentEditorKernel = null;
}