Goal
Color Picker Plugin extension for RTE (Rich Text Editor) in 61 Touch UI. Click here for Color Picker widget documentation
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...
This is a better implementation of check this post
For Classic UI check this post
For AEM 62 Dialog RTE Color Picker Plugin - check this post
Demo | Package Install
Configuration
Picker with Free Style Palette
Palette Edit Mode
RTE text with Color applied
Remove Color
Solution
1) Login to CRXDE Lite http://localhost:4502/crx/de, add nt:folder /apps/touchui-rte-color-picker-plugin
2) To show the color picker in a dialog create /apps/touchui-rte-color-picker-plugin/color-picker-popover of type sling:Folder and /apps/touchui-rte-color-picker-plugin/color-picker-popover/cq:dialog of type nt:unstructured
3) XML representation of /apps/touchui-rte-color-picker-plugin/color-picker-popover/cq:dialog and structure in CRXDE Lite
<?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/foundation/form/colorpicker" classicPaletteType="{Boolean}true" editType="{Boolean}true" fieldLabel="Select Color" 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-rte-color-picker-plugin/color-picker-popover/cq:dialog/content/items/column/items/picker/colors
5) Create clientlib (cq:ClientLibraryFolder) /apps/touchui-rte-color-picker-plugin/clientlib set property categories to rte.coralui2 and dependencies to [underscore]
6) Create file (nt:file) /apps/touchui-rte-color-picker-plugin/clientlib/js.txt, add the following
color-picker-plugin.js
7) Create file (nt:file) /apps/touchui-rte-color-picker-plugin/clientlib/color-picker-plugin.js, add the following code
(function ($, $document, Handlebars) { "use strict"; var _ = window._, Class = window.Class, CUI = window.CUI, REQUESTER = "requester", GROUP = "experience-aem", COLOR_PICKER_FEATURE = "colorPicker", COLOR_PICKER_DIALOG = "colorPickerDialog", DIALOG_URL = "/apps/touchui-rte-color-picker-plugin/color-picker-popover/cq:dialog", PICKER_NAME_IN_POPOVER = "color", EAEMCuiToolbarBuilder, EAEMColorPickerPluginDialog, EAEMDialogManager, EAEMToolkitImpl, EAEMColorPickerPlugin, EAEMColorPickerCmd; function getUISetting() { return GROUP + "#" + COLOR_PICKER_FEATURE; } //extend the toolbar builder to register plugin icon in fullscreen mode EAEMCuiToolbarBuilder = new Class({ toString: "EAEMCuiToolbarBuilder", extend: CUI.rte.ui.cui.CuiToolbarBuilder, _getUISettings: function (options) { var uiSettings = this.superClass._getUISettings(options), toolbar = uiSettings.fullscreen.toolbar, feature = getUISetting(); if (toolbar.indexOf(feature) === -1) { toolbar.splice(3, 0, feature); } if (!this._getClassesForCommand(feature)) { //.coral-ColorPicker-button this.registerAdditionalClasses(feature, "coral-Icon coral-Icon--textColor"); } return uiSettings; } }); //popover dialog hosting iframe EAEMColorPickerPluginDialog = new Class({ extend: CUI.rte.ui.cui.AbstractBaseDialog, toString: "EAEMColorPickerPluginDialog", getDataType: function () { return COLOR_PICKER_DIALOG; } }); //extend the CUI dialog manager to register popover dialog EAEMDialogManager = new Class({ toString: "EAEMDialogManager", extend: CUI.rte.ui.cui.CuiDialogManager, create: function (dialogId, config) { if (dialogId !== COLOR_PICKER_DIALOG) { return this.superClass.create.call(this, dialogId, config); } var context = this.editorKernel.getEditContext(), $container = CUI.rte.UIUtils.getUIContainer($(context.root)), dialog = new EAEMColorPickerPluginDialog(); dialog.attach(config, $container, this.editorKernel, true); return dialog; } }); //extend the toolkit implementation for custom toolbar builder and dialog manager EAEMToolkitImpl = new Class({ toString: "EAEMToolkitImpl", extend: CUI.rte.ui.cui.ToolkitImpl, createToolbarBuilder: function () { return new EAEMCuiToolbarBuilder(); }, createDialogManager: function (editorKernel) { return new EAEMDialogManager(editorKernel); } }); CUI.rte.ui.ToolkitRegistry.register("cui", EAEMToolkitImpl); EAEMColorPickerPlugin = new Class({ toString: "ColorPickerDialogPlugin", 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, "Select Color"); tbGenerator.addElement(GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 120); }, execute: function (id, value, envOptions) { var ek = this.editorKernel, dm = ek.getDialogManager(), $popover, dialog, context = envOptions.editContext; if(!isValidSelection()){ return; } var dialogConfig = { parameters: { "command": getUISetting() } }; dialog = this.dialog = dm.create(COLOR_PICKER_DIALOG, dialogConfig); dialog.restoreSelectionOnHide = false; dm.prepareShow(this.dialog); dm.show(this.dialog); $popover = this.dialog.$dialog.find(".coral-Popover-content"); var selection = CUI.rte.Selection.createProcessingSelection(context), tag = CUI.rte.Common.getTagInPath(context, selection.startNode, "span" ); loadPopoverUI($popover, $(tag).css("color")); function isValidSelection(){ var winSel = window.getSelection(); return winSel && winSel.type && winSel.type.toUpperCase() == "RANGE"; } 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); } dialog.hide(); removeReceiveDataListener(receiveMessage); } function loadPopoverUI($popover, color) { var url = DIALOG_URL + ".html?" + REQUESTER + "=" + GROUP; if(!_.isEmpty(color)){ url = url + "&" + PICKER_NAME_IN_POPOVER + "=" + color; } $popover.parent().css("width", ".1px").height(".1px").css("border", "none"); $popover.css("width", ".1px").height(".1px"); $popover.find("iframe").attr("src", url); //receive the dialog values from child window registerReceiveDataListener(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, EAEMColorPickerPlugin); EAEMColorPickerCmd = new Class({ toString: "ColorPickerDialogCmd", 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, EAEMColorPickerCmd); //returns the picker dialog html //Handlebars doesn't do anything useful here, but the framework expects a template function dlgTemplate() { CUI.rte.Templates["dlg-" + COLOR_PICKER_DIALOG] = Handlebars.compile('<div data-rte-dialog="' + COLOR_PICKER_DIALOG + '" class="coral--dark coral-Popover coral-RichText-dialog">' + '<iframe width="525px" height="465px"></iframe>' + '</div>'); } dlgTemplate(); })(jQuery, jQuery(document), Handlebars); (function($, $document){ var SENDER = "experience-aem", REQUESTER = "requester", COLOR = "color", ADD_COLOR_BUT = "#EAEM_CP_ADD_COLOR", REMOVE_COLOR_BUT = "#EAEM_CP_REMOVE_COLOR", PICKER_COLORS = location.pathname.replace(".html", "") + "/content/items/column/items/picker/colors.infinity.json", HELP_BUTTON_SEL = ".cq-dialog-help", CANCEL_BUTTON_SEL = ".cq-dialog-cancel", SUBMIT_BUTTON_SEL = ".cq-dialog-submit", pickerInstance; 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(); if(!_.isEmpty(queryParameters()[COLOR])){ pickerInstance._setColor(decodeURIComponent(queryParams[COLOR])); } var $dialog = $(".cq-dialog"), $cancel = $dialog.find(CANCEL_BUTTON_SEL), $submit = $dialog.find(SUBMIT_BUTTON_SEL), $addColor = $dialog.find(ADD_COLOR_BUT), $removeColor = $dialog.find(REMOVE_COLOR_BUT); $dialog.css("border", "solid 2px"); $dialog.find(HELP_BUTTON_SEL).hide(); $document.find(".coral-ColorPicker").closest(".coral-Form-fieldwrapper") .css("margin-bottom", "20px"); $document.off("click", CANCEL_BUTTON_SEL); $document.off("click", SUBMIT_BUTTON_SEL); $document.off("submit"); $cancel.click(sendCloseMessage); $submit.click(sendDataMessage); $addColor.click(sendDataMessage); $removeColor.click(sendRemoveMessage); } function sendCloseMessage(){ var message = { sender: SENDER, action: "close" }; 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), "*"); } CUI.Colorpicker = new Class({ toString: "Colorpicker", extend: CUI.Colorpicker, _readDataFromMarkup: function () { this.superClass._readDataFromMarkup.call(this); var el = this.$element; //extend otb CUI.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); if (el.data('config').modes) { this.options.config.displayModes = el.data('config').modes; } pickerInstance = this; function setColors(data){ if(_.isEmpty(data)){ return; } var colors = {}; _.each(data, function(color, key){ if(key.indexOf("jcr:") >= 0){ return; } colors[color.name] = color.value; }); pickerInstance.options.config.colors = colors; } $.ajax({ url: PICKER_COLORS, async: false, dataType: 'json' } ).done(setColors); } }); CUI.Widget.registry.register("colorpicker", CUI.Colorpicker); })(jQuery, jQuery(document));
8) To set palette type to Free style or Classic, set the following properties on /apps/touchui-rte-color-picker-plugin/color-picker-popover/cq:dialog/content/items/column/items/picker
freestylePaletteType="{Boolean}true"
classicPaletteType="{Boolean}true"
9) To enable Color Picker for a component, create node experience-aem under rtePlugins (eg. /libs/foundation/components/text/dialog/items/tab1/items/text/rtePlugins/experience-aem) and set property features String[] to colorPicker
How to make it shown on editor toolbar? At previous version there was step:
ReplyDelete"Add any text component with RichText editor and in the rtePlugins path of dialog add touchuicolorpicker node to enable color picker plugin"
I installed the package but it does not show up at the standard Text component.
thanks for reporting, updated the post - To enable Color Picker for a component, create node experience-aem under rtePlugins (eg. /libs/foundation/components/text/dialog/items/tab1/items/text/rtePlugins/experience-aem) and set property features String[] to colorPicker
DeleteIs there a way to add !important to the color?
ReplyDeleteThis is great Sreekanth! Thanks so much for publishing this, saving people a TON of work.
ReplyDeleteI did run into a couple bugs in Firefox. Perhaps you can update your solution, or at least people can grab the fixes from my post here.
Bug 1: The colorpicker popup will not open.
Fix: In the isValidSelection() funciton, change:
return winSel && winSel.type && winSel.type.toUpperCase() == "RANGE";
to:
return winSel && winSel.rangeCount == 1 && winSel.getRangeAt(0).toString().length > 0;
Bug 2: Selecting a colored word in the middle of a sentence and opening the colorpicker does not have the current color defaulted.
Fix: In the execute() function, change:
var selection = CUI.rte.Selection.createProcessingSelection(context),
tag = CUI.rte.Common.getTagInPath(context, selection.startNode, "span" );
to:
var selection = CUI.rte.Selection.createProcessingSelection(context);
var startNode = selection.startNode;
if (selection.startOffset == selection.startNode.length && startNode != selection.endNode) {
startNode = startNode.nextSibling;
}
var tag = CUI.rte.Common.getTagInPath(context, startNode, "span");
Thank you Brett, ill test and update the post later
DeleteHi there, can this color picker be used outside the RTE on a custom component? I want to replace the buggy Granite colorpicker with this one :)
ReplyDeleteHi, very good post, i tried to test your component but seems the colorpicker's modes are broken.
ReplyDeleteWhich version of AEM are you using for this? the colorpicker in my AEM 6.1 doesn't set neither edit mode nor free style mode? do you have any idea why?
Regards.
EsteBusta,
ReplyDeleteThe Javascript fix above gives some information about the bug in the 6.0/6.1 AEM colorpicker.
//in granite/ui/components/foundation/form/colorpicker/render.jsp
//colorpickerJson.put("modes", pickerModes); should have been
//colorpickerJson.put("pickerModes", pickerModes);
If anyone happens to know if I can use this new colorpicker standalone (without RTE), that would be great!
This comment has been removed by the author.
ReplyDeleteThanks a lot David Pfundt for the anwser, i missed that part.
ReplyDeleteIn order to use this "fixed" colorpicker without the RTE, i think is enough if you extend the colorpicker to a new component and just "rewrite" the render.jsp with the solution.
Fantastic! Is there a way to have this available in the inline toolbar also? So combined inline and in fullscreen editor?
ReplyDeleteThank you!
This comment has been removed by the author.
ReplyDeletehey Brett Birschbach, even after making the changes you suggested, colorpicker popup not getting opened.
ReplyDeleteGetting error: Cannot serve request to /apps/touchui-rte-color-picker-plugin/color-picker-popover/cq:dialog.html in /libs/cq/gui/components/authoring/dialog/dialog.jsp
Can you please suggests how this issue can be resolved?
I am facing the same issue. Were you able to solve it ?
DeleteThis comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteWhatever fixes I do, I am not able to see the colorpicker in AEM 6.1 SP1 version.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHello, it seems that the plugin is not working for IE 11, when I select the text and try to select a color in the panel, the text is immediatly unselected and I can't apply the color.
ReplyDeleteAgilestorelocator.com is the professional in wordpress plugin and plugin is for WordPress. Here is also options available for the Location finder WordPress.
ReplyDeleteDoes anyone have an update to this for 6.2 I'm not seeing the colorpicker in a text component as well.
ReplyDeletenice blog too informative. looking and reading your points its so impressive. doing more blog like this. i really appreciated doing like this.
ReplyDeleteSchool Brading UK
it seems that plugin is not working in IE 11 .Does it work for anyone else in IE 11 ?
ReplyDeleteI was analyzing the Importance of Online Editing Services before I landed on this page and I have learned a lot about the adobe program and I have the skills to use the adobe program effectively. Thanks for sharing this post with us, I have found it to be very educative and entertaining.
ReplyDeleteI was wondering if anyone had managed to get this addon working in IE or firefox lately?
ReplyDeleteIn case the text is listed using bullet/numbers the color is only applied to the text not to the bullet/numbers.
ReplyDeleteIs there any way to make it work or its a product limitation!