AEM Cloud Service - Multiple Composite Multifields in Content Fragments Editor

 Goal

Adobe Experience Manager 2023.4.12142.20230526T152858Z-230200

Create a Content Fragment Model with Multiple Composite Multifields loaded in Content Fragments Editor. Authors can edit the configuration of a multifield or add additional composite multifields...

Demo | Package Install | CF Model | Github


Configure Composite Multifield - productsListCMFTemplate


Configure Composite Multifield - countriesListCMFTemplate


Composite Multifield in Editor - productsListCMF


Composite Multifield in Editor - countriesListCMF


Data Saved in CRX


Solution

1) Create the project eaem-cf-composite-mf

mvn -B org.apache.maven.plugins:maven-archetype-plugin:3.2.1:generate -D archetypeGroupId=com.adobe.aem 
-D archetypeArtifactId=aem-project-archetype -D archetypeVersion=36 -D aemVersion=cloud
-D appTitle="Experience AEM CS CF Composite Multifield" -D appId="eaem-cf-composite-mf" -D groupId="apps.experienceaem.assets"
-D frontendModule=none -D includeExamples=n -D includeDispatcherConfig=n


2) Add a helper thirdparty loadash client library /apps/eaem-cf-composite-mf/lodash with property categories="[eaem.lodash]"


3) For the multiple composite multifields configuration and runtime create a clientlib /apps/eaem-cf-composite-mf/clientlib-mf with categories="[dam.cfm.authoring.contenteditor.v2]" and dependencies="[eaem.lodash]". Add the following code (note the conventions, composite multifield names must end with word CMF and template name with Multifield name and word Template)

