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
No comments:
Post a Comment