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 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="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));
Hi Srikanth,
ReplyDeleteI am trying to have a composite multifield in a scaffold form. Can you please guide me how i can have two textfields in a multifield for a scaffold form?
One of the biggest challenges that organizations face today is having inaccurate data and being unresponsive to the needs of the Adobe CQ5 CMS Email List organization.
ReplyDeletePerfect solution: https://aemblogger.wordpress.com/2017/03/14/aem-touchui-multifield-nested-nodes/
ReplyDeleteI am trying to use this nested multifield in multible tabs of same dialog. Its working on first tab only . Any solution for this ?
ReplyDeleteHi Karthik
ReplyDeleteI am also facing same issue. Have you got any solution?