Monday, August 27, 2018

AEM 62 Touch UI Dialog RTE Rich Text Editor Color Picker Plugin

AEM 62 Touch UI Dialog RTE Rich Text Editor Color Picker Plugin


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


visit link download