Thursday, July 5, 2018
AEM 62 Touch UI Nested Multi Multi Composite Multifield storing data as JSON
AEM 62 Touch UI Nested Multi Multi Composite Multifield storing data as JSON
Goal
Create a 62 Touch UI Nested Composite Multifield aka Multi-Multi Field storing the entered data as JSON. Package Install contains a sample component using this multifield extension
For storing the nested multifield data as child nodes - check this post
PS: If you are using any other Touch UI multifield extension the re-registering of multifield using CUI.Widget.registry.register("multifield", CUI.Multifield); may cause issues
Demo | Package Install
Nested Multifield

Stored as JSON

Sample Dialog XML
#45 empty valued flag eaem-nested makes the multifield widget, nested composite multifield
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root
jcr_primaryType="nt:unstructured"
jcr_title="EAEM 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/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="Sample 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"
name="./dashboard"/>
<countries
jcr_primaryType="nt:unstructured"
sling_resourceType="granite/ui/components/foundation/form/multifield"
class="full-width"
fieldDescription="Click + to add a new page"
fieldLabel="Countries">
<field
jcr_primaryType="nt:unstructured"
sling_resourceType="granite/ui/components/foundation/form/fieldset"
eaem-nested=""
name="./countries">
<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">
<country
jcr_primaryType="nt:unstructured"
sling_resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Name of Country"
fieldLabel="Country Name"
name="./country"/>
<states
jcr_primaryType="nt:unstructured"
sling_resourceType="granite/ui/components/foundation/form/multifield"
class="full-width"
fieldDescription="Click + to add a new page"
fieldLabel="States">
<field
jcr_primaryType="nt:unstructured"
sling_resourceType="granite/ui/components/foundation/form/fieldset"
name="./states">
<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">
<state
jcr_primaryType="nt:unstructured"
sling_resourceType="granite/ui/components/foundation/form/textfield"
fieldDescription="Name of State"
fieldLabel="State Name"
name="./state"/>
<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>
</states>
</items>
</column>
</items>
</field>
</countries>
</items>
</column>
</items>
</fieldset>
</items>
</column>
</items>
</content>
</jcr:root>
Solution
1) Login to CRXDE Lite (http://localhost:4502/crx/de), create folder /apps/eaem-touch-ui-nested-multi-field-panel
2) Create node /apps/eaem-touch-ui-nested-multi-field-panel/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.authoring.dialog, String property dependencies with value underscore
3) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-panel/clientlib/js.txt, add
nested-multifield.js
4) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-panel/clientlib/nested-multifield.js, add the following code
(function ($, $document) {
var DATA_EAEM_NESTED = "data-eaem-nested",
CFFW = ".coral-Form-fieldwrapper";
//reads multifield data from server, creates the nested composite multifields and fills them
function addDataInFields() {
$document.on("dialog-ready", dlgReadyHandler);
function dlgReadyHandler() {
var mName = $("[" + DATA_EAEM_NESTED + "]").data("name");
if(!mName){
return;
}
//strip ./
mName = mName.substring(2);
var $fieldSets = $("[" + DATA_EAEM_NESTED + "][class=coral-Form-fieldset]"),
$form = $fieldSets.closest("form.foundation-form"),
actionUrl = $form.attr("action") + ".json";
$.ajax(actionUrl).done(postProcess);
function postProcess(data){
if(!data || !data[mName]){
return;
}
var mValues = data[mName], $field, name;
if(_.isString(mValues)){
mValues = [ JSON.parse(mValues) ];
}
_.each(mValues, function (record, i) {
if (!record) {
return;
}
if(_.isString(record)){
record = JSON.parse(record);
}
_.each(record, function(rValue, rKey){
$field = $($fieldSets[i]).find("[name=./" + rKey + "]");
if(_.isArray(rValue) && !_.isEmpty(rValue)){
fillNestedFields( $($fieldSets[i]).find("[data-init=multifield]"), rValue);
}else{
$field.val(rValue);
}
});
});
}
//creates & fills the nested multifield with data
function fillNestedFields($multifield, valueArr){
_.each(valueArr, function(record, index){
$multifield.find(".js-coral-Multifield-add").click();
_.each(record, function(value, key){
var $field = $($multifield.find("[name=./" + key + "]")[index]);
$field.val(value);
})
})
}
}
}
function fillValue($field, record){
var name = $field.attr("name");
if (!name) {
return;
}
//strip ./
if (name.indexOf("./") == 0) {
name = name.substring(2);
}
record[name] = $field.val();
//remove the field, so that individual values are not POSTed
$field.remove();
}
//for getting the nested multifield data as js objects
function getRecordFromMultiField($multifield){
var $fieldSets = $multifield.find("[class=coral-Form-fieldset]");
var records = [], record, $fields, name;
$fieldSets.each(function (i, fieldSet) {
$fields = $(fieldSet).find("[name]");
record = {};
$fields.each(function (j, field) {
fillValue($(field), record);
});
if(!$.isEmptyObject(record)){
records.push(record)
}
});
return records;
}
//collect data from widgets in multifield and POST them to CRX as JSON
function collectDataFromFields(){
$document.on("click", ".cq-dialog-submit", collectHandler);
function collectHandler() {
var $form = $(this).closest("form.foundation-form"),
mName = $("[" + DATA_EAEM_NESTED + "]").data("name"),
$fieldSets = $("[" + DATA_EAEM_NESTED + "][class=coral-Form-fieldset]");
var record, $fields, $field, name, $nestedMultiField;
$fieldSets.each(function (i, fieldSet) {
$fields = $(fieldSet).children().children(CFFW);
record = {};
$fields.each(function (j, field) {
$field = $(field);
//may be a nested multifield
$nestedMultiField = $field.find("[data-init=multifield]");
if($nestedMultiField.length == 0){
fillValue($field.find("[name]"), record);
}else{
name = $nestedMultiField.find("[class=coral-Form-fieldset]").data("name");
if(!name){
return;
}
//strip ./
name = name.substring(2);
record[name] = getRecordFromMultiField($nestedMultiField);
}
});
if ($.isEmptyObject(record)) {
return;
}
//add the record JSON in a hidden field as string
$(<input />).attr(type, hidden)
.attr(name, mName)
.attr(value, JSON.stringify(record))
.appendTo($form);
});
}
}
$document.ready(function () {
addDataInFields();
collectDataFromFields();
});
//extend otb multifield for adjusting event propagation when there are nested multifields
//for working around the nested multifield add and reorder
CUI.Multifield = new Class({
toString: "Multifield",
extend: CUI.Multifield,
construct: function (options) {
this.script = this.$element.find(".js-coral-Multifield-input-template:last");
},
_addListeners: function () {
this.superClass._addListeners.call(this);
//otb coral event handler is added on selector .js-coral-Multifield-add
//any nested multifield add click events are propagated to the parent multifield
//to prevent adding a new composite field in both nested multifield and parent multifield
//when user clicks on add of nested multifield, stop the event propagation to parent multifield
this.$element.on("click", ".js-coral-Multifield-add", function (e) {
e.stopPropagation();
});
this.$element.on("drop", function (e) {
e.stopPropagation();
});
}
});
CUI.Widget.registry.register("multifield", CUI.Multifield);
})(jQuery, jQuery(document));