March 30, 2020
Estimated Post Reading Time ~

Touch UI Composite Multi-field with Drop-Down in AEM 6.2

Composite Multi-Field is the widely used feature in AEM. Multifield is used to store multiple values. Multifield can contain single or multiple fields
Steps to create Composite Multi-Field:

Step1: Create a component having touch UI dialog with the following hierarchy:
<?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="Multifield"
sling:resourceType="cq/gui/components/authoring/dialog">

<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">

<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/tabs"
type="nav"/>

<items jcr:primaryType="nt:unstructured">
<basic
jcr:primaryType="nt:unstructured"
jcr:title="Basic"
sling:resourceType="granite/ui/components/foundation/section">

<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"
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">

<items
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/multifield"
fieldDescription="Click 'Add field' button to add a new field."
fieldLabel="Product List">

<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/fieldset"
eaem-nested=""
name="./items">

<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">

<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldLabel="Social Title"
name="./socialTitle"/>

<icon
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/select"
fieldLabel="Select Social Icon"
name="./socialIcon">
<items jcr:primaryType="nt:unstructured">

<select
jcr:primaryType="nt:unstructured"
text="Select Icon"/>

<facebook
jcr:primaryType="nt:unstructured"
text="Facebook"
value="facebook"/>

<linkedin
jcr:primaryType="nt:unstructured"
text="Linkedin"
value="linkedin"/>

<twitter
jcr:primaryType="nt:unstructured"
text="Twitter"
value="twitter"/>

<googlePlus
jcr:primaryType="nt:unstructured"
text="Google +"
value="google-plus"/>

</items>
</icon>
</items>
</column>
</items>
</field>
</items>
</items>
</column>
</items>
</fieldset>
</items>
</column>
</items>
</basic>
</items>
</content>
</jcr:root>

Step2: Create node "/apps/aem-learning/components/content/multifield/clientlibs" of type cq:ClientLibraryFolder and add a String property categories with value cq.authoring.dialog.

Step3: Create file (nt:file) "/apps/aem-learning/components/content/multifield/clientlibs/js.txt" and add the following js files:

Multifield.js
(function() {
var DATA_EAEM_NESTED = "data-eaem-nested";
var CFFW = ".coral-Form-fieldwrapper";
//reads multifield data from a server, creates the nested composite multifields and fills them

function addDataInFields() {
$(document).on("dialog-ready", function() {
var $fieldSets = $("[" + DATA_EAEM_NESTED + "][class='coral-Form-fieldset']");
if (_.isEmpty($fieldSets)) {
return;
}

var mNames = [];
$fieldSets.each(function(i, fieldSet) {
mNames.push($(fieldSet).data("name"));
});

mNames = _.uniq(mNames);
var actionUrl = $fieldSets.closest("form.foundation-form").attr("action") + ".json";
$.ajax(actionUrl).done(postProcess);


function postProcess(data) {
_.each(mNames, function(mName) {
buildMultiField(data, mName);
});
}

//creates & fills the nested multifield with data
function fillNestedFields($multifield, valueArr) {
_.each(valueArr, function(record, index) {
$multifield.find(".js-coral-Multifield-add").click();
//a setTimeout may be needed
_.each(record, function(value, key) {
var $field = $($multifield.find("[name='./" + key + "']")[index]);
$field.val(value);
})
})
}

function buildMultiField(data, mName) {
if (_.isEmpty(mName)) {
return;
}

$fieldSets = $("[data-name='" + mName + "']");
//strip ./
mName = mName.substring(2);
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);
}
});
});
}
});
}

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", function() {
var $form = $(this).closest("form.foundation-form");
var $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);

//maybe 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 a string
$('<input />').attr('type', 'hidden')
.attr('name', $(fieldSet).data("name"))
.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.CustomMultifield = 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 the 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.CustomMultifield);
})();

This is how we create a Composite Multi-Field, But there are some common issues related to this component.

Below are some of the examples:
Issue 1: When we use the drop-down widget in Composite Multi-Field, Selected values are saved in node but when we re-open the dialog, saved values are not visible in the dialog box.

Fig - Multifield Values saved in node under the content hierarchy

A dropdown will look like below:

Fig - Saved Values not showing up in a drop-down

To solve this problem we need to update multifield.js

Go to Line 61: and replace the else section with the below code:
else{
var select = $field.closest(".coral-Select").data("select");
if(select){
select.setValue(rValue);
}
else{
$field.val(rValue);
}
}


Fig - Drop-down with the expected values

Issue 2: By default, the multifield container looks so weird as it is so small. Let's see how to beautify the look and feel of the same.

Fig- Container with small width

Steps to follow :
Add the "dialog-width-50rem" css class in the dialog hierarchy to content node ("/apps/aem-learning/components/content/multifield/cq:dialog/content" ) shown below-

Fig- Adding the class in content node

Add the "column-full-width" css class in the dialog hierarchy to content node ("/apps/aem-learning/components/content/multifield/cq:dialog/content/items/basic/items/column") shown below-

Fig- Adding the class in the column node

Create file (nt:file) "/apps/aem-learning/components/content/multifield/clientlibs/css.txt" and add the following CSS file

Multifield.css
.dialog-width-50rem{
width:50rem;
}
.coral--dark + .dialog-width-50rem{
width:100%;
}
.coral--dark + .dialog-width-50rem
.column-full-width{ width:60%;
}
.column-full-width{
width:95%;
}
.column-full-width
.coral-Form-fieldlabel{
width:15%;
}

Now you can see the updated layout of the dialog

Fig- Added the css to make the dialog Clearly

Refer to the package and try it yourself.
DEMO PACKAGE INSTALL


By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.