Day 21 of 40
WKND: A Custom Component
Granite UI dialogs, the Sling Model, and the HTL that binds them
By the end of today you should be able to
- Build a custom component end to end: node, dialog, model, HTL
- Write a Granite UI dialog with several field types including a multifield
- Give the component a sensible empty state in the editor
- Add a component icon and make it presentable to authors
The full stack of a component
The WKND tutorial builds a Byline component: a contributor's name, a photo, and a list of occupations. It is small but exercises everything — dialog with a multifield, a Sling Model with real logic, HTL that handles an empty state. Here is the shape of the work.
1. The component node
/apps/wknd/components/byline/.content.xml
jcr:primaryType = "cq:Component"
jcr:title = "Byline"
jcr:description = "Displays a contributor's name, photo and occupations"
componentGroup = "WKND - Content"
No sling:resourceSuperType here — this one is genuinely custom rather than a proxy.
2. The dialog
Three fields: an image (via a pathfield), a name, and a multifield of occupations.
<name
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Name"
name="./name"
required="{Boolean}true"/>
<occupations
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
fieldLabel="Occupations"
composite="{Boolean}true">
<field
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./occupations">
<items>
<occupation
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Occupation"
name="./occupation"/>
</items>
</field>
</occupations>
A composite multifield stores each entry as a child node with named properties, which is
what you want as soon as an entry has more than one field. It produces
occupations/item0/occupation, occupations/item1/occupation and so on.
3. The Sling Model
@Model(adaptables = SlingHttpServletRequest.class,
adapters = { Byline.class },
resourceType = "wknd/components/byline",
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class BylineImpl implements Byline {
@ValueMapValue
private String name;
@ChildResource
private List<Resource> occupations;
@Override
public List<String> getOccupations() {
if (occupations == null) return Collections.emptyList();
return occupations.stream()
.map(r -> r.getValueMap().get("occupation", String.class))
.filter(Objects::nonNull)
.sorted()
.collect(Collectors.toList());
}
@Override
public boolean isEmpty() {
return StringUtils.isBlank(name) || getOccupations().isEmpty();
}
}
Note where the logic lives: null-handling, filtering and sorting are all in Java, where they are testable. HTL never has to know any of it.
4. The HTL, including the empty state
<sly data-sly-use.byline="com.adobe.aem.guides.wknd.core.models.Byline"/>
<sly data-sly-test="${byline.empty}"
data-sly-call="${placeholderTemplate.placeholder @ isEmpty=true}"/>
<div class="cmp-byline" data-sly-test="${!byline.empty}">
<h2 class="cmp-byline__name">${byline.name}</h2>
<ul class="cmp-byline__occupations"
data-sly-list.occupation="${byline.occupations}">
<li>${occupation}</li>
</ul>
</div>
A component with no content should render a visible placeholder in the editor and nothing at all on publish. Without it, an author drops the component, sees nothing, and cannot click it to open the dialog. The Core Components placeholder template gives you the standard treatment for free.
5. The icon
Components appear in the author's component browser, and a distinguishable icon genuinely helps. Options, in order of preference:
cq:icon— the name of a Coral UI icon, e.g.textEdit. Simplest and consistent with the rest of the UI.abbreviation— a two-character abbreviation, derived fromjcr:titleif unset.- A
.svgfile in the component folder, for a bespoke mark.
6. Allow it in a policy
The component will not appear until a template policy allows it. This is the step people forget after building everything else — the component is complete, correct, and invisible. Add it to the layout container's allowed components in the relevant template.
Node, dialog, model, HTL, icon, policy. Logic in the model, empty state in the HTL, and remember that the policy is what makes it real for authors.
Watch
Adobe's own videos for this topic. They load only when you press play.
Read on Experience League
The primary sources these notes are drawn from.
Your notes
Saved automatically to this browser.
Check yourself
10 questions on today's material. 80% to pass.