Hey @ChrisPhillipsUC Create a custom Sling Model that:
-
Accepts multiple parent paths (multifield in dialog)
-
Accepts tag selection
-
Queries child pages across all paths
-
Filters results by tags
-
Returns aggregated list items
Implementation
1. Sling Model Interface
package com.myproject.core.models;
import com.adobe.cq.wcm.core.components.models.List;
public interface MultiPathTaggedList extends List {
String[] getParentPaths();
}
2. Sling Model Implementation
package com.myproject.core.models.impl;
import com.adobe.cq.wcm.core.components.models.List;
import com.adobe.cq.wcm.core.components.models.ListItem;
import com.day.cq.tagging.Tag;
import com.day.cq.tagging.TagManager;
import com.myproject.core.models.MultiPathTaggedList;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.models.annotations.*;
import org.apache.sling.models.annotations.injectorspecific.*;
import org.apache.sling.models.annotations.via.ResourceSuperType;
import javax.annotation.PostConstruct;
import java.util.*;
import java.util.stream.*;
@Model(
adaptables = SlingHttpServletRequest.class,
adapters = {MultiPathTaggedList.class, List.class},
resourceType = "myproject/components/multipath-list",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class MultiPathTaggedListImpl implements MultiPathTaggedList {
@Self @Via(type = ResourceSuperType.class)
private List delegate;
@Self
private SlingHttpServletRequest request;
@ValueMapValue
private String[] parentPaths;
@ValueMapValue
private String[] tags;
private Collection<ListItem> items;
@PostConstruct
protected void init() {
if (parentPaths != null && parentPaths.length > 0 && tags != null && tags.length > 0) {
ResourceResolver resolver = request.getResourceResolver();
TagManager tagManager = resolver.adaptTo(TagManager.class);
Set<String> tagIds = new HashSet<>(Arrays.asList(tags));
items = Arrays.stream(parentPaths)
.map(resolver::getResource)
.filter(Objects::nonNull)
.flatMap(parent -> StreamSupport.stream(parent.getChildren().spliterator(), false))
.filter(res -> hasMatchingTag(res, tagManager, tagIds))
.map(SimpleListItem::new)
.collect(Collectors.toList());
} else {
items = delegate.getListItems();
}
}
private boolean hasMatchingTag(Resource resource, TagManager tagManager, Set<String> requiredTags) {
Tag[] pageTags = tagManager.getTags(resource);
return pageTags != null && Arrays.stream(pageTags)
.map(Tag::getTagID)
.anyMatch(requiredTags::contains);
}
@Override
public Collection<ListItem> getListItems() {
return items != null ? items : Collections.emptyList();
}
@Override
public String[] getParentPaths() {
return parentPaths;
}
@Override public boolean linkItems() { return delegate.linkItems(); }
@Override public boolean showDescription() { return delegate.showDescription(); }
@Override public boolean showModificationDate() { return delegate.showModificationDate(); }
@Override public String getDateFormatString() { return delegate.getDateFormatString(); }
private static class SimpleListItem implements ListItem {
private final Resource resource;
SimpleListItem(Resource resource) { this.resource = resource; }
@Override public String getTitle() {
return resource.getValueMap().get("jcr:title", resource.getName());
}
@Override public String getURL() { return resource.getPath() + ".html"; }
@Override public Resource getResource() { return resource; }
}
}
3. Component Definition (
_cq_dialog/.content.xml
)
<?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="Multi-Path Tagged List"
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">
<properties jcr:primaryType="nt:unstructured" jcr:title="Properties"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<parentPaths jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
fieldLabel="Parent Paths" required="{Boolean}true">
<field jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
name="./parentPaths" rootPath="/content"/>
</parentPaths>
<tags jcr:primaryType="nt:unstructured"
sling:resourceType="cq/gui/components/coral/common/form/tagfield"
fieldLabel="Tags" multiple="{Boolean}true" name="./tags" required="{Boolean}true"/>
</items>
</properties>
</items>
</tabs>
</items>
</content>
</jcr:root>
4. Component
.content.xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:Component"
jcr:title="Multi-Path Tagged List"
sling:resourceSuperType="core/wcm/components/list/v3/list"
componentGroup="My Project"/>
Usage Authors can now:
The component leverages
sling:resourceSuperType
to inherit all rendering and display options from the Core Components List.