Front-End Adventures in AEM: Part III

What a Sightly

Benjamin Solum
16 min readMar 5, 2021

In Parts I and II, I wrote about Clientlibs and then how they’re composed. In Part III, I want to get back to the markup and talk in-depth about AEM’s HTML Template Language or “HTL”, which is often referred to as Sightly. I’ll also be referring to HTL as Sightly for the rest of the series to avoid confusion with “HTML”. This part includes tips on working within Sightly, common Front-End gotchas working with Sightly, and loading our Clientlibs within our templates.

As a reminder, this article series is broken into several parts as seen below. Feel free to jump around to the parts that seem interesting to you and please feel free to ask any questions you have about the material covered.

*note* — This article series is written with AEM 6.4 and 6.5 in mind but the concepts should apply to future versions and even some older (6.2) Touch UI versions.

*note* — Adobe is notorious for moving their documentation around and their helpx docs and forums often have broken links. I’ll occasionally link to their docs so if the links stop working let me know and I’ll try and update the article.

Sightly Syntax

Sightly’s documentation can be found here and it’s definitely a must-read for the first time Sightly author. You can also download an installable REPL to play around within your local environment.

Instead of regurgitating all of the information found in the documentation, I instead want to contrast Sightly with features from other common templating frameworks/languages. Sightly is a step-up from working with JSP but it also leaves a lot to be desired in comparison to the competition.

The <sly> element

The <sly> element should be used in instances where you need to use Sightly’s Block Statements but do not want to output an HTML element. Ideally, our Sightly template’s markup will match the final DOM as much as possible. Therefore, whenever a block statement should output an HTML element, prefer that element over <sly>. <sly> does not require a closing tag; it can be self-closed.

<!-- Sets a variable "one" in scope -->
<sly data-sly-test.one="${properties.yourProp}" />
<!-- Outputs "Hello World" if the variable "one" is truthy -->
<sly data-sly-test="${one}">Hello World</sly>

Variables

If you’re familiar with JavaScript (or any programming language), you’re familiar with what a variable is. Within the context of templating, a variable is a value you’re wanting to store, typically a result of some sort of computation. For example, perhaps you’re looping through a list of items and you need to refer to the length of the list. You might store that in a variable so that you don’t need to calculate it on every loop. In JavaScript, we could do something like this:

var VARNAME = arr.length;

In AEM 6.4 and lower, we only have one option to set a variable and it doubles as a conditional:

<sly data-sly-test.VARNAME="${arr.size}" />

This can be very problematic depending on what you use data-sly-test because the result can prevent your HTML from rendering. Consider this example:

<div data-sly-test.falsy="${false}">Hello World!</div>

I may have needed the value of data-sly-test elsewhere, but now I’ve also hidden my <div>. The example above is pretty obvious, but let’s take it a step further:

<div data-sly-test.ln="${arr.size}">Hello World!</div>

In this example, if ${arr.size} evaluates to zero, the <div> will be removed.

We’ll talk more about data-sly-test in the conditionals section. The overall takeaway on AEM 6.4 and lower is that if you’re looking to set a variable and you do not intend for it to be used to render conditionally, you should set it on a separate <sly> tag (discussed later) like so:

<sly data-sly-test.ln="${arr.size}" />
<sly data-sly-test.falsy="${false}" />

Why separate? The condition will also prevent the setting of any variables succeeding it (and on any children). If the above two conditions were on the same <sly>, only the first would evaluate and the latter would be skipped.

Fortunately, this annoying issue was addressed in AEM 6.5+ with the addition of the data-sly-set statement. The above example will render “Hello World” just fine:

<span
data-sly-set.ln="${arr.size}"
data-sly-set.falsy="${false}">
Hello World
</span>

Variable Scope

For better or worse, once set, variables are available to the rest of the succeeding Sightly document (Globally Scoped) with two exceptions:

  1. Loops. Values set by the data-sly-repeat or data-sly-list statements are only available within their corresponding blocks.
  2. Templates. The data-sly-template statement (to be discussed later) creates a brand new scope so variables outside of that template do not exist. If you want to use external variables they must be passed in.

Variable Types

Just like JavaScript, variables are loosely typed in Sightly so you can store what you need in any variable.

Conditionals

