Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all articles
Browse latest Browse all 525

AEM 63 - Touch UI Nested ( Multi-Multi ) Coral 2 Composite Multifield

$
0
0

Goal


AEM 63 Touch UI Nested Composite Multifield (Multi-Multi Field) storing the entered data as Child Nodes.

This implementation uses Coral 2 multifield/libs/granite/ui/components/foundation/form/multifield; AEM 63 provides otb implementation of composite multifield (NOT nested composite multifield) using Coral 3 widget - /libs/granite/ui/components/coral/foundation/form/multifield

This post is on nested composite multifield; For composite multifield use otb widget /libs/granite/ui/components/coral/foundation/form/multifield (set property composite=true).

For AEM 62 check this post

Demo | Package Install | Github


Nested Composite Multifield



Stored as Child Nodes



Sample Dialog 



Sample Dialog XML

#45 eaem-nested=NODE_STORE 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 TouchUI Nested Multifield"
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="NODE_STORE"
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"/>
<startDate
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/datepicker"
class="field"
displayedFormat="YYYY-MM-DD HH:mm"
fieldLabel="Start Date"
name="./startDate"
type="datetime"/>
<show
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/checkbox"
name="./show"
text="Show?"
value="yes"/>
<type
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/select"
fieldDescription="Select Size"
fieldLabel="Size"
name="./size">
<items jcr:primaryType="nt:unstructured">
<def
jcr:primaryType="nt:unstructured"
text="Select Size"
value=""/>
<small
jcr:primaryType="nt:unstructured"
text="Small"
value="small"/>
<medium
jcr:primaryType="nt:unstructured"
text="Medium"
value="medium"/>
<large
jcr:primaryType="nt:unstructured"
text="Large"
value="large"/>
</items>
</type>
<tags
jcr:primaryType="nt:unstructured"
sling:resourceType="cq/gui/components/common/tagspicker"
allowCreate="{Boolean}true"
fieldLabel="Tags"
name="./tags"/>
</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-node-store

2) Create node /apps/eaem-touch-ui-nested-multi-field-node-store/clientlib of type cq:ClientLibraryFolder, add String property categories with value cq.authoring.dialog.all, String property dependencies with value underscore

3) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-node-store/clientlib/js.txt, add

                       nested-multifield.js

4) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-node-store/clientlib/nested-multifield.js, add the following code

