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}"> &gt; </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, uses itemList
  • data-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

  1. Use descriptive variable names: product, user, image instead of item
  2. Leverage itemList.first/last for special styling
  3. Combine with data-sly-test for filtering
  4. Lazy loading for non-immediate images: ${!imageList.first}
  5. Handle empty lists with separate data-sly-test
  6. 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

  1. Navigation Menu: List with different first/last page
  2. Product Grid: 3 columns with zebra striping
  3. Comments: Comment list with separators
  4. 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 →