April 10, 2020
Estimated Post Reading Time ~

Working with Granite Datasources

In AEM development, you might have requirements to populate the same data at multiple places. For e.g., suppose you have a requirement to create a text, an image, and a video component and you need to provide background color to these components. The background colors are the same throughout your application code.
A naive way to achieve this is to create the same color nodes under all the component's cq:dialog. Here we are not getting any code reusability. Wouldn't it be great if we create all the color nodes once and reuse it anywhere we want? Granite Datasource is the answer to this question.

A datasource is the factory to provide a collection of Resource and normally used to dynamically populate data in the dialog fields such as dropdowns.
In this post, we are going to create a dynamic datasource and we will use it to populate a dropdown in our component.

Create a Datasource

  • Navigate to CRXDE and create a component with the following configuration
Create a component
  • No create a node cq:dialog of type nt:unstructured under this component with the following configuration
<?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="Datasource Demo"
    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">
            <tab
                jcr:primaryType="nt:unstructured"
                jcr:title="Properties"
                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">
                    <columns
                        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"
                                class="field-whitespace"
                                fieldDescription="Enter the title"
                                fieldLabel="Title"
                                name="./title"/>
                            <colors
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/foundation/form/select"
                                fieldDescription="Select a color"
                                fieldLabel="Color"
                                name="./color">
                                <datasource
                                    jcr:primaryType="nt:unstructured"
                                    sling:resourceType="/apps/demoproject/components/datasource/lists/datalist.html"
                                    data_path="colors"/>
                            </colors>
                        </items>
                    </columns>
                </items>
            </tab>
        </items>
    </content>
</jcr:root>
  • Note that in the /apps/demoproject/components/content/datasourceDemo/cq:dialog/content/items/tab/items/columns/items/colors/datasource we have datasource node whose sling:resourceType property is equal to the path of the HTML file where we have written the code of calling Java class.
  • Create the following structure in your project folder
Datasource and Data nodes
  • /apps/demoproject/components/datasource node has the HTML file in which we are calling the Java backend class
  • While /apps/demoproject/components/common/data node has the nodes which represent data. In our case, we have color nodes. Note the folders common and data are of type sling:Folder.
  • Add following code in the datalist.html file
<sly data-sly-use.data="org.redquark.demo.core.datasource.Datasource" />
  • Create a class named Datasource and paste the following code in it.
package org.redquark.demo.core.datasource;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.jcr.Node;
import javax.jcr.NodeIterator;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.iterators.TransformIterator;
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.ResourceUtil;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.osgi.service.component.annotations.Activate;
import org.redquark.demo.core.constants.AppConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.cq.sightly.WCMUsePojo;
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.SimpleDataSource;
import com.adobe.granite.ui.components.ds.ValueMapResource;

/**
 * @author Anirudh Sharma
 *
 */
public class Datasource extends WCMUsePojo {

 /**
  * Logger
  */
 private static final Logger log = LoggerFactory.getLogger(Datasource.class);

 @Activate
 public void activate() throws Exception {

  try {

   final ResourceResolver resolver = getResourceResolver();

   String dataPath = ResourceUtil.getValueMap(getResource().getChild("datasource")).get("data_path", String.class);

   log.info("Data path is: {}", dataPath);

   Resource resource = resolver.getResource(AppConstants.DATASOURCE_PATH + dataPath);

   log.info("Resource: {}", resource);

   Map < String, String > data = new LinkedHashMap < > ();

   Node currentNode = resource.adaptTo(Node.class);

   NodeIterator nodeIterator = currentNode.getNodes();

   while (nodeIterator.hasNext()) {

    Node node = nodeIterator.nextNode();

    if (!node.hasProperty("value")) {
     data.put(node.getName(), node.getProperty("name").getString());
    } else if (node.hasProperty("name")) {
     data.put(node.getProperty("value").getValue().getString(),
      node.getProperty("name").getValue().getString());
    } else {
     data.put(node.getProperty("value").getValue().getString(), node.getName());
    }
   }

   @SuppressWarnings({
    "unchecked",
    "rawtypes"
   })
   DataSource ds = new SimpleDataSource(new TransformIterator < > (data.keySet().iterator(), new Transformer() {

    @Override
    public Object transform(Object o) {

     String dropValue = (String) o;

     ValueMap vm = new ValueMapDecorator(new HashMap < > ());

     vm.put("value", dropValue);
     vm.put("text", data.get(dropValue));

     return new ValueMapResource(resolver, new ResourceMetadata(), "nt:unstructured", vm);
    };
   }));

   getRequest().setAttribute(DataSource.class.getName(), ds);

  } catch (Exception e) {

   log.error(e.getMessage(), e);
  }

 }

}
  • Here datapath is the value of the property data_path stored under the colors node of the component's cq:dialog. In our case, it will read as "colors".
  • Then we are the resource object of the node /apps/demoproject/components/common/data/colors 
  • Then we are storing the colors in the Map<String, String> via node iteration.
  • Then comes the most important logic of creating com.adobe.granite.ui.components.ds.DataSource object. Here we are converting the Map created previously into the DataSource object ds.
  • After setting the ds, we are setting it in the request object.
  • Now go to any page of your website and drag & drop the datasourceDemo component and open the dialog. You will then see the colours are populated in the dropdown.
Edit dialog


By aem4beginner

No comments:

Post a Comment

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