Create TouchUI Multifield Component using HTL

The aim of this tutorial is to learn how to create Touch UI Multifield component using HTL formerly known as sightly. As out of the box multifield component has many limitations thanks to ACS common (Adobe consulting services) an open source community for enhancing the existing granite multifield component (granite/ui/components/foundation/form/multifield) that allows developers to create a multifield of a fieldset (group of different fields) . That’s why we are going to use a ACS common multifield component in this tutorial and show you how to read JSON value saved to the JCR .

Post Updated with creating TouchUI Mutlifield Component using HTL in (Adobe Experience Manager) AEM 6.5

After completing this tutorial, you will have a clear understanding about:-

Create TouchUI MultiField Component AEM 6.3:-

Lets take a simple use case of creating a user menu for your website and retrieving its value stored in the form of JSON Array from crxde/JCR repository . Follow below steps to create acs commons multi field component :-

touchui-multified-aem-6.3

  • Login to crxde.
  • Go to /apps/<project-name>/<path-to-component> [For Ex:- /apps/my-aem-project/components/content/ ]
  • Create a new component under content node [For Ex:- In our example usermenu]
  • Paste below content.xml of our multi field component.

myusermenu content.xml

<?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:description="User menu component"
    jcr:primaryType="cq:Component"
    jcr:title="User Menu Custom"
    componentGroup="aemcq5tutorials"/>

cq:dialog content.xml:-

<?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="User Menu"
    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">
            <tabs
                jcr:primaryType="nt:unstructured"
                jcr:title="General"
                sling:resourceType="granite/ui/components/foundation/container">
                <items jcr:primaryType="nt:unstructured">
                    <usersubmenu
                        jcr:primaryType="nt:unstructured"
                        jcr:title="User Submenu"
                        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">
                            <tab
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/foundation/container">
                                <items jcr:primaryType="nt:unstructured">
                                    <usersubmenudetails
                                        jcr:primaryType="nt:unstructured"
                                        sling:resourceType="granite/ui/components/foundation/form/multifield"
                                        class="full-width"
                                        fieldDescription="Click 'Add field' to add a new User Submenu title and links"
                                        fieldLabel="User Submenu Items">
                                        <field
                                            jcr:primaryType="nt:unstructured"
                                            sling:resourceType="granite/ui/components/foundation/form/fieldset"
                                            acs-commons-nested=""
                                            name="./myUserSubmenu">
                                            <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">
                                                        <title
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/foundation/form/textfield"
                                                            fieldDescription="Enter User Submenu title"
                                                            fieldLabel="User Submenu Title"
                                                            name="./title"/>
                                                        <link
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
                                                            fieldDescription="Enter User Submenu Link"
                                                            fieldLabel="User Submenu Link"
                                                            name="./link"
                                                            rootPath="/content"/>
                                                        <flag
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="granite/ui/components/foundation/form/checkbox"
                                                            checked="{Boolean}false"
                                                            fieldDescription="Testing Flag for checkbox"
                                                            name="./flag"
                                                            text="Flag"
                                                            title="Checkbox Tooltip"/>
                                                    </items>
                                                </column>
                                            </items>
                                        </field>
                                    </usersubmenudetails>
                                </items>
                            </tab>
                        </items>
                    </usersubmenu>
                </items>
            </tabs>
        </items>
    </content>
</jcr:root>

Note:- To use acs commons multifield it is mandatory to add acs-commons-nested=”” property to a fieldset within a multifield node  like at myUserSubmenu node in our example. acs-commons-nested=”” acs-commons-nested=”JSON_STORE” or means we want to store multifiled data as JSON Array. To store multi filed data as seperate child node use  acs-commons-nested=”NODE_STORE”.

cq:editConfig content.xml:-

<?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:EditConfig">
    <cq:listeners
        jcr:primaryType="cq:EditListenersConfig"
        afteredit="REFRESH_SELF"/>
</jcr:root>

dialog.xml

<?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"
    xtype="dialog"/>

Note:- dialog.xml or editConfig.xml is required because if we don’t have dialog in our component then it wont get displayed in side rail of Touch UI. 

  • After adding these nodes content.xml. Open component rending script or html file [For EX:- myusermenu.html] and add below code to it.
Click here to configure Multi Field User Menu
<div data-sly-use.multiItems="aemlearning.pojo.TouchMultiFieldComponentUse">
<div data-sly-list.head="${multiItems.multiFieldItems}">
    			<p><b>Page Name:</b> ${head.title}</p>
        		<p><b>Page Path:</b> ${head.link}</p>
        		<p><b>Flag:</b> ${head.flag}</p>
            </div>
</div>

Now our component structure should look like below screenshot:-

