Goal
Create a Classic UI Composite Multifield, storing the composite field values as child nodes (useful when executing search queries for exact matches).
For storing values in json format, check this post
For Touch UI Composite Multifield storing values as child nodes check this post
Tested on AEM 61; should work ok on 60 and 561 too... Demo | Package Install
Multi Field Panel
Image may be NSFW.
Clik here to view.
Clik here to view.

Value Nodes in CRX
Image may be NSFW.
Clik here to view.
Clik here to view.

Dialog in CRX
Image may be NSFW.
Clik here to view.
Clik here to view.

Dialog XML
#31, #37, #43 - property dName used in creating fully qualified name (./<multifield-name>/<order>/<dName>) eg ./stock/1/year
<?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">
<stock
jcr:primaryType="cq:Widget"
hideLabel="false"
name="./stock"
title="Stock"
xtype="multifield">
<fieldConfig
jcr:primaryType="cq:Widget"
border="true"
hideLabel="true"
layout="form"
padding="10px"
width="1000"
xtype="multi-field-panel">
<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"/>
</items>
</fieldConfig>
</stock>
</items>
</tab1>
</items>
</items>
</jcr:root>
Solution
1) Login to CRXDE Lite, create folder (nt:folder) /apps/classic-ui-multi-field-panel-node-store
2) Create clientlib (type cq:ClientLibraryFolder) /apps/classic-ui-multi-field-panel-node-store/clientlib and set a property categories of String type to cq.widgets
3) Create file ( type nt:file ) /apps/classic-ui-multi-field-panel-node-store/clientlib/js.txt, add the following
multi-field.js
4) Create file ( type nt:file ) /apps/classic-ui-multi-field-panel-node-store/clientlib/multi-field.js, add the following code
CQ.Ext.ns("ExperienceAEM");
ExperienceAEM.MultiFieldPanel = CQ.Ext.extend(CQ.Ext.Panel, {
constructor: function(config){
config = config || {};
ExperienceAEM.MultiFieldPanel.superclass.constructor.call(this, config);
},
initComponent: function () {
ExperienceAEM.MultiFieldPanel.superclass.initComponent.call(this);
function addName(items, prefix, counter){
items.each(function(i){
if(!i.hasOwnProperty("dName")){
return;
}
i.name = prefix + "/" + (counter) + "/" + i.dName;
if(i.el && i.el.dom){ //form serialization workaround
i.el.dom.name = prefix + "/" + (counter) + "/" + i.dName;
}
},this);
}
var multi = this.findParentByType("multifield"),
multiPanels = multi.findByType("multi-field-panel");
addName(this.items, this.name, multiPanels.length + 1);
multi.on("removeditem", function(){
multiPanels = multi.findByType("multi-field-panel");
for(var x = 1; x <= multiPanels.length; x++){
addName(multiPanels[x-1].items, multiPanels[x-1].name, x);
}
});
},
afterRender : function(){
ExperienceAEM.MultiFieldPanel.superclass.afterRender.call(this);
this.items.each(function(){
if(!this.contentBasedOptionsURL
|| this.contentBasedOptionsURL.indexOf(CQ.form.Selection.PATH_PLACEHOLDER) < 0){
return;
}
this.processPath(this.findParentByType('dialog').path);
})
},
getValue: function () {
var pData = {};
this.items.each(function(i){
if(!i.hasOwnProperty("dName")){
return;
}
pData[i.dName] = i.getValue();
});
return pData;
},
setValue: function (value) {
var counter = 1, item,
multi = this.findParentByType("multifield"),
multiPanels = multi.findByType("multi-field-panel");
if(multiPanels.length == 1){
item = value[counter];
}else{
item = value;
}
this.items.each(function(i){
if(!i.hasOwnProperty("dName")){
return;
}
i.setValue(item[i.dName]);
});
if(multiPanels.length == 1){
while(true){
item = value[++counter];
if(!item){
break;
}
multi.addItem(item);
}
}
},
validate: function(){
return true;
},
getName: function(){
return this.name;
}
});
CQ.Ext.reg("multi-field-panel", ExperienceAEM.MultiFieldPanel);
5) A sample exact match query performed with query builder, on property name year, returning multifield nodes
http://localhost:4502/bin/querybuilder.json?property=year&property.value=2010&p.hits=full
Image may be NSFW.
Clik here to view.![]()
Clik here to view.