Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all articles
Browse latest Browse all 525

AEM Cloud Service - Content Fragment RTE Plugin for Dynamic Variables

$
0
0

Goal

Adobe Experience Manager 2021.6.5586.20210628T210726Z-210600 (June 28, 2021)

Create a Content Fragment RTE Plugin (RichTextEditor) for Dynamic Variables. Variable is resolved with a value, when the CF is added on a page, fetched from Page Properties. As an example consider Credit Cards and Interest Rates. Interest Rate content is added in AEM as a Content Fragment and the actual interest rate is replaced with value when the CF is added on a specific Card page...

Demo | Package Install | CF Model | Github


Add Dynamic Variable


CF with Dynamic Variables


Dynamic Variable Unresolved


Dynamic Variable Value entered in Page Properties


Dynamic Variable Resolved with Value



Solution

1) Add the plugin /apps/eaem-cs-cf-rte-dyn-var/cfm-dyn-var-plugin/clientlib with categories=[dam.cfm.authoring.contenteditor.v2, eaem-cfm.rte.plugin] and dependencies=eaem.lodash. Add the plugin logic JS file /apps/eaem-cs-cf-rte-dyn-var/cfm-dyn-var-plugin/clientlib/dyn-var-plugin.js with following code...

(function ($, $document) {
var EAEM_PLUGIN_ID = "eaem-dyn-var",
EAEM_TEXT_DYN_VAR_FEATURE = "eaemDynVar",
EAEM_DYN_VAR_ICON = EAEM_PLUGIN_ID + "#" + EAEM_TEXT_DYN_VAR_FEATURE,
CANCEL_CSS = "[data-foundation-wizard-control-action='cancel']",
DYN_VAR_SELECTOR_URL = "/apps/eaem-cs-cf-rte-dyn-var/cfm-dyn-var-plugin/dyn-var-selector.html",
SENDER = "experience-aem", REQUESTER = "requester", $eaemDynVarPicker,
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(DYN_VAR_SELECTOR_URL) == 0){
handlePicker();
}

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

$document.submit(sendSelectedVars);
}