(function ($) {
const URL = document.location.pathname,
CFFW = ".coral-Form-fieldwrapper",
MASTER = "master",
CFM_EDITOR_SEL = ".content-fragment-editor",
CMF_SELECTOR = "[data-granite-coral-multifield-name$='CMF']",
CMF_TEMPLATE = "Template";

let initialized = false;

if( !isCFEditor() ){
return;
}

init();

function init(){
if(initialized){
return;
}

initialized = true;

window.Dam.CFM.Core.registerReadyHandler(() => {
extendRequestSave();

addCMFMultiFieldListener();

Dam.CFM.editor.UI.addBeforeApplyHandler( () => {
Dam.CFM.EditSession.notifyActiveSession();
Dam.CFM.EditSession.setDirty(true);
});
});
}

function addCMFMultiFieldListener(){
const $cmfMultis = $(CMF_SELECTOR);

createMultiFieldTemplates();

_.each($cmfMultis, (cmfMulti) => {
Coral.commons.ready(cmfMulti, splitKeyValueJSONIntoFields);
})
}

function splitKeyValueJSONIntoFields(cmfMFField){
const $cmfMFField = $(cmfMFField),
cmfMFName = $cmfMFField.attr("data-granite-coral-multifield-name");

_.each(cmfMFField.items.getAll(), function(item) {
const $content = $(item).find("coral-multifield-item-content");
let jsonData = $content.find("[name=" + cmfMFName + "]").val();

if(!jsonData){
return;
}

jsonData = JSON.parse(jsonData);

$content.html(getParkedMFHtml($cmfMFField));

fillMultiFieldItem(item, jsonData);
});
}

function fillMultiFieldItem(mfItem, jsonData){
_.each(jsonData, function(fValue, fKey){
const field = mfItem.querySelector("[name='" + fKey + "']");

if(field == null){
return;
}

if(field.tagName === 'CORAL-DATEPICKER'){
field.valueAsDate = new Date(fValue);
}else{
field.value = fValue;
}
});
}

function createMultiFieldTemplates(){
const $cmfMultis = $(CMF_SELECTOR);

_.each($cmfMultis, (cmfMulti) => {
let $cmfMulti = $(cmfMulti);

$cmfMulti.find("template").remove();

let template = '<template coral-multifield-template=""><div>' + getParkedMFHtml($cmfMulti) + '</div></template>';

hideTemplateTab($cmfMulti);

$cmfMulti.append(template);
})
}

function getParkedMFHtml($cmfMulti){
let $tabView = $cmfMulti.closest("coral-tabview");
return $($tabView.find("coral-panel").get(getTemplateIndex($cmfMulti))).find("coral-panel-content").html();
}

function getTemplateIndex($cmfMulti){
let cmfMultiName = $cmfMulti.attr("data-granite-coral-multifield-name"),
cmfMultiTemplateName = cmfMultiName + CMF_TEMPLATE,
$tabView = $cmfMulti.closest("coral-tabview"),
$tabLabels = $tabView.find('coral-tab-label'),
templateIndex;

_.each($tabLabels, (tabLabel, index) => {
if($(tabLabel).html().trim() == cmfMultiTemplateName){
templateIndex = index;
}
})

return templateIndex;
}

function hideTemplateTab($cmfMulti){
let $tabView = $cmfMulti.closest("coral-tabview");
$($tabView.find("coral-tab").get(getTemplateIndex($cmfMulti))).hide();
}

function getCompositeFieldsData(){
const $cmfMultis = $(CMF_SELECTOR), allData = {};

_.each($cmfMultis, (cmfMulti) => {
let $cmfMulti = $(cmfMulti),
kevValueData = [],
cmfName = $cmfMulti.attr("data-granite-coral-multifield-name");

_.each(cmfMulti.items.getAll(), function(item) {
const $fields = $(item.content).find("[name]"),
cmfData = {};

_.each($fields, function(field){
if(canBeSkipped(field)){
return;
}

cmfData[field.getAttribute("name")] = field.value;
});

kevValueData.push(JSON.stringify(cmfData));
});

allData[cmfName] = kevValueData;
})

return allData ;
}

function canBeSkipped(field){
return (($(field).attr("type") == "hidden") || !field.value);
}

function extendRequestSave(){
const CFM = window.Dam.CFM,
orignFn = CFM.editor.Page.requestSave;

CFM.editor.Page.requestSave = requestSave;

function requestSave(callback, options) {
orignFn.call(this, callback, options);

const kvData = getCompositeFieldsData();

if(_.isEmpty(kvData)){
return;
}

const url = CFM.EditSession.fragment.urlBase + ".cfm.content.json",
variation = getVariation(),
createNewVersion = (options && !!options.newVersion) || false;

let data = {
":type": "multiple",
":newVersion": createNewVersion,
"_charset_": "utf-8"
};

if(variation !== MASTER){
data[":variation"] = variation;
}

const request = {
url: url,
method: "post",
dataType: "json",
data: _.merge(data, kvData),
cache: false
};

CFM.RequestManager.schedule({
request: request,
type: CFM.RequestManager.REQ_BLOCKING,
condition: CFM.RequestManager.COND_EDITSESSION,
ui: (options && options.ui)
})
}
}

function getVariation(){
var variation = $(CFM_EDITOR_SEL).data('variation');

variation = variation || "master";

return variation;
}

function isCFEditor(){
return ((URL.indexOf("/editor.html") == 0)
|| (URL.indexOf("/mnt/overlay/dam/cfm/admin/content/v2/fragment-editor.html") == 0) )
}
}(jQuery));

3 comments:

  1. Hi Sreekanth,

    Thank you so much for this blog post, it's working just fine except for the rte and checkbox (Multi line text and Boolean from the CFM Editor). Do you have a fix for that?

    Thanks!

    ReplyDelete
  2. I am having issues when i make changes to composite multifield and then refresh the content fragment multiple times. After 3-4 refresh I notice that data gets updated to a different value which is not a JSON. Any reason why?

    ReplyDelete
  3. this solution is not working in AEMaaCS Content Fragment New editor.

    ReplyDelete