Components in aem are basic building blocks of content in web pages. Components in AEM can be developed in two ways, using JSP and HTML. It is recommended to use HTL (HTML Template Language) for creating components. Refer Sling Models and HTL for more details.
Model: Sling Model
View: HTL
Follow below steps to create a HTL Component and associated Sling Model.
In this DIY session we will create Promo Hero Component which has a Image, Tile, Description and Link fields.
1. Open CRXDE and Go to project folder (/apps/aemexplained/components/content).
2. Right-click on content folder and select create component.
3. Enter Label, Title and ComponentGroup in create component window.
4. Click Next and OK.
5. Rename promoherocomponent.jsp to promoherocomponent.html
6. Create cq:dialog under /apps/aemexplained/components/content/promoherocomponent as shown below.
<?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" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
jcr:primaryType="nt:unstructured"
jcr:title="Promo Hero"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
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">
<items jcr:primaryType="nt:unstructured">
<image
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
fieldLabel="Image"
name="./image"
rootPath="/content/dam"/>
<title
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Title"
name="./title"/>
<description
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
fieldLabel="Description"
name="./description"/>
<buttons
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
class="full-width"
composite="{Boolean}true"
fieldDescription="Click '+' to add a new button"
fieldLabel="Buttons">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container"
name="./buttons">
<items jcr:primaryType="nt:unstructured">
<well
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<layout
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/layouts/well"/>
<items jcr:primaryType="nt:unstructured">
<name
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldLabel="Name"
name="name"/>
<link
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/pathfield"
fieldLabel="Link"
name="link"
rootPath="/content/aemexplained"/>
</items>
</well>
</items>
</field>
</buttons>
</items>
</column>
</items>
</content>
</jcr:root>
7. Import created component and dialog to the project.
8. Create Sling Model for the component as shown below. we create Interface so as to hide data members and method body.
PromoHero.java
package com.aem.explained.platform.core.models;
import java.util.List;
public interface PromoHero {
String RESOURCE_TYPE ="aemexplained/components/content/promoherocomponent";
String getImage();
String getTitle();
String getDescription();
List<Link> getButtons();
}
PromoHeroComponent.java
package com.aem.explained.platform.core.components;
import com.aem.explained.platform.core.models.Link;
import com.aem.explained.platform.core.models.PromoHero;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.ExporterOption;
import org.apache.sling.models.annotations.Model;
import javax.inject.Inject;
import java.util.List;
@Model( adaptables = Resource.class,
adapters = {PromoHero.class, PromoHeroComponent.class},
resourceType = PromoHero.RESOURCE_TYPE,
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json", options = {
@ExporterOption(name = "MapperFeature.SORT_PROPERTIES_ALPHABETICALLY", value="true")
})
public class PromoHeroComponent implements PromoHero {
@Inject
private String image;
@Inject
private String title;
@Inject
private String description;
@Inject
private List<Link> buttons;
@Override
public String getImage() {
return image;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getDescription() {
return description;
}
@Override
public List<Link> getButtons() {
return buttons;
}
}
Since we are using interface as Sling Model, we need to mention interface and its implementation class in adapter property of @Model annotation. defaultInjectionStrategy is mentioned to make all injection in model to optional. @Exporter annotation is used to export model in json format when requested.
In the above model we have used Link Model as a multi field item. Below is the Link Model and its implementation class.
Link.java
package com.aem.explained.platform.core.models;
public interface Link {
String getName();
String getLink();
}
LinkComponent.java
package com.aem.explained.platform.core.components;
import com.aem.explained.platform.core.models.Link;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.ExporterOption;
import org.apache.sling.models.annotations.Model;
import javax.inject.Inject;
@Model( adaptables = Resource.class,
adapters = {Link.class, LinkComponent.class},
defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = "jackson", extensions = "json", options = {
@ExporterOption(name = "MapperFeature.SORT_PROPERTIES_ALPHABETICALLY", value="true")
})
public class LinkComponent implements Link {
@Inject
private String name;
@Inject
private String link;
@Override
public String getName() {
return name;
}
@Override
public String getLink() {
return link;
}
}
9. Now, open promoherocomponent.html file and initialise com.aem.explained.platform.core.models.PromoHero to get all the values saved in dialog. HTL for component is given below.
<div data-sly-use.promohero="com.aem.explained.platform.core.models.PromoHero"
data-sly-use.templates="core/wcm/components/commons/v1/templates.html"
data-sly-test.isEmpty="${promohero.image || promohero.title}"
class="img-container ">
<img data-sly-test="${promohero.image}" src="${promohero.image}" class="img-responsive "
title="${promohero.title}"/>
<div class="card" data-sly-test="${promohero.title || promohero.description}">
<h3>${promohero.title}</h3>
<p>${promohero.description}</p>
<div class="buttons" data-sly-list.button="${promohero.buttons}">
<a class="button" href="${button.link @extension='html'}">${button.name}</a>
</div>
</div>
</div>
<sly data-sly-call="${templates.placeholder @ isEmpty = !isEmpty, classAppend = 'cmp-image cq-dd-image'}"></sly>
Here “core/wcm/components/commons/v1/templates.html” is used to display placeholder when component is unauthored.
10. Now build code and deploy in AEM by using build configuration or by running maven command.
mvn clean install -PautoInstallPackge
11. Open any page and add Promo Hero Component. (If you are not able to see Promo Hero Component in allowed components, open the page template editor and select layout container policies, in the allowed components list, check Promo Hero Component/AEM Explained componentGroup. Save and refresh page now you should be able to add component).
12. Open Promo Hero dialog by double-click and author values.
13. Add link by clicking “Add” button.
14. Submit dialog.
You have successfully created HTL Component.
we have used following features Sling Models, Sling Exporters, HTL.