AEM Dialog Components #5: PathBrowser - Selezione Contenuti AEM

AEM Dialog Components #5: PathBrowser

Cos'è un PathBrowser?

Il PathBrowser (o PathField) è un componente Granite UI che permette agli autori di selezionare contenuti dal repository AEM tramite un'interfaccia di navigazione.

Casi d'uso comuni:

  • Selezione pagine interne
  • Link a contenuti
  • Selezione immagini da DAM
  • Reference ad assets
  • Selezione Experience Fragments

Configurazione Base

XML Dialog Minimo

<pagePath
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Page"
    name="./pagePath"
    rootPath="/content"/>

Proprietà essenziali:

  • sling:resourceType - Sempre granite/ui/components/coral/foundation/form/pathfield
  • fieldLabel - Etichetta
  • name - Percorso JCR (usa sempre ./)
  • rootPath - Percorso radice della navigazione

Proprietà Principali

1. rootPath (Percorso Radice)

<!-- Pagine sotto /content -->
<pagePath
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Internal Page"
    name="./pagePath"
    rootPath="/content/mysite"/>

<!-- Assets dal DAM -->
<imagePath
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Image"
    name="./imagePath"
    rootPath="/content/dam"/>

Best practice: Limita sempre a un sottopercorso specifico!


2. filter (Filtro Tipo Risorsa)

<!-- Solo pagine -->
<pagePath
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Page Link"
    name="./pagePath"
    rootPath="/content/mysite"
    filter="hierarchyNotFile"/>

<!-- Solo immagini -->
<imagePath
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Image"
    name="./imagePath"
    rootPath="/content/dam"
    filter="image"/>

Filtri comuni:

  • hierarchyNotFile - Solo pagine (no assets)
  • image - Solo immagini
  • video - Solo video
  • file - Solo file
  • Custom predicates

3. Required

<imagePath
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Hero Image"
    name="./imagePath"
    rootPath="/content/dam/mysite"
    filter="image"
    required="{Boolean}true"/>

4. forceSelection (Forza Selezione via Browser)

<pagePath
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Page"
    name="./pagePath"
    rootPath="/content"
    forceSelection="{Boolean}true"/>

Comportamento: Disabilita input manuale, obbliga a usare il browser.


Esempi Pratici

Esempio 1: Link Interno a Pagina

<internalLink
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Internal Link"
    fieldDescription="Select a page from your site"
    name="./internalLink"
    rootPath="/content/mysite"
    filter="hierarchyNotFile"/>

HTL:

<a href="${properties.internalLink}.html">
  ${properties.linkText}
</a>

Esempio 2: Immagine da DAM

<fileReference
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Image"
    fieldDescription="Select an image from DAM"
    name="./fileReference"
    rootPath="/content/dam/mysite"
    filter="image"
    required="{Boolean}true"/>

HTL:

<img src="${properties.fileReference}" alt="${properties.altText}"/>

Esempio 3: Experience Fragment

<experienceFragment
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Experience Fragment"
    fieldDescription="Select a header experience fragment"
    name="./experienceFragment"
    rootPath="/content/experience-fragments/mysite"
    filter="folder,cq:Page"/>

Esempio 4: Video da DAM

<videoPath
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Video"
    name="./videoPath"
    rootPath="/content/dam/mysite/videos"
    filter="video"/>

HTL:

<video controls>
  <source src="${properties.videoPath}" type="video/mp4"/>
</video>

Esempio 5: PDF o Documento

<documentPath
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Download Document"
    fieldDescription="Select a PDF or document"
    name="./documentPath"
    rootPath="/content/dam/mysite/documents"
    filter="file"/>

HTL:

<a href="${properties.documentPath}" download>
  Download ${properties.documentTitle}
</a>

Dialog Completa con PathBrowser

<?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="Content Card"
    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"/>

                            <!-- Image from DAM -->
                            <fileReference
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
                                fieldLabel="Image"
                                fieldDescription="Select card image"
                                name="./fileReference"
                                rootPath="/content/dam/mysite"
                                filter="image"
                                required="{Boolean}true"/>

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

                            <!-- Link to Page -->
                            <linkPath
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
                                fieldLabel="Link"
                                fieldDescription="Link to internal page"
                                name="./linkPath"
                                rootPath="/content/mysite"
                                filter="hierarchyNotFile"/>

                        </items>
                    </content>

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

Uso in HTL

Pagina Interna

<a data-sly-test="${properties.linkPath}"
   href="${properties.linkPath}.html">
  ${properties.linkText}
</a>

Immagine da DAM

<img data-sly-test="${properties.fileReference}"
     src="${properties.fileReference}"
     alt="${properties.altText || properties.title}"/>

Con Sling Model

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

    @ValueMapValue
    private String title;

    @ValueMapValue
    private String fileReference;

    @ValueMapValue
    private String altText;

    @ValueMapValue
    private String linkPath;

    public String getTitle() {
        return title;
    }

    public String getImagePath() {
        return fileReference;
    }

    public String getAltText() {
        return altText != null ? altText : title;
    }

    public String getLinkUrl() {
        return linkPath != null ? linkPath + ".html" : null;
    }

    public boolean hasImage() {
        return fileReference != null && !fileReference.isEmpty();
    }

    public boolean hasLink() {
        return linkPath != null && !linkPath.isEmpty();
    }
}

