HTL Tutorial #8: data-sly-use - Caricare Logica Java e JavaScript

HTL Tutorial #8: data-sly-use - Caricare Logica Java e JavaScript

Cos'è data-sly-use?

data-sly-use carica logica esterna (Java o JavaScript) e la rende disponibile come variabile nel template HTL.

Principio chiave: La logica complessa appartiene a Java/JS, non al template HTL!

Sintassi Base

<div data-sly-use.varName="percorso.classe.Java">
  ${varName.property}
</div>

Caricare Sling Model Java

1. Creare il Sling Model

package com.example.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(adaptables = Resource.class)
public class AuthorModel {

    @ValueMapValue
    private String name;

    @ValueMapValue
    private String bio;

    @ValueMapValue
    private String avatar;

    // Getters
    public String getName() {
        return name;
    }

    public String getBio() {
        return bio;
    }

    public String getAvatar() {
        return avatar != null ? avatar : "/images/default-avatar.png";
    }

    // Logica aggiuntiva
    public String getShortBio() {
        if (bio != null && bio.length() > 100) {
            return bio.substring(0, 100) + "...";
        }
        return bio;
    }

    public boolean hasBio() {
        return bio != null && !bio.isEmpty();
    }
}

2. Usare nel Template HTL

<div data-sly-use.author="com.example.models.AuthorModel">
  <!-- Accesso ai metodi del model -->
  <h2>${author.name}</h2>

  <img src="${author.avatar}" alt="${author.name}">

  <!-- Metodo con logica -->
  <p data-sly-test="${author.hasBio}">
    ${author.shortBio}
  </p>
</div>

Output:

<div>
  <h2>Mario Rossi</h2>
  <img src="/images/mario.jpg" alt="Mario Rossi">
  <p>Sviluppatore AEM con 10 anni di esperienza...</p>
</div>

Caricare Script JavaScript

1. Creare lo Script JS

File: /apps/mysite/components/author/author.js

"use strict";

use(function () {
    var name = properties.get("name", "");
    var bio = properties.get("bio", "");

    return {
        name: name,
        bio: bio,
        shortBio: bio.length > 100 ? bio.substring(0, 100) + "..." : bio,
        hasBio: bio && bio.length > 0,
        avatar: properties.get("avatar", "/images/default-avatar.png")
    };
});

2. Usare nel Template

<!-- Carica JS locale -->
<div data-sly-use.author="author.js">
  <h2>${author.name}</h2>
  <p>${author.shortBio}</p>
</div>

Passare Parametri

Puoi passare parametri al model/script usando l'oggetto Options:

Sintassi

<div data-sly-use.model="${'com.example.Model' @ param1='value', param2=123}">
  ${model.result}
</div>

Esempio Java

Model:

@Model(adaptables = Resource.class)
public class ProductModel {

    @Inject
    private Resource resource;

    private String category;
    private int maxResults;

    @PostConstruct
    protected void init() {
        // Recupera parametri da Options
        ValueMap props = resource.getValueMap();
        category = props.get("category", String.class);
        maxResults = props.get("maxResults", 10);
    }

    public List<Product> getProducts() {
        // Usa parametri per filtrare
        return productService.findByCategory(category, maxResults);
    }
}

HTL:

<div data-sly-use.products="${'com.example.ProductModel' @ category='electronics', maxResults=5}">
  <ul data-sly-list.product="${products.products}">
    <li>${product.name}</li>
  </ul>
</div>

Esempio JavaScript

Script products.js:

use(function () {
    var category = this.category || "all";
    var maxResults = this.maxResults || 10;

    // Usa parametri
    var products = getProductsByCategory(category, maxResults);

    return {
        products: products,
        count: products.length
    };
});

HTL:

<div data-sly-use.data="${'products.js' @ category='books', maxResults=3}">
  <p>${data.count} prodotti trovati</p>
</div>

Scope delle Variabili

Le variabili create con data-sly-use sono disponibili:

  • Nell'elemento e tutti i suoi figli
  • Non fuori dall'elemento
<div data-sly-use.model="com.example.Model">
  ${model.title} <!-- OK -->

  <p>${model.description}</p> <!-- OK -->
</div>

${model.title} <!-- ERRORE - fuori scope! -->

Use Globale (Root Element)

Per rendere disponibile in tutto il template:

<div data-sly-use.page="com.example.PageModel">
  <!-- Tutto il contenuto può usare 'page' -->

  <header>
    <h1>${page.title}</h1>
  </header>

  <main>
    <p>${page.content}</p>
  </main>

  <footer>
    <p>${page.author}</p>
  </footer>
</div>

Pattern Comuni

1. Component Model

Struttura tipica componente AEM:

/apps/mysite/components/article/
  ├── article.html       (template HTL)
  ├── ArticleModel.java  (Sling Model)
  ├── _cq_dialog.xml     (dialog)
  └── .content.xml       (metadata)

article.html:

<article data-sly-use.model="com.mysite.models.ArticleModel">
  <header>
    <h1>${model.title}</h1>
    <time datetime="${model.publishDate}">${model.formattedDate}</time>
    <p class="author">di ${model.authorName}</p>
  </header>

  <div class="content">
    ${model.content @ context='html'}
  </div>

  <footer>
    <ul class="tags" data-sly-list.tag="${model.tags}">
      <li>${tag}</li>
    </ul>
  </footer>
</article>

2. Service Injection

Inject AEM services nel Model:

@Model(adaptables = {Resource.class, SlingHttpServletRequest.class})
public class NavigationModel {

