Creating a custom navigation component in AEM that dynamically fetches and displays pages based on a specific template is feasible. Here’s a high-level overview of how you can achieve this:
Steps to Create Custom Navigation Component
1. Component Definition:
- Define a new AEM component for your custom navigation in the `/apps` directory.
- Create a corresponding dialog to allow authors to select the template and configure other properties if needed.
2. Sling Model:
- Create a Sling Model to handle the business logic of fetching pages based on the selected template.
- Inject the template path and other required properties using annotations like `@ValueMapValue`.
3. JCR Query:
- Use JCR-SQL2 or Query Builder API to fetch pages that use the selected template.
- Example JCR-SQL2 query to find pages:
SELECT * FROM [cq:Page] AS s WHERE ISDESCENDANTNODE([/content]) AND [jcr:content/cq:template] = '/apps/myproject/templates/mytemplate'
4. HTL Script:
- Create an HTL (Sightly) script to render the navigation.
- Use the Sling Model to populate the list of pages and iterate over them in the HTL script to build the navigation structure.
Example Implementation
1. Component Definition
Create the component under `/apps/myproject/components/custom-navigation` and add a dialog to allow template selection.
dialog.xml:
<jcr:root
xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Custom Navigation"
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">
<tab1
jcr:primaryType="nt:unstructured"
jcr:title="Properties"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<templatePath
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
fieldLabel="Template Path"
name="./templatePath"
rootPath="/apps/myproject/templates"/>
</items>
</tab1>
</items>
</tabs>
</items>
</content>
</jcr:root>
2. Sling Model
Create a Sling Model to fetch the pages.
NavigationModel.java:
package com.myproject.core.models;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.PageManagerFactory;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.api.resource.QueryBuilder;
import org.apache.sling.api.resource.Query;
import org.apache.sling.api.resource.query.QueryBuilderException;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class NavigationModel {
@586265
private ResourceResolver resourceResolver;
@586265
private QueryBuilder queryBuilder;
@ValueMapValue
private String templatePath;
public List<Page> getPages() {
List<Page> pages = new ArrayList<>();
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
try {
Query query = queryBuilder.createQuery("SELECT * FROM [cq:Page] AS s WHERE ISDESCENDANTNODE([/content]) AND [jcr:content/cq:template] = '" + templatePath + "'", resourceResolver);
query.getResult().getNodes().forEachRemaining(node -> {
Page page = pageManager.getContainingPage(node);
if (page != null) {
pages.add(page);
}
});
} catch (QueryBuilderException e) {
// Handle exception
}
return pages;
}
}
3. HTL Script
Create an HTL script to render the navigation.
custom-navigation.html:
<sly data-sly-use.navigationModel="com.myproject.core.models.NavigationModel">
<ul>
<sly data-sly-list.page="${navigationModel.pages}">
<li>
<a href="${page.path}">${page.title}</a>
</li>
</sly>
</ul>
</sly>
Feasibility and Considerations
1. Performance: Ensure the query is optimized to avoid performance issues, especially if there are many pages.
2. Security: Validate the user input for the template path to avoid injection attacks.
3. Caching: Implement caching mechanisms to reduce the load on the repository for frequently accessed navigation components.
4. Error Handling: Handle errors gracefully to ensure a robust implementation.
By following these steps, you can create a custom navigation component in AEM that dynamically displays pages based on a selected template.