Nested Coral Multifield in AEM 6.5
Goal
Create a component that uses a nested multifield as simple as possible.
Procedure
Let’s create our dialog as follows:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/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="Nested Accordion"
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"
maximized="{Boolean}true">
<items
jcr:primaryType="nt:unstructured">
<componentProperties
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
jcr:title="Component Properties">
<items
jcr:primaryType="nt:unstructured">
<columns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
<items
jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
margin="{Boolean}true">
<items
jcr:primaryType="nt:unstructured">
<title
jcr:primaryType="nt:unstructured"
fieldLabel="Title"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Title"
name="./title"/>
<accordions
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
composite="{Boolean}true"
fieldLabel="Accordions">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./accordions">
<items jcr:primaryType="nt:unstructured">
<title
jcr:primaryType="nt:unstructured"
fieldLabel="Accordion Title"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Title"
name="./title"/>
<list
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
composite="{Boolean}true"
fieldLabel="Bullet list">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./list">
<items jcr:primaryType="nt:unstructured">
<description
fieldLabel="Description Label"
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
emptyText="Description"
name="./description"/>
</items>
</field>
</list>
</items>
</field>
</accordions>
</items>
</column>
</items>
</columns>
</items>
</componentProperties>
</items>
</tabs>
</items>
</content>
</jcr:root>
The outer multfield starts with the tag “accordions”, while the inner multifield starts with the tag “list”.
The dialog layout is the following:

This is how our data will be stored in the CRX:

And this is our data will be displayed, obviously I’m not using any kind of css style:

The HTL file I used. In this case I preferred to use a Sling Model to retrieve our data
<sly data-sly-use.accordion="${'com.adobe.training.core.models.accordion.AccordionListModel'}"></sly>
<sly data-sly-test.configured="${accordion.configured}"></sly>
<sly data-sly-test="${wcmmode.edit && !configured}">
<div data-emptytext="${component.title}" class="cq-placeholder"></div>
</sly>
<div data-sly-test="${configured}">
<h2 class="accordion__title js-accordion-title">${accordion.title}</h2>
<div class="" data-sly-list.accordions="${accordion.accordionList}">
<h4> ${accordions.title}</h4>
<ul data-sly-list="${accordions.bulletPointList}">
<li class="accordion__text">${item.description @ context='html'}</li>
</ul>
</div>
</div>
Let’s go in deep and have a look to the Java Code. The main model used in the HTL file, “AccordionListModel”, is the following
package com.adobe.training.core.models.accordion;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.List;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class AccordionListModel {
@Inject
private String title;
@Inject
@Named("accordions/.")
public List<AccordionModel> accordionList;
public List<AccordionModel> getAccordionList() {
return accordionList;
}
public String getTitle() {
return title;
}
public boolean isConfigured() {
return accordionList != null && !accordionList.isEmpty();
}
}
Similarly to the other article, I’ve injected properties and children resources (using the annotation @Named(“accordions/.”)). And instead of injecting a list of Resource, I preferred to inject immediately the adapted model so that you can easily access to its properties in the HTL code.
Since in this case we are using a nested multifield, the model “AccordionBulletPointModel” (the outer multifield) will follow the same logic:
package com.adobe.training.core.models.accordion;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.List;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class AccordionModel {
@Inject
private String title;
@Inject
@Named("list/.")
private List<AccordionBulletPointModel> bulletPointList;
public String getTitle() {
return title;
}
public List<AccordionBulletPointModel> getBulletPointList() {
return bulletPointList;
}
}
And finally our last model, which contains the inner multifield:
package com.adobe.training.core.models.accordion;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
import javax.inject.Inject;
@Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class AccordionBulletPointModel {
@Inject
private String description;
public String getDescription() {
return description;
}
}
That’s all! As for the other article, this has been tested on AEM 6.5 but I’m pretty sure that can work on AEM 6.4 and 6.3. I’m not sure in the previous versions because the sling:resourceType will be different (not the coral one).
Of course you can create more complex multifield in order to make the most of your Sling Models.
If you are looking at how to limit the number of items in a multifield have a look here.
Cheers! 🍸