HTL Tutorial #7: data-sly-list - Iterating Over Arrays and Collections
HTL Tutorial #7: data-sly-list - Iterating Over Arrays and Collections
What is data-sly-list?
data-sly-list iterates over arrays, lists, or collections and repeats the element for each item.
Basic Syntax
<element data-sly-list="${array}">
<!-- This element is repeated for each item -->
</element>With Variable Name
<element data-sly-list.itemName="${array}">
${itemName}
</element>Simple Example
<!-- Array of strings -->
<ul data-sly-list.tag="${properties.tags}">
<li>${tag}</li>
</ul>Input: properties.tags = ["HTML", "CSS", "JavaScript"]
Output:
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
</ul>Without Variable Name
If you don't specify a name, HTL automatically uses item:
<!-- Without name -->
<ul data-sly-list="${properties.tags}">
<li>${item}</li>
</ul>
<!-- Equivalent to -->
<ul data-sly-list.item="${properties.tags}">
<li>${item}</li>
</ul>Iterating Over Arrays of Objects
Example: Product List
<div class="products" data-sly-use.model="com.example.ProductsModel">
<div data-sly-list.product="${model.products}" class="product-card">
<h3>${product.name}</h3>
<p class="price">€ ${product.price}</p>
<p>${product.description}</p>
<button>Add to Cart</button>
</div>
</div>Java Model (example):
public class ProductsModel {
public List<Product> getProducts() {
return Arrays.asList(
new Product("Laptop", 999.00, "Powerful laptop"),
new Product("Mouse", 29.99, "Wireless mouse"),
new Product("Keyboard", 79.99, "Mechanical keyboard")
);
}
}Output:
<div class="products">
<div class="product-card">
<h3>Laptop</h3>
<p class="price">€ 999.0</p>
<p>Powerful laptop</p>
<button>Add to Cart</button>
</div>
<div class="product-card">
<h3>Mouse</h3>
<p class="price">€ 29.99</p>
<p>Wireless mouse</p>
<button>Add to Cart</button>
</div>
<div class="product-card">
<h3>Keyboard</h3>
<p class="price">€ 79.99</p>
<p>Mechanical keyboard</p>
<button>Add to Cart</button>
</div>
</div>The itemList Variable
HTL automatically provides the itemList variable with iteration information:
<ul data-sly-list.item="${properties.items}">
<li>
${item}
(${itemList.index + 1}/${itemList.count})
</li>
</ul>itemList Properties
| Property | Description | Type |
|---|---|---|
index |
Current index (starts from 0) | Number |
count |
Total number of elements | Number |
first |
True if it's the first element | Boolean |
last |
True if it's the last element | Boolean |
odd |
True if index is odd | Boolean |
even |
True if index is even | Boolean |
middle |
True if NOT first nor last | Boolean |
Complete example:
<ul data-sly-list.item="${properties.navItems}">
<li class="${itemList.first ? 'first' : ''}
${itemList.last ? 'last' : ''}
${itemList.odd ? 'odd' : 'even'}">
${itemList.index + 1}. ${item.title}
</li>
</ul>Common Patterns
1. List with Index
<ol data-sly-list.step="${properties.steps}">
<li>
<strong>Step ${stepList.index + 1}:</strong>
${step}
</li>
</ol>2. Zebra Striping (Alternating Rows)
<table>
<tbody data-sly-list.row="${model.data}">
<tr class="${rowList.odd ? 'bg-light' : 'bg-white'}">
<td>${row.name}</td>
<td>${row.value}</td>
</tr>
</tbody>
</table>3. Different First and Last
<nav>
<ul data-sly-list.page="${model.pages}">
<li>
<a href="${page.url}"
class="${pageList.first ? 'home-link' : ''}
${pageList.last ? 'last-link' : ''}">
${page.title}
</a>
</li>
</ul>
</nav>4. Separators Between Elements
<div class="breadcrumb">
<span data-sly-list.crumb="${model.breadcrumbs}">
<a href="${crumb.url}">${crumb.title}</a>
<span data-sly-test="${!crumbList.last}"> > </span>
</span>
</div>Output: Home > Products > Category > Item
5. First N Elements
<!-- Show only the first 5 -->
<ul data-sly-list.item="${properties.items}">
<li data-sly-test="${itemList.index < 5}">
${item}
</li>
</ul>Nested Lists
You can nest data-sly-list for complex structures:
<div data-sly-use.nav="com.example.NavigationModel">
<!-- Main menu -->
<nav>
<ul data-sly-list.section="${nav.sections}">
<li>
<h3>${section.title}</h3>
<!-- Submenu -->
<ul data-sly-list.item="${section.items}">
<li>
<a href="${item.url}">${item.title}</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>Combining with data-sly-test
Conditional List
<!-- Show list only if it has elements -->
<ul data-sly-test="${properties.tags}"
data-sly-list.tag="${properties.tags}">
<li>${tag}</li>
</ul>
<!-- Message if empty -->
<p data-sly-test="${!properties.tags}">
No tags available
</p>Filtering Elements
<ul data-sly-list.user="${model.users}">
<!-- Show only active users -->
<li data-sly-test="${user.active}">
${user.name} (${user.email})
</li>
</ul>Complete Practical Examples
Example 1: Image Gallery
<div class="gallery" data-sly-use.gallery="com.example.GalleryModel">
<div class="grid">
<div data-sly-list.image="${gallery.images}" class="grid-item">
<img src="${image.url}"
alt="${image.alt}"
loading="${imageList.first ? 'eager' : 'lazy'}">
<div class="caption">
<h4>${image.title}</h4>
<p>${image.description}</p>
<small>Image ${imageList.index + 1} of ${imageList.count}</small>
</div>
</div>
</div>
</div>Example 2: Events Timeline
<div class="timeline" data-sly-use.events="com.example.EventsModel">
<div data-sly-list.event="${events.items}"
class="timeline-item ${eventList.odd ? 'left' : 'right'}">
<!-- Timeline marker -->
<div class="timeline-marker ${eventList.first ? 'first' : ''}
${eventList.last ? 'last' : ''}">
${eventList.index + 1}
</div>
<!-- Event content -->
<div class="timeline-content">
<h3>${event.title}</h3>
<time datetime="${event.date}">${event.formattedDate}</time>
<p>${event.description}</p>
<!-- Badge for first event -->
<span data-sly-test="${eventList.first}" class="badge-latest">
Latest Event
</span>
</div>
</div>
</div>Example 3: Table with Total
<table data-sly-use.report="com.example.ReportModel">
<thead>
<tr>
<th>#</th>
<th>Description</th>
<th>Quantity</th>
<th>Price</th>
</tr>
</thead>
<tbody>
<tr data-sly-list.item="${report.items}">
<td>${itemList.index + 1}</td>
<td>${item.description}</td>
<td>${item.quantity}</td>
<td>€ ${item.price}</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3"><strong>Total</strong></td>
<td><strong>€ ${report.total}</strong></td>
</tr>
</tfoot>
</table>Example 4: Responsive Card Grid
<div class="card-grid" data-sly-use.blog="com.example.BlogModel">
<article data-sly-list.post="${blog.recentPosts}"
class="card ${postList.first ? 'featured' : ''}">
<!-- Image -->
<div class="card-image">
<img src="${post.thumbnail}"
alt="${post.title}"
loading="${postList.index < 3 ? 'eager' : 'lazy'}">
<!-- Featured badge only for the first -->
<span data-sly-test="${postList.first}" class="badge-featured">
Featured
</span>
</div>
<!-- Content -->
<div class="card-body">
<h3>${post.title}</h3>
<p class="excerpt">${post.excerpt}</p>
<!-- Meta info -->
<div class="meta">
<time datetime="${post.date}">${post.formattedDate}</time>
<span class="dot">•</span>
<span>${post.readTime} min read</span>
</div>
<a href="${post.url}" class="btn">Read More</a>
</div>
</article>
</div>data-sly-list vs data-sly-repeat
HTL has two statements for iteration:
data-sly-list: Simpler, usesitemListdata-sly-repeat: More detailed, we'll see it in the next lesson
For most cases, data-sly-list is sufficient!
Empty Lists
If the array is empty/null, the element is NOT rendered:
<ul data-sly-list.tag="${properties.tags}">
<li>${tag}</li>
</ul>
<!-- If tags is empty: NO output (not even <ul>) -->Handling empty lists:
<div data-sly-test.hasTags="${properties.tags}">
<h3>Tags:</h3>
<ul data-sly-list.tag="${properties.tags}">
<li>${tag}</li>
</ul>
</div>
<p data-sly-test="${!hasTags}">
No tags available
</p>Best Practices
- Use descriptive variable names:
product,user,imageinstead ofitem - Leverage
itemList.first/lastfor special styling - Combine with
data-sly-testfor filtering - Lazy loading for non-immediate images:
${!imageList.first} - Handle empty lists with separate
data-sly-test - Avoid complex logic in loops - move it to Sling Model
Common Mistakes
❌ Modifying the array during iteration
<!-- NOT supported - read-only! -->
<ul data-sly-list.item="${items}">
<li>${item}</li>
</ul>❌ Using variable outside scope
<ul data-sly-list.tag="${properties.tags}">
<li>${tag}</li>
</ul>
${tag} <!-- ERROR - out of scope! -->Practical Exercises
- Navigation Menu: List with different first/last page
- Product Grid: 3 columns with zebra striping
- Comments: Comment list with separators
- FAQ Accordion: Questions/answers with first 3 elements open by default
Next Lesson
In the next lesson we'll discover data-sly-repeat, the advanced version of list with more iteration information available.
Lesson #7 of the HTL Tutorial series. ← Previous lesson | Next lesson →