HTL Tutorial #11: data-sly-element - Tag HTML Dinamici
HTL Tutorial #11: data-sly-element - Tag HTML Dinamici
Cos'è data-sly-element?
data-sly-element sostituisce il tag HTML dell'elemento con un altro tag specificato dinamicamente.
Sintassi
<div data-sly-element="${nomeTag}">
Contenuto
</div>Esempio Base
<!-- Il div diventa h2 -->
<div data-sly-element="h2">
Titolo Dinamico
</div>Output:
<h2>
Titolo Dinamico
</h2>Perché Usarlo?
1. Headings Dinamici
Livello di heading basato sulla profondità della pagina:
<div data-sly-use.page="com.example.PageModel"
data-sly-element="${page.headingLevel}">
${page.title}
</div>Java Model:
@Model(adaptables = Resource.class)
public class PageModel {
@Inject
private Page currentPage;
public String getHeadingLevel() {
int depth = currentPage.getDepth();
// Homepage: h1, Level 1: h2, Level 2: h3, etc.
int level = Math.min(depth, 6);
return "h" + level;
}
public String getTitle() {
return currentPage.getTitle();
}
}Output (depth=2):
<h2>
Pagina di Esempio
</h2>2. Liste Ordinate vs Non Ordinate
<div data-sly-element="${properties.ordered ? 'ol' : 'ul'}">
<li data-sly-list.item="${properties.items}">
${item}
</li>
</div>Se properties.ordered = true:
<ol>
<li>Primo</li>
<li>Secondo</li>
<li>Terzo</li>
</ol>Se properties.ordered = false:
<ul>
<li>Primo</li>
<li>Secondo</li>
<li>Terzo</li>
</ul>3. Semantic HTML Elements
<div data-sly-element="${properties.semantic || 'div'}">
${properties.content}
</div>L'autore può scegliere: article, section, aside, nav, main, etc.
Whitelist di Tag Sicuri
HTL ha una whitelist di tag HTML permessi per motivi di sicurezza. Se specifichi un tag non nella lista, HTL usa <div> come fallback.
Tag Permessi
a, abbr, address, article, aside, b, bdi, bdo, blockquote, br, caption,
cite, code, col, colgroup, data, dd, del, dfn, div, dl, dt, em, figcaption,
figure, footer, h1, h2, h3, h4, h5, h6, header, hr, i, ins, kbd, li, main,
mark, nav, ol, p, pre, q, rp, rt, ruby, s, samp, section, small, span,
strong, sub, sup, table, tbody, td, tfoot, th, thead, time, tr, u, ul, var,
wbrTag NON Permessi (Esempio)
<!-- BLOCCATO - script non è nella whitelist -->
<div data-sly-element="script">
alert('XSS')
</div>
<!-- Output (fallback a div): -->
<div>
alert('XSS')
</div>Questo previene XSS injection!
Pattern Comuni
1. Heading Gerarchico
<sly data-sly-template.heading="${@ level, text}">
<div data-sly-element="${'h' + level}">
${text}
</div>
</sly>
<!-- Uso -->
<sly data-sly-call="${heading @ level=1, text='Titolo Principale'}"></sly>
<sly data-sly-call="${heading @ level=2, text='Sottotitolo'}"></sly>
<sly data-sly-call="${heading @ level=3, text='Sezione'}"></sly>Output:
<h1>Titolo Principale</h1>
<h2>Sottotitolo</h2>
<h3>Sezione</h3>2. Link vs Span
<div data-sly-element="${link.url ? 'a' : 'span'}"
data-sly-attribute.href="${link.url}">
${link.text}
</div>Con URL:
<a href="/page">Clicca qui</a>Senza URL:
<span>Testo semplice</span>3. Container Semantico
<div data-sly-use.comp="com.example.ContainerModel"
data-sly-element="${comp.semanticTag}"
data-sly-attribute.class="${comp.cssClass}">
<div data-sly-list.child="${comp.children}">
${child}
</div>
</div>Model:
public class ContainerModel {
@ValueMapValue
private String containerType; // "article", "section", "aside", "div"
public String getSemanticTag() {
if (containerType != null) {
return containerType;
}
return "div"; // default
}
public String getCssClass() {
return "container-" + getSemanticTag();
}
}Combinare con Altri Statement
Con data-sly-test
<!-- Heading solo se c'è un titolo -->
<div data-sly-test="${properties.title}"
data-sly-element="${properties.headingLevel || 'h2'}"
data-sly-text="${properties.title}">
Placeholder
</div>Con data-sly-list
<div data-sly-element="${properties.listType || 'ul'}">
<li data-sly-list.item="${properties.items}">
${item}
</li>
</div>Con data-sly-attribute
<div data-sly-element="${properties.tag || 'div'}"
data-sly-attribute.id="${properties.id}"
data-sly-attribute.class="${properties.cssClass}">
${properties.content}
</div>Esempio Completo: Title Component
<div data-sly-use.title="com.example.TitleComponent"
data-sly-test="${title.text}"
data-sly-element="${title.type}"
data-sly-attribute.class="${'title ' + title.styleClass}"
data-sly-attribute.id="${title.anchorId}">
${title.text}
</div>Java Model:
@Model(adaptables = Resource.class)
public class TitleComponent {
@ValueMapValue
private String text;
@ValueMapValue
private String type; // h1, h2, h3, h4, h5, h6
@ValueMapValue
private String size; // small, medium, large
@ValueMapValue
private String anchorId;
public String getText() {
return text;
}
public String getType() {
return type != null ? type : "h2";
}
public String getStyleClass() {
String sizeClass = "";
if ("small".equals(size)) {
sizeClass = "title-sm";
} else if ("large".equals(size)) {
sizeClass = "title-lg";
}
return sizeClass;
}
public String getAnchorId() {
return anchorId;
}
}Dialog XML (_cq_dialog.xml snippet):
<type
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/select"
fieldLabel="Tipo Heading"
name="./type">
<items jcr:primaryType="nt:unstructured">
<h1 jcr:primaryType="nt:unstructured" text="H1" value="h1"/>
<h2 jcr:primaryType="nt:unstructured" text="H2" value="h2"/>
<h3 jcr:primaryType="nt:unstructured" text="H3" value="h3"/>
<h4 jcr:primaryType="nt:unstructured" text="H4" value="h4"/>
<h5 jcr:primaryType="nt:unstructured" text="H5" value="h5"/>
<h6 jcr:primaryType="nt:unstructured" text="H6" value="h6"/>
</items>
</type>Output (type=h3, size=large, anchorId=intro):
<h3 class="title title-lg" id="intro">
Introduzione al Progetto
</h3>Esempio Completo: Button Component
<div data-sly-use.button="com.example.ButtonComponent"
data-sly-element="${button.tag}"
data-sly-attribute.href="${button.href}"
data-sly-attribute.type="${button.type}"
data-sly-attribute.class="${button.classes}"
data-sly-attribute.target="${button.target}"
data-sly-attribute.rel="${button.rel}">
${button.text}
</div>Model:
public class ButtonComponent {
@ValueMapValue
private String text;
@ValueMapValue
private String link;
@ValueMapValue
private boolean openInNewTab;
@ValueMapValue
private String style; // primary, secondary, link
public String getTag() {
return link != null && !link.isEmpty() ? "a" : "button";
}
public String getHref() {
return "a".equals(getTag()) ? link : null;
}
public String getType() {
return "button".equals(getTag()) ? "button" : null;
}
public String getClasses() {
String baseClass = "btn";
String styleClass = "btn-" + (style != null ? style : "primary");
return baseClass + " " + styleClass;
}
public String getTarget() {
return openInNewTab && "a".equals(getTag()) ? "_blank" : null;
}
public String getRel() {
return openInNewTab && "a".equals(getTag()) ? "noopener noreferrer" : null;
}
public String getText() {
return text;
}
}Output (link con new tab):
<a href="/page" class="btn btn-primary" target="_blank" rel="noopener noreferrer">
Scopri di più
</a>Output (button senza link):
<button type="button" class="btn btn-primary">
Invia
</button>Best Practice
- Fornisci sempre un fallback:
${tag || 'div'} - Valida input dall'autore: Usa whitelist nel model
- Semantic HTML: Preferisci tag semantici (article, section, nav)
- Heading levels: Mantieni gerarchia corretta (h1 → h2 → h3)
- Accessibilità: Usa tag appropriati per screen readers
- Non abusare: Usa solo quando necessario
- Testing: Testa con vari valori, inclusi null/empty
Validazione Tag nel Model
private static final Set<String> ALLOWED_TAGS = Set.of(
"div", "section", "article", "aside", "nav", "main"
);
public String getSafeTag() {
if (tag != null && ALLOWED_TAGS.contains(tag.toLowerCase())) {
return tag.toLowerCase();
}
return "div"; // fallback sicuro
}Errori Comuni
❌ Tag dinamico senza fallback
<!-- RISCHIOSO - se properties.tag è null -->
<div data-sly-element="${properties.tag}">
<!-- CORRETTO - con fallback -->
<div data-sly-element="${properties.tag || 'div'}">❌ Tag non sicuro
<!-- BLOCCATO - script non permesso -->
<div data-sly-element="script">
<!-- Usa solo tag nella whitelist -->
<div data-sly-element="section">❌ Confondere con data-sly-attribute
<!-- SBAGLIATO - questo cambia attributo, non tag -->
<div data-sly-attribute.element="h2">
<!-- CORRETTO -->
<div data-sly-element="h2">Esercizi Pratici
- Heading Component: Heading con livelli h1-h6 selezionabili
- List Component: Lista ol/ul con type personalizzabile
- Container Component: Scegli tra div/section/article/aside
- Button/Link: Cambia tra e
Prossima Lezione
Nella prossima lezione vedremo data-sly-unwrap per rimuovere elementi wrapper mantenendo solo il contenuto.
Lezione #11 della serie HTL Tutorial. ← Lezione precedente | Lezione successiva →