There are several statements in Sightly that can do something based on a condition. This is by no means a comprehensive list, but these are definitely the most common:

  1. data-sly-test - This statement is used to conditionally render a tag and all of it’s children. Optionally, it can be used to set a variable as discussed above.
  2. data-sly-element - This statement is used to conditionally replace the element type on a tag. This is a great statement for enriching the semantic value of your components. For example, depending on certain conditions, it may make more sense for an <a> tag to become a <button> if it lacks an href value:
<a class="button" href="${href}" data-sly-element="${!href && 'button' @ context='unsafe'}">Text</a>

For security reasons, data-sly-element restricts the tags you can set to a predefined list. However, you can enable all tags, including <button>, by disabling XSS security.

3. data-sly-attribute - Similar todata-sly-element, this conditionally sets an attribute on a tag. This allows you to easily set authoring fallback values:

<div
title="Lorem Ipsum"
data-sly-attribute.title="${properties.jcr:title}">
Text
</div>

4. data-sly-unwrap - This statement acts like a display: contents, only physically removing the tag from the final render. If it’s not provided a condition, it will always apply:

<!-- Results in "Hello!" -->
<div data-sly-unwrap>Hello!</div>
<!-- Either "<div>World!</div>" or "World!" based on CONDITION -->
<div data-sly-unwrap="${CONDITION}">
World!
</div>

Math

Sightly is a step backwards in the Math department and pales in comparison to other templating languages. There’s really not a whole lot of Math we can do. For example, in Twig we can do remainder division which allows us to switch the class of a <div> depending on it’s list position:

{% for i in 0..10 %}
<div class="{{i % 2 == 0 ? 'odd' : 'even'}}">
Hello
</div>
{% endfor %}

In Sightly… we can’t even perform basic addition:

<!-- Works -->
<sly data-sly-test.i="${0}" />
<div data-attr="${i}">Hello</div>
<!-- Explodes -->
<div data-attr="${i + 1}">Hello</div>

Now to be fair, templating should not have a lot of logic in it. That should be relegated to the Sling/Use models. Sightly also provides us with variables where we can access the index (zero-based counter) or count (one-based counter) in our loops. Still, it’s not enough. This should absolutely be possible without needing to involve any sort of back end model.

<!--
Using index (zero-based counter) with Addition *should be*: <li>2</li>, <li>3</li>, <li>4</li>
-->

<ol data-sly-list="${[0,0,0]}">
<li>${itemList.index + 2}</li>
</ol>

Long story short is that you may need to massage your objects coming out of your models to get the display information and classes you need. It’s a bummer.

String Interpolation and Concatenation

String interpolation allows you to stick dynamic values within your strings. It’s a fairly fundamental feature to any templating language. In Sightly, we can interpolate using ${}. For example:

<!-- Works: <div>Hey</div> -->
<sly data-sly-test.val="Hey" />
<div>${val}</div>
<!-- Works: <div class="AHeyB"></div> -->
<div class="A${val}B"></div>

As for concatenating, it’s a little more nuanced. Ideally, we’d be able to do something like the below, however, Sightly lacks a concatenation operator:

<!-- Doesn't Work -->
<sly data-sly-test.helloval="Hello" />
<sly data-sly-test.val="${helloval + ' World'}" />
<div>${val}</div>

Instead we have three options to work around this limitation:

  1. @ join - We can pass an array of values to be joined together where we specify the delimiter:
<!-- Works: <div>Hello World</div> -->
<sly data-sly-test.helloval="Hello" />
<sly data-sly-test.val="${[helloval, 'World'] @ join=' '}" />
<div>${val}</div>

2. @ format - Format works similarly to PHP’s sprintf or Java’s String.format. We can take an array of values and then define how they fit together using their array index:

<!-- Works: <div>Hello World</div> -->
<sly data-sly-test.helloval="Hello" />
<sly data-sly-test.val="${'{0} {1}' @ format=[helloval, 'World']}" />
<div>${val}</div>

3. Interpolate - You can also simply interpolate some strings, store the result in a variable, and then spit out the result:

<!-- Works: <div>Hello World</div> -->
<sly data-sly-test.helloval="Hello" />
<sly data-sly-test.val="${helloval} World" />
<div>${val}</div>

There really isn’t a wrong option. Choose whichever makes the most sense for the problem at hand!

Comments

There are two types of Sightly comments available to us:

1. Those that persist to the document:

<!-- This comment will show up in the final HTML -->

2. Those that do not persist to the document:

<!--/* This comment will not show up in the final HTML */-->

Loops

