AEM 6.5.12.0 - Content Fragments Select Tokens or Placeholders in Rich Text Editor

Goal

Add a Plugin in Content Fragments RTE (Rich Text Editor) for search and selecting tokens. These tokens are replaced dynamically by the consuming application with real data in a AEM headless scenario...

Demo | Package Install | Github


Token Plugin in RTE


Token DB in CRX


Solution

1) Create the datasource /apps/eaem-sites-rte-secure-tokens/token-ds/token-ds.jsp for reading tokens from /var/eaem/tokens

<%@include file="/libs/granite/ui/global.jsp"%>

<%@ page import="com.adobe.granite.ui.components.ds.DataSource" %>
<%@ page import="com.adobe.granite.ui.components.ds.ValueMapResource" %>
<%@ page import="org.apache.sling.api.wrappers.ValueMapDecorator" %>
<%@ page import="com.adobe.granite.ui.components.ds.SimpleDataSource" %>
<%@ page import="org.apache.commons.collections.iterators.TransformIterator" %>
<%@ page import="org.apache.commons.collections.Transformer" %>
<%@ page import="org.apache.sling.api.resource.*" %>
<%@ page import="java.util.*" %>

<%
String TOKEN_PATH = "/var/eaem/tokens";

final ResourceResolver resolver = resourceResolver;

Resource tokenPath = resolver.getResource(TOKEN_PATH);

DataSource ds = new SimpleDataSource(new TransformIterator(tokenPath.getValueMap().entrySet().iterator(), new Transformer() {
public Object transform(Object o) {
Map.Entry entry = (Map.Entry) o;

if(entry.getKey().equals("jcr:primaryType")){
return null;
}

ValueMap vm = new ValueMapDecorator(new HashMap<String, Object>());

vm.put("value", entry.getKey());
vm.put("text", entry.getValue());

return new ValueMapResource(resolver, new ResourceMetadata(), "nt:unstructured", vm);
}
}));

request.setAttribute(DataSource.class.getName(), ds);
%>


2) Create the page /apps/eaem-sites-rte-secure-tokens/rte-tokens for Token Search UI

<?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="RTE Tokens"
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="[lodash.compat,coralui3,granite.ui.coral.foundation,granite.ui.shell,dam.gui.admin.coral, eaem-cfm.rte.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 the Token..."
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<properties
jcr:primaryType="nt:unstructured"
granite:class="eaem-container-margin"
jcr:title="Properties"
sling:resourceType="granite/ui/components/coral/foundation/container"
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">
<token
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/autocomplete"
emptyText="Select Token"
margin="{Boolean}true"
multiple="{Boolean}false"
name="./token"
required="{Boolean}false">
<datasource
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/eaem-sites-rte-secure-tokens/token-ds"/>
<options
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/autocomplete/list"/>
</token>
</items>
</column>
</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="Insert"
type="submit"
variant="primary">
<granite:data
jcr:primaryType="nt:unstructured"
foundation-wizard-control-action="next"/>
</next>
</parentConfig>
</properties>
</items>
</wizard>
</items>
</form>
</items>
</body>
</jcr:content>
</jcr:root>


3) Create clientlib /apps/eaem-sites-rte-secure-tokens/clientlibs/clientlib-tokens with categories=[dam.cfm.authoring.contenteditor.v2, eaem-cfm.rte.plugin] and dependencies=lodash.compat, add the following code...

(function ($, $document) {
var EAEM_PLUGIN_ID = "eaem-token-var",
EAEM_TOKEN_FEATURE = "eaemTokenVar",
EAEM_TOKEN_VAR_ICON = EAEM_PLUGIN_ID + "#" + EAEM_TOKEN_FEATURE,
CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
TOKEN_PAGE_URL = "/apps/eaem-sites-rte-secure-tokens/rte-tokens.html",
SENDER = "experience-aem", REQUESTER = "requester", $eaemTokenVarPicker,
TOKEN_PLACEHOLDER = "${SECURE_CONTENT:ID=TOKEN_PLACEHOLDER}",
url = document.location.pathname;

if( (url.indexOf("/editor.html") == 0)
|| ( url.indexOf("/mnt/overlay/dam/cfm/admin/content/v2/fragment-editor.html") == 0) ){
extendStyledTextEditor();
registerPlugin();
}else if(url.indexOf(TOKEN_PAGE_URL) == 0){
handlePicker();
}

function handlePicker(){
$document.on("click", CANCEL_CSS, sendCancelMessage);

$document.submit(sendSelectedToken);
}

function sendSelectedToken(){
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();
});

parent.postMessage(JSON.stringify(message), "*");
}

