Contents
Objective
After reading this article, you should have an understanding of :
- Granite DataSource.
- AEM Tags value in the Dynamic Selection Dropdown
- JSON Data in the Dynamic Selection Dropdown
- AEM Packages in the Dynamic Selection Dropdown
- AEM ACS Commons Generic List in the Dynamic Selection Dropdown
- Dynamic Selection Dropdown in Workflow Dialog Participant Step
- Dynamic Selection Dropdown in Content Fragment Model
Introduction
In AEM development work, sometimes we come across the scenario of populating the same set of data in a dialog dropdown at different places. We can achieve this use case by creating a node in the cq:dialog. Here we will do the same work multiple times with no code reusability. We can resolve this issue with the help of Granite DataSource.
Hence, without any further delay, let’s get started:
Granite DataSource
A DataSource is a factory that provides a collection of resources and is typically used to dynamically populate data in dialog fields like dropdowns. Thus, we only have to maintain our dropdown values at one location (i.e. AEM Tags and Packages, JSON Data, etc.) and they can be used throughout our entire code.Â
AEM Tags value in Dynamic Selection Dropdown
To show tags value in a dynamic selection dropdown, first we need to create the dropdown node structure along with other fields (text field, checkbox, etc.) in cq:dialog and then we need to write a servlet that reads all the tags at a given path and exports them through a DataSource.
Sometimes we need a TagManager for resolving and creating tags by paths and names. This interface is generic and we can get it by adopting ResourceResolver. Also, there is a JCR-based reference implementation which can be obtained by the JcrTagManagerFactory – all we need is an existing JCR Session
Dropdown Node in Component cq:dialog .content.xml
TagDropdownServlet.java
package com.adobe.learning.core.servlets;
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import com.day.cq.tagging.Tag;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
/**
* @author Shiv
*/
@Component(service = Servlet.class, property = {Constants.SERVICE_DESCRIPTION + "= Tags value in dynamic Dropdown",
"sling.servlet.paths=" + "/bin/colorTagLists", "sling.servlet.methods=" + HttpConstants.METHOD_GET
})
public class TagDropdownServlet extends SlingSafeMethodsServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(TagDropdownServlet.class);
transient ResourceResolver resourceResolver;
transient Resource pathResource;
transient ValueMap valueMap;
transient List resourceList;
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
resourceResolver = request.getResourceResolver();
pathResource = request.getResource();
resourceList = new ArrayList<>();
/* Getting AEM Tags Path given on datasource Node */
String tagsPath = Objects.requireNonNull(pathResource.getChild("datasource")).getValueMap().get("tagsPath", String.class);
assert tagsPath != null;
//Getting Tag Resource using Tag Path
Resource tagsResource = request.getResourceResolver().getResource(tagsPath);
assert tagsResource != null;
//Iterating over child tag resource
for (Resource childTags : tagsResource.getChildren()) {
valueMap = new ValueMapDecorator(new HashMap<>());
//Adopting Tag resource into Tag
Tag colorTag = childTags.adaptTo(Tag.class);
assert colorTag != null;
String tagFullName = colorTag.getTagID();
String tagName = tagFullName.substring(tagFullName.lastIndexOf("/") + 1);
String tagTitle = colorTag.getTitle();
valueMap.put("value", tagName);
valueMap.put("text", tagTitle);
resourceList.add(new ValueMapResource(resourceResolver, new ResourceMetadata(), "nt:unstructured", valueMap));
}
/*Create a DataSource that is used to populate the drop-down control*/
DataSource dataSource = new SimpleDataSource(resourceList.iterator());
request.setAttribute(DataSource.class.getName(), dataSource);
LOGGER.info("Tags successfully exported using DataSource!!!");
}
}
JSON Data in the Dynamic Selection Dropdown
In this case, the cq:dialog node structure will be almost the same as the tag structure. Here we have to pass jsonDataPath instead of tagsPath and update sling:resourceType with a servlet path(/bin/jsonDataDropdown) that will export JSON Data using Datasource.
Also, we have to create a sample json file with a name dropdown.json parallel to the datasource node and update JSON Data as a key-value pair.
Let’s see the Java implementation to read the JSON data and export the value in the selection dropdown.
Dropdown Node in Component cq:dialog .content.xml
Sample dropdown.json
{
"red": "Red",
"white": "Whte",
"yellow": "Yellow",
"blue": "Blue"
}
JsonDataDropdownServlet.java
package com.adobe.learning.core.servlets;
import com.adobe.granite.asset.api.Asset;
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import com.day.crx.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.json.JSONException;
import org.json.JSONObject;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.servlet.Servlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* @author Shiv
*/
@Component(service = Servlet.class, property = {Constants.SERVICE_DESCRIPTION + "= Json Data in dynamic Dropdown",
"sling.servlet.paths=" + "/bin/jsonDataDropdown", "sling.servlet.methods=" + HttpConstants.METHOD_GET
})
public class JsonDataDropdownServlet extends SlingSafeMethodsServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(JsonDataDropdownServlet.class);
transient ResourceResolver resourceResolver;
transient Resource pathResource;
transient ValueMap valueMap;
transient List resourceList;
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
resourceResolver = request.getResourceResolver();
pathResource = request.getResource();
resourceList = new ArrayList<>();
try {
/* Getting AEM Tags Path given on datasource Node */
String jsonDataPath = Objects.requireNonNull(pathResource.getChild("datasource")).getValueMap().get("jsonDataPath", String.class);
assert jsonDataPath != null;
//Getting Tag Resource using JsonData
Resource jsonResource = request.getResourceResolver().getResource(jsonDataPath + "/" + JcrConstants.JCR_CONTENT);
assert jsonResource != null;
//Getting Node from jsonResource
Node jsonNode = jsonResource.adaptTo(Node.class);
assert jsonNode != null;
//Converting input stream to JSON Object
InputStream inputStream = jsonNode.getProperty("jcr:data").getBinary().getStream();
StringBuilder stringBuilder = new StringBuilder();
String eachLine;
assert inputStream != null;
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
while ((eachLine = bufferedReader.readLine()) != null) {
stringBuilder.append(eachLine);
}
JSONObject jsonObject = new JSONObject(stringBuilder.toString());
Iterator jsonKeys = jsonObject.keys();
//Iterating JSON Objects over key
while (jsonKeys.hasNext()) {
String jsonKey = jsonKeys.next();
String jsonValue = jsonObject.getString(jsonKey);
valueMap = new ValueMapDecorator(new HashMap<>());
valueMap.put("value", jsonKey);
valueMap.put("text", jsonValue);
resourceList.add(new ValueMapResource(resourceResolver, new ResourceMetadata(), "nt:unstructured", valueMap));
}
/*Create a DataSource that is used to populate the drop-down control*/
DataSource dataSource = new SimpleDataSource(resourceList.iterator());
request.setAttribute(DataSource.class.getName(), dataSource);
} catch (JSONException | IOException | RepositoryException e) {
LOGGER.error("Error in Json Data Exporting : {}", e.getMessage());
}
}
}
Note: If the JSON file is in DAM, we can use Asset API to get the Input Stream from the original as shown below, and the rest of the code will remain the same.
Use of the Asset API to obtain input stream
Resource jsonResource = request.getResourceResolver().getResource(jsonDataPath);
assert jsonResource != null;
//Getting Asset from jsonResource
Asset jsonAsset = jsonResource.adaptTo(Asset.class);
assert jsonAsset != null;
//Converting Asset into input stream for JSON Object
InputStream inputStream = jsonAsset.getOriginal().getStream();
AEM Packages in the Dynamic Selection Dropdown
In this case, the cq:dialog node structure will be almost the same as the tag structure. Here we have to pass packagePath instead of tagsPath and update sling:resourceType with a servlet path(/bin/packageDropdown) that will export JSON Data using Datasource
Let’s see the Java implementation of the package available under the given path and export the value in the selection dropdown. Packages will show up in the dropdown in descending order by Creation Date.
Dropdown Node in Component cq:dialog .content.xml
PackageDropdownServlet.java
package com.adobe.learning.core.servlets;
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;
import com.day.cq.tagging.Tag;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.Servlet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
/**
* @author Shiv
*/
@Component(service = Servlet.class, property = {Constants.SERVICE_DESCRIPTION + "= Tags value in dynamic Dropdown",
"sling.servlet.paths=" + "/bin/colorTagLists", "sling.servlet.methods=" + HttpConstants.METHOD_GET
})
public class TagDropdownServlet extends SlingSafeMethodsServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(TagDropdownServlet.class);
transient ResourceResolver resourceResolver;
transient Resource pathResource;
transient ValueMap valueMap;
transient List resourceList;
@Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) {
resourceResolver = request.getResourceResolver();
pathResource = request.getResource();
resourceList = new ArrayList<>();
/* Getting AEM Tags Path given on datasource Node */
String tagsPath = Objects.requireNonNull(pathResource.getChild("datasource")).getValueMap().get("tagsPath", String.class);
assert tagsPath != null;
//Getting Tag Resource using Tag Path
Resource tagsResource = request.getResourceResolver().getResource(tagsPath);
assert tagsResource != null;
//Iterating over child tag resource
for (Resource childTags : tagsResource.getChildren()) {
valueMap = new ValueMapDecorator(new HashMap<>());
//Adopting Tag resource into Tag
Tag colorTag = childTags.adaptTo(Tag.class);
assert colorTag != null;
String tagFullName = colorTag.getTagID();
String tagName = tagFullName.substring(tagFullName.lastIndexOf("/") + 1);
String tagTitle = colorTag.getTitle();
valueMap.put("value", tagName);
valueMap.put("text", tagTitle);
resourceList.add(new ValueMapResource(resourceResolver, new ResourceMetadata(), "nt:unstructured", valueMap));
}
/*Create a DataSource that is used to populate the drop-down control*/
DataSource dataSource = new SimpleDataSource(resourceList.iterator());
request.setAttribute(DataSource.class.getName(), dataSource);
LOGGER.info("Tags successfully exported using DataSource!!!");
}
}
AEM ACS Commons Generic List in the Dynamic Selection Dropdown
The Adobe Experience Manager ACS Commons Generic List facilitates authorable title and value pairs that we use to populate drop downs from a centrally authored location. Generic List are used as a data source for Touch UI dialogues.
Let’s see the implementation of the Generic List creation and its use in the selection dropdown.
AEM ACS Commons Generic List Creation
- Â Navigate to Tools -> ACS AEM Commons -> Generic Lists
- Or directly navigate to http://localhost:4502/generic-lists.html/etc/acs-commons/lists
- Click on create -> Generic List -> Author title and value in multifield
- Click save
Dropdown Node in Component cq:dialog .content.xml
Dynamic Selection Dropdown in Workflow Dialog Participant Step
Dialog Participant Step is used collect information from the user who is assigned the work item. This step is useful for collecting small amounts of data that is used later in the workflow.Â
Upon completing the step, the Complete Work Item dialog contains the fields that you define in your dialog. The data that is collected in the fields is stored in nodes of the workflow payload. Subsequent workflow steps can then read the value from the repository.
Steps to get the dynamic selection dropdown :
- Create the dialog for select dropdown along with other fields.
- Select the dialog path in Dialog Participant Step of the workflow
- Write a servlet that will export the dropdown value via DataSource (Here we will use /bin/packageDropdown as shown in the above Package Dropdown Section).
Dropdown Node in Component cq:dialog .content.xml
Note:Â Field name should not be start with ./ in dialog participant step.
Dynamic Selection Dropdown in Content Fragment Model
Content Fragment Models in AEM define the structure of content for your content fragments, serving as a foundation of your headless content.
Creation of Static Dropdown in Content Fragment Model
- Â Navigate to Tool -> General -> Configuration Browser -> Enable for Content Fragment Models.
- Navigate to Tool -> Assets -> Content Fragment Models -> Select folder
- Click on Create -> Model Title -> Enable Model -> Create -> Open the Content Fragment Model
- In right panel we have given Data Type that we can drag and drop to create the fields in the model.
- Â Drag and drop Enumeration and change the Field Level as Select Package.
- Add Options as Select/select and save.
Modifying Static Dropdown to Dynamic from CRX/DE
We do some customization to the Enumeration node stored at –/conf/learning/settings/dam/cfm/models/dynamic-dropdown/jcr:content/model/cq:dialog/content/items
- Here we will use the normal datasource node structure in place of optionrenderer.Â
- Delete optionsmultifield and granite:data nodes.
- Change the sling:resourceType of datasource to /bin/packageDropdown.
- Add property packagePath as /etc/packages/my_packages on datasource node.Â
 Note – Here we will use servlet path /bin/packageDropdown as shown in the above Package Dropdown Section
