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>
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...
ReplyDeleteTyler can you pls paste the dialog xml here
DeleteNice article.It helped me a lot. :)
ReplyDeleteglad :)
DeleteThis comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteSreekanth,
ReplyDeleteNice article. You probably want to add the following line to the multifieldpanel.jsp so your clientlib code gets invoked:
Thanks
cq:includeClientLib categories="cq:widgets" - please add the opening and closing tags. Blogspot removes it.
DeleteThis comment has been removed by the author.
ReplyDeleteNice Article!
ReplyDeleteHowever 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!
Hi, the demo shows scrollbar...
DeleteThis comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteAdd a "selection" xtype, with type "select" to a multifieldpanel that loads its options via a servlet.
ReplyDeletedropdown_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 ?
you bet, check the Bug Fixes section above
DeleteThanks a lot. It helped me.
DeleteThis comment has been removed by the author.
ReplyDeleteHi, 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 ?
ReplyDeleteThis is related to the reorder functionality of the dialog entry sets.
DeleteI think the demo also reveals this problem if we notice the unchecked checkbox in the second entry set which became true after the reordering.
DeleteSomnath, 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; }
DeleteThanks a lot Sreekanth. This is now fixed.
DeleteSreekanth, 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.
DeleteSo it is now like a page refresh after every image drag-n-drop on the multi-widget.
DeleteIs 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..
DeleteTIA
Hi,
ReplyDeleteGoody 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?
how to get json values of multifieldpanel using sling model
ReplyDeleteI am trying to create a component for classic UI which has nested multifields. The values of them should be stored as a JSON object.
ReplyDeleteI am trying to create a version of the component for classic UI which is explained here (for Touch UI)
I found a post where I am able to do this for a composite field but nested multifield is what I am struggling with.
Can you please guide me in the right direction please?
thanks.
I did everything same also include client library to component but it giving me error
ReplyDeletewidgets.js:25800 Uncaught cannot create Component: xtype 'mymultipanel' not found and no default supplied