Goal
AEM Cloud Version : 2021.3.5026.20210309T210727Z-210225 (March 09, 2021)
This post explains adding a plugin to RTE (Rich Text Editor - /libs/cq/gui/components/authoring/dialog/richtext) for adding custom CSS and color to text...
Package install contains a Text component with design dialog with plugin configuration - /apps/eaem-cs-rte-plugin-color-picker/components/text/cq:design_dialog/content/items/tabs/items/plugins/items/eaem-aem-fonts
Demo | Package Install | Github
Plugin Picker
Applying Color
Design Dialog Configuration
Enable Plugin Component Policy
Plugin Config saved in Component Policy
Solution
1) Create the Picker configuration dialog /apps/eaem-cs-rte-plugin-color-picker/clientlibs/fonts-plugin/font-selector and add categories=eaem.rte.font.plugin in /apps/eaem-cs-rte-plugin-color-picker/clientlibs/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="Experience AEM 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">
<contents
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldDescription="Appply specific background style..."
fieldLabel="Background Style"
name="./style">
<items jcr:primaryType="nt:unstructured">
<none
jcr:primaryType="nt:unstructured"
text="None"
value=""/>
<gray
jcr:primaryType="nt:unstructured"
text="Gray"
value="eaem--background-gray"/>
<white
jcr:primaryType="nt:unstructured"
text="White"
value="eaem--background-white"/>
<black
jcr:primaryType="nt:unstructured"
text="Black"
value="eaem--background-black"/>
</items>
</contents>
<hideOnDesktop
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
fieldDescription="Hide the text on desktop ( >= 992px)"
name="./hideOnDesktop"
text="Hide on desktop"
value="true"/>
<hideOnTablet
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
fieldDescription="Hide the text on tablet (768 - 991.95 px)"
name="./hideOnTablet"
text="Hide on Tablet"
value="true"/>
<hideOnMobile
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
fieldDescription="Hide the text on mobile breakpoints (< 768px)"
name="./hideOnMobile"
text="Hide on mobile"
value="true"/>
<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>
2) Add any custom styles for the author user to (pick and apply on text using the plugin) in clientlib base eg. /apps/eaem-cs-rte-plugin-color-picker/clientlibs/clientlib-base/rte-fonts.css
.eaem--background-gray{
background-color: #f1f1f1;
}
.eaem--background-white{
background-color: #ffffff;
}
.eaem--background-black{
background-color: #000000;
}
@media (min-width: 992px){
.eaem--content-desktop-hide {
display: none !important;
}
}
@media (min-width:768px) and (max-width:991.95px){
.eaem--content-tablet-hide {
display: none !important;
}
}
@media (max-width: 767.95px){
.eaem--content-mobile-hide {
display: none !important;
}
}
3) Add the plugin execution logic in /apps/eaem-cs-rte-plugin-color-picker/clientlibs/fonts-plugin/clientlib/rte-fonts-plugin.js with categories=[cq.authoring.dialog.all, eaem.rte.font.plugin]
(function($, CUI, $document){
var GROUP = "eaem-aem-fonts",
FONT_FEATURE = "applyFont",
TEXT_COLOR_FEATURE = "textColor",
TEXT_BG_COLOR_FEATURE = "textBackgroundColor",
EAEM_APPLY_FONT_DIALOG = "eaemTouchUIApplyFontDialog",
SENDER = "eaem-aem", REQUESTER = "requester", $eaemFontPicker,
CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
FONT_SELECTOR_URL = "/apps/eaem-cs-rte-plugin-color-picker/clientlibs/fonts-plugin/font-selector.html",
MOBILE_HIDE_CONTENT_CLASS = "eaem--content-mobile-hide",
DESKTOP_HIDE_CONTENT_CLASS = "eaem--content-desktop-hide",
TABLET_HIDE_CONTENT_CLASS = "eaem--content-tablet-hide",
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) {
if(field.tagName == "CORAL-CHECKBOX"){
if(value == "true"){
field.checked = true;
}
}else{
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='./style']", queryParams.class, true);
setWidgetValue($form[0], "[name='./hideOnMobile']", queryParams.hideOnMobile, true);
setWidgetValue($form[0], "[name='./hideOnTablet']", queryParams.hideOnTablet, true);
setWidgetValue($form[0], "[name='./hideOnDesktop']", queryParams.hideOnDesktop, true);
setWidgetValue($form[0], "[name='./color']", queryParams.color, features.includes(TEXT_COLOR_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();
});
addCheckboxValue(message, $form, "./hideOnDesktop");
addCheckboxValue(message, $form, "./hideOnTablet");
addCheckboxValue(message, $form, "./hideOnMobile");
getParent().postMessage(JSON.stringify(message), "*");
}
function addCheckboxValue(message, $form, cbName){
var $checkbox = $form.find("coral-checkbox[name='" + cbName + "']");
if(!_.isEmpty($checkbox)){
message.data[$checkbox.attr("name").substr(2)] = $checkbox[0].checked;
}
}
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='600px' height='450px' frameBorder='0' src='" + url + "'></iframe>";
if(_.isUndefined(CUI.rte.Templates)){
CUI.rte.Templates = {};
}
if(_.isUndefined(CUI.rte.templates)){
CUI.rte.templates = {};
}
try{
CUI.rte.templates['dlg-' + EAEM_APPLY_FONT_DIALOG] = CUI.rte.Templates['dlg-' + EAEM_APPLY_FONT_DIALOG] = Handlebars.compile(html);
}catch(err){
console.log("Ignoring font plugin error", err);
}
}
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, "colorPalette");
},
notifyPluginConfig: function (pluginConfig) {
pluginConfig = pluginConfig || {};
CUI.rte.Utils.applyDefaults(pluginConfig, {
'tooltips': {
applyFont: {
'title': 'Apply Font',
'text': 'Apply Font to selected text'
}
}
});
this.config = pluginConfig;
},
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")),
clazz = $tag.attr("class"), hideOnMobile = false, hideOnDesktop = false, hideOnTablet = false,
size = $tag.css("font-size"),dialog,dm = ek.getDialogManager(),
$container = CUI.rte.UIUtils.getUIContainer($(context.root)),
propConfig = {
'parameters': {
'command': this.pluginId + '#' + FONT_FEATURE
}
};
if(clazz && clazz.includes(MOBILE_HIDE_CONTENT_CLASS)){
hideOnMobile = true;
clazz = clazz.replace(MOBILE_HIDE_CONTENT_CLASS, "").trim();
}
if(clazz && clazz.includes(TABLET_HIDE_CONTENT_CLASS)){
hideOnTablet = true;
clazz = clazz.replace(TABLET_HIDE_CONTENT_CLASS, "").trim();
}
if(clazz && clazz.includes(DESKTOP_HIDE_CONTENT_CLASS)){
hideOnDesktop = true;
clazz = clazz.replace(DESKTOP_HIDE_CONTENT_CLASS, "").trim();
}
var color = this.getColorAttributes($tag);
if(this.eaemApplyFontDialog){
dialog = this.eaemApplyFontDialog;
dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl(this.config.features, size, clazz,
hideOnMobile, hideOnTablet, hideOnDesktop, 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, clazz, hideOnMobile, hideOnTablet, hideOnDesktop, 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, clazz, hideOnMobile, hideOnTablet, hideOnDesktop, color, bgColor){
var url = Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;
if(features === "*"){
features = [TEXT_COLOR_FEATURE , TEXT_BG_COLOR_FEATURE];
}
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;
}
if(!_.isEmpty(clazz)){
url = url + "&class=" + clazz;
}
if(hideOnMobile){
url = url + "&hideOnMobile=" + hideOnMobile;
}
if(hideOnTablet){
url = url + "&hideOnTablet=" + hideOnTablet;
}
if(hideOnDesktop){
url = url + "&hideOnDesktop=" + hideOnDesktop;
}
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;
}
var spanTag = {
"tag": "span",
"attributes": {
"style" : style
}
};
var clazz = textData.style;
if(!_.isEmpty(clazz)){
spanTag.attributes.class = clazz;
}
if(textData.hideOnMobile){
addClazz(spanTag, MOBILE_HIDE_CONTENT_CLASS);
}
if(textData.hideOnTablet){
addClazz(spanTag, TABLET_HIDE_CONTENT_CLASS);
}
if(textData.hideOnDesktop){
addClazz(spanTag, DESKTOP_HIDE_CONTENT_CLASS);
}
return spanTag;
function addClazz(tag, tagClazz){
tag.attributes.class = tag.attributes.class ? (tag.attributes.class + "") : "";
tag.attributes.class = tag.attributes.class + tagClazz;
}
},
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) && _.isEmpty(textData.style)
&& !textData.hideOnMobile && !textData.hideOnDesktop && !textData.hideOnTablet){
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)));