AEM Dialog Components #4: Select - Dropdown with Options

AEM Dialog Components #4: Select (Dropdown)

What is a Select?

The Select is a Granite UI component that allows authors to choose a single option from a predefined list (dropdown).

Common use cases:

  • Alignment (left, center, right)
  • Style variants (primary, secondary, tertiary)
  • Sizes (small, medium, large)
  • Content types (article, video, gallery)
  • Configurations with limited options

Basic Configuration

Minimal XML Dialog

<alignment
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Text Alignment"
    name="./alignment">
    <items jcr:primaryType="nt:unstructured">
        <left
            jcr:primaryType="nt:unstructured"
            text="Left"
            value="left"/>
        <center
            jcr:primaryType="nt:unstructured"
            text="Center"
            value="center"/>
        <right
            jcr:primaryType="nt:unstructured"
            text="Right"
            value="right"/>
    </items>
</alignment>

Structure:

  • Container <select> with sling:resourceType and name
  • Child node <items> containing the options
  • Each option has text (visible label) and value (saved value)

Main Properties

1. Default Value

<alignment
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Text Alignment"
    name="./alignment"
    value="left">
    <items jcr:primaryType="nt:unstructured">
        <left
            text="Left"
            value="left"
            selected="{Boolean}true"/>
        <center
            text="Center"
            value="center"/>
        <right
            text="Right"
            value="right"/>
    </items>
</alignment>

Two ways:

  1. value="left" on the container
  2. selected="{Boolean}true" on the option

2. Required

<componentType
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Component Type"
    name="./componentType"
    required="{Boolean}true">
    <items jcr:primaryType="nt:unstructured">
        <!-- options -->
    </items>
</componentType>

3. Empty Option

<theme
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Theme"
    name="./theme"
    emptyText="-- Select Theme --">
    <items jcr:primaryType="nt:unstructured">
        <light
            text="Light"
            value="light"/>
        <dark
            text="Dark"
            value="dark"/>
    </items>
</theme>

emptyText adds an empty option at the top of the list.


Practical Examples

Example 1: Text Alignment

<alignment
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Text Alignment"
    name="./alignment"
    value="left">
    <items jcr:primaryType="nt:unstructured">
        <left
            jcr:primaryType="nt:unstructured"
            text="Left"
            value="left"/>
        <center
            jcr:primaryType="nt:unstructured"
            text="Center"
            value="center"/>
        <right
            jcr:primaryType="nt:unstructured"
            text="Right"
            value="right"/>
        <justify
            jcr:primaryType="nt:unstructured"
            text="Justify"
            value="justify"/>
    </items>
</alignment>

HTL:

<div class="content text-${properties.alignment || 'left'}">
  ${properties.text}
</div>

Example 2: Style Variants

<variant
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Button Variant"
    name="./variant"
    value="primary">
    <items jcr:primaryType="nt:unstructured">
        <primary
            jcr:primaryType="nt:unstructured"
            text="Primary (Blue)"
            value="primary"/>
        <secondary
            jcr:primaryType="nt:unstructured"
            text="Secondary (Gray)"
            value="secondary"/>
        <success
            jcr:primaryType="nt:unstructured"
            text="Success (Green)"
            value="success"/>
        <danger
            jcr:primaryType="nt:unstructured"
            text="Danger (Red)"
            value="danger"/>
    </items>
</variant>

HTL:

<button class="btn btn-${properties.variant || 'primary'}">
  ${properties.buttonText}
</button>

Example 3: Sizes

<size
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Component Size"
    name="./size"
    value="medium">
    <items jcr:primaryType="nt:unstructured">
        <small
            jcr:primaryType="nt:unstructured"
            text="Small"
            value="sm"/>
        <medium
            jcr:primaryType="nt:unstructured"
            text="Medium"
            value="md"
            selected="{Boolean}true"/>
        <large
            jcr:primaryType="nt:unstructured"
            text="Large"
            value="lg"/>
        <xlarge
            jcr:primaryType="nt:unstructured"
            text="Extra Large"
            value="xl"/>
    </items>
</size>

HTL:

<div class="component component-${properties.size || 'md'}">
  Content
</div>

Example 4: Content Type