(function ($, $document) {
var EAEM_NESTED = "eaem-nested",
DATA_EAEM_NESTED = "data-" + EAEM_NESTED,
CFFW = ".coral-Form-fieldwrapper",
NODE_STORE = "NODE_STORE";

if(CUI.Multifield.eaemNMFExtended){
return;
}

CUI.Multifield.eaemNMFExtended = true;

function isNodeStoreMultifield(type) {
return (type === NODE_STORE);
}

function isSelectOne($field) {
return !_.isEmpty($field) && ($field.prop("type") === "select-one");
}

function setSelectOne($field, value) {
var select = $field.closest(".coral-Select").data("select");

if (select) {
select.setValue(value);
}
}

function isCheckbox($field) {
return !_.isEmpty($field) && ($field.prop("type") === "checkbox");
}

function setCheckBox($field, value) {
$field.prop("checked", $field.attr("value") === value);
}

function isDateField($field) {
return !_.isEmpty($field) && $field.parent().hasClass("coral-DatePicker");
}

function setDateField($field, value) {
var date = moment(new Date(value)),
$parent = $field.parent();

$parent.find("input.coral-Textfield").val(date.format($parent.data("displayed-format")));

$field.val(date.format($parent.data("stored-format")));
}

function isTagsField($fieldWrapper) {
return !_.isEmpty($fieldWrapper) && ($fieldWrapper.children(".js-cq-TagsPickerField").length > 0);
}

function getTagsFieldName($fieldWrapper) {
return $fieldWrapper.children(".js-cq-TagsPickerField").data("property-path").substr(2);
}

function getTagObject(tag){
var tagPath = "/etc/tags/" + tag.replace(":", "/");
return $.get(tagPath + ".tag.json");
}

function setTagsField($fieldWrapper, tags) {
if(_.isEmpty(tags)){
return;
}

var cuiTagList = $fieldWrapper.find(".coral-TagList").data("tagList");

_.each(tags, function(tag){
getTagObject(tag).done(function(data){
cuiTagList._appendItem( { value: data.tagID, display: data.titlePath} );
});
});
}

function isMultifield($formFieldWrapper){
return ($formFieldWrapper.children("[data-init='multifield']").length > 0);
}

function setWidgetValue($field, value) {
if (_.isEmpty($field)) {
return;
}

if(isSelectOne($field)) {
setSelectOne($field, value);
}else if(isCheckbox($field)) {
setCheckBox($field, value);
}else if(isDateField($field)) {
setDateField($field, value);
}else {
$field.val(value);
}
}

function getMultifields($formField, isInner){
var mNames = {}, mName, $multifield, $template,
$multiTemplates = $formField.find(".js-coral-Multifield-input-template");

$multiTemplates.each(function (i, template) {
$template = $(template);
$multifield = $($template.html());

if(!isInner && !isNodeStoreMultifield($multifield.data(EAEM_NESTED))){
return;
}

mName = $multifield.data("name").substring(2);

mNames[mName] = $template.closest(".coral-Multifield");
});

return mNames;
}

function buildMultifield(data, $multifield, mName){
var $formFieldWrapper, $field, $fieldSet, name,
innerMultifields;

_.each(data, function (value, key) {
if(key.indexOf("jcr:") === 0){
return;
}

$multifield.children(".js-coral-Multifield-add").click();

$fieldSet = $multifield.find(".coral-Form-fieldset").last();

_.each($fieldSet.find(CFFW), function (formFieldWrapper) {
$formFieldWrapper = $(formFieldWrapper);

if(isMultifield($formFieldWrapper)){
innerMultifields = getMultifields($formFieldWrapper, true);

_.each(innerMultifields, function($innerMultifield, nName){
buildMultifield(value[nName], $innerMultifield, nName);
});

return;
}else if(isTagsField($formFieldWrapper)){
setTagsField($formFieldWrapper, value[getTagsFieldName($formFieldWrapper)]);
return;
}

$field = $formFieldWrapper.find("[name]");

if(_.isEmpty($field)){
return;
}

name = $field.attr("name").substr(2);

if(_.isEmpty(value[name])){
return;
}

setWidgetValue($field, value[name]);
});
})
}

function addDataInFields() {
$document.on("dialog-ready", dlgReadyHandler);

function dlgReadyHandler() {
var outerMultifields = getMultifields($(this), false),
$form = $("form.cq-dialog"),
actionUrl = $form.attr("action") + ".infinity.json";

$.ajax(actionUrl).done(postProcess);

function postProcess(data){
_.each(outerMultifields, function($outerMultifield, mName){
buildMultifield(data[mName], $outerMultifield, mName);
});
}
}
}

function fillValue($form, fieldSetName, $field, counter){
var name = $field.attr("name"), value;

if (!name) {
return;
}

if (name.indexOf("./") === 0) {
name = name.substring(2);
}

value = $field.val();

if (isCheckbox($field)) {
value = $field.prop("checked") ? $field.val() : "";
}

//remove the field, so that individual values are not POSTed
$field.remove();

$('<input />').attr('type', 'hidden')
.attr('name', fieldSetName + "/" + counter + "/" + name)
.attr('value', value)
.appendTo($form);
}

function addNestedMultifieldData($form, outerMultiName, $nestedMultiField){
var $fieldSets = $nestedMultiField.find("[class='coral-Form-fieldset']"),
nName = $fieldSets.data("name"), $fields;

if(!nName){
return;
}

nName = outerMultiName + "/" + nName.substring(2);

$fieldSets.each(function (iCounter, fieldSet) {
$fields = $(fieldSet).find("[name]");

$fields.each(function (counter, field) {
fillValue($form, nName, $(field), (iCounter + 1));
});
});
}

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 $fields, $field, name, $nestedMultiField;

$fieldSets.each(function (oCounter, fieldSet) {
$fields = $(fieldSet).children().children(CFFW);

$fields.each(function (counter, field) {
$field = $(field);

//may be a nested multifield
$nestedMultiField = $field.find("[data-init='multifield']");

if($nestedMultiField.length == 0){
fillValue($form, mName, $(field).find("[name]"), (oCounter + 1));
}else{
addNestedMultifieldData($form, mName + "/" + (oCounter + 1) , $nestedMultiField);
}
});
});
}
}

$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 () {
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));


5) Create file (nt:file) /apps/eaem-touch-ui-nested-multi-field-node-store/eaem-sample-nested-multi-field/eaem-sample-nested-multi-field.jsp for rendering the stored multifield data

<%@ page import="java.io.PrintWriter" %>
<%@ page import="org.apache.commons.lang.StringUtils" %>
<%@include file="/libs/foundation/global.jsp" %>
<%@page session="false" %>

<div style="display: block; border-style: solid; border-width: 1px; margin: 10px; padding: 10px">
<b>Countries and States</b>

<%
try {
if (currentNode.hasNode("countries")) {
Node countriesNode = currentNode.getNode("countries"), cNode;
int counter = 1; PropertyIterator itr = null; Property property;

while(true){
if(!countriesNode.hasNode(String.valueOf(counter))){
break;
}

cNode = countriesNode.getNode(String.valueOf(counter));

itr = cNode.getProperties();

while(itr.hasNext()){
property = itr.nextProperty();

if(property.getName().equals("jcr:primaryType")){
continue;
}
%>
<%=property.getName()%> : <b><%=property.getString()%></b>
<%
}

if(cNode.hasNode("states")){
Node statesNode = cNode.getNode("states"), sNode;
int sCounter = 1; PropertyIterator sTtr = null; Property sProperty;

while(true){
if(!statesNode.hasNode(String.valueOf(sCounter))){
break;
}

sNode = statesNode.getNode(String.valueOf(sCounter));

itr = sNode.getProperties();

while(itr.hasNext()){
sProperty = itr.nextProperty();

if(sProperty.getName().equals("jcr:primaryType")){
continue;
}

String value = null;

if (sProperty.isMultiple()) {
Value[] values = sProperty.getValues();
value = StringUtils.join(values, ",");
} else {
value = sProperty.getString();
}

%>
<div style="margin-left:30px">
<%=sProperty.getName()%> : <b><%=value%></b>
</div>
<%
}

%>

<%

sCounter = sCounter + 1;
}
}

counter = counter + 1;
}
} else {
%>
Add countries and states in dialog</b>
<%
}
} catch (Exception e) {
e.printStackTrace(new PrintWriter(out));
}
%>
</div>


Viewing all articles
Browse latest Browse all 525

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>