Touch UI Emoji Picker Plugin for RTE (Rich Text Editor) Dialog - /libs/cq/gui/components/authoring/dialog/richtext
In Core Components 2.8.0uiSettings/cui/dialogFullScreen was removed and the plugin settings were moved to Template (Component) Policies, this post is for core components >= 2.8.0
Uses Open Emoji API https://emoji-api.com/
For demo purposes, design dialog of core text component v2 was modified to add the emoji picker configuration - /apps/core/wcm/components/text/v2/text/cq:design_dialog/content/items/tabs/items/plugins/items/experience-aem-fonts
Emoji Plugin
Plugin Configuration in Design
Enable Plugin in Component Policy
Let's Do It
1) Login to CRXDE Lite, add nt:folder /apps/eaem-emojis-rte-plugin
2) To show the Emoji Picker in a dialog create cq:Page /apps/eaem-emojis-rte-plugin/emoji-selector and add eaem.rte.emojis.plugincategories in /apps/eaem-emojis-rte-plugin/emoji-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: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="EAEM Emoji 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.emojis.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">
<emoji
jcr:primaryType="nt:unstructured"
jcr:title="Click on the Emoji select.."
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">
<emojiPicker
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/eaem-emojis-rte-plugin/emoji-widget"
fieldDescription="Select Emoji..."
fieldLabel="Select Emoji..."
name="./eaemEmoji"/>
</items>
</column>
</items>
</fixedColumns>
</items>
</emoji>
</items>
</form>
</items>
</body>
</jcr:content>
</jcr:root>
3) Create widget /apps/eaem-emojis-rte-plugin/emoji-widget/emoji-widget.jsp to add a div container, search code for the emojis...
<%@ include file="/libs/granite/ui/global.jsp" %>
<ui:includeClientLib categories="lodash" />
<%
String defaultKeyword = "face";
%>
<div class="coral-Form-fieldwrapper">
<label class="coral-Form-fieldlabel">Search</label>
<input is="coral-textfield" class="coral-Form-field" placeholder="<%= defaultKeyword %>"
id="eaem-emoji-input"
value="<%= defaultKeyword %>">
</div>
<div class="coral-Form-fieldwrapper" id="eaem-emojis">
</div>
<script>
(function($){
var URL = "https://emoji-api.com/emojis?access_key=bcbd6980f7392ceda8914932404927e9b198486e&search=";
function showEmojis(keyword) {
$.ajax(URL + keyword).done(handler);
function handler(data) {
var $emojis = $("#eaem-emojis"), html = "";
_.each(data, function (emoji) {
html = html + getEmojiHtml(emoji["character"]);
});
$emojis.html(html);
}
function getEmojiHtml(character) {
return "<span style='cursor:pointer'>" + character + "</span>";
}
}
function addListener(){
$("form").on("submit", function(){
showEmojis($("#eaem-emoji-input").val());
return false;
});
}
showEmojis("<%=defaultKeyword%>");
addListener();
}(jQuery));
</script>
4) Create clientlib (cq:ClientLibraryFolder) /apps/eaem-emojis-rte-plugin/clientlib set property categories to [cq.authoring.dialog.all, eaem.rte.emojis.plugin] and dependencies to [lodash]
5) Create file (nt:file) /apps/eaem-emojis-rte-plugin/clientlib/js.txt, add the following content
eaem-emojis.js
6) Create file (nt:file) /apps/eaem-emojis-rte-plugin/clientlib/eaem-emojis.js, add the following code
(function($, CUI, $document){
var GROUP = "experience-aem-emojis",
INSERT_EMOJI_FEATURE = "insertEmoji",
EAEM_INSERT_EMOJI_DIALOG = "eaemTouchUIInsertEmojiDialog",
SENDER = "experience-aem", REQUESTER = "requester",
FONT_SELECTOR_URL = "/apps/eaem-emojis-rte-plugin/emoji-selector.html",
url = document.location.pathname;
if( url.indexOf(FONT_SELECTOR_URL) == 0 ){
handlePicker();
return;
}
function handlePicker(){
$document.on("click", "#eaem-emojis span", addEmojiSelectListener);
}
function addEmojiSelectListener(){
var message = {
sender: SENDER,
action: "submit",
data: {
emoji: $(this).html()
}
};
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='700px' height='300px' frameBorder='0' src='" + url + "'></iframe>";
if(_.isUndefined(CUI.rte.Templates)){
CUI.rte.Templates = {};
}
if(_.isUndefined(CUI.rte.templates)){
CUI.rte.templates = {};
}
CUI.rte.templates['dlg-' + EAEM_INSERT_EMOJI_DIALOG] = CUI.rte.Templates['dlg-' + EAEM_INSERT_EMOJI_DIALOG] = Handlebars.compile(html);
}
function addPluginToDefaultUISettings(){
var groupFeature = GROUP + "#" + INSERT_EMOJI_FEATURE,
toolbar = CUI.rte.ui.cui.DEFAULT_UI_SETTINGS.dialogFullScreen.toolbar;
if(toolbar.includes(groupFeature)){
return;
}
toolbar.splice(3, 0, groupFeature);
}
var EAEMInsertEmojiDialog = new Class({
extend: CUI.rte.ui.cui.AbstractDialog,
toString: "EAEMInsertEmojiDialog",
initialize: function(config) {
this.exec = config.execute;
},
getDataType: function() {
return EAEM_INSERT_EMOJI_DIALOG;
}
});
function addPlugin(){
var EAEMTouchUIInsertEmojiPlugin = new Class({
toString: "EAEMTouchUIInsertEmojiPlugin",
extend: CUI.rte.plugins.Plugin,
pickerUI: null,
getFeatures: function() {
return [ INSERT_EMOJI_FEATURE ];
},
initializeUI: function(tbGenerator) {
var plg = CUI.rte.plugins;
addPluginToDefaultUISettings();
if (!this.isFeatureEnabled(INSERT_EMOJI_FEATURE)) {
return;
}
this.pickerUI = tbGenerator.createElement(INSERT_EMOJI_FEATURE, this, false, { title: "Select Emoji..." });
tbGenerator.addElement(GROUP, plg.Plugin.SORT_FORMAT, this.pickerUI, 10);
var groupFeature = GROUP + "#" + INSERT_EMOJI_FEATURE;
tbGenerator.registerIcon(groupFeature, "heart");
},
execute: function (pluginCommand, value, envOptions) {
var context = envOptions.editContext,
ek = this.editorKernel;
if (pluginCommand != INSERT_EMOJI_FEATURE) {
return;
}
var dialog,dm = ek.getDialogManager(),
$container = CUI.rte.UIUtils.getUIContainer($(context.root)),
propConfig = {
'parameters': {
'command': this.pluginId + '#' + INSERT_EMOJI_FEATURE
}
};
if(this.eaemInsertEmojiDialog){
dialog = this.eaemInsertEmojiDialog;
dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl());
}else{
dialog = new EAEMInsertEmojiDialog();
dialog.attach(propConfig, $container, this.editorKernel);
dialog.$dialog.find("iframe").attr("src", this.getPickerIFrameUrl());
this.eaemInsertEmojiDialog = dialog;
$(window).off('message', receiveMessage).on('message', receiveMessage);
}
dm.show(dialog);
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();
}
},
getPickerIFrameUrl: function(){
return Granite.HTTP.externalize(FONT_SELECTOR_URL) + "?" + REQUESTER + "=" + SENDER;
},
updateState: function(selDef) {
var hasUC = this.editorKernel.queryState(INSERT_EMOJI_FEATURE, selDef);
if (this.pickerUI != null) {
this.pickerUI.setSelected(hasUC);
}
}
});
var EAEMTouchUIEmojiCmd = new Class({
toString: "EAEMTouchUIEmojiCmd",
extend: CUI.rte.commands.Command,
isCommand: function (cmdStr) {
return (cmdStr.toLowerCase() == INSERT_EMOJI_FEATURE);
},
getProcessingOptions: function () {
var cmd = CUI.rte.commands.Command;
return cmd.PO_SELECTION | cmd.PO_BOOKMARK | cmd.PO_NODELIST;
},
execute: function (execDef) {
var emoji = execDef.value.emoji, context = execDef.editContext,
emojiSpan = context.doc.createElement("span");
emojiSpan.innerHTML = emoji;
var range = CUI.rte.Common.ua.isIE ? CUI.rte.Selection.saveNativeSelection(context)
: CUI.rte.Selection.getLeadRange(context);
range.insertNode(emojiSpan);
},
queryState: function(selectionDef, cmd) {
return false;
}
});
CUI.rte.commands.CommandRegistry.register(INSERT_EMOJI_FEATURE, EAEMTouchUIEmojiCmd);
CUI.rte.plugins.PluginRegistry.register(GROUP,EAEMTouchUIInsertEmojiPlugin);
}
}(jQuery, window.CUI,jQuery(document)));