Creating Content Fragment Using Content Fragment Model
Content Fragments allow you to design, create, curate and publish page-independent content, We can create it under DAM.
- Navigate to Assets -> Select the folder -> Click on create -> Select Content Fragment Model -> Enter the title ->Create.
- Open the newly created Content Fragment.
- That’s all. We can see the dropdown now.
Dropdown Node in Component cq:dialog .content.xml
<_x0031_657388547880
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
emptyOption="{Boolean}true"
fieldLabel="Select Package"
listOrder="21"
metaType="enumeration"
name="selectPackage"
renderReadOnly="false"
showEmptyInReadOnly="true"
valueType="string">
Conclusion
- So in this post, we tried to cover all the possible ways to make a dynamic selection dropdown in AEM. I hope you enjoyed this post. If you find it useful, leave us a comment. I would love to hear your thoughts and suggestions to make it better. Also, you can connect with me on LinkedIn, Instagram or Facebook. All links are active now.
Thanks a lot Shiv Prakash for this detailed article. Thanks a lot for dedicated efforts. Keep going 🙂
Wonderful. Super helpful.
Thanks a lot dear. very helpful.
Very Helpful article Shiv prakash.
I got the solution to my requirement what I was looking for.
Thanks!
It’s rеally a cool and useful piece of info.
I am satiѕfied that yⲟս jսst shɑred this useful infߋ with սs.
Please keep us uⲣ to date like tһіs. Thanks for sharing.
The first use case is not working I’m getting null pointer exception
java.lang.NullPointerException: null
at org.apache.sling.api.resource.AbstractResource.getChild(AbstractResource.java:78) [org.apache.sling.api:2.22.0]
at com.adobe.granite.ui.components.ds.ValueMapResource.getChild(ValueMapResource.java:159)
Thank you very much. very helpful
This article is great! Thanks for sharing an in-depth overview of how to create a dynamic dropdown. Just wondering if it’s possible to enable multiple selection for dynamic dropdowns? If so, what extra steps are needed to achieve it.