HTL con Model:

<div data-sly-use.model="com.mysite.models.ContentCardModel" class="card">

  <!-- Image -->
  <img data-sly-test="${model.hasImage}"
       src="${model.imagePath}"
       alt="${model.altText}"
       class="card-image"/>

  <!-- Title -->
  <h3 class="card-title">${model.title}</h3>

  <!-- Link -->
  <a data-sly-test="${model.hasLink}"
     href="${model.linkUrl}"
     class="card-link">
    Read More
  </a>

</div>

Filtri Avanzati

Filter Personalizzato con Predicate

<customPathField
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Custom Filter"
    name="./customPath"
    rootPath="/content"
    filter="myCustomFilter"
    optionLoader="com.mysite.predicates.CustomPathFieldPredicateLoader"/>

Java Predicate Loader

@Component(service = PredicateLoader.class)
public class CustomPathFieldPredicateLoader implements PredicateLoader {

    @Override
    public Predicate load(Request request) {
        return new Predicate() {
            @Override
            public boolean includes(Resource resource) {
                // Custom logic per filtrare risorse
                return resource.getPath().contains("specific-folder");
            }
        };
    }
}

PathField vs PathBrowser

Sono sinonimi - stesso componente, nomi diversi:

  • granite/ui/components/coral/foundation/form/pathfield
  • granite/ui/components/coral/foundation/form/pathbrowser

Usa pathfield (più comune nella documentazione).


Problemi Comuni

❌ Problema 1: rootPath troppo ampio

<!-- SCONSIGLIATO - permette navigazione ovunque -->
<pagePath
    rootPath="/"/>

<!-- MEGLIO - limita alla tua area -->
<pagePath
    rootPath="/content/mysite"/>

Perché: Troppa libertà confonde gli autori e permette selezioni sbagliate.


❌ Problema 2: Manca .html nell'HTL

<!-- SBAGLIATO - path senza estensione -->
<a href="${properties.linkPath}">Link</a>

<!-- CORRETTO - aggiungi .html -->
<a href="${properties.linkPath}.html">Link</a>

<!-- OPPURE usa Link Externalizer nel Model -->

❌ Problema 3: Image path senza validazione

<!-- RISCHIOSO - se path è vuoto -->
<img src="${properties.fileReference}"/>

<!-- SICURO - controlla esistenza -->
<img data-sly-test="${properties.fileReference}"
     src="${properties.fileReference}"
     alt="${properties.altText}"/>

❌ Problema 4: Filter scorretto

<!-- SBAGLIATO - filter su immagini ma root non è DAM -->
<image
    rootPath="/content/mysite"
    filter="image"/>

<!-- CORRETTO - root su DAM per immagini -->
<image
    rootPath="/content/dam"
    filter="image"/>

Best Practices

  1. rootPath specifico: Limita sempre a un sottopercorso logico
  2. filter appropriato: Usa filter per guidare la selezione
  3. required per assets critici: Immagine hero, logo, etc.
  4. fieldDescription: Spiega cosa selezionare
  5. Validazione HTL: Controlla sempre che il path esista
  6. .html per pagine: Aggiungi estensione nei link
  7. Alt text obbligatorio: Per immagini, richiedi sempre alt text
  8. Link Externalizer: Usa in Model per URL esterni

PathBrowser vs FileUpload

PathBrowser FileUpload
Uso Seleziona contenuto esistente Carica nuovo file
Source DAM / Content Locale dell'autore
Quando Immagini già nel DAM Upload nuove immagini

Regola: Se il contenuto è già in AEM, usa PathBrowser!


Pattern Avanzati

1. Link Esterno o Interno

<!-- Checkbox: Link esterno? -->
<isExternalLink
    sling:resourceType="granite/ui/components/coral/foundation/form/checkbox"
    name="./isExternalLink"
    text="External link"
    value="{Boolean}true"
    uncheckedValue="{Boolean}false"/>

<!-- PathBrowser: Link interno (se non esterno) -->
<internalLink
    sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
    fieldLabel="Internal Link"
    name="./internalLink"
    rootPath="/content/mysite"
    filter="hierarchyNotFile"
    granite:hide="${properties.isExternalLink}"/>

<!-- Textfield: URL esterno (se esterno) -->
<externalUrl
    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
    fieldLabel="External URL"
    name="./externalUrl"
    granite:hide="${!properties.isExternalLink}"/>

Sling Model:

public String getLinkUrl() {
    if (isExternalLink && externalUrl != null) {
        return externalUrl;
    } else if (internalLink != null) {
        return internalLink + ".html";
    }
    return null;
}

2. Immagine con Renditions

<picture>
  <source srcset="${properties.fileReference}./_jcr_content/renditions/cq5dam.web.1280.1280.jpeg"
          media="(min-width: 1024px)"/>
  <source srcset="${properties.fileReference}./_jcr_content/renditions/cq5dam.web.640.640.jpeg"
          media="(min-width: 640px)"/>
  <img src="${properties.fileReference}"
       alt="${properties.altText}"/>
</picture>

Prossima Lezione

Nella prossima lezione vedremo il componente RichText Editor per contenuti formattati.

Risorse:


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