AEM 61 - Touch UI Composite Multifield Store Values as Child Nodes

Goal


Create a Touch UI Composite Multifield, storing field values as child nodes (useful when executing search queries for exact matches).

For storing values as json check this post

For Classic UI Composite Multifield , storing values as child nodes check this post

For Touch UI Image Multifield check this post

Tested on AEM 61; should work ok on 60

Demo | Package Install


Bug Fixes

AEM 61 - Select granite/ui/components/foundation/form/select and Checkbox granite/ui/components/foundation/form/checkbox show incorrect values on dialog open - Demo | Package Install

AEM 60 SP2 - Select granite/ui/components/foundation/form/select and Checkbox granite/ui/components/foundation/form/checkbox show incorrect values on dialog open -  Demo | Package Install


Composite Multifield in Dialog





Value Nodes in CRX






Dialog Structure






Dialog XML

Sample dialog with 3 composite multifields added in 3 tabs. #49, #125, #202 mark these multifields as composite, by specifying the flag eaem-nested

<?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"
    jcr:title="Multifield TouchUI Component"
    sling:resourceType="cq/gui/components/authoring/dialog"
    helpPath="en/cq/current/wcm/default_components.html#Text">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/foundation/container">
        <layout
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/foundation/layouts/tabs"
            type="nav"/>
        <items jcr:primaryType="nt:unstructured">
            <india
                jcr:primaryType="nt:unstructured"
                jcr:title="India"
                sling:resourceType="granite/ui/components/foundation/section">
                <layout
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
                <items jcr:primaryType="nt:unstructured">
                    <column
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/foundation/container">
                        <items jcr:primaryType="nt:unstructured">
                            <fieldset
                                jcr:primaryType="nt:unstructured"
                                jcr:title="India Dashboard"
                                sling:resourceType="granite/ui/components/foundation/form/fieldset">
                                <layout
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
                                <items jcr:primaryType="nt:unstructured">
                                    <column
                                        jcr:primaryType="nt:unstructured"
                                        sling:resourceType="granite/ui/components/foundation/container">
                                        <items jcr:primaryType="nt:unstructured">
                                            <dashboard
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/foundation/form/textfield"
                                                fieldDescription="Enter Dashboard name"
                                                fieldLabel="Dashboard"
                                                name="./iDashboard"/>
                                            <pages
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/foundation/form/multifield"
                                                class="full-width"
                                                eaem-nested=""
                                                fieldDescription="Click '+' to add a new page"
                                                fieldLabel="URLs">
                                                <field
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/foundation/form/fieldset"
                                                    name="./iItems">
                                                    <layout
                                                        jcr:primaryType="nt:unstructured"
                                                        sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
                                                        method="absolute"/>
                                                    <items jcr:primaryType="nt:unstructured">
                                                        <column
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/foundation/container">
                                                            <items jcr:primaryType="nt:unstructured">
                                                                <page
                                                                    jcr:primaryType="nt:unstructured"
                                                                    sling:resourceType="granite/ui/components/foundation/form/textfield"
                                                                    fieldDescription="Enter Page Name"
                                                                    fieldLabel="Page Name"
                                                                    name="./page"/>
                                                                <path
                                                                    jcr:primaryType="nt:unstructured"
                                                                    sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
                                                                    fieldDescription="Select Path"
                                                                    fieldLabel="Path"
                                                                    name="./path"
                                                                    rootPath="/content"/>
                                                            </items>
                                                        </column>
                                                    </items>
                                                </field>
                                            </pages>
                                        </items>
                                    </column>
                                </items>
                            </fieldset>
                        </items>
                    </column>
                </items>
            </india>
            <usa
                jcr:primaryType="nt:unstructured"
                jcr:title="USA"
                sling:resourceType="granite/ui/components/foundation/section">
                <layout
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
                <items jcr:primaryType="nt:unstructured">
                    <column
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/foundation/container">
                        <items jcr:primaryType="nt:unstructured">
                            <fieldset
                                jcr:primaryType="nt:unstructured"
                                jcr:title="USA Dashboard"
                                sling:resourceType="granite/ui/components/foundation/form/fieldset">
                                <layout
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
                                <items jcr:primaryType="nt:unstructured">
                                    <column
                                        jcr:primaryType="nt:unstructured"
                                        sling:resourceType="granite/ui/components/foundation/container">
                                        <items jcr:primaryType="nt:unstructured">
                                            <dashboard
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/foundation/form/textfield"
                                                fieldDescription="Enter Dashboard name"
                                                fieldLabel="Dashboard"
                                                name="./uDashboard"/>
                                            <pages
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/foundation/form/multifield"
                                                class="full-width"
                                                eaem-nested=""
                                                fieldDescription="Click '+' to add a new page"
                                                fieldLabel="URLs">
                                                <field
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/foundation/form/fieldset"
                                                    eaem-nested=""
                                                    name="./uItems">
                                                    <layout
                                                        jcr:primaryType="nt:unstructured"
                                                        sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
                                                        method="absolute"/>
                                                    <items jcr:primaryType="nt:unstructured">
                                                        <column
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/foundation/container">
                                                            <items jcr:primaryType="nt:unstructured">
                                                                <page
                                                                    jcr:primaryType="nt:unstructured"
                                                                    sling:resourceType="granite/ui/components/foundation/form/textfield"
                                                                    fieldDescription="Enter Page Name"
                                                                    fieldLabel="Page Name"
                                                                    name="./page"/>
                                                                <path
                                                                    jcr:primaryType="nt:unstructured"
                                                                    sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
                                                                    fieldDescription="Select Path"
                                                                    fieldLabel="Path"
                                                                    name="./path"
                                                                    rootPath="/content"/>
                                                            </items>
                                                        </column>
                                                    </items>
                                                </field>
                                            </pages>
                                        </items>
                                    </column>
                                </items>
                            </fieldset>
                        </items>
                    </column>
                </items>
            </usa>
            <uk
                jcr:primaryType="nt:unstructured"
                jcr:title="UK"
                sling:resourceType="granite/ui/components/foundation/section">
                <layout
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
                <items jcr:primaryType="nt:unstructured">
                    <column
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/foundation/container">
                        <items jcr:primaryType="nt:unstructured">
                            <fieldset
                                jcr:primaryType="nt:unstructured"
                                jcr:title="UK Dashboard"
                                sling:resourceType="granite/ui/components/foundation/form/fieldset">
                                <layout
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/>
                                <items jcr:primaryType="nt:unstructured">
                                    <column
                                        jcr:primaryType="nt:unstructured"
                                        sling:resourceType="granite/ui/components/foundation/container">
                                        <items jcr:primaryType="nt:unstructured">
                                            <dashboard
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/foundation/form/textfield"
                                                fieldDescription="Enter Dashboard name"
                                                fieldLabel="Dashboard"
                                                name="./ukDashboard"/>
                                            <pages
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/foundation/form/multifield"
                                                class="full-width"
                                                eaem-nested=""
                                                fieldDescription="Click '+' to add a new page"
                                                fieldLabel="URLs">
                                                <field
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/foundation/form/fieldset"
                                                    eaem-nested=""
                                                    name="./ukItems">
                                                    <layout
                                                        jcr:primaryType="nt:unstructured"
                                                        sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"
                                                        method="absolute"/>
                                                    <items jcr:primaryType="nt:unstructured">
                                                        <column
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/foundation/container">
                                                            <items jcr:primaryType="nt:unstructured">
                                                                <page
                                                                    jcr:primaryType="nt:unstructured"
                                                                    sling:resourceType="granite/ui/components/foundation/form/textfield"
                                                                    fieldDescription="Enter Page Name"
                                                                    fieldLabel="Page Name"
                                                                    name="./page"/>
                                                                <path
                                                                    jcr:primaryType="nt:unstructured"
                                                                    sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
                                                                    fieldDescription="Select Path"
                                                                    fieldLabel="Path"
                                                                    name="./path"
                                                                    rootPath="/content"/>
                                                            </items>
                                                        </column>
                                                    </items>
                                                </field>
                                            </pages>
                                        </items>
                                    </column>
                                </items>
                            </fieldset>
                        </items>
                    </column>
                </items>
            </uk>
        </items>
    </content>