function sendSelectedVars(){
var message = {
sender: SENDER,
action: "submit",
data: {}
}, $form = $("form"), $field;

_.each($form.find("[name^='./']"), function(field){
if(!field.checked || (field.tagName !== "CORAL-CHECKBOX")){
return;
}

$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"){
$eaemDynVarPicker.eaemFontPlugin.editorKernel.execCmd(EAEM_TEXT_DYN_VAR_FEATURE, message.data);
}

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

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

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

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

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

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

function registerPlugin(){
var EAEM_CFM_DYN_VAR_PLUGIN = new Class({
toString: "eaemCFMDynVarPlugin",

extend: CUI.rte.plugins.Plugin,

textFontUI: null,

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

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

defaults.tooltips[EAEM_TEXT_DYN_VAR_FEATURE] = {
title: "Select Dynamic Variable..."
};

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

this.config = pluginConfig;
},

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

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

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

if (tbGenerator.registerIcon) {
tbGenerator.registerIcon(EAEM_DYN_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_TEXT_DYN_VAR_FEATURE) {
return;
}

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

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

$eaemDynVarPicker = $modal;

$eaemDynVarPicker.eaemFontPlugin = self;

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

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

var EAEM_CFM_DYN_VAR_CMD = new Class({
toString: "eaemDynVarCmd",

extend: CUI.rte.commands.Command,

isCommand: function (cmdStr) {
return (cmdStr.toLowerCase() == EAEM_TEXT_DYN_VAR_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("");

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

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

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

CUI.rte.commands.CommandRegistry.register(EAEM_TEXT_DYN_VAR_FEATURE, EAEM_CFM_DYN_VAR_CMD);
}
}(jQuery, jQuery(document)));


2) Create the plugin modal page /apps/eaem-cs-cf-rte-dyn-var/cfm-dyn-var-plugin/dyn-var-selector with Dynamic Variables configuration...

<?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="Dyn Variable 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-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 Dynamic Variable..."
sling:resourceType="granite/ui/components/coral/foundation/wizard">
<items jcr:primaryType="nt:unstructured">
<text
jcr:primaryType="nt:unstructured"
jcr:title="Select the Dynamic Variable..."
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<accordion
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/accordion"
margin="{Boolean}true"
variant="quiet">
<items jcr:primaryType="nt:unstructured">
<fees
jcr:primaryType="nt:unstructured"
jcr:title="Fees"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<membershipFee
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
name="./membershipFee"
text="\{{membershipFee}}"
value="\{{membershipFee}}"/>
<balanceTransferMinFee
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
name="./balanceTransferMinFee"
text="\{{balanceTransferMinFee}}"
value="\{{balanceTransferMinFee}}"/>
<cashAdvanceMinFee
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
name="./cashAdvanceMinFee"
text="\{{cashAdvanceMinFee}}"
value="\{{cashAdvanceMinFee}}"/>
</items>
</fees>
</items>
</accordion>
</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>
</text>
</items>
</wizard>
</items>
</form>
</items>
</body>
</jcr:content>
</jcr:root>


3) Add a simple Static Template Component in project for testing purposes /apps/eaem-cs-cf-rte-dyn-var/components/basic-htl-page-component

<div  style="margin: 10px 25px 10px 25px">
<h2 style="text-align: center">Experience AEM CF Dynamic Variables Demo</h2>
<div data-sly-resource="${'content' @ resourceType='wcm/foundation/components/parsys'}"></div>
</div>


4) Add necessary properties for entering Dynamic Variable values in Page Dialog configuration /apps/eaem-cs-cf-rte-dyn-var/components/basic-htl-page-component/cq:dialog

<?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">
<content jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<tabs jcr:primaryType="nt:unstructured">
<items jcr:primaryType="nt:unstructured">
<eaem
cq:showOnCreate="{Boolean}true"
jcr:primaryType="nt:unstructured"
jcr:title="Experience AEM"
sling:orderBefore="socialmedia"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<accordion
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/accordion"
margin="{Boolean}true"
variant="quiet">
<items jcr:primaryType="nt:unstructured">
<fees
jcr:primaryType="nt:unstructured"
jcr:title="Fees"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<membershipFee
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
name="./dynVarmembershipFee"
fieldLabel="\{{membershipFee}}"/>
<balanceTransferMinFee
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
name="./dynVarbalanceTransferMinFee"
fieldLabel="\{{balanceTransferMinFee}}"/>
<cashAdvanceMinFee
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
name="./dynVarcashAdvanceMinFee"
fieldLabel="\{{cashAdvanceMinFee}}"/>
<foreignTransactionFee
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
name="./dynVarforeignTransactionFee"
fieldLabel="\{{foreignTransactionFee}}"/>
</items>
</fees>
</items>
</accordion>
</items>
</column>
</items>
</eaem>
</items>
</tabs>
</items>
</content>
</jcr:root>


5) Add a Content Fragment Component for selecting the content fragment, resolving Dynamic Variables and rendering the html /apps/eaem-cs-cf-rte-dyn-var/components/dyn-vars-cf

<div style="width: 100%; border: 1px solid; padding: 20px"
data-sly-use.model="apps.experienceaem.assets.core.models.DynVarsCFModel"
data-sly-test="${model.modalData}">
<div style="color: red">
${model.modalData.eaemHeader}
</div>
<div style="margin-top: 10px">
${model.modalData.eaemContent @context='html'}
</div>
</div>
<div style="width: 100%; height: 30px; margin-top: 30px"
data-sly-use.model="apps.experienceaem.assets.core.models.DynVarsCFModel" data-sly-test="${!model.cfSelectedFrom && wcmmode.edit}">
Content Fragment not configured
</div>


6) Add a sling model apps.experienceaem.assets.core.models.DynVarsCFModel for resolving the Dynamic Variables with Values from Page Properties

package apps.experienceaem.assets.core.models;

import com.adobe.cq.dam.cfm.ContentElement;
import com.adobe.cq.dam.cfm.ContentFragment;
import com.adobe.cq.dam.cfm.FragmentData;
import com.day.cq.wcm.api.Page;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.*;

@Model(
adaptables = {SlingHttpServletRequest.class}
)
public class DynVarsCFModel {
private static Logger log = LoggerFactory.getLogger(DynVarsCFModel.class);

@Inject
SlingHttpServletRequest request;

@Inject
Page currentPage;

@ValueMapValue
@Optional
private String fragmentPath;

@ValueMapValue
@Optional
private String cfSelectedFrom;

private String variation;
private Map<String,Object> modalData = new HashMap<String, Object>();

@PostConstruct
protected void init() {
SlingHttpServletRequest slingRequest = (SlingHttpServletRequest)request;
ResourceResolver resolver = slingRequest.getResourceResolver();

Resource cfResource = null;
variation = slingRequest.getParameter("variation");

if(StringUtils.isEmpty(variation)){
variation = "master";
}

if("URL".equals(cfSelectedFrom)){
cfResource = slingRequest.getRequestPathInfo().getSuffixResource();
}else if(StringUtils.isNotEmpty(fragmentPath)){
cfResource = resolver.getResource(fragmentPath);
}

if(cfResource == null){
return;
}

modalData = getCFData(cfResource.adaptTo(ContentFragment.class), resolver, currentPage.getProperties());
}

private Map<String,Object> getCFData(ContentFragment cf, ResourceResolver resolver, ValueMap pageProps){
Map<String,Object> cfData = new HashMap<String, Object>();

Iterator<ContentElement> cfElementsItr = cf.getElements();

while(cfElementsItr.hasNext()){
ContentElement cfElement = cfElementsItr.next();

if(cfElement == null ){
continue;
}

Object fragValue = getVariationValue(cfElement, variation).getValue();

if(fragValue == null){
continue;
}else if(isMultiCF(cfElement)){
List<Object> multis = new ArrayList<Object>();

for(String linkPath : (String[])fragValue){
multis.add(getCFData(resolver.getResource(linkPath).adaptTo(ContentFragment.class), resolver, pageProps));
}

cfData.put(cfElement.getName(), multis);
}else{
cfData.put(cfElement.getName(), replaceDynVars(String.valueOf(fragValue), pageProps));
}
}

return cfData;
}

private String replaceDynVars(String fragValue, ValueMap pageProps){
Iterator<String> itr = pageProps.keySet().iterator();
String key;

while(itr.hasNext()){
key = itr.next();

if(!key.startsWith("dynVar")){
continue;
}

fragValue = fragValue.replace("{{" + key.substring("dynVar".length()) + "}}", String.valueOf(pageProps.get(key)));
}

return fragValue;
}

private boolean isMultiCF(ContentElement cfElement){
return cfElement.getValue().getDataType().isMultiValue();
}

public FragmentData getVariationValue(ContentElement cfElement, String variationName){
if(StringUtils.isEmpty(variationName) || "master".equals(variationName)){
return cfElement.getValue();
}

return cfElement.getVariation(variation).getValue();
}

public String getCfSelectedFrom() {
return cfSelectedFrom;
}

public Map<String,Object> getModalData(){
return modalData;
}
}


7) Add Design for configuring the Parsys component Allowed Components in /apps/settings/wcm/designs/experience-aem (In Cloud Services for static templates design always comes from configuration in /apps; there is no Design option like in AEM 65 or CS SDK to configure allowed components and stored in for eg. /libs/settings/wcm/designs/default/jcr:content/basic-htl-page-component)

<?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
cq:lastModified="{Date}2017-07-20T15:17:11.670+01:00"
cq:lastModifiedBy="admin"
jcr:primaryType="nt:unstructured"
jcr:title="Experience AEM Design"
sling:resourceType="wcm/core/components/designer">
<basic-htl-page-component jcr:primaryType="nt:unstructured">
<content
jcr:lastModified="{Date}2021-06-30T16:00:11.535-05:00"
jcr:lastModifiedBy="admin"
jcr:primaryType="nt:unstructured"
sling:resourceType="wcm/foundation/components/parsys"
components="[/apps/eaem-cs-cf-rte-dyn-var/components/dyn-vars-cf]">
<section jcr:primaryType="nt:unstructured"/>
</content>
</basic-htl-page-component>
</jcr:content>
</jcr:root>


8) Select the design /apps/settings/wcm/designs/experience-aem in Page Properties> Advanced



Viewing all articles
Browse latest Browse all 525

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>