There are two kinds of loops in Sightly and they’re more or less functionally the same with a slight twist:

1. data-sly-list - Allows us to loop through a list of items repeating all of the children elements:

<!-- <dl> does not repeat -->
<dl data-sly-list="${page.listChildren}">
<!-- <dt> and <dd> repeat -->
<dt>index: ${itemList.index}</dt>
<dd>value: ${item.title}</dd>
</dl>

2. data-sly-repeat - Allows us to loop through a list of items repeating the parent and any children:

<!-- <div> repeats -->
<div data-sly-repeat="${page.listChildren}">
<span>${item.name}</span>
</div>

Using data-sly-repeat and the <sly> tag we can also functionally duplicate thedata-sly-list statement:

<dl>
<sly data-sly-repeat="${page.listChildren}">
<dt>index: ${itemList.index}</dt>
<dd>value: ${item.title}</dd>
</sly>
</dl>

Also, as mentioned earlier, within a loop, there are additional variables that are available to us. Check out the docs for an up-to-date list, but here’s a quick reference:

item: The current item in the iteration.
itemList: Object holding the following properties:
- index: zero-based counter ( 0..length-1 ).
- count: one-based counter ( 1..length ).
- first: true if the current item is the first item.
- middle: true if the current item is neither the first nor the last item.
- last: true if the current item is the last item.
- odd: true if index is odd.
- even: true if index is even.

Defining a variable on the loop statement allows you to rename the itemList and item variables. item will become the specified variable and itemList will become *<variable>*List:

<dl data-sly-list.child="${currentPage.listChildren}">
<dt>index: ${childList.index}</dt>
<dd>value: ${child.title}</dd>
</dl>

One final note on loops. The variable created by the loop statement doesn’t seem to be available on the tag where its created. So doing the below isn’t possible:

<dl
data-sly-list.child="${currentPage.listChildren}"
data-sly-set.title="${child.title}">

Include / Resource

There are a few ways to include bits of one file into another in Sightly and they all have their particular use cases. I’ve linked to the appropriate documentation for each but will summarize the purpose of each.

1. data-sly-include - This is the most basic form of include. It simply replaces the content of the host element with the markup generated by the indicated HTML template file (.html, .jsp, etc.).

<section data-sly-include="path/to/partial.html">
I'll be replaced!
</section>

If you just want the HTML without a host element, you can use <sly>:

<sly data-sly-include="path/to/partial.html" />

Generally, you should consider using data-sly-include when you want to break up a component’s markup across several files. It should be noted that these included files will not have access to the current context so you’ll need to reestablish any use contexts in the individual pieces. More on that later!

2. data-sly-resource - A more advanced form of include. This statement includes the result of rendering the indicated resource through the sling resolution and rendering process. This allows you to include other components into your host component while still being authorable. This include has a lot of options so definitely check out the documentation for advanced usages.

<article data-sly-resource="path/to/resource"></article>

*note* - There’s a big gotcha for Front-End developers when using data-sly-resource. By default, the decoration tag is removed when resourcing another component in (more about decoration tags below). You can change this default using decorationTagName to use a new element, cssClassName to change the decoration tag’s class name, or simply set decoration=true to restore the decoration wrapper as initially set up in your component. It should also be noted that without the decoration tag, AEM won’t allow you to access the components authoring dialog in the editor.

Generally, whenever you’re needing to put one component inside of another, you’ll want use data-sly-resource with decoration set to true.

 <sly data-sly-resource="${'JCR_PATH' @ resourceType='PROJECT/components/content/TARGET_COMPONENT' cssClassName='ANY_ADDITIONAL_CLASSS' decoration=true}" />

Selectors
Selectors are bits of data passed into a request that can change the returned markup. When resourcing you can also add, remove, or replace selectors. This comes in handy when needing to swap out elements (say an <a> tag for a <button>) or replacing sections of HTML wholesale. Selectors can be accessed in Sightly with request.requestPathInfo.selectors.

<!-- Resoure Call -->
<sly data-sly-resource="COMPONENT_PATH @ selectors=['section']" />
<!--
Component: <div> by default or <section> if resourced as above
-->
<div data-sly-set.element="${request.requestPathInfo.selectors[0]}"
data-sly-element="${element}"></div>

3. data-sly-template and data-sly-call - These statements go together to form reusable pieces of template. The data-sly-template statement sets a template (which is not output by Sightly). data-sly-call can then call the defined template (optionally with parameters) which replaces the content of the host element of the call:

