Héritage et réutilisabilité des templates
Les mécanismes de réutilisation et d'héritage des templates augmenteront votre productivité, car chaque template ne contient que son contenu unique, et les éléments et structures répétés sont réutilisés. Nous introduisons trois concepts : Héritage de layout, Réutilisation horizontale et Héritage unitaire.
Le concept d'héritage de template Latte est similaire à l'héritage de classe en PHP. Vous définissez un template parent, dont d'autres templates enfants peuvent hériter et peuvent remplacer des parties du template parent. Cela fonctionne très bien lorsque les éléments partagent une structure commune. Cela semble compliqué ? Ne vous inquiétez pas, c'est très facile.
Héritage de layout {layout}
Voyons l'héritage de template de layout, c'est-à-dire la mise en page, avec un exemple. Ceci est le template parent, que nous
appellerons par exemple layout.latte
, et qui définit le squelette du document HTML :
<!doctype html>
<html lang="en">
<head>
<title>{block title}{/block}</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="content">
{block content}{/block}
</div>
<div id="footer">
{block footer}© Copyright 2008{/block}
</div>
</body>
</html>
Les balises {block}
définissent trois blocs que les templates enfants peuvent remplir. La balise block ne fait
qu'indiquer que cet emplacement peut être remplacé par un template enfant en définissant son propre bloc avec le
même nom.
Un template enfant peut ressembler à ceci :
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
La clé ici est la balise {layout}
. Elle indique à Latte que ce template “étend” un autre template. Lorsque
Latte rend ce template, il trouve d'abord le template parent – dans ce cas, layout.latte
.
À ce stade, Latte remarque les trois balises block dans layout.latte
et remplace ces blocs par le contenu du
template enfant. Étant donné que le template enfant n'a pas défini de bloc footer, le contenu du template parent est
utilisé à la place. Le contenu de la balise {block}
dans le template parent est toujours utilisé comme solution de
secours.
La sortie peut ressembler à ceci :
<!doctype html>
<html lang="en">
<head>
<title>My amazing blog</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="content">
<p>Welcome to my awesome homepage.</p>
</div>
<div id="footer">
© Copyright 2008
</div>
</body>
</html>
Dans le template enfant, les blocs ne peuvent être placés qu'au niveau supérieur ou à l'intérieur d'un autre bloc, c'est-à-dire :
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
De plus, un bloc sera toujours créé, que la condition {if}
environnante soit évaluée comme vraie ou fausse.
Donc, même si cela ne semble pas être le cas, ce template définira le bloc.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Si vous souhaitez que la sortie à l'intérieur du bloc s'affiche conditionnellement, utilisez plutôt ce qui suit :
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
L'espace en dehors des blocs dans le template enfant est exécuté avant le rendu du template de layout, vous pouvez donc
l'utiliser pour définir des variables comme {var $foo = bar}
et pour propager des données à travers toute la
chaîne d'héritage :
{layout 'layout.latte'}
{var $robots = noindex}
...
Héritage à plusieurs niveaux
Vous pouvez utiliser autant de niveaux d'héritage que nécessaire. Une manière courante d'utiliser l'héritage de layout est l'approche à trois niveaux suivante :
- Créez un template
layout.latte
qui contient le squelette principal de l'apparence du site. - Créez un template
layout-SECTIONNAME.latte
pour chaque section de votre site. Par exemple,layout-news.latte
,layout-blog.latte
, etc. Tous ces templates étendentlayout.latte
et incluent les styles & design spécifiques à chaque section. - Créez des templates individuels pour chaque type de page, par exemple un article de journal ou une entrée de blog. Ces templates étendent le template de section approprié.
Héritage dynamique
Le nom du template parent peut être une variable ou toute expression PHP, de sorte que l'héritage peut se comporter de manière dynamique :
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
Vous pouvez également utiliser l'API Latte pour sélectionner automatiquement le template de layout.
Conseils
Voici quelques conseils pour travailler avec l'héritage de layout :
- Si vous utilisez
{layout}
dans un template, ce doit être la première balise du template. - Le layout peut être recherché
automatiquement (comme par exemple dans les presenters). Dans ce cas, si le template ne
doit pas avoir de layout, il l'indique avec la balise
{layout none}
. - La balise
{layout}
a un alias{extends}
. - Le nom du fichier de layout dépend du chargeur.
- Vous pouvez avoir autant de blocs que vous le souhaitez. N'oubliez pas que les templates enfants n'ont pas besoin de définir tous les blocs parents, vous pouvez donc remplir des valeurs par défaut raisonnables dans plusieurs blocs, puis ne définir que ceux dont vous avez besoin plus tard.
Blocs {block}
Voir aussi le {block}
anonyme
Un bloc représente une manière de modifier la façon dont une certaine partie du template est rendue, mais n'interfère en aucune façon avec la logique qui l'entoure. Dans l'exemple suivant, nous montrerons comment un bloc fonctionne, mais aussi comment il ne fonctionne pas :
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
Si vous rendez ce template, le résultat sera exactement le même avec ou sans les balises {block}
. Les blocs ont
accès aux variables des portées externes. Ils donnent simplement la possibilité d'être remplacés par un template
enfant :
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Maintenant, lors du rendu du template enfant, la boucle utilisera le bloc défini dans le template enfant
child.Latte
au lieu du bloc défini dans parent.Latte
; le template exécuté est alors équivalent à ce
qui suit :
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
Cependant, si nous créons une nouvelle variable à l'intérieur d'un bloc nommé ou remplaçons la valeur d'une variable existante, le changement ne sera visible qu'à l'intérieur du bloc :
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // affiche : foo
bar: {$bar ?? 'not defined'} // affiche : not defined
Le contenu du bloc peut être modifié à l'aide de filtres. L'exemple suivant supprime tout le HTML et modifie la casse :
<title>{block title|stripHtml|capitalize}...{/block}</title>
La balise peut également être écrite comme un n:attribut :
<article n:block=post>
...
</article>
Blocs locaux
Chaque bloc remplace le contenu du bloc parent du même nom – à l'exception des blocs locaux. Dans les classes, ce serait quelque chose comme des méthodes privées. Vous pouvez ainsi créer un template sans craindre que, en raison de la correspondance des noms de blocs, ils soient remplacés par un autre template.
{block local helper}
...
{/block}
Rendu des blocs {include}
Voir aussi {include file}
Pour afficher un bloc à un endroit spécifique, utilisez la balise {include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
Il est également possible d'afficher un bloc d'un autre template :
{include footer from 'main.latte'}
Le bloc rendu n'a pas accès aux variables du contexte actif, sauf si le bloc est défini dans le même fichier où il est inclus. Cependant, il a accès aux variables globales.
Vous pouvez passer des variables au bloc de cette manière :
{include footer, foo: bar, id: 123}
Le nom du bloc peut être une variable ou toute expression PHP. Dans ce cas, nous ajoutons le mot-clé block
avant
la variable pour que Latte sache déjà au moment de la compilation qu'il s'agit d'un bloc et non d'une inclusion de template, dont le nom pourrait également être dans une
variable :
{var $name = footer}
{include block $name}
Un bloc peut être rendu à l'intérieur de lui-même, ce qui est utile par exemple pour rendre une structure arborescente :
{define menu, $items}
<ul>
{foreach $items as $item}
<li>
{if is_array($item)}
{include menu, $item}
{else}
{$item}
{/if}
</li>
{/foreach}
</ul>
{/define}
Au lieu de {include menu, ...}
, nous pouvons alors écrire {include this, ...}
, où this
signifie le bloc actuel.
Le bloc rendu peut être modifié à l'aide de filtres. L'exemple suivant supprime tout le HTML et modifie la casse :
<title>{include heading|stripHtml|capitalize}</title>
Bloc parent
Si vous avez besoin d'afficher le contenu d'un bloc du template parent, utilisez {include parent}
. C'est utile si
vous souhaitez simplement compléter le contenu du bloc parent au lieu de le remplacer complètement.
{block footer}
{include parent}
<a href="https://github.com/nette">GitHub</a>
<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
Définitions {define}
En plus des blocs, Latte propose également des “définitions”. Dans les langages de programmation courants, nous les comparerions à des fonctions. Elles sont utiles pour réutiliser des fragments de template afin de ne pas vous répéter.
Latte essaie de simplifier les choses, donc en gros, les définitions sont identiques aux blocs et tout ce qui est dit sur les blocs s'applique également aux définitions. Elles diffèrent des blocs en ce que :
- elles sont enfermées dans des balises
{define}
- elles ne sont rendues que lorsque vous les incluez via
{include}
- on peut leur définir des paramètres de la même manière que les fonctions en PHP
{block foo}<p>Hello</p>{/block}
{* affiche : <p>Hello</p> *}
{define bar}<p>World</p>{/define}
{* n'affiche rien *}
{include bar}
{* affiche : <p>World</p> *}
Imaginez que vous ayez un template d'aide avec une collection de définitions sur la façon de dessiner des formulaires HTML.
{define input, $name, $value, $type = 'text'}
<input type={$type} name={$name} value={$value}>
{/define}
{define textarea, $name, $value}
<textarea name={$name}>{$value}</textarea>
{/define}
Les arguments sont toujours facultatifs avec une valeur par défaut de null
, sauf si une valeur par défaut est
spécifiée (ici 'text'
est la valeur par défaut pour $type
). Les types de paramètres peuvent
également être déclarés : {define input, string $name, ...}
.
Nous chargeons le template avec les définitions à l'aide de {import}
. Les définitions elles-mêmes sont rendues de la même manière que les blocs :
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
Les définitions n'ont pas accès aux variables du contexte actif, mais elles ont accès aux variables globales.
Noms de blocs dynamiques
Latte permet une grande flexibilité dans la définition des blocs, car le nom du bloc peut être n'importe quelle expression
PHP. Cet exemple définit trois blocs nommés hi-Peter
, hi-John
et hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
Dans le template enfant, nous pouvons alors redéfinir, par exemple, un seul bloc :
{block hi-John}Hello. I am {$name}.{/block}
Ainsi, la sortie ressemblera à ceci :
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Vérification de l'existence des blocs {ifset}
Voir aussi {ifset $var}
À l'aide du test {ifset blockname}
, nous vérifions si un bloc (ou plusieurs blocs) existe dans le contexte
actuel :
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Le nom du bloc peut être une variable ou toute expression PHP. Dans ce cas, nous ajoutons le mot-clé block
avant
la variable pour qu'il soit clair qu'il ne s'agit pas d'un test d'existence de variables :
{ifset block $name}
...
{/ifset}
L'existence des blocs est également vérifiée par la fonction hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Conseils
Quelques conseils pour travailler avec les blocs :
- Le dernier bloc de niveau supérieur n'a pas besoin d'avoir une balise de fermeture (le bloc se termine à la fin du document). Cela simplifie l'écriture des templates enfants qui contiennent un bloc principal unique.
- Pour une meilleure lisibilité, vous pouvez indiquer le nom du bloc dans la balise
{/block}
, par exemple{/block footer}
. Cependant, le nom doit correspondre au nom du bloc. Dans les templates plus volumineux, cette technique vous aidera à voir quelles balises de bloc se ferment. - Vous ne pouvez pas définir directement plusieurs balises de bloc avec le même nom dans le même template. Cependant, cela peut être réalisé en utilisant des noms de blocs dynamiques.
- Vous pouvez utiliser des n:attributs pour définir des blocs
comme
<h1 n:block=title>Welcome to my awesome homepage</h1>
- Les blocs peuvent également être utilisés sans nom uniquement pour appliquer des filtres :
{block|strip} hello {/block}
Réutilisation horizontale {import}
La réutilisation horizontale est le troisième mécanisme de réutilisation et d'héritage dans Latte. Elle permet de charger
des blocs à partir d'autres templates. C'est similaire à la création d'un fichier avec des fonctions d'aide en PHP, que nous
chargeons ensuite à l'aide de require
.
Bien que l'héritage de layout de template soit l'une des fonctionnalités les plus puissantes de Latte, il est limité à l'héritage simple – un template ne peut étendre qu'un seul autre template. La réutilisation horizontale est un moyen d'atteindre l'héritage multiple.
Ayons un fichier avec des définitions de blocs :
{block sidebar}...{/block}
{block menu}...{/block}
À l'aide de la commande {import}
, nous importons tous les blocs et définitions
définis dans blocks.latte
dans un autre template :
{import 'blocks.latte'}
{* maintenant les blocs sidebar et menu peuvent être utilisés *}
Si vous importez des blocs dans le template parent (c'est-à-dire que vous utilisez {import}
dans
layout.latte
), les blocs seront également disponibles dans tous les templates enfants, ce qui est très
pratique.
Le template destiné à être importé (par exemple blocks.latte
) ne doit pas étendre un autre template, c'est-à-dire utiliser {layout}
. Cependant, il peut
importer d'autres templates.
La balise {import}
doit être la première balise du template après {layout}
. Le nom du template
peut être n'importe quelle expression PHP :
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
Vous pouvez utiliser autant de commandes {import}
que vous le souhaitez dans un template. Si deux templates
importés définissent le même bloc, le premier l'emporte. Cependant, le template principal a la priorité la plus élevée et
peut remplacer n'importe quel bloc importé.
Le contenu des blocs remplacés peut être préservé en insérant le bloc de la même manière que le bloc parent est inséré :
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
Dans cet exemple, {include parent}
appelle le bloc sidebar
du template blocks.latte
.
Héritage unitaire {embed}
L'héritage unitaire étend l'idée de l'héritage de layout au niveau des fragments de contenu. Alors que l'héritage de layout fonctionne avec le “squelette du document”, qui est animé par les templates enfants, l'héritage unitaire vous permet de créer des squelettes pour des unités de contenu plus petites et de les réutiliser où vous le souhaitez.
Dans l'héritage unitaire, la clé est la balise {embed}
. Elle combine le comportement de {include}
et {layout}
. Elle permet d'insérer le contenu d'un autre template ou bloc et de passer éventuellement des
variables, tout comme avec {include}
. Elle permet également de remplacer n'importe quel bloc défini à l'intérieur
du template inséré, comme lors de l'utilisation de {layout}
.
Par exemple, utilisons un élément accordéon. Regardons le squelette de l'élément stocké dans le template
collapsible.latte
:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
Les balises {block}
définissent deux blocs que les templates enfants peuvent remplir. Oui, comme dans le cas du
template parent dans l'héritage de layout. Vous voyez également la variable $modifierClass
.
Utilisons notre élément dans un template. C'est là qu'intervient {embed}
. C'est une balise extrêmement
puissante qui nous permet de faire toutes ces choses : insérer le contenu du template de l'élément, y ajouter des variables et
y ajouter des blocs avec notre propre HTML :
{embed 'collapsible.latte', modifierClass: my-style}
{block title}
Hello World
{/block}
{block content}
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
{/block}
{/embed}
La sortie peut ressembler à ceci :
<section class="collapsible my-style">
<h4 class="collapsible__title">
Hello World
</h4>
<div class="collapsible__content">
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Nunc dapibus tortor vel mi dapibus sollicitudin.</p>
</div>
</section>
Les blocs à l'intérieur des balises insérées forment une couche distincte indépendante des autres blocs. Par conséquent,
ils peuvent avoir le même nom qu'un bloc en dehors de l'insertion et ne sont en aucun cas affectés. En utilisant la balise include à l'intérieur des balises {embed}
, vous pouvez insérer les blocs créés
ici, les blocs du template inséré (qui ne sont pas locaux) ainsi que les blocs du
template principal qui, au contraire, sont locaux. Vous pouvez également importer
des blocs à partir d'autres fichiers :
{block outer}…{/block}
{block local hello}…{/block}
{embed 'collapsible.latte', modifierClass: my-style}
{import 'blocks.latte'}
{block inner}…{/block}
{block title}
{include inner} {* fonctionne, le bloc est défini à l'intérieur de embed *}
{include hello} {* fonctionne, le bloc est local dans ce template *}
{include content} {* fonctionne, le bloc est défini dans le template inséré *}
{include aBlockDefinedInImportedTemplate} {* fonctionne *}
{include outer} {* ne fonctionne pas ! - le bloc est dans la couche externe *}
{/block}
{/embed}
Les templates insérés n'ont pas accès aux variables du contexte actif, mais ils ont accès aux variables globales.
Avec {embed}
, on peut insérer non seulement des templates, mais aussi d'autres blocs, et donc l'exemple
précédent pourrait être écrit de cette manière :
{define collapsible}
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
...
</section>
{/define}
{embed collapsible, modifierClass: my-style}
{block title}
Hello World
{/block}
...
{/embed}
Si nous passons une expression à {embed}
et qu'il n'est pas clair s'il s'agit d'un nom de bloc ou de fichier,
nous ajoutons le mot-clé block
ou file
:
{embed block $name} ... {/embed}
Cas d'utilisation
Dans Latte, il existe différents types d'héritage et de réutilisation de code. Résumons les concepts principaux pour une meilleure clarté :
{include template}
Cas d'utilisation: Utilisation de header.latte
et footer.latte
à l'intérieur de
layout.latte
.
header.latte
<nav>
<div>Home</div>
<div>About</div>
</nav>
footer.latte
<footer>
<div>Copyright</div>
</footer>
layout.latte
{include 'header.latte'}
<main>{block main}{/block}</main>
{include 'footer.latte'}
{layout}
Cas d'utilisation: Extension de layout.latte
à l'intérieur de homepage.latte
et
about.latte
.
layout.latte
{include 'header.latte'}
<main>{block main}{/block}</main>
{include 'footer.latte'}
homepage.latte
{layout 'layout.latte'}
{block main}
<p>Homepage</p>
{/block}
about.latte
{layout 'layout.latte'}
{block main}
<p>About page</p>
{/block}
{import}
Cas d'utilisation: sidebar.latte
dans single.product.latte
et
single.service.latte
.
sidebar.latte
{block sidebar}<aside>This is sidebar</aside>{/block}
single.product.latte
{layout 'product.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Product page</main>{/block}
single.service.latte
{layout 'service.layout.latte'}
{import 'sidebar.latte'}
{block main}<main>Service page</main>{/block}
{define}
Cas d'utilisation: Fonctions auxquelles on passe des variables et qui affichent quelque chose.
form.latte
{define form-input, $name, $value, $type = 'text'}
<input type={$type} name={$name} value={$value}>
{/define}
profile.service.latte
{import 'form.latte'}
<form action="" method="post">
<div>{include form-input, username}</div>
<div>{include form-input, password}</div>
<div>{include form-input, submit, Submit, submit}</div>
</form>
{embed}
Cas d'utilisation: Insertion de pagination.latte
dans product.table.latte
et
service.table.latte
.
pagination.latte
<div id="pagination">
<div>{block first}{/block}</div>
{for $i = $min + 1; $i < $max - 1; $i++}
<div>{$i}</div>
{/for}
<div>{block last}{/block}</div>
</div>
product.table.latte
{embed 'pagination.latte', min: 1, max: $products->count}
{block first}First Product Page{/block}
{block last}Last Product Page{/block}
{/embed}
service.table.latte
{embed 'pagination.latte', min: 1, max: $services->count}
{block first}First Service Page{/block}
{block last}Last Service Page{/block}
{/embed}