<contentType
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Content Type"
    fieldDescription="Select the type of content to display"
    name="./contentType"
    required="{Boolean}true">
    <items jcr:primaryType="nt:unstructured">
        <article
            jcr:primaryType="nt:unstructured"
            text="Article"
            value="article"/>
        <video
            jcr:primaryType="nt:unstructured"
            text="Video"
            value="video"/>
        <gallery
            jcr:primaryType="nt:unstructured"
            text="Image Gallery"
            value="gallery"/>
        <infographic
            jcr:primaryType="nt:unstructured"
            text="Infographic"
            value="infographic"/>
    </items>
</contentType>

Usage: Show different templates/layouts based on type.


Complete Dialog with Select

<?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="Hero Banner"
    sling:resourceType="cq/gui/components/authoring/dialog">
    <content
        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">
                <items jcr:primaryType="nt:unstructured">

                    <!-- Content Tab -->
                    <content
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Content"
                        sling:resourceType="granite/ui/components/coral/foundation/container">
                        <items jcr:primaryType="nt:unstructured">

                            <!-- Title -->
                            <title
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                fieldLabel="Title"
                                name="./title"
                                required="{Boolean}true"/>

                            <!-- Subtitle -->
                            <subtitle
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                fieldLabel="Subtitle"
                                name="./subtitle"/>

                        </items>
                    </content>

                    <!-- Style Tab -->
                    <style
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Style"
                        sling:resourceType="granite/ui/components/coral/foundation/container">
                        <items jcr:primaryType="nt:unstructured">

                            <!-- Text Alignment -->
                            <alignment
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                fieldLabel="Text Alignment"
                                name="./alignment"
                                value="center">
                                <items jcr:primaryType="nt:unstructured">
                                    <left
                                        jcr:primaryType="nt:unstructured"
                                        text="Left"
                                        value="left"/>
                                    <center
                                        jcr:primaryType="nt:unstructured"
                                        text="Center"
                                        value="center"
                                        selected="{Boolean}true"/>
                                    <right
                                        jcr:primaryType="nt:unstructured"
                                        text="Right"
                                        value="right"/>
                                </items>
                            </alignment>

                            <!-- Theme -->
                            <theme
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                fieldLabel="Theme"
                                name="./theme"
                                value="light">
                                <items jcr:primaryType="nt:unstructured">
                                    <light
                                        jcr:primaryType="nt:unstructured"
                                        text="Light"
                                        value="light"/>
                                    <dark
                                        jcr:primaryType="nt:unstructured"
                                        text="Dark"
                                        value="dark"/>
                                </items>
                            </theme>

                            <!-- Size -->
                            <size
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                fieldLabel="Height"
                                name="./size"
                                value="medium">
                                <items jcr:primaryType="nt:unstructured">
                                    <small
                                        jcr:primaryType="nt:unstructured"
                                        text="Small (400px)"
                                        value="small"/>
                                    <medium
                                        jcr:primaryType="nt:unstructured"
                                        text="Medium (600px)"
                                        value="medium"/>
                                    <large
                                        jcr:primaryType="nt:unstructured"
                                        text="Large (800px)"
                                        value="large"/>
                                    <fullscreen
                                        jcr:primaryType="nt:unstructured"
                                        text="Full Screen"
                                        value="fullscreen"/>
                                </items>
                            </size>

                        </items>
                    </style>

                </items>
            </tabs>
        </items>
    </content>
</jcr:root>

Usage in HTL

Simple Value Reading

<div class="hero
            hero-${properties.size || 'medium'}
            hero-${properties.theme || 'light'}
            text-${properties.alignment || 'center'}">
  <h1>${properties.title}</h1>
  <p>${properties.subtitle}</p>
</div>

With Fallback

<!-- Fallback with || operator -->
<div class="component-${properties.variant || 'default'}">
  Content
</div>

<!-- Fallback with data-sly-test -->
<div class="component">
  <sly data-sly-test="${properties.theme == 'dark'}">
    <!-- Dark theme content -->
  </sly>
  <sly data-sly-test="${!properties.theme || properties.theme == 'light'}">
    <!-- Light theme content (default) -->
  </sly>
</div>

With Sling Model

@Model(adaptables = Resource.class)
public class HeroModel {

    @ValueMapValue
    private String title;

    @ValueMapValue
    private String subtitle;

    @ValueMapValue
    private String alignment;

    @ValueMapValue
    private String theme;

    @ValueMapValue
    private String size;

    public String getTitle() {
        return title;
    }

    public String getSubtitle() {
        return subtitle;
    }

    public String getAlignment() {
        return alignment != null ? alignment : "center";
    }

    public String getTheme() {
        return theme != null ? theme : "light";
    }

