ADOBE EXPERIENCE MANAGER (AEM)
Sightly HTL Tips & Resources
Tips & Resources for using Sightly Hypertext Template Language (HTL) in Adobe Experience Manager (AEM). For more information, the HTML Template Language Specification is a comprehensive HTL resource.
HTL Updates in AEM 6.5
HTL version 1.4 changes include some long sought after enhancements. One of these is the in
relational operator we can now use to check if a substring exist in an object.
Example Scenario
Suppose you have have an element you’re applying multiple css classes to from different objects or properties and you want to test for the existence of a particular css class before rendering child elements. This can be done using the new in
operator. For example, consider this template that contains a classList
parameter with the value of dark-mode arrow inline
. We only want to render the svg arrow icon when the class arrow
exists in this list of classes.
<template data-sly-template.link="${@ href, text, target, classList}">
<sly data-sly-set.tab="${target == 'true' ? '_blank' : ''}"/>
<a href="${href @ context='uri'}" class="${classList}" rel="${tab == '_blank' ? 'noopener' : ''}">${text @ context='html'}
<svg data-sly-test="${'arrow' in classList}" width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.06825 0.905047C5.67772 0.514523 5.04456 0.514522 4.65403 0.905047C4.26351 1.29557 4.26351 1.92874 4.65404 2.31926L8.18912 5.85434L4.65321 9.39025C4.26269 9.78077 4.26269 10.4139 4.65321 10.8045C5.04373 11.195 5.6769 11.195 6.06742 10.8045L10.2849 6.58702C10.2937 6.57884 10.3023 6.57046 10.3109 6.5619C10.7014 6.17138 10.7014 5.53821 10.3109 5.14769L6.06825 0.905047Z" fill="currentColor"/>
</svg>
</a>
</template>
The in
relational operator also works for arrays and object maps. e.g.,
<sly data-sly-set.myArray="['one', 'two']" />
<sly data-sly-test="${'two' in myArray}">
<!--/* do something */-->
</sly>
Applying Edit Mode Only CSS Changes
Use the available ${wcmmode.edit}
and ${wcmmode.preview}
boolean variables in the HTL.
For example, you have an element that is hidden which contains a parsys. Like a confirmation banner that is shown to the user after they submit a form.
.html
<sly data-sly-test.editModeStyle="${wcmmode.edit || wcmmode.preview ? 'display:block' : ''}" />
<div class="confirmation-banner" style="${editModeStyle @ context='unsafe'}" aria-hidden="true">
...
</div>
Another use case example is to remove global CSS classes when they are hiding components with display:none;
. For example,
.css
@media screen and (min-width:768px){
.mobile-only {
display: none;
}
}
In the HTL template, if edit mode, set the ${classMobileOnly}
variable to empty. Otherwise it contains the mobile-only
class name for inclusion.
.html
<sly data-sly-test.classMobileOnly="${wcmmode.edit || wcmmode.preview ? '' : 'mobile-only'}" />
<div class="${['links-container', classMobileOnly] @ join=' '}" aria-hidden="true">
...
</div>
Note the sightly expression contains an array of css class names with a join in the class attribute value. This helps prevent extra whitespace around class names.
Background Style
Use this example HTL to create a div
with an authorable linear-gradient or solid background color.
_cq_dialog/.content.xml
...
<items jcr:primaryType="nt:unstructured">
<well
jcr:primaryType="nt:unstructured"
jcr:title="OptionsWell1"
sling:resourceType="granite/ui/components/coral/foundation/well"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<textinfo
jcr:primaryType="nt:unstructured"
sling:resourceType="/libs/granite/ui/components/coral/foundation/text"
text="Use the background color selectors below to create a linear gradient or solid color background." />
<topColor
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
fieldDescription="Optional top background color (default is transparent)"
fieldLabel="Top Background Color"
name="./bgColorTop" />
<bottomColor
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/colorfield"
fieldDescription="Optional bottom background color (default is transparent)"
fieldLabel="Bottom Background Color"
name="./bgColorBottom" />
</items>
</well>
</items>
...
.html
<sly data-sly-test="${properties.bgColorTop && !properties.bgColorBottom}"
data-sly-set.styleBgColor="background: linear-gradient(${properties.bgColorTop}, transparent);"
/>
<sly data-sly-test="${!properties.bgColorTop && properties.bgColorBottom}"
data-sly-set.styleBgColor="background: linear-gradient(transparent, ${properties.bgColorBottom});"
/>
<sly data-sly-test="${properties.bgColorTop && properties.bgColorBottom}"
data-sly-set.styleBgColor="background: linear-gradient(${properties.bgColorTop}, ${properties.bgColorBottom});"
/>
<div data-sly-test="${styleBgColor}" class="my-component-bg" style="${styleBgColor @ context='unsafe'}"></div>
This css
can be used for making the div
into a layer of the position: relative
parent element.
.css
.my-component-bg {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: -1;
}
Component Nesting
Scenario: you have components that you would like to bring into another component template. In this example, we have a header component in the content page template that has two components nested within its header
element.
For the header component, AEM won’t let you bring up an edit dialog unless the resource has a div
element wrapper instead of sly
tag. e.g.,
<header id="myapp-header">
<div data-sly-resource="${'top-nav' @ resourceType='/apps/myapp/components/structure/top-nav'}"></div>
<div data-sly-resource="${'main-nav' @ resourceType='/apps/myapp/components/structure/main-nav'}"></div>
</header>
Instead of this, which only allows you to bring up the dialog for the main-nav in author mode.
<header id="myapp-header">
<sly data-sly-resource="${'top-nav' @ resourceType='/apps/myapp/components/structure/top-nav'}"></sly>
<sly data-sly-resource="${'main-nav' @ resourceType='/apps/myapp/components/structure/main-nav'}"></sly>
</header>
Lists
These examples show how to iterate over an iterable object. Our example foobar
data object contains the following field names:
./foo
./bar
The item
identifier is used for each list item when a custom identifier is not specified. e.g.,
<ul data-sly-list="${foobar}">
<li>
<h2>${item.foo}</h2>
<div>${item.bar}</div>
</li>
</ul>
These examples show how to use an index
list item member. First, append .itemsList
to the iterable object. Then, for each item, you access the member using itemList
. e.g.,
<ul data-sly-list="${foobar.itemsList}">
<li>
<h2>${item.foo}</h2>
<div>${item.bar}</div>
<div>Index: ${itemList.index}</div>
</li>
</ul>
When a custom identifier is used, e.g., foobar
, you append List
to the identifier to access the member. e.g., foobarList.index
.
<ul data-sly-list.foobar="${foobar.itemsList}">
<li>
<h2>${foobar.foo}</h2>
<div>${foobar.bar}</div>
<pre>Members:
Index: ${foobarList.index}
Count: ${foobarList.count}
First: ${foobarList.first}
Middle: ${foobarList.middle}
Last: ${foobarList.last}
</pre>
</li>
</ul>
Sling Methods
To use SlingRequestPaths
in HTL, call request
followed by the method name with get
removed from the beginning of it and camel case. e.g.,
getRequestURI
${request.requestURI}
getRequestPathInfo
${request.requestPathInfo}
Selectors
Use the RequestPathInfo interface to access the selectors
method.
${request.requestPathInfo.selectors}
Simply access a selector in the array using its index. For example, given this URI, /a/b.s1.s2.html
, you would access the first (s1) and second (s2) selectors as follows:
<ol>
<li>${request.requestPathInfo.selectors[0]}</li>
<li>${request.requestPathInfo.selectors[1]}</li>
</ol>
You can also create a variable for the selectors. e.g.,
<sly data-sly-set.selectors="${request.requestPathInfo.selectors}"/>
<ol>
<li>${selectors[0]}</li>
<li>${selectors[1]}</li>
</ol>
Templates
This example demonstrates loading a template for a selector or selector combination, etc..
<sly data-sly-set.selectors="${request.requestPathInfo.selectors}"/>
<!--/* no selectors, e.g. directory.html */-->
<sly data-sly-test="${!selectors[0]}"
data-sly-set.template="default.html"/>
<!--/* directory.usa.html */-->
<sly data-sly-test="${selectors[0] && !selectors[1]}"
data-sly-set.template="level1.html"/>
<!--/* directory.usa.virginia.html */-->
<sly data-sly-test="${selectors[1] && !selectors[2]}"
data-sly-set.template="level2.html"/>
<!--/* directory.usa.virginia.alexandria.html */-->
<sly data-sly-test="${selectors[2] && !selectors[3]}"
data-sly-set.template="level3.html"/>
<!--/* call the template and pass the selectors parameter */-->
<sly data-sly-test="${template}"
data-sly-use.tmpl="${template}"
data-sly-call="${tmpl.directory @ selectors=selectors}"/>
Template filenames default.html
, level1.html
, level2.html
and level3.html
.
<template data-sly-template.directory="${ @ selectors}">
<!--/* template HTL */-->
<pre>
selectors: ${selectors @ context='unsafe'}
</pre>
</template>
Resources
Experience Manager HTL Help
HTML Template Language Specification