AEM CQ 56 - Panel in CQ.form.MultiField

Goal


Create a Composite Multifield Component (CQ.form.MultiField) with panel field config to store data as json, in Classic UI. For Touch UI multifield check this post

Logic for AEM 60 and AEM 56 is same, i just didn't want to remove the link to 56 code

To store multi field values as child nodes (not json) check this post


AEM 60

Demo | Package Install


Bug Fixes

Reordering items overrides empty fields, Using CQ.form.Selection.PATH_PLACEHOLDER like $PATH/countries.options.json doesn't load options of a selection drop down in multi-field-panel - Demo | Package Install

LoadContent event does not fire on multifield items; Drag and drop to pathfield (in multifield) doesn't work; Widgets added in multifield in a tab other than the visible one, shrink; supporting allowBlank on widgets added in multifield - Demo | Package Install






AEM 56

Source code (not package install) and Demo


Prerequisites


If you are new to CQ

1) Read this post on how to create a sample page component

2) Here is another blog post on multifield fieldConfig customization

Create Component


1) Create component /apps/multifieldpanel/multifieldpanel with the following properties




2) Create clientlib /apps/multifieldpanel/multifieldpanel/clientlib of type cq:ClientLibraryFolder with the following properties




3) Create file /apps/multifieldpanel/multifieldpanel/clientlib/multipanel.js and add the following code. Here we are creating a panel extension and registering it as xtype mymultipanel

var MyClientLib = MyClientLib || {};

MyClientLib.MyMultiPanel = CQ.Ext.extend(CQ.Ext.Panel, {
    panelValue: '',

    constructor: function(config){
        config = config || {};
        MyClientLib.MyMultiPanel.superclass.constructor.call(this, config);
    },

    initComponent: function () {
        MyClientLib.MyMultiPanel.superclass.initComponent.call(this);

        this.panelValue = new CQ.Ext.form.Hidden({
            name: this.name
        });

        this.add(this.panelValue);

        var dialog = this.findParentByType('dialog');

        dialog.on('beforesubmit', function(){
            var value = this.getValue();

            if(value){
                this.panelValue.setValue(value);
            }
        },this);
    },

    getValue: function () {
        var pData = {};

        this.items.each(function(i){
            if(i.xtype == "label" || i.xtype == "hidden" || !i.hasOwnProperty("dName")){
                return;
            }

            pData[i.dName] = i.getValue();
        });

        return $.isEmptyObject(pData) ? "" : JSON.stringify(pData);
    },

    setValue: function (value) {
        this.panelValue.setValue(value);

        var pData = JSON.parse(value);

        this.items.each(function(i){
            if(i.xtype == "label" || i.xtype == "hidden" || !i.hasOwnProperty("dName")){
                return;
            }

            if(!pData[i.dName]){
                return;
            }

            i.setValue(pData[i.dName]);
        });
    },

    validate: function(){
        return true;
    },

    getName: function(){
        return this.name;
    }
});

CQ.Ext.reg("mymultipanel", MyClientLib.MyMultiPanel);

4) Create file /apps/multifieldpanel/multifieldpanel/clientlib/js.txt and add the following code

               multipanel.js

5) Create dialog /apps/multifieldpanel/multifieldpanel/dialog for component above with the following properties. Here,

       a) The node /apps/multifieldpanel/multifieldpanel/dialog/items/items/tab1/items/map/fieldConfig has xtype mymultipanel

       b) Each widget in the multipanel (eg. /apps/multifieldpanel/multifieldpanel/dialog/items/items/tab1/items/map/fieldConfig/items/product-year-value) should have a dName property, which is read by multi panel to store value entered by author for the field

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:Dialog"
    title="Multi Field"
    xtype="dialog">
    <items
        jcr:primaryType="cq:Widget"
        xtype="tabpanel">
        <items jcr:primaryType="cq:WidgetCollection">
            <tab1
                jcr:primaryType="cq:Panel"
                title="Add">
                <items jcr:primaryType="cq:WidgetCollection">
                    <map
                        jcr:primaryType="cq:Widget"
                        hideLabel="false"
                        name="./map"
                        title="Map"
                        xtype="multifield">
                        <fieldConfig
                            jcr:primaryType="cq:Widget"
                            border="true"
                            hideLabel="true"
                            layout="form"
                            padding="10px"
                            width="1000"
                            xtype="mymultipanel">
                            <items jcr:primaryType="cq:WidgetCollection">
                                <product-year-value
                                    jcr:primaryType="cq:Widget"
                                    dName="year"
                                    fieldLabel="Year"
                                    width="60"
                                    xtype="textfield"/>
                                <product-price-value
                                    jcr:primaryType="cq:Widget"
                                    dName="price"
                                    fieldLabel="Price"
                                    width="60"
                                    xtype="textfield"/>
                                <product-version-value
                                    jcr:primaryType="cq:Widget"
                                    dName="version"
                                    fieldLabel="Path to Version"
                                    xtype="pathfield"/>
                                <product-lowStock-value
                                    jcr:primaryType="cq:Widget"
                                    dName="lowStock"
                                    fieldLabel="Low Stock ?"
                                    width="25"
                                    xtype="checkbox"/>
                            </items>
                        </fieldConfig>
                    </map>
                </items>
            </tab1>
        </items>
    </items>
