Goal
Create a Coral 3 Composite Multifield for Content Fragments
The fragment created by this extension stores multifield data in JSON format
Package Install has a sample content fragment model Experience AEM Composite CF Model - /conf/we-retail/settings/dam/cfm/models/experience-aem-composite-cf-model
Demo | Package Install | Github
Master
Sample Variation "Mobile"
Sling Model
Solution
1) Create the Content Fragment Model, drag a Single line text widget, set the Render As to multifield to create a regular multifield
2) In CRXDe Lite, navigate to the created content fragment model and set property composite=true
eg. /conf/we-retail/settings/dam/cfm/models/experience-aem-composite-cf-model/jcr:content/model/cq:dialog/content/items/1539961047566
3) Remove or adjust the field node added by product and create a composite multifield node structure
<?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:Template" allowedPaths="[/content/entities(/.*)?]" ranking="{Long}100"> <jcr:content cq:lastModified="{Date}2018-10-19T09:57:39.788-05:00" cq:lastModifiedBy="admin" cq:scaffolding="/conf/we-retail/settings/dam/cfm/models/experience-aem-composite-cf-model/jcr:content/model" cq:templateType="/libs/settings/dam/cfm/model-types/fragment" jcr:primaryType="cq:PageContent" jcr:title="Experience AEM Composite CF Model" sling:resourceSuperType="dam/cfm/models/console/components/data/entity" sling:resourceType="dam/cfm/models/console/components/data/entity/default"> <model cq:targetPath="/content/entities" jcr:primaryType="cq:PageContent" sling:resourceType="wcm/scaffolding/components/scaffolding" dataTypesConfig="/mnt/overlay/settings/dam/cfm/models/formbuilderconfig/datatypes" maxGeneratedOrder="20"> <cq:dialog jcr:primaryType="nt:unstructured" sling:resourceType="cq/gui/components/authoring/dialog"> <content jcr:lastModified="{Date}2018-10-19T09:57:39.788-05:00" jcr:lastModifiedBy="admin" jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"> <items jcr:primaryType="nt:unstructured" maxGeneratedOrder="22"> <_x0031_539961036041 jcr:primaryType="nt:unstructured" sling:resourceType="dam/cfm/admin/components/authoring/contenteditor/multieditor" cfm-element="Company" listOrder="21" metaType="text-multi" name="company" renderReadOnly="false" showEmptyInReadOnly="true" valueType="string"/> <_x0031_539961047566 jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/multifield" composite="{Boolean}true" fieldLabel="Products" listOrder="22" maxlength="255" metaType="text-single" name="products" renderReadOnly="false" showEmptyInReadOnly="true" valueType="string[]"> <field jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container" name="./products"> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/container"> <items jcr:primaryType="nt:unstructured"> <product jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/textfield" fieldDescription="Name of Product" fieldLabel="Product Name" name="./product"/> <path jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/pathbrowser" fieldDescription="Select Path" fieldLabel="Path" name="./path" rootPath="/content"/> <show jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/checkbox" name="./show" text="Show?" value="yes"/> <type jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/coral/foundation/form/select" fieldDescription="Select Size" fieldLabel="Size" name="./size"> <items jcr:primaryType="nt:unstructured"> <def jcr:primaryType="nt:unstructured" text="Select Size" value=""/> <small jcr:primaryType="nt:unstructured" text="Small" value="small"/> <medium jcr:primaryType="nt:unstructured" text="Medium" value="medium"/> <large jcr:primaryType="nt:unstructured" text="Large" value="large"/> </items> </type> </items> </column> </items> </field> </_x0031_539961047566> </items> </content> </cq:dialog> </model> </jcr:content> </jcr:root>
4) Login to CRXDE Lite, create folder (nt:folder) /apps/eaem-touchui-cfm-composite-multifield
5) Create clientlib (type cq:ClientLibraryFolder) /apps/eaem-touchui-cfm-composite-multifield/clientlib and set property categories of String[] type to dam.cfm.authoring.contenteditor.v2 and dependencies String[] to [lodash]
6) Create file ( type nt:file ) /apps/eaem-touchui-cfm-composite-multifield/clientlib/js.txt, add the following
cfm-composite-multifield.js
7) Create file (type nt:file) /apps/eaem-touchui-cfm-composite-multifield/clientlib/cfm-composite-multifield.js, add the following code
(function ($) { var CFM = window.Dam.CFM, MASTER = "master", CFM_EDITOR_SEL = ".content-fragment-editor", CORAL_MF_ITEM = "coral-multifield-item", EAEM_COMPOSITE_ITEM_VALUE = "data-eaem-composite-item-value", MF_NAME_ATTR = "data-granite-coral-multifield-name", COMPOSITE_MF_SEL = "[data-granite-coral-multifield-composite]"; CFM.Core.registerReadyHandler(getMultifieldsContent); extendRequestSave(); function getMultifieldsContent(){ if(!compositeMutifieldsExist()){ return; } var url = CFM.EditSession.fragment.urlBase + "/jcr:content/data.2.json"; $.ajax(url).done(loadContentIntoMultiFields); } function loadContentIntoMultiFields(data){ var $composites = $(COMPOSITE_MF_SEL), mfValArr, mfAddEle, vData = data[getVariation()], $lastItem; if(_.isEmpty(vData)){ return; } _.each($composites, function(mField){ mfValArr = vData[getNameDotSlashRemoved(($(mField)).attr(MF_NAME_ATTR))]; if(_.isEmpty(mfValArr)){ return; } mfAddEle = mField.querySelector("[coral-multifield-add]"); _.each(mfValArr, function(mfMap){ mfAddEle.click(); $lastItem = $(mField).find(CORAL_MF_ITEM).last(); $lastItem.attr(EAEM_COMPOSITE_ITEM_VALUE, mfMap); Coral.commons.ready($lastItem[0], function (lastItem) { fillMultifieldItems(lastItem); }); }); }); } function fillMultifieldItems(mfItem){ if(mfItem == null){ return; } var mfMap = mfItem.getAttribute(EAEM_COMPOSITE_ITEM_VALUE); if(_.isEmpty(mfMap)){ return; } mfMap = JSON.parse(mfMap); _.each(mfMap, function(fValue, fKey){ var field = mfItem.querySelector("[name='./" + fKey + "']"); if(_.isEmpty(field)){ field = mfItem.querySelector("[name='" + fKey + "']"); } if(field == null){ return; } setFieldValue(field, fValue); }); } function setFieldValue(field, value){ if( field.tagName == "CORAL-CHECKBOX"){ field.checked = (field.getAttribute("value") == value); }else{ field.value = value; } } function getVariation(){ var variation = $(CFM_EDITOR_SEL).data('variation'); variation = variation || "master"; return variation; } function compositeMutifieldsExist(){ return !_.isEmpty($(COMPOSITE_MF_SEL)); } function extendRequestSave(){ var orignFn = CFM.editor.Page.requestSave; CFM.editor.Page.requestSave = requestSave; function requestSave(callback, options) { orignFn.call(this, callback, options); if(!compositeMutifieldsExist()){ return; } var mfsData = getMultifieldData(); if(_.isEmpty(mfsData)){ return; } var url = CFM.EditSession.fragment.urlBase + ".cfm.content.json", variation = getVariation(), createNewVersion = (options && !!options.newVersion) || false; var data = { ":type": "multiple", ":newVersion": createNewVersion, "_charset_": "utf-8" }; if(variation !== MASTER){ data[":variation"] = variation; } var request = { url: url, method: "post", dataType: "json", data: _.merge(data, mfsData), cache: false }; CFM.RequestManager.schedule({ request: request, type: CFM.RequestManager.REQ_BLOCKING, condition: CFM.RequestManager.COND_EDITSESSION, ui: (options && options.ui) }) } } function getMultifieldData(){ var $composites = $(COMPOSITE_MF_SEL), value, mfData = {}, values, $fields; _.each($composites, function(mField){ values = []; _.each(mField.items.getAll(), function(item) { $fields = $(item.content).find("[name]"); value = {}; _.each($fields, function(field){ if(canbeSkipped(field)){ return; } value[getNameDotSlashRemoved(field.getAttribute("name"))] = getFieldValue(field); }); values.push(JSON.stringify(value)); }); mfData[ getNameDotSlashRemoved(($(mField)).attr(MF_NAME_ATTR))] = values; }); return mfData; } function getFieldValue(field){ var value; if( field.tagName == "CORAL-CHECKBOX"){ value = field.checked ? field.getAttribute("value") : ""; }else{ value = field.value; } return value; } function canbeSkipped(field){ return (($(field).attr("type") == "hidden") || (field.type == "checkbox")); } function getNameDotSlashRemoved(name){ if(_.isEmpty(name)){ return name; } return ((name.indexOf("./") == 0) ? name.substr(2) : name); } }(jQuery));
've tried it, and it works fine, but when I save Content Fragment Model via the toolbar(Tools =>Assets =>Content Fragment Models), all the nodes inside multifield disappear. Is there any solution to this?
ReplyDeletewhen the content fragment model is modified you have to reset the composite=true via crxde (steps 2 and 3) *before* editing or creating new content instances of this model
Deletethis is not working in AEM 6.5 - the values are getting stored as
ReplyDelete{"products/item0/./product":"product 1 ","products/item0/./path":"/content/dam/wknd-events","products/item0/./show":"yes","products/item0/./size":"small"}
{"products/item1/./product":"product 2","products/item1/./path":"/content/dam/wknd-events","products/item1/./show":"yes","products/item1/./size":"medium"}
in the CF (notice the key - products/item1/./product)
we have to modify the script to make it work
Hi Sreekanth,
DeleteI'm on AEM 6.5.5.0
As suggested made changes to the script. Data is getting saved properly but the data in the multifield is still empty. Any suggestions?
{"product":"first product","path":"/content/test/en/us/home.html","show":"yes","size":"small"}
Thanks!
Hi All,
DeleteI am also getting same issue, in AEM 6.5.
{"products/item0/./product":"product 1 ","products/item0/./path":"/content/dam/wknd-events","products/item0/./show":"yes","products/item0/./size":"small"}
The structure is not coming properly. Please let me know if any solution is there.
Thank in Advance!
Yes, after changes it's working fine
Deletewhat are the changes that you made to script for 6.5. Can you please share ?
DeleteIs there a way i can add this as a drag and drop data type on the create fragment model screen? Instead of creating the nodes in crxde everytime a model is created.
ReplyDeletethis might help https://experience-aem.blogspot.com/2019/08/aem-6510-add-photo-gallery-composite-multifield-in-content-fragments.html
DeleteThanks for you reply sreekanth because that works. But i was looking for a more cleaner approach which does not require me to append raw HTML into the model editor because when i want to add a "multiline text" in a similar way it becomes very complex.
ReplyDeleteThe article on the current page is exactly what i was looking for but instead of addition of nodes manually through crxde i would want to add them to the model by drag and drop of custom data types which will be of type composite multifield.
Hi, I am trying to implement composite multi filed with same approach which will have RTE's i.e., Single Line text, RTE, RTE as set of multifield. The problem is the values for single line text is getting stored whereas the values entered in RTE fields are not getting stored/retrieved. Any pointer or example implementation to address this scenario? I am trying on AEM 6.4
ReplyDeleteHi Sreekanth,
ReplyDeleteIs there a way to create a nested multifield in a component fragment? I tried extending the above post but i doesn't work.
Hi Suri,
DeleteDid you find a solution for your query? I am also looking to create a composite multifield with a "label" and "fragment reference". With the above approach it just stores the reference path as normal link instead of fetching the data from the referenced content fragment.
Please let me know if there is solution for this?
Thanks
Nitesh
If helps to anyone, it looks like in AEM 6.5.10 this extension won't work due the lodash dependency, as you may know lodash got deprecated and removed since this last SP, so you may need either to refactor to use fully vanilla JS or add your own lodash dependency.
ReplyDeletewere you able to resolve this issue in AEM 6.5.xx because of lodash dependency? We were partially successful, it works fine when we add the lodash dependency exclusively but fails on back button functionality. Whenever we hit the back button, the values of multifield turn out to be blank. But eventually when you re-open the content fragment by closing, and not saving, the values show again. But again, if you keep hitting back button and toggle between multiple content fragments which use this multifield then the values get corrupted and store as blank.
DeleteGreat post, Sreekanth! I appreciate the detailed breakdown on creating a Coral 3 Composite Multifield for Content Fragments in AEM. It's always helpful to see examples and hands-on demos, especially for those of us working with content in different formats.
ReplyDeleteFor anyone looking to dive deeper into AEM, I recently wrote a blog that covers the differences between Content Fragments and Experience Fragments in AEM. If you're exploring both features and want to understand when to use each, feel free to check it out! It’s an informative guide that breaks down use cases and best practices to help you leverage AEM’s full potential.
Thanks again for sharing this!