    @Inject
    private PageManager pageManager;

    @Inject
    private Page currentPage;

    @SlingObject
    private ResourceResolver resourceResolver;

    public List<NavigationItem> getNavigationItems() {
        // Usa pageManager per navigare
        Page rootPage = currentPage.getAbsoluteParent(2);
        List<NavigationItem> items = new ArrayList<>();

        Iterator<Page> children = rootPage.listChildren();
        while (children.hasNext()) {
            Page child = children.next();
            if (!child.isHideInNav()) {
                items.add(new NavigationItem(child.getTitle(), child.getPath()));
            }
        }

        return items;
    }
}

HTL:

<nav data-sly-use.nav="com.mysite.models.NavigationModel">
  <ul>
    <li data-sly-list.item="${nav.navigationItems}">
      <a href="${item.path}">${item.title}</a>
    </li>
  </ul>
</nav>

3. Multipli data-sly-use

Puoi usare multipli models nello stesso template:

<div data-sly-use.page="com.example.PageModel"
     data-sly-use.user="com.example.UserModel"
     data-sly-use.config="com.example.ConfigModel">

  <h1>${page.title}</h1>
  <p>Benvenuto, ${user.name}</p>
  <p>Lingua: ${config.locale}</p>
</div>

Differenze Java vs JavaScript

Aspetto Java (Sling Model) JavaScript
Performance ✓ Più veloce (compilato) Più lento (interpretato)
Type Safety ✓ Fortemente tipizzato Tipizzazione debole
IDE Support ✓ Ottimo (autocomplete, refactoring) Limitato
Testing ✓ JUnit test completi Più difficile
Injection ✓ @Inject, @ValueMapValue Limitato
Services ✓ Accesso a tutti i service AEM Solo resource access
Best For Logica complessa, enterprise Script semplici, prototipi

Raccomandazione: Usa Java Sling Models per progetti enterprise!

Esempio Completo: Blog Post

Java Model

@Model(adaptables = {Resource.class, SlingHttpServletRequest.class})
public class BlogPostModel {

    @Inject
    private Page currentPage;

    @ValueMapValue
    private String title;

    @ValueMapValue
    private Calendar publishDate;

    @ValueMapValue
    private String[] tags;

    @ValueMapValue
    private String content;

    public String getTitle() {
        return title != null ? title : currentPage.getTitle();
    }

    public String getFormattedDate() {
        if (publishDate == null) return "";

        SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy", Locale.ITALIAN);
        return sdf.format(publishDate.getTime());
    }

    public List<String> getTags() {
        return tags != null ? Arrays.asList(tags) : Collections.emptyList();
    }

    public String getExcerpt() {
        if (content == null) return "";

        String plainText = content.replaceAll("<[^>]*>", "");
        return plainText.length() > 200 ? plainText.substring(0, 200) + "..." : plainText;
    }

    public int getReadingTime() {
        if (content == null) return 0;

        String plainText = content.replaceAll("<[^>]*>", "");
        int wordCount = plainText.split("\\s+").length;
        return Math.max(1, wordCount / 200); // 200 words per minute
    }

    public boolean hasTags() {
        return tags != null && tags.length > 0;
    }
}

Template HTL

<article data-sly-use.post="com.mysite.models.BlogPostModel" class="blog-post">
  <!-- Header -->
  <header class="post-header">
    <h1>${post.title}</h1>

    <div class="post-meta">
      <time datetime="${post.publishDate}">${post.formattedDate}</time>
      <span class="dot"></span>
      <span>${post.readingTime} min lettura</span>
    </div>
  </header>

  <!-- Contenuto -->
  <div class="post-content">
    ${post.content @ context='html'}
  </div>

  <!-- Tags -->
  <footer data-sly-test="${post.hasTags}" class="post-footer">
    <h3>Tags:</h3>
    <ul class="tag-list">
      <li data-sly-list.tag="${post.tags}">
        <a href="/blog/tag/${tag}">${tag}</a>
      </li>
    </ul>
  </footer>
</article>

Best Practice

  1. Preferisci Java Sling Models per progetti production
  2. Separa logica da presentazione: calcoli complessi nel model
  3. Use all'inizio: carica models nel root element quando possibile
  4. Naming chiaro: author, product, config invece di model, data
  5. Injection over properties: usa @Inject invece di properties.get()
  6. Unit test dei models: testa la logica separatamente
  7. Documentazione: commenta methods pubblici
  8. Null safety: gestisci null/empty nei getters

Errori Comuni

❌ Path Java errato

<!-- SBAGLIATO - typo nel package -->
<div data-sly-use.model="com.exampel.Model">

<!-- CORRETTO -->
<div data-sly-use.model="com.example.Model">

❌ Accesso fuori scope

<div data-sly-use.model="com.example.Model">
  ${model.title}
</div>
${model.title} <!-- ERRORE! -->

❌ Non fare override di properties

// ❌ SBAGLIATO - shadow properties globale
public String getProperties() {
    return "something";
}

// ✓ CORRETTO - nome diverso
public Map<String, Object> getComponentProperties() {
    return componentProps;
}

Esercizi Pratici

  1. User Profile Model: Crea model con avatar, bio, social links
  2. Product Catalog: Model con filtering e sorting
  3. Navigation Builder: Genera menu da page tree

Prossima Lezione

Nella prossima lezione vedremo data-sly-template e data-sly-call per creare template riutilizzabili e macro in HTL.


Lezione #8 della serie HTL Tutorial. ← Lezione precedente | Lezione successiva →