function sendCancelMessage(){
var message = {
sender: SENDER,
action: "cancel"
};

getParent().postMessage(JSON.stringify(message), "*");
}

function getParent() {
if (window.opener) {
return window.opener;
}

return parent;
}

function closePicker(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"){
$eaemTokenVarPicker.eaemTokenPlugin.editorKernel.execCmd(EAEM_TOKEN_FEATURE, message.data);
}

var modal = $eaemTokenVarPicker.data('modal');
modal.hide();
modal.$element.remove();
}

function extendStyledTextEditor(){
var origFn = Dam.CFM.StyledTextEditor.prototype._start;

Dam.CFM.StyledTextEditor.prototype._start = function(){
addTokenVarPluginSettings(this);
origFn.call(this);
}
}

function addTokenVarPluginSettings(editor){
var config = editor.$editable.data("config");

config.rtePlugins[EAEM_PLUGIN_ID] = {
features: "*"
};

config.uiSettings.cui.multieditorFullscreen.toolbar.push(EAEM_TOKEN_VAR_ICON);
config.uiSettings.cui.inline.toolbar.push(EAEM_TOKEN_VAR_ICON);
}

function registerPlugin(){
var EAEM_CFM_TOKEN_PLUGIN = new Class({
toString: "eaemCFMTokenVarPlugin",

extend: CUI.rte.plugins.Plugin,

textFontUI: null,

getFeatures: function () {
return [ EAEM_TOKEN_FEATURE ];
},

notifyPluginConfig: function (pluginConfig) {
var defaults = {
tooltips: {}
};

defaults.tooltips[EAEM_TOKEN_FEATURE] = {
title: "Select Tokenamic Variable..."
};

CUI.rte.Utils.applyDefaults(pluginConfig, defaults);

this.config = pluginConfig;
},

initializeUI: function (tbGenerator) {
if (!this.isFeatureEnabled(EAEM_TOKEN_FEATURE)) {
return;
}

this.textFontUI = new tbGenerator.createElement(EAEM_TOKEN_FEATURE, this, false,
this.config.tooltips[EAEM_TOKEN_FEATURE]);

tbGenerator.addElement(EAEM_TOKEN_FEATURE, 999, this.textFontUI, 999);

if (tbGenerator.registerIcon) {
tbGenerator.registerIcon(EAEM_TOKEN_VAR_ICON, "brackets");
}

$(window).off('message', closePicker).on('message', closePicker);
},

isValidSelection: function(){
var winSel = window.getSelection();
return winSel && winSel.rangeCount == 1 && winSel.getRangeAt(0).toString().length > 0;
},

execute: function (pluginCommand, value, envOptions) {
if (pluginCommand != EAEM_TOKEN_FEATURE) {
return;
}

this.showTokenModal(this.getPickerIFrameUrl());
},

showTokenModal: 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
});

$eaemTokenVarPicker = $modal;

$eaemTokenVarPicker.eaemTokenPlugin = self;

$modal.nextAll(".coral-Modal-backdrop").addClass("cfm-coral2-backdrop");
},

getPickerIFrameUrl: function(){
return Granite.HTTP.externalize(TOKEN_PAGE_URL) + "?" + REQUESTER + "=" + SENDER;
}
});

var EAEM_TOKEN_CMD = new Class({
toString: "eaemTokenCmd",

extend: CUI.rte.commands.Command,

isCommand: function (cmdStr) {
return (cmdStr.toLowerCase() == EAEM_TOKEN_FEATURE);
},

getProcessingOptions: function () {
var cmd = CUI.rte.commands.Command;
return cmd.PO_SELECTION | cmd.PO_BOOKMARK | cmd.PO_NODELIST;
},

execute: function (execDef) {
execDef.value = Object.values(execDef.value).join("");

execDef.value = TOKEN_PLACEHOLDER.replace("TOKEN_PLACEHOLDER",execDef.value);

CUI.rte.commands.InsertHtml().execute(execDef);
},

queryState: function(selectionDef, cmd) {
return false;
}
});

CUI.rte.plugins.PluginRegistry.register(EAEM_PLUGIN_ID, EAEM_CFM_TOKEN_PLUGIN);

CUI.rte.commands.CommandRegistry.register(EAEM_TOKEN_FEATURE, EAEM_TOKEN_CMD);
}
}(jQuery, jQuery(document)));



No comments:

Post a Comment