</jcr:root>


Solution


1) Login to CRXDE Lite, create folder (nt:folder) /apps/touch-ui-composite-multi-field-store-as-child-nodes

2) Create clientlib (type cq:ClientLibraryFolder/apps/touch-ui-composite-multi-field-store-as-child-nodes/clientlib and set a property categories of String type to cq.authoring.dialogdependencies of type String[] with value underscore

3) Create file ( type nt:file ) /apps/touch-ui-composite-multi-field-store-as-child-nodes/clientlib/js.txt, add the following

                         multifield.js

4) Create file ( type nt:file ) /apps/touch-ui-composite-multi-field-store-as-child-nodes/clientlib/multifield.js, add the following code

(function () {
    var DATA_EAEM_NESTED = "data-eaem-nested";
    var CFFW = ".coral-Form-fieldwrapper";

    //reads multifield data from server, creates the nested composite multifields and fills them
    function addDataInFields() {
        function getMultiFieldNames($multifields){
            var mNames = {}, mName;

            $multifields.each(function (i, multifield) {
                mName = $(multifield).children("[name$='@Delete']").attr("name");

                mName = mName.substring(0, mName.indexOf("@"));

                mName = mName.substring(2);

                mNames[mName] = $(multifield);
            });

            return mNames;
        }

        function buildMultiField(data, $multifield, mName){
            if(_.isEmpty(mName) || _.isEmpty(data)){
                return;
            }

            _.each(data, function(value, key){
                if(key == "jcr:primaryType"){
                    return;
                }

                $multifield.find(".js-coral-Multifield-add").click();

                _.each(value, function(fValue, fKey){
                    if(fKey == "jcr:primaryType"){
                        return;
                    }

                    var $field = $multifield.find("[name='./" + fKey + "']").last();

                    if(_.isEmpty($field)){
                        return;
                    }

                    $field.val(fValue);
                });
            });
        }

        $(document).on("dialog-ready", function() {
            var $multifields = $("[" + DATA_EAEM_NESTED + "]");

            if(_.isEmpty($multifields)){
                return;
            }

            var mNames = getMultiFieldNames($multifields),
                $form = $(".cq-dialog"),
                actionUrl = $form.attr("action") + ".infinity.json";

            $.ajax(actionUrl).done(postProcess);

            function postProcess(data){
                _.each(mNames, function($multifield, mName){
                    buildMultiField(data[mName], $multifield, mName);
                });
            }
        });
    }

    //collect data from widgets in multifield and POST them to CRX
    function collectDataFromFields(){
        function fillValue($form, fieldSetName, $field, counter){
            var name = $field.attr("name");

            if (!name) {
                return;
            }

            //strip ./
            if (name.indexOf("./") == 0) {
                name = name.substring(2);
            }

            //remove the field, so that individual values are not POSTed
            $field.remove();

            $('<input />').attr('type', 'hidden')
                .attr('name', fieldSetName + "/" + counter + "/" + name)
                .attr('value', $field.val())
                .appendTo($form);
        }

        $(document).on("click", ".cq-dialog-submit", function () {
            var $multifields = $("[" + DATA_EAEM_NESTED + "]");

            if(_.isEmpty($multifields)){
                return;
            }

            var $form = $(this).closest("form.foundation-form"),
                $fieldSets, $fields;

            $multifields.each(function(i, multifield){
                $fieldSets = $(multifield).find("[class='coral-Form-fieldset']");

                $fieldSets.each(function (counter, fieldSet) {
                    $fields = $(fieldSet).children().children(CFFW);

                    $fields.each(function (j, field) {
                        fillValue($form, $(fieldSet).data("name"), $(field).find("[name]"), (counter + 1));
                    });
                });
            });
        });
    }

    $(document).ready(function () {
        addDataInFields();
        collectDataFromFields();
    });
})();


