AEM Dialog Components #5: PathBrowser - AEM Content Selection

AEM Dialog Components #5: PathBrowser

What is a PathBrowser?

The PathBrowser (or PathField) is a Granite UI component that allows authors to select content from the AEM repository through a navigation interface.

Common use cases:

  • Internal page selection
  • Content linking
  • Image selection from DAM
  • Asset references
  • Experience Fragment selection

Basic Configuration

Minimal XML Dialog

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

Essential properties:

  • sling:resourceType - Always granite/ui/components/coral/foundation/form/pathfield
  • fieldLabel - Label
  • name - JCR path (always use ./)
  • rootPath - Navigation root path

Main Properties

1. rootPath (Root Path)

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

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

Best practice: Always limit to a specific subpath!


2. filter (Resource Type Filter)

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

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

Common filters:

  • hierarchyNotFile - Pages only (no assets)
  • image - Images only
  • video - Videos only
  • file - Files only
  • 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 (Force Browser Selection)

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

Behavior: Disables manual input, forces browser usage.


Practical Examples

Example 1: Internal Page Link

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

Example 2: Image from 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}"/>

Example 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"/>

Example 4: Video from 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>

Example 5: PDF or Document

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

Complete Dialog with 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>

Using in HTL

Internal Page

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

Image from DAM

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

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

Advanced Filters

Custom Filter with 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 to filter resources
                return resource.getPath().contains("specific-folder");
            }
        };
    }
}

PathField vs PathBrowser

They are synonyms - same component, different names:

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

Use pathfield (more common in documentation).


Common Issues

❌ Issue 1: rootPath too broad

<!-- NOT RECOMMENDED - allows navigation anywhere -->
<pagePath
    rootPath="/"/>

<!-- BETTER - limits to your area -->
<pagePath
    rootPath="/content/mysite"/>

Why: Too much freedom confuses authors and allows wrong selections.


❌ Issue 2: Missing .html in HTL

<!-- WRONG - path without extension -->
<a href="${properties.linkPath}">Link</a>

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

<!-- OR use Link Externalizer in Model -->

❌ Issue 3: Image path without validation

<!-- RISKY - if path is empty -->
<img src="${properties.fileReference}"/>

<!-- SAFE - check existence -->
<img data-sly-test="${properties.fileReference}"
     src="${properties.fileReference}"
     alt="${properties.altText}"/>

❌ Issue 4: Incorrect filter

<!-- WRONG - filter on images but root is not DAM -->
<image
    rootPath="/content/mysite"
    filter="image"/>

<!-- CORRECT - root on DAM for images -->
<image
    rootPath="/content/dam"
    filter="image"/>

Best Practices

  1. Specific rootPath: Always limit to a logical subpath
  2. Appropriate filter: Use filter to guide selection
  3. Required for critical assets: Hero image, logo, etc.
  4. fieldDescription: Explain what to select
  5. HTL validation: Always check that the path exists
  6. .html for pages: Add extension in links
  7. Mandatory alt text: For images, always require alt text
  8. Link Externalizer: Use in Model for external URLs

PathBrowser vs FileUpload

PathBrowser FileUpload
Use Select existing content Upload new file
Source DAM / Content Author's local
When Images already in DAM Upload new images

Rule: If the content is already in AEM, use PathBrowser!


Advanced Patterns

1. External or Internal Link

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

<!-- PathBrowser: Internal link (if not external) -->
<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: External URL (if external) -->
<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. Image with 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>

Next Lesson

In the next lesson we'll explore the RichText Editor component for formatted content.

Resources:


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