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}

Class SlingRequestPaths

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

comments powered by Disqus