41 comments:

  1. It did not work for me on AEM 6.0, the values are saved under a String[] instead of each element in a node.

    ReplyDelete
    Replies
    1. Looks like 60 doesn't POST @Delete attribute on dialog close... have added a fix in the composite multifield extension

      Demo on 60: https://drive.google.com/file/d/0B4d6KmbLkAumUUZpRXAxM1g3ZmM/view?usp=sharing
      Demo on 60 SP2: https://drive.google.com/file/d/0B4d6KmbLkAumTnpYWjZvejFHVHM/view?usp=sharing
      Package Install: https://drive.google.com/file/d/0B4d6KmbLkAumV1AtMDRYMmF3cHc/view?usp=sharing

      Delete
  2. Great component, but it doesn't work for me with some field types. I tried granite/ui/components/foundation/form/select and checkbox. The value entered into these field types is saved, but the value is not loaded/displayed when I open the dialog again. Do you have any idea why this is happening? Thanks!

    ReplyDelete
    Replies
    1. Hi, thanks for reporting

      61 package install - https://drive.google.com/file/d/0B4d6KmbLkAumRk5OeUlQY3N1c3c/view?usp=sharing
      61 demo - https://drive.google.com/file/d/0B4d6KmbLkAumLV9LUW5HeDFMVU0/view?usp=sharing

      Delete
  3. Hi Sreekanth, I tried both the original package as well as the one with bugfix for AEM6.1. After saving the data in dialog and reopeing the dialog, I am seeing the values multiple times. Stuck there. :-(

    ReplyDelete
    Replies
    1. hi DJ, can you upload the dialog xml and share url here?

      Delete
    2. Hi Sreekanth,
      I too am facing the same issue where the data is shown two times upon opening the dialog for subsequent edits. I followed the similar dialog structure mentioned here in the blog.
      I had modified nested-multifield.js to have the multi-field node names as item_1, item_2, etc... instead of 1,2 and so on.
      Please let me know how to fix this issue.
      Let me know a way to upload the cq:dialog xml.

      Delete
    3. hi Srikanth, can you upload the component (with dialog xml and clientlib) to google drive or dropbox and post the link here....

      Delete
    4. Hi Sreekanth,
      Here is the google drive link for dialog xml and clientlib
      https://www.dropbox.com/sh/l49jejjomr2bq81/AADC3r2l4XNSwwQiM9-irnLra?dl=0

      Delete
    5. Srikanth, tested with the uploaded dialog xml and it seems to be working fine, other than the selects are not set to correct value in multifield on dialog reopen (please check the bugfixes section above for fix) the following demo shows nodes created as item_ and select widget fix...

      https://drive.google.com/file/d/0B4d6KmbLkAumMzNGVU0yNElKZ3M/view?usp=sharing

      need more detail or a video showing the steps to reproduce would be great...

      Delete
    6. This issue existed in only my local AEM instance. Works fine in my teammates' instances. Thanks for the help Sreekanth.

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

      Delete
    8. Hello Sreekanth, I need to have the control 'Select' inside the nested multifield, and it needs to be dynamically populated using JS.
      Can you please suggest any solution.

      Delete
  4. Hi Sreekanth,
    I am testing this code on AEM6.1 and it works fine except in one scenario when i have FileUpload widget in my dialog. When FileUpload dialog is present in dialog, images are displayed as many times as .js-coral-Multifield-add click event is called. I did some debugging and found that click event is trigerring cui-contentloaded event which in turns trigger CUI.FileChunkedUpload.init. This method is responsible for displaying additional images. Have you faced this issue or do you know a solution for this issue?

    Thanks for all your help.

    Regards,
    Mayank

    ReplyDelete
    Replies
    1. Mayank, are you looking for something like - http://experience-aem.blogspot.com/2015/06/aem-61-touch-ui-image-multifield.html

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

      Delete
    3. Sreekanth,
      Thanks for your reply. I am not creating image multifield rather i am creating normal multifield similar to the one in this post but in dialog i have FileUpload widget as well along with multifield i created.

      Regards,
      Mayank

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

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete
  7. Hi Sreekanth!

    I've tried to implement your solution for nested multifield in multifield, but does not seems to work. What I'm trying to achieve is a first level multifield for countries, then a nested multifield with Language and Url for the specific language.


    Any advice?

    ReplyDelete
    Replies
    1. hi, can you upload the package to dropbox and share url....

      Delete
    2. Hi Srikanth I am also trying for nested multi composite field. Can you please help how to make this work. https://www.dropbox.com/s/4gu0mavwbdnmg41/multimultifield.xml?dl=0

      Delete
  8. Hi,

    I need to implement same in the commerce screens. Can you please help

    ReplyDelete
  9. Hi Sreekanth, after added in few more granite UI dialogs, things gone haywire. Tested with two additional dialog on top of your package, text area and richtext.
    When I created just one set of content, not multiple. It saved into jcr as node = 1. But when I open up my dialog, it displayed more than one set.
    You can take a look on my package with below link (contains a lot of dialogs as test cases) and hope you will be able to help support all of them. Thanks.

    https://www.dropbox.com/s/zotmxcc3imbfmdd/touch-ui-composite-multi-field-store-as-child-nodes.zip?dl=0

    ReplyDelete
  10. I'm looking for something that will allow me to have a numberfield and a checkbox inside a multifield. Whenever I do this, it saves the values in the node correctly as a String[], but it never re-displays the values when I get back to the dialog.
    Any thoughts?

    ReplyDelete
  11. Hey Dude,

    This Multi field values are not working when we have a selection or drop-down .
    Values are not getting retained when we open the dialog.

    Thanks,
    Venkatesh Seetha

    ReplyDelete
  12. Thank you so much for this! I wish Adobe would build this solution into AEM6.2. It solves a common problem with dialogs where we need to have a multifield containing groups of related input. For example, is there an OOTB way to enable a list of hyperlinks (i.e. href, title and text)?

    ReplyDelete
  13. Hi sreekanth,
    everything is working fine and nodes are created but, after adding values to the dialog and reloading the page, the properties are duplicated and appearing twice in the dialog.
    can you help me with this.

    Thanks

    ReplyDelete
    Replies
    1. Hi Karthick ,
      Am also facing similar issue can u help me on this.Is this issue got resolved

      Delete
    2. Hi Sambasivaraja,

      The fields got replicated because, there was already a similar script added in my project.

      Delete
  14. Replies
    1. JS console error:

      clientlib.js:187 Uncaught RangeError: Maximum call stack size exceededCUI.Multifield.Class._addListeners @ clientlib.js:187CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188CUI.Multifield.Class._addListeners @ clientlib.js:188

      Delete
  15. Hi, its not working for richtext , value not pre-poulating in dialog

    ReplyDelete
  16. Hi All,

    I need to restrict my path browser to a particular path in touch UI. On selection of a wrong path, it should throw an error.
    Any help?

    Thanks in advance

    ReplyDelete
  17. Hi All,

    I've created a custom touch-ui multi-field dialog with respective js. Im facing a random behavior with the populating the multi-field. At times the fields get populated and at times they don't. There is no definitive pattern. Please help.

    Thanks,
    Mayur

    ReplyDelete
  18. Hi All,
    Touch ui composite multi-field is not working in Safari browser.Any help on this

    ReplyDelete
  19. Hi All,

    I tried this blog post in my local instance it works but my question is what happens if we upgrade to newer AEM instance do we have to update the nested-multifiled.js as well everytime? Do we have any newer version of this JS file ? The package I used was of 2015 from here: https://drive.google.com/file/d/0B4d6KmbLkAumRk5OeUlQY3N1c3c/view?usp=sharing

    It would be good if the nested multifiled for component development becomes out of the box feature instead as its such common use case for component development. Any suggestions ?

    ReplyDelete
  20. Hi Srikanth,

    when i am trying to use chekbox or selection box inside multifileds. Value of checkbox do not retain when again i am opening th dialog . Plz help

    ReplyDelete
  21. Srikanth,

    You're a life saver, thanks for providing this plugin.

    I notice you have contributed this to ACS AEM Commons as well.

    ReplyDelete
  22. Hi Srikanth,

    I tried in AEM 6.2 but it is not working. I installed your package and tested. So i need to do more?

    ReplyDelete