</jcr:root>



6) Add the following code in /apps/multifieldpanel/multifieldpanel/multifieldpanel.jsp

<%@ page import="org.apache.sling.commons.json.JSONObject" %>
<%@ page import="java.io.PrintWriter" %>
<%@include file="/libs/foundation/global.jsp" %>
<%@page session="false" %>

<div style="display: block; border-style: solid; border-width: 1px; margin: 10px; padding: 10px">
    <b>Multi Field Sample</b>

    <%
        try {
            Property property = null;

            if(currentNode.hasProperty("map")){
                property = currentNode.getProperty("map");
            }

            if (property != null) {
                JSONObject obj = null;
                Value[] values = null;

                if(property.isMultiple()){
                    values = property.getValues();
                }else{
                    values = new Value[1];
                    values[0] = property.getValue();
                }

                for (Value val : values) {
                    obj = new JSONObject(val.getString());
    %>
                        Year : <b><%= obj.get("year") %></b>,
                        Price : <b><%= obj.get("price") %></b>,
                        Version : <b><%= obj.get("version")%></b>,
                        Low Stock : <b><%=obj.get("lowStock")%></b>
    <%
                }
            } else {
    %>
                Add values in dialog
    <%
            }
        } catch (Exception e) {
            e.printStackTrace(new PrintWriter(out));
        }
    %>

</div>



28 comments:

  1. Sreekanth for some reason when I create the node structure my labels for products are hidden. From the CQ css the label display is none. Even when I change the fieldconfig 'hideLabel' to true...

    ReplyDelete
    Replies
    1. Tyler can you pls paste the dialog xml here

      Delete
  2. Nice article.It helped me a lot. :)

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

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. Sreekanth,
    Nice article. You probably want to add the following line to the multifieldpanel.jsp so your clientlib code gets invoked:


    Thanks

    ReplyDelete
    Replies
    1. cq:includeClientLib categories="cq:widgets" - please add the opening and closing tags. Blogspot removes it.

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

    ReplyDelete
  7. Nice Article!
    However I noticed that the scrollbar is not shown in the dialog when adding multiple items, hence it's not possible to scroll down to see all the items. Have you noticed this issue? Any idea on how to handle it?
    Thanks!

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
  9. This comment has been removed by the author.

    ReplyDelete
  10. Add a "selection" xtype, with type "select" to a multifieldpanel that loads its options via a servlet.

    dropdown_list
    jcr:primaryType="cq:Widget"
    dName="dropdown_list"
    fieldLabel="dropdown list"
    options="$PATH.unitid.options.json"
    type="select"
    xtype="selection"



    The drop down generated by the "selection" xtype fails to dynamically load its options as configured.

    Can you fix it ?

    ReplyDelete
    Replies
    1. you bet, check the Bug Fixes section above

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

      Delete
    3. Thanks a lot. It helped me.

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

    ReplyDelete
  12. Hi, I have tried to use this example and found that empty dialog fields are getting overwritten by the moved item (entry) on the dialog, This is happening for both Up and Down buttons. This has really affected the effectiveness of this entire dialog. Can anyone help me on this ?

    ReplyDelete
    Replies
    1. This is related to the reorder functionality of the dialog entry sets.

      Delete
    2. I think the demo also reveals this problem if we notice the unchecked checkbox in the second entry set which became true after the reordering.

      Delete
    3. Somnath, Get the package in bugfixes section above (if you are trying out the component in package, make sure the countries node exists as shown in demo or remove the "product-country" in dialog )... the fix is removing #57 to #59 if(!pData[i.dName]){ return; }

      Delete
    4. Thanks a lot Sreekanth. This is now fixed.

      Delete
    5. Sreekanth, can you please help me once more regarding your blog on using RTE on multi-widget: http://experience-aem.blogspot.in/2014/01/aem-cq-56-adding-richtexteditor-in-multifield-drag-drop.html. I am facing the issue that once I open the dialog and drag drop an image it works. Now if I cancel the dialog (-ve testing) or press on Ok, the next time this drag-n-drop is not working. For Ok press, I can still enforce a page refresh using cq:editConfig -> cq:listener, but for a Cancel click on dialog, I am left with no other way. This time I have checked, there's no Bug Fixes section on that page :-) ... looking forward to your kind help on this. Warm Regards, Somnath.

      Delete
    6. So it is now like a page refresh after every image drag-n-drop on the multi-widget.

      Delete
    7. Is there any fix for the issue mentioned above ? i am also facing the same issue. i have checked a number of blogs, but still no resolution..

      TIA

      Delete
  13. Hi,

    Goody Day! nice article. However when i try to create new xtype it will return "Uncaught cannot create Component: xtype 'mymultipanel' not found and no default supplied". Any idea can resolved this?

    ReplyDelete
  14. how to get json values of multifieldpanel using sling model

    ReplyDelete