Granite UI: Empowering the Select Field

Granite UI: Empowering the Select Field

There have been vast improvements to the new touch ui interface since its inception back with version 5.6. Some of the authoring form fields however are still catching up to their extjs predecessors. One such field that lacks some helpful functionality is the select field(or /libs/granite/ui/components/foundation/form/select). Let's take a look at what is missing and how we can enhance the granite ui select field.

What's missing?

Let's start with an example. Say we are attempting to create a dropdown selection from a list of holidays authored elsewhere in the repository. For this example we are making the assumption that each holiday has been authored as a resource. First let's take a look at how we would achieve generating the dropdown with the old xtype="selection" and type="select:

<holidays
    jcr:primaryType="cq:Widget"
   fieldLabel="Holiday"
   name="./holiday"
   type="select"
   options="path/to/holidays.-1.json."
   optionsTextField ="jcr:title"
   optionsValueField="identifier"
   xtype="selection"/>

The markup is clean and simple. It does not require any additional java and by specifying a few properties, we can generate a dropdown list for the authors to use. The same solution with granite ui select field would be much more complicated:

<holidays
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/uicomponents/foundation/form/select"
    fieldLabel="Holidays"
    options="path/to/holidays"
    name="./holiday">
    <datasource 
        jcr:primaryType="nt:unstructured"
        sling:resourceType="/path/to/a/component/that/is/a/datasource"
        name="datasource" />
</holidays>

The dialog looks simple, but now we also need to create a custom datasource component. If you haven't worked with the select form datasource before, I recommend reading the tutorial (here)[https://helpx.adobe.com/experience-manager/using/creating-granite-datasource.html]. Now, in order to get the same list from the extjs example we have to define that datasource component:

<%@include file="/libs/granite/ui/global.jsp" %><%
%><%@page session="false" 
        import="com.adobe.granite.ui.components.ds.DataSource,
                com.adobe.granite.ui.components.ds.EmptyDataSource,
                com.adobe.granite.ui.components.ds.ResourceDataSource" %><%
%><%
    request.setAttribute(DataSource.class.getName(), EmptyDataSource.instance());
    ValueMap props = resource.adaptTo(ValueMap.class);
    String path = props.get("options", String.class);
    if (path != null) {
        Resource res = resourceResolver.getResource(path);
        if (res != null && res.hasChildren()) {
           DataSource ds = new ResourceDataSource(res);
           request.setAttribute(DataSource.class.getName(), ds);
        }
    }
%>

In addition to this, we also need the holiday resource properties to conform to having a property called text and a property called value:

+ path/to/holidays
    + jcr:content
        + list
            + christmas
            - text="Christmas"
            - value="christmas"

There is no option to allow for jcr:title and identifier like we see in the extjs example. if text and value don't exist on the christmas resource, the select field won't populate the values.

Requirements

We want the flexibility on the text and value properties that are used to pull in data. Before we get to extending the granite select component, let's first look at a desired dialog xml and work backwards. Let's add in the two option value fields from the extjs example:

<holidays
    jcr:primaryType="nt:unstructured"
    sling:resourceType="apps/path/to/custom/select"
    fieldLabel="Holidays"
    options="path/to/holidays"
    optionsTextField="jcr:title"
    optionsValueField="identifier"
    name="./holiday">
    <datasource 
        jcr:primaryType="nt:unstructured"
        sling:resourceType="/apps/path/to/custom/select/datasource/resource"            
        name="datasource" />
</holidays>

Extending Granite Select Component

We can use the datasource that was defined above, so the next step is to extend the granite/uicomponents/foundation/form/select component and copy over the render.jsp. The new properties are now available in the render.jsp via the (Config)[https://docs.adobe.com/content/docs/en/aem/6-0/develop/ref/javadoc/com/adobe/granite/ui/components/Config.html] object. We need to use optionsTextField and optionsValueField property values for building the dropdown list, so we need to make a few changes. Scanning the jsp, we'll notice the first change we need to make from within the if(cfg,.get("ordered",false) {...} statement. The method getOptionsText() needs to be updated to take the text property value. Render.jsp should be updated to something like:

String textProp = cfg.get("optionsTextField", "text");
if (cfg.get("ordered", false)) {
    List<Resource> items = (List<Resource>) IteratorUtils.toList(itemIterator);
    final Collator langCollator = Collator.getInstance(request.getLocale());
    Collections.sort(items, new Comparator<Resource>() {
        public int compare(Resource o1, Resource o2) {
            //pass in the textProp
            return langCollator.compare(getOptionText(o1, cmp, textProp), getOptionText(o2, cmp, textProp));
        }
    });
    itemIterator = items.iterator();
}

And update getOptionText() to take accept a String as the third parameter and use it to get the text value:

private String getOptionText(Resource option, ComponentHelper cmp, String textProp) {
    Config optionCfg = new Config(option);
    String text = optionCfg.get(textProp, "");
    if (cmp.getConfig().get("translateOptions", true)) {
        text = cmp.getI18n().getVar(text);
    }
    return text;
}

Furtherdown there is a call to printOption(), which handles generating the <option> and <select> html elements. Here we will need to modify this method to pass the optionsTextField and optionsValueField values. Once that is done, we will change String value = cmp.getExpressionHelper().getString(optionCfg.get("value", String.class)); from using "value" to our optionsValueField value. Lastly we will need to update the recursive printOption() call and the final call to getOptionText() to use our values. And thats it! We can now use the the new component in author dialogs for generating dropdown list items!

Note on DataSource

You may have noticed I referenced 'resource datasource' a in this post. A resource datasource is only one type of datasource that you can use. DataSource itself is an abstract class that you could easily extend to create your own implementation.

Share this post

0 Comments

comments powered by Disqus