<sly data-sly-template.one="${ @ title}"><h1>${title}</h1></sly>
<div data-sly-call="${one @ title=properties.jcr:title}">
I'll be replaced!
</div>
<!-- <div><h1>Title</h1></div> -->

These are excellent statements for things like navigation menus where you’re likely to have nested data structures where you’ll be repeating lists of links over and over.

Another cool thing about these statements is that they’re not limited to the scope of a single file. Templates located in a different file, can be initialized with data-sly-use (more on data-sly-use later).

You can actually data-sly-use files with templates in them and call them in a completely different file:

<div
data-sly-use.prevExample="path/to/previous/example.html"
data-sly-call="${prevExample.one @ title=properties.jcr:title}">
I'll be replaced!
</div>
<!-- <div><h1>This Page's Title</h1></div> -->

*note* - As mentioned earlier, data-sly-template form’s an entirely new variable scope. You must pass all variables in as parameters!

*note* - It’s very common to see the data-sly-template statement on a <template> html tag; unlike the example above where I used <sly>. The host element for data-sly-template is never output. With native web components slowly gaining traction, we’re going to see more instances of <template> tags in the final DOM. Therefore, it seems more appropriate to use <sly> in my opinion as that indicates to the author that this tag will not be in the final DOM. However, always use whatever makes the most sense to your team!

XSS Filtering and @context

Depending on where you’re outputting your variables, you may notice that some/all of that data goes missing. This is due to AEM’s built in XSS filtering which uses AntiSamy. The configuration for AntiSamy can be found here /libs/cq/xssprotection/config.xml but do note that configuring AntiSammy is NOT for the faint of heart and it does require you to re-set all config values, not just the ones you want to change from the defaults.

You should leave XSS protection on and properly configured. However, you’ll run into cases where you need to spit out data in parts of your HTML that AntiSamy doesn’t approve. In those cases, we use @context. The link will take you to a table showing the various settings and what they do.

While it is important that you opt into the most restrictive context setting I do want to destigmatize ‘unsafe’ just a bit. AntiSammy can be quirky and you’re often filtering already sanitized sling model data from your own content authors. In these cases ‘unsafe’ is likely fine. For a recent example, on a recent project I ran into issues where AntiSammy was killing emoji characters in my css class names. Instead of re-configuring AntiSammy to allow emojis, I just used ‘unsafe’.

Parsys (Paragraph System) Component

The poorly named “Parsys” is a portmanteau of two words: “Paragraph” and “System”. This component(s), only in the loosest of terms, relates to an actual paragraph (a self-contained unit of discourse in writing) and has absolutely nothing to do with the <p>element. This caused me a great deal of confusion starting out as a Front-End AEM Developer.

To add to this confusion: There are multiple parsys components.

To add even more to this confusion: The term “parsys” colloquially refers to two components, of which, one isn’t even a “parsys”.

Let’s break this down.

First, the purpose of a parsys component is to act as a layout container. This container is an area where a content author can put additional components: buttons, accordions, carousels, you name it. As I mentioned before, it has absolutely nothing to do with any particular html element nor must it contain any text at all. It’s much more of a “Component System” than a “Paragraph System”; it’s where we drop our components!

Secondly, there are three components that can be referred to as a “parsys”:

  1. Parsys: wcm/foundation/components/parsys
  2. iParsys: wcm/foundation/components/iparsys
  3. Responsive Grid or Layout Container: wcm/foundation/components/responsivegrid

Of these three, only the Parsys and Reponsive Grid are commonly used. Of those two, when someone says “parsys” (in my circles), they’re typically referring to the Responsive Grid.

So, a parsys has really nothing to do with paragraphs or writing at the technical level, can refer to multiple components but usually the three above and to top it all off, it’s usually the component that’s not technically a “parsys”.

That all make sense? Clear as mud? You still with me?

Finally, let’s dive in to why we have three primary parsys components:

Parsys: This component acts as a drop zone container where you can add other components. A component’s markup added to this parsys will be nested within the parsys element. I do not believe there is any way to target a parsys with policies, therefore, any component (with a dialog and a category that’s not .hidden) can be added here.

<sly data-sly-resource="${'NAMESPACE' @ resourceType='wcm/foundation/components/parsys'}" /><!-- example markup, with a button component added -->
<div> <!-- Parsys Container Element; <div> by default -->
<div class="button">
<!-- BUTTON CONTENT HERE -->
</div>
</div>