multifield-component-aem6.3

Retrieve values from Multi field dialog:-

Once you have created the touchui multi field component using HTL, lets see how to retrieve its value.

Create two java file TouchMultiFieldBean and TouchMultiFieldComponentUse to retrieve values from crxde.

TouchMultiFieldBean.java

package aemlearning.bean;
 
/**
* POJO for Multi Field items
* @author aahlawat
*
*/
public class TouchMultiFieldBean {
 
private String title;
private String link;
private String flag;
 
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
 
}

TouchMultiFieldComponentUse.java

package aemlearning.pojo;
 
import java.util.ArrayList;
import java.util.List;
 
import org.apache.sling.commons.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import com.adobe.cq.sightly.WCMUsePojo;

import aemlearning.bean.TouchMultiFieldBean;
 
public class TouchMultiFieldComponentUse extends WCMUsePojo {
 
private static final Logger LOGGER = LoggerFactory.getLogger(TouchMultiFieldComponentUse.class);
private List<TouchMultiFieldBean> submenuItems = new ArrayList<>();
 
@Override
public void activate() throws Exception {
setMultiFieldItems();
}
 
/**
* Method to get Multi field data
* @return submenuItems
*/
private List<TouchMultiFieldBean> setMultiFieldItems() {
 
@SuppressWarnings("deprecation")
JSONObject jObj;
try{
String[] itemsProps = getProperties().get("myUserSubmenu", String[].class);
if (itemsProps != null) {
for (int i = 0; i < itemsProps.length; i++) {
 
jObj = new JSONObject(itemsProps[i]);
TouchMultiFieldBean menuItem = new TouchMultiFieldBean();
 
String title = jObj.getString("title");
String path = jObj.getString("link");
String flag = jObj.getString("flag");
 
menuItem.setTitle(title);
menuItem.setLink(path);
menuItem.setFlag(flag);
submenuItems.add(menuItem);
}
}
}catch(Exception e){
LOGGER.error("Exception while Multifield data {}", e.getMessage(), e);
}
return submenuItems;
}
 
public List<TouchMultiFieldBean> getMultiFieldItems() {
return submenuItems;
}
}

That’s it you are done

  • Just build the component from eclipse.
  • Drag and drop usermenu component on any parsys.
  • Enter the values and save it.

Value stored in CRX/JCR in form of JSON Array:-

multifiled-json-array-data-aem

Note:- But our Multi field is still having one issue ACS commons Multifield has issues with retrieving values from checkbox and radio. That’s why adobe suggest to use dropdown instead of checkbox. But as nothing is impossible there is a slight change in acs commons js using which we can make multi field checkbox working.

Retrieve checkbox values from Multi field dialog:-


Follow below steps to retrive checkbox values from multi field component in aem, If you are using AEM 6.3 with service pack 1 then below fix is not required. Below fix is available as part of AEM 6.3 SP1 :-

  • Got to /apps/acs-commons/touchui-widgets/composite-multifield/source/touchui-widgets-init.js.
  • Navigate to Line no. 69
  • Replace code $field.prop(“checked”, $field.attr(“value”) === value); inside setCheckbox method with below code
setCheckBox: function ($field, value) {
if(value != ""){
$field.prop("checked", true);
}else{
$field.prop("checked", $field.attr("value") === value);
}
}

Note:- It is not advised to change the code in acs commons , as when new version of acs commons comes, your changes will be overwritten.  

You can download the above code for AEM 6.3 my git hub repository https://bitbucket.org/aahlawa/aem-learning/branch/multifield

Trouble Shooting Multi Field Component in AEM:- 

  • Getting Uncaught RangeError: Maximum call stack size exceeded on click of Add Field button

If you are getting Uncaught RangeError: Maximum call stack size exceeded on click of Add Field button, make sure you have not added any customized Multifield.js files.

Create Touch UI Multi Field Component in AEM 6.5:-

This section contains updated code as per AEM 6.5 instance. Link of git repo will be available at end of the tutorial

