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

17 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. The following changes to the original posted code seem to work on both AEM 6.3.2.2 and AEM 6.3.3.6. They're probably compatible with AEM 6.5 as well (although it's untested).

      1) In function "fillMultifieldItems(mfItem)" (line 55), apply the following changes:

      Replace the following piece of code


      var field = mfItem.querySelector("[name='./" + fKey + "']");

      if(_.isEmpty(field)){
      field = mfItem.querySelector("[name='" + fKey + "']");
      }


      ... with this one:


      let field = mfItem.querySelector("[name$='" + fKey + "']");


      2) Additionally, in function "getNameDotSlashRemoved(name)" (line 197), apply the following changes:

      Replace the following piece of code


      return ((name.indexOf("./") == 0) ? name.substr(2) : name);


      ... with this one:


      let indexOfLastSlash = name.lastIndexOf("/");
      return (indexOfLastSlash === -1) ? name : name.substr(indexOfLastSlash + 1);


      Hope it works for you!

      Delete
    2. 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
    3. Hi Sreekanth,
      I am also facing same issue in AEM 6.5.5, I don't see "data-eaem-composite-item-value" attribute is not adding to "coral-multifield-item" element, $lastItem.attr(EAEM_COMPOSITE_ITEM_VALUE, mfMap), I kept the debug point at that line, main issue is "coral-multifield-item" element is not present during that line execution, not sure why it is missing. Please let know, if any solution to it. Thanks!

      Delete
    4. 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
    5. [Tested in AEM 6.5.6.0]
      Hi Nas,

      Make the changes suggested by Nireas and at line #202 replace the function getNameDotSlashRemoved with below code

      function getNameDotSlashRemoved(name){
      if(_.isEmpty(name)){
      return name;
      }

      //return ((name.indexOf("./") == 0) ? name.substr(2) : name);
      var parts = name.split("/");
      return parts[parts.length-1];
      }

      I hope this solves the issue for you.

      Thanks!!

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

      Delete
  3. This comment has been removed by the author.

    ReplyDelete
  4. 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
    2. This comment has been removed by the author.

      Delete
  5. 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
  6. 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
  7. This comment has been removed by the author.

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