iParsys: This component acts as a drop zone container where you can add other components. It will also inherit components from ancestors in the content tree given an identical structure. This component was the predecessor to Experience Fragments and was often used in static templates to ensure a consistent Header/Footer experience. I do not believe there is any way to target an iParsys with policies, therefore, any component (with a dialog and a category that’s not .hidden) can be added here.

*note* - Good use cases for this component are rare now that we have expfrags, so it should almost always be skipped in favor of the other two.

<sly data-sly-resource="${'NAMESPACE' @ resourceType='wcm/foundation/components/iparsys'}" /><!-- example markup, with a header component inherited -->
<div> <!-- iParsys Container Element; <div> by default -->
<header class="header">
<!-- HEADER CONTENT HERE -->
</header>
</div>

Responsive Grid: This component acts as a drop zone container where you can add other components. A component’s markup added to this parsys will be nested within the parsys element. This component can be targeted with policies, giving you granular control over the components that can be added to this component (as long as they have a dialog and a category that’s not .hidden). These policies apply to all nested responsivegrid components. This component also allows for responsive layouts.

*note* - More information on building responsive layouts can be found here.

<sly data-sly-resource="${'NAMESPACE' @ resourceType='wcm/foundation/components/responsivegrid'}" /><!-- example markup, with a button component added -->
<div class="aem-Grid aem-Grid--12">
<div class="button">
<!-- BUTTON CONTENT HERE -->
</div>
</div>

data-sly-use

Sometimes we need dynamic or processed data in our templates. To do that, we need the help of either Java or JavaScript (processed via Rhino) which we can then expose to our templates via data-sly-use through a variable. Generally speaking, you’ll likely be using Sling Models as they’re more performant and feature-rich than their Use API counterparts (Java, JS).

I won’t be going into detail on Sling Models or the JS/Java Use APIs (definitely refer to Adobe’s documentation) or when you should use one or the other. There are cases to be made for both and you can certainly use both simultaneously in your components. That said, I definitely want to highlight a somewhat fringe benefit when using a JS Use model: You can inject build data and/or JS libraries into your project.

How might this be used? Well, I use it most commonly to identify Webpack generated SVG Sprites in my project. If this sounds interesting to you, check out my Webpack plugin for doing exactly this! I’ve also used JS Use models for Server Side Rendering React components be using Rollup to spit out Rhino compatible versions of React DOM Server and my components and rendering the HTML within the Use model.

The need for these sorts of things are uncommon but it’s nice to know we have the option!

Sightly and Clientlibs

I covered clientlibs in detail in my first two articles but wanted to make a quick note on exactly how we might link out to them from our Sightly components/templates.

<!-- First, we need access to the clientlib markup template -->
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html" />
<!-- For both JS and CSS in a clientlib: -->
<sly data-sly-call="clientlib.all @ categories='CLIENTLIB_CATEGORY'" />
<!-- For JS only: -->
<sly data-sly-call="clientlib.js @ categories='CLIENTLIB_CATEGORY'" />
<!-- For CSS only: -->
<sly data-sly-call="clientlib.css @ categories='CLIENTLIB_CATEGORY'" />

When developing editable templates, it’s common not to use any of the above as clientlibs can be specified via policies. Unfortunately, within a policy, we cannot make the distinction of whether or not a clientlib is for authoring only. This can be done via a clientlibs category via cq.authoring.page, however, doing so will result in that clientlib being added in the authoring environment for all sites on that AEM instance.

So, when including an authoring only clientlib for only one site, you’ll often result to adding the below to one of the page components HTML files:

<sly
data-sly-test="${(wcmmode.edit || wcmmode.preview)}"
data-sly-call="clientlib.all @ categories='project.author'" />

Conclusion

There’s a ton of additional detail and nuance to the information above contained within the official documentation for HTL (referred to as Sightly throughout this article) but hopefully this article provides you with a succinct overview of what can be expected when starting out. I also highly recommend checking out the Core Components Github to see how Adobe builds out their core components. Next we’ll be looking at how to incorporate our Webpack/Rollup builds!

Next: Part IV: The Bundling [Coming Soon!]

--

--

Benjamin Solum
Benjamin Solum

Written by Benjamin Solum

Christian, husband, father, web developer, gamer, scuba diver, @Vikings fan, and aquatic biology enthusiast. Soli Deo gloria!

Responses (3)