Updated cq:dialog content.xml

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/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="Nested Multifield Component"
    sling:resourceType="cq/gui/components/authoring/dialog"
    extraClientlibs="[core.wcm.components.navigation.v1.editor]"
    helpPath="https://www.adobe.com/go/aem_cmp_navigation_v1">
    <content
        granite:class="cmp-navigation__editor"
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/container">
        <items jcr:primaryType="nt:unstructured">
            <tabs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/tabs"
                maximized="{Boolean}true">
                <items jcr:primaryType="nt:unstructured">
                    <usersubmenu
                        jcr:primaryType="nt:unstructured"
                        jcr:title="User Submenu"
                        sling:resourceType="granite/ui/components/coral/foundation/container"
                        margin="{Boolean}true">
                        <items jcr:primaryType="nt:unstructured">
                            <columns
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
                                <items jcr:primaryType="nt:unstructured">
                                    <column
                                        jcr:primaryType="nt:unstructured"
                                        sling:resourceType="granite/ui/components/coral/foundation/container">
                                        <items jcr:primaryType="nt:unstructured">
                                            <listitems
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                                                class="full-width"
                                                composite="{Boolean}true"
                                                fieldDescription="Click 'Add field' to add a new User Submenu title and links"
                                                fieldLabel="">
                                                <field
                                                    jcr:primaryType="nt:unstructured"
                                                    sling:resourceType="granite/ui/components/coral/foundation/form/fieldset"
                                                    acs-commons-nested=""
                                                    name="./myUserSubmenu">
                                                    <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">
															<title
																jcr:primaryType="nt:unstructured"
																sling:resourceType="granite/ui/components/foundation/form/textfield"
																fieldDescription="Enter User Submenu title"
																fieldLabel="User Submenu Title"
																name="./title"/>
															<link
																jcr:primaryType="nt:unstructured"
																sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
																fieldDescription="Enter User Submenu Link"
																fieldLabel="User Submenu Link"
																name="./link"
																rootPath="/content"/>
															<flag
																jcr:primaryType="nt:unstructured"
																sling:resourceType="granite/ui/components/foundation/form/checkbox"
																checked="{Boolean}false"
																fieldDescription="Testing Flag for checkbox"
																name="./flag"
																text="Flag"
																title="Checkbox Tooltip"/>
                                                            </items>
                                                        </column>
                                                    </items>
                                                </field>
                                            </listitems>                                        
                                        </items>
                                    </column>
                                </items>
                            </columns>
                        </items>
                    </usersubmenu>
                </items>
            </tabs>
        </items>
    </content>
</jcr:root>

Updated myusermenu.html

Click here to configure Multi Field User Menu
<div data-sly-use.multiItems="aemcq5tutorials.core.models.TouchMultiFieldComponentModel">
<div data-sly-list.head="${multiItems.multiFieldItems}">
    			<p><b>Page Name:</b> ${head.title}</p>
        		<p><b>Page Path:</b> ${head.link}</p>
        		<p><b>Flag:</b> ${head.flag}</p>
            </div>
</div>

Updated TouchMultiFieldComponentModel.java:-

From AEM 6.4 it is advised to use Model instead of Wcmuse class, updated the code accordingly below:-

package aemcq5tutorials.core.models;

import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.Optional;
import org.apache.sling.models.annotations.Via;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aemcq5tutorials.core.bean.TouchMultiFieldBean;

@Model(adaptables = { SlingHttpServletRequest.class, Resource.class })
public class TouchMultiFieldComponentModel {

	private static final Logger LOGGER = LoggerFactory.getLogger(TouchMultiFieldComponentModel.class);

	/**
	 * Tabs title multifield value.
	 */
	@Optional
	@Inject
	@Via("resource")
	private List<Resource> myUserSubmenu;

	private List<TouchMultiFieldBean> submenuItems;

	/**
	 * Init Method of Model.
	 */
	@PostConstruct
	public final void init() {
		populateMultiFieldItems(myUserSubmenu);
	}

	/**
	* Method to get Multi field data
	* @return submenuItems
	*/
	private void populateMultiFieldItems(List<Resource> resourceList) {		
		if (null != resourceList && !resourceList.isEmpty()) {
			submenuItems = new ArrayList<TouchMultiFieldBean>();
			for (Resource item : resourceList) {
				if (item != null) {
					TouchMultiFieldBean menuItem = new TouchMultiFieldBean();
					ValueMap vm = item.getValueMap();
					String title = getPropertyValue(vm, "title");
					String link = getPropertyValue(vm, "link");
					String flag = getPropertyValue(vm, "flag");
					menuItem.setTitle(title);
					menuItem.setLink(link);
					menuItem.setFlag(flag);
					submenuItems.add(menuItem);
					
				} else {
					LOGGER.info("ValueMap not found for resource  : {}", item);
				}
			}
		}
	}
	
	private String getPropertyValue(final ValueMap properties, final String propertyName) {
		return properties.containsKey(propertyName) ? properties.get(propertyName, String.class) : StringUtils.EMPTY;
	}
	
	public List<TouchMultiFieldBean> getMultiFieldItems() {
		return this.submenuItems;
	}
}

You can download the above code for AEM 6.5 my git hub repository https://bitbucket.org/aahlawa/aemcq5tutorials6.5/src/develop/

Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.