    public String getSize() {
        return size != null ? size : "medium";
    }

    /**
     * Complete CSS classes
     */
    public String getHeroClasses() {
        return String.format("hero hero-%s hero-%s text-%s",
            getSize(), getTheme(), getAlignment());
    }

    /**
     * Check if dark theme
     */
    public boolean isDarkTheme() {
        return "dark".equals(getTheme());
    }
}

HTL with Model:

<div data-sly-use.model="com.mysite.models.HeroModel"
     class="${model.heroClasses}">
  <div class="hero-content">
    <h1>${model.title}</h1>
    <p data-sly-test="${model.subtitle}">
      ${model.subtitle}
    </p>
  </div>
</div>

Dynamic Options from Datasource

Select Configuration with Datasource

<categorySelect
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Category"
    name="./category"
    datasource="com.mysite.datasources.CategoryDataSource">
</categorySelect>

Java Datasource

package com.mysite.datasources;

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 org.apache.sling.api.SlingHttpServletRequest;
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.wrappers.ValueMapDecorator;
import org.osgi.service.component.annotations.Component;

import javax.servlet.Servlet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@Component(
    service = Servlet.class,
    property = {
        "sling.servlet.resourceTypes=com.mysite.datasources.CategoryDataSource"
    }
)
public class CategoryDataSource extends SlingSafeMethodsServlet {

    @Override
    protected void doGet(SlingHttpServletRequest request,
                         SlingHttpServletResponse response) {

        ResourceResolver resolver = request.getResourceResolver();
        List<Resource> resources = new ArrayList<>();

        // Fetch categories from your system
        List<Category> categories = getCategoriesFromService();

        for (Category cat : categories) {
            ValueMap vm = new ValueMapDecorator(new HashMap<>());
            vm.put("value", cat.getId());
            vm.put("text", cat.getTitle());

            resources.add(new ValueMapResource(resolver,
                new ResourceMetadata(),
                "nt:unstructured",
                vm));
        }

        DataSource ds = new SimpleDataSource(resources.iterator());
        request.setAttribute(DataSource.class.getName(), ds);
    }

    private List<Category> getCategoriesFromService() {
        // Implementation to fetch categories
        return new ArrayList<>();
    }
}

Select vs Radio vs Checkbox

Component When to Use Values
Select 3+ options, limited space 1 value from N
Radio 2-5 options, visibility important 1 value from N
Checkbox Enable/disable single option Boolean
Checkbox Group Multiple selection Array of values

Use Select when: You have many options and want to save space in the dialog.


Common Issues

❌ Problem 1: Value not saved

<!-- WRONG - missing value in option -->
<items jcr:primaryType="nt:unstructured">
    <left
        text="Left"/>
</items>

<!-- CORRECT -->
<items jcr:primaryType="nt:unstructured">
    <left
        text="Left"
        value="left"/>
</items>

❌ Problem 2: Default not working

<!-- WRONG - value as string and selected together -->
<alignment
    value="center">
    <items jcr:primaryType="nt:unstructured">
        <left
            selected="true"/>
    </items>
</alignment>

<!-- CORRECT - use one or the other with type hint -->
<alignment
    value="center">
    <!-- OR -->
    <items jcr:primaryType="nt:unstructured">
        <left
            selected="{Boolean}true"/>
    </items>
</alignment>

❌ Problem 3: Duplicate options

<!-- WRONG - node names must be unique -->
<items jcr:primaryType="nt:unstructured">
    <option
        text="Option 1"
        value="opt1"/>
    <option
        text="Option 2"
        value="opt2"/>
</items>

<!-- CORRECT - different node names -->
<items jcr:primaryType="nt:unstructured">
    <option1
        text="Option 1"
        value="opt1"/>
    <option2
        text="Option 2"
        value="opt2"/>
</items>

Best Practices

  1. Clear values: value="left" not value="l" or value="0"
  2. Descriptive text: "Left Align" not just "Left"
  3. Sensible default: Always set a default value
  4. Unique node names: <left>, <center>, <right> not <option>
  5. Fallback in HTL: Use || operator for default values
  6. fieldDescription: Explain the impact of the choice
  7. Logical order: Put the most common options at the top
  8. Datasource for dynamic lists: Don't hard-code long lists

Next Lesson

In the next lesson we'll cover the PathBrowser component for selecting AEM content.

Resources:


Guide #4 of the AEM Dialog Components series - ← Previous lesson | Next lesson →