AEM 6420 - Assets Content Fragments Coral 3 Composite Multifield

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

16 comments:

  1. '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?

    ReplyDelete
    Replies
    1. when 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

      Delete
  2. this is not working in AEM 6.5 - the values are getting stored as
    {"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

    ReplyDelete
    Replies
    1. Hi Sreekanth,

      I'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!

      Delete
    2. Hi All,
      I 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!

      Delete
    3. Yes, after changes it's working fine

      Delete
    4. what are the changes that you made to script for 6.5. Can you please share ?

      Delete
  3. Is 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.

    ReplyDelete
    Replies
    1. this might help https://experience-aem.blogspot.com/2019/08/aem-6510-add-photo-gallery-composite-multifield-in-content-fragments.html

      Delete
  4. Thanks 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.
    The 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.

    ReplyDelete
  5. 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

    ReplyDelete
  6. Hi Sreekanth,

    Is there a way to create a nested multifield in a component fragment? I tried extending the above post but i doesn't work.

    ReplyDelete
    Replies
    1. Hi Suri,

      Did 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

      Delete
  7. 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.

    ReplyDelete
    Replies
    1. were 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.

      Delete
  8. Great 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.

    For 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!

    ReplyDelete