AEM Dialog Components #4: Select - Dropdown con Opzioni

AEM Dialog Components #4: Select (Dropdown)

Cos'è un Select?

Il Select è un componente Granite UI che permette agli autori di scegliere una singola opzione da un elenco predefinito (dropdown).

Casi d'uso comuni:

  • Allineamento (left, center, right)
  • Varianti di stile (primary, secondary, tertiary)
  • Dimensioni (small, medium, large)
  • Tipi di contenuto (article, video, gallery)
  • Configurazioni con opzioni limitate

Configurazione Base

XML Dialog Minimo

<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>

Struttura:

  • Container <select> con sling:resourceType e name
  • Child node <items> contenente le opzioni
  • Ogni opzione ha text (label visibile) e value (valore salvato)

Proprietà Principali

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>

Due modi:

  1. value="left" sul container
  2. selected="{Boolean}true" sull'opzione

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 (Opzione Vuota)

<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 aggiunge un'opzione vuota in cima alla lista.


Esempi Pratici

Esempio 1: Allineamento Testo

<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>

Esempio 2: Varianti Stile

<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>

Esempio 3: Dimensioni

<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>

Esempio 4: Tipo di Contenuto

<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>

Uso: Mostrare template/layout diversi basati sul tipo.


Dialog Completa con 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>

Uso in HTL

Lettura Valore Semplice

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

Con Fallback

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

<!-- Fallback con 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>

Con 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";
    }

    /**
     * CSS classes complete
     */
    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 con 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>

Opzioni Dynamiche da Datasource

Configurazione Select con 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

Componente Quando Usare Valori
Select 3+ opzioni, spazio limitato 1 valore da N
Radio 2-5 opzioni, visibilità importante 1 valore da N
Checkbox Abilitare/disabilitare singola opzione Boolean
Checkbox Group Selezione multipla Array di valori

Usa Select quando: Hai molte opzioni e vuoi risparmiare spazio nella dialog.


Problemi Comuni

❌ Problema 1: Valore non salvato

<!-- SBAGLIATO - manca value nell'opzione -->
<items jcr:primaryType="nt:unstructured">
    <left
        text="Left"/>
</items>

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

❌ Problema 2: Default non funziona

<!-- SBAGLIATO - value come stringa e selected insieme -->
<alignment
    value="center">
    <items jcr:primaryType="nt:unstructured">
        <left
            selected="true"/>
    </items>
</alignment>

<!-- CORRETTO - usa uno o l'altro con type hint -->
<alignment
    value="center">
    <!-- OPPURE -->
    <items jcr:primaryType="nt:unstructured">
        <left
            selected="{Boolean}true"/>
    </items>
</alignment>

❌ Problema 3: Opzioni duplicate

<!-- SBAGLIATO - nomi nodi devono essere univoci -->
<items jcr:primaryType="nt:unstructured">
    <option
        text="Option 1"
        value="opt1"/>
    <option
        text="Option 2"
        value="opt2"/>
</items>

<!-- CORRETTO - nomi nodi diversi -->
<items jcr:primaryType="nt:unstructured">
    <option1
        text="Option 1"
        value="opt1"/>
    <option2
        text="Option 2"
        value="opt2"/>
</items>

Best Practices

  1. Valori chiari: value="left" non value="l" o value="0"
  2. Text descrittivo: "Left Align" non solo "Left"
  3. Default sensato: Sempre imposta un valore di default
  4. Nomi nodi univoci: <left>, <center>, <right> non <option>
  5. Fallback in HTL: Usa || operator per valori di default
  6. fieldDescription: Spiega l'impatto della scelta
  7. Ordine logico: Metti le opzioni più comuni in cima
  8. Datasource per liste dinamiche: Non hard-codare lunghe liste

Prossima Lezione

Nella prossima lezione vedremo il componente PathBrowser per selezionare contenuti AEM.

Risorse:


Guida #4 della serie AEM Dialog Components - ← Lezione precedente | Prossima lezione →