Héritage et réutilisabilité des modèles
Les mécanismes de réutilisation et d'héritage des modèles sont là pour booster votre productivité car chaque modèle ne contient que son contenu unique et les éléments et structures répétés sont réutilisés. Nous présentons trois concepts : l'héritage des modèles, la réutilisation horizontale et l'héritage des unités.
Le concept d'héritage des modèles Latte est similaire à l'héritage des classes PHP. Vous définissez un modèle parent à partir duquel les autres modèles enfants peuvent s'étendre et remplacer certaines parties du modèle parent. Ce concept fonctionne parfaitement lorsque les éléments partagent une structure commune. Cela vous semble compliqué ? Ne vous inquiétez pas, ce n'est pas le cas.
Héritage de la mise en page {layout}
Examinons l'héritage des modèles de mise en page en commençant par un exemple. Il s'agit d'un modèle parent que nous
appellerons par exemple layout.latte
et qui définit un squelette de 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>
La balise {block}
définit trois blocs que les modèles enfants peuvent remplir. La balise block indique au moteur
de modèle qu'un modèle enfant peut remplacer ces parties du modèle en définissant son propre bloc du même nom.
Un modèle enfant peut ressembler à ceci :
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
La balise {layout}
est la clé ici. Elle indique au moteur de modèle que ce modèle “étend” un autre
modèle. Lorsque Latte rend ce modèle, il localise d'abord le parent – dans ce cas, layout.latte
.
À ce stade, le moteur de modèle remarque les trois balises de bloc dans layout.latte
et remplace ces blocs par
le contenu du modèle enfant. Notez que, comme le modèle enfant n'a pas défini le bloc footer, le contenu du modèle
parent est utilisé à la place. Le contenu d'une balise {block}
dans un modèle parent est toujours utilisé comme
solution de repli.
Le résultat 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 un modèle enfant, les blocs ne peuvent être situés qu'au niveau supérieur ou à l'intérieur d'un autre bloc, par exemple :
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
De plus, un bloc sera toujours créé dans le modèle, que la condition environnante {if}
soit évaluée comme
étant vraie ou fausse. Contrairement à ce que vous pourriez penser, ce modèle définit bien un bloc.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Si vous souhaitez que la sortie à l'intérieur du bloc soit affichée de manière conditionnelle, utilisez plutôt ce qui suit :
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
Les données situées à l'extérieur d'un bloc dans un modèle enfant sont exécutées avant que le modèle de présentation
ne soit rendu. Vous pouvez donc l'utiliser pour définir des variables telles que {var $foo = bar}
et propager les
données à l'ensemble de la chaîne d'héritage :
{layout 'layout.latte'}
{var $robots = noindex}
...
Héritage multi-niveaux
Vous pouvez utiliser autant de niveaux d'héritage que nécessaire. Une façon courante d'utiliser l'héritage de mise en page est l'approche à trois niveaux suivante :
- Créez un modèle
layout.latte
qui contient l'aspect et la convivialité principaux de votre site. - Créez un modèle
layout-SECTIONNAME.latte
pour chaque section de votre site. Par exemple,layout-news.latte
,layout-blog.latte
etc. Ces modèles étendent touslayout.latte
et incluent des styles/conceptions spécifiques à chaque section. - Créez des modèles individuels pour chaque type de page, par exemple un article d'actualité ou une entrée de blog. Ces modèles étendent le modèle de section approprié.
Héritage dynamique de la mise en page
Vous pouvez utiliser une variable ou toute expression PHP comme nom du modèle parent, 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 choisir automatiquement le modèle de mise en page.
Conseils
Voici quelques conseils pour travailler avec l'héritage de mise en page :
- Si vous utilisez
{layout}
dans un modèle, il doit s'agir de la première balise de modèle de ce modèle. - La mise en page peut être recherchée
automatiquement (comme dans les présentateurs). Dans ce cas, si le modèle ne doit
pas avoir de mise en page, il l'indiquera avec la balise
{layout none}
. - La balise
{layout}
a un alias{extends}
. - Le nom de fichier du modèle étendu dépend du chargeur de modèle.
- Vous pouvez avoir autant de blocs que vous le souhaitez. Rappelez-vous que les modèles enfants n'ont pas à définir tous les blocs parents, vous pouvez donc remplir des valeurs par défaut raisonnables dans un certain nombre de blocs, puis ne définir que ceux dont vous avez besoin plus tard.
Blocs {block}
Voir aussi anonyme {block}
Un bloc permet de modifier la façon dont une certaine partie d'un modèle est rendue, mais il n'interfère en aucune façon avec la logique qui l'entoure. Prenons l'exemple suivant pour illustrer comment un bloc fonctionne et, surtout, 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 modèle, le résultat sera exactement le même avec ou sans les balises de bloc. Les blocs ont accès aux variables des scopes externes. Il s'agit simplement d'un moyen de les rendre surmontables par un modèle enfant :
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Maintenant, lors du rendu du modèle enfant, la boucle va utiliser le bloc défini dans le modèle enfant
child.Latte
au lieu de celui défini dans le modèle de base parent.Latte
; le modèle exécuté est
alors équivalent au modèle suivant :
{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 si nous remplaçons une valeur d'une variable existante, le changement sera visible uniquement à l'intérieur du bloc :
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined
Le contenu du bloc peut être modifié par des filtres. L'exemple suivant supprime tout le HTML et met le titre en majuscule :
<title>{block title|stripHtml|capitalize}...{/block}</title>
La balise peut aussi être écrite comme n:attribute:
<article n:block=post>
...
</article>
Blocs locaux
Chaque bloc remplace le contenu du bloc parent du même nom. Sauf pour les blocs locaux. Ils sont un peu comme les méthodes privées d'une classe. Vous pouvez créer un modèle sans craindre que – en raison de la coïncidence des noms des blocs – ils soient écrasés par le second modèle.
{block local helper}
...
{/block}
Blocs d'impression {include}
Voir aussi {include file}
Pour imprimer un bloc à un endroit précis, utilisez la balise {include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
Vous pouvez également afficher le bloc à partir d'un autre modèle :
{include footer from 'main.latte'}
Les blocs imprimés n'ont 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, ils ont accès aux variables globales.
Vous pouvez transmettre des variables au bloc de la manière suivante :
{include footer, foo: bar, id: 123}
Vous pouvez utiliser une variable ou toute expression en PHP comme nom de bloc. Dans ce cas, ajoutez le mot-clé
block
devant la variable, afin que l'on sache à la compilation qu'il s'agit d'un bloc, et non d'un modèle d'insertion, dont le nom pourrait également se trouver dans la
variable :
{var $name = footer}
{include block $name}
Le bloc peut également être imprimé à l'intérieur de lui-même, ce qui est utile, par exemple, lors du rendu d'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 également écrire {include this, ...}
où
this
signifie le bloc actuel.
Le contenu imprimé peut être modifié par des filtres. L'exemple suivant supprime tout le HTML et met le titre en majuscule :
<title>{include heading|stripHtml|capitalize}</title>
Bloc parent
Si vous devez imprimer le contenu du bloc à partir du modèle parent, l'instruction {include parent}
fera
l'affaire. Elle est utile si vous souhaitez compléter le contenu d'un 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, il existe également des “définitions” dans Latte. Elles sont comparables aux fonctions dans les langages de programmation ordinaires. Elles sont utiles pour réutiliser des fragments de modèles afin de ne pas se répéter.
Latte essaie de garder les choses simples, donc les définitions sont les mêmes que les blocs, et tout ce qui est dit à propos des blocs s'applique également aux définitions. Elles diffèrent des blocs en ce sens que
- elles sont entourées de balises
{define}
- elles ne sont rendues que lorsqu'elles sont insérées par l'intermédiaire d'une balise
{include}
- elles peuvent être paramétrées comme des fonctions en PHP
{block foo}<p>Hello</p>{/block}
{* prints: <p>Hello</p> *}
{define bar}<p>World</p>{/define}
{* prints nothing *}
{include bar}
{* prints: <p>World</p> *}
Imaginez que vous disposiez d'un modèle d'aide contenant un ensemble de définitions sur la manière 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 d'une définition sont toujours facultatifs et ont une valeur par défaut : null
, sauf si une valeur
par défaut est spécifiée (ici, 'text'
est la valeur par défaut de $type
). Les types de paramètres
peuvent également être déclarés : {define input, string $name, ...}
.
Le modèle contenant les définitions est chargé à l'aide de la commande {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 [Pierre, Jean, Marie] as $name}
{block "hi-$name"}Hi, je suis {$name}.{/block}
{/foreach}
Par exemple, nous ne pouvons redéfinir qu'un seul bloc dans un modèle enfant :
{block hi-Jean}Bonjour. Je suis {$name}.{/block}
Ainsi, la sortie ressemblera à ceci :
Hi, I am Pierre.
Hello. I am Jean.
Hi, I am Marie.
Checking Block Existence {ifset}
Voir aussi {ifset $var}
Utilisez le test {ifset blockname}
pour vérifier si un bloc (ou plusieurs blocs) existe dans le contexte
actuel :
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Vous pouvez utiliser une variable ou toute expression en PHP comme nom de bloc. Dans ce cas, ajoutez le mot-clé
block
devant la variable pour indiquer clairement que ce n'est pas la variable qui est vérifiée :
{ifset block $name}
...
{/ifset}
L'existence de blocs est également renvoyée par la fonction hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Conseils
Voici quelques conseils pour travailler avec des blocs :
- Le dernier bloc de premier niveau n'a pas besoin d'avoir de balise de fermeture (le bloc se termine à la fin du document). Cela simplifie l'écriture des modèles enfants, qui n'ont qu'un seul bloc primaire.
- Pour plus de lisibilité, vous pouvez éventuellement donner un nom à votre balise
{/block}
, par exemple{/block footer}
. Toutefois, ce nom doit correspondre au nom du bloc. Dans les modèles de grande taille, cette technique vous aide à voir quelles balises de bloc sont fermées. - Vous ne pouvez pas définir directement plusieurs balises de bloc portant le même nom dans un même modèle. Mais vous pouvez y parvenir en utilisant des noms de blocs dynamiques.
- Vous pouvez utiliser n:attributes pour définir des blocs
tels que
<h1 n:block=title>Welcome to my awesome homepage</h1>
- Les blocs peuvent également être utilisés sans nom uniquement pour appliquer les filtres à la sortie :
{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. Il permet de charger
des blocs à partir d'autres modèles. Il est similaire à la création d'un fichier avec des fonctions d'aide en PHP, puis à son
chargement à l'aide de require
.
Bien que l'héritage de modèles soit l'une des fonctionnalités les plus puissantes de Latte, il est limité à l'héritage simple – un modèle ne peut étendre qu'un seul autre modèle. La réutilisation horizontale est un moyen d'obtenir un héritage multiple.
Prenons un ensemble de définitions de blocs :
{block sidebar}...{/block}
{block menu}...{/block}
À l'aide de la commande {import}
, importez tous les blocs et définitions définis
dans blocks.latte
dans un autre modèle :
{import 'blocks.latte'}
{* les blocs sidebar et menu peuvent maintenant être utilisés *}
Si vous importez les blocs dans le modèle parent (c'est-à-dire si vous utilisez {import}
dans
layout.latte
), les blocs seront également disponibles dans tous les modèles enfants, ce qui est très pratique.
Le modèle destiné à être importé (par exemple blocks.latte
) ne doit pas étendre un autre modèle, c'est-à-dire utiliser {layout}
. Toutefois, il peut
importer d'autres modèles.
La balise {import}
doit être la première balise de modèle après {layout}
. Le nom du modèle peut
être une expression PHP quelconque :
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
Vous pouvez utiliser autant d'instructions {import}
que vous le souhaitez dans un modèle donné. Si deux modèles
importés définissent le même bloc, le premier l'emporte. Toutefois, la plus haute priorité est accordée au modèle principal,
qui peut écraser tout bloc importé.
Le contenu des blocs écrasés peut être préservé en insérant le bloc de la même manière qu'un bloc parent:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
Dans cet exemple, {include parent}
appellera correctement le bloc sidebar
à partir du modèle
blocks.latte
.
Héritage des unités {embed}
L'héritage des unités reprend l'idée de l'héritage des mises en page au niveau des fragments de contenu. Alors que l'héritage de la mise en page fonctionne avec des “squelettes de documents”, qui prennent vie grâce à des modèles enfants, l'héritage des unités vous permet de créer des squelettes pour de plus petites unités de contenu et de les réutiliser où vous le souhaitez.
Dans l'héritage d'unités, la balise {embed}
est la clé. Elle combine le comportement de {include}
et {layout}
. Elle vous permet d'inclure le contenu d'un autre modèle ou bloc et de transmettre éventuellement des
variables, comme le fait {include}
. Elle vous permet également de remplacer tout bloc défini à l'intérieur du
modèle inclus, comme le fait {layout}
.
Par exemple, nous allons utiliser l'élément accordéon pliable. Jetons un coup d'œil au squelette de l'élément dans le
modèle 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 modèles enfants peuvent remplir. Oui, comme dans le cas du
modèle parent dans le modèle d'héritage de mise en page. Vous voyez aussi la variable $modifierClass
.
Utilisons notre élément dans le modèle. C'est là que {embed}
entre en jeu. Il s'agit d'un kit super puissant
qui nous permet de tout faire : inclure le contenu du modèle de l'élément, y ajouter des variables et y ajouter des blocs avec
du HTML personnalisé :
{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}
Le résultat pourrait ressembler à ça :
<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 d'intégration forment une couche distincte indépendante des autres blocs. Par
conséquent, ils peuvent avoir le même nom que le bloc à l'extérieur de la balise embed et ne sont en aucun cas affectés. En
utilisant la balise include à l'intérieur des balises {embed}
, vous pouvez
insérer des blocs créés ici, des blocs du modèle incorporé (qui ne sont pas locaux),
ainsi que des blocs du modèle principal qui 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 dans l'inclusion *}
{include hello} {* fonctionne, le bloc est local dans ce modèle *}
{include content} {* fonctionne, le bloc est défini dans le modèle intégré *}
{include aBlockDefinedInImportedTemplate} {* fonctionne *}
{include outer} {* ne fonctionne pas ! - le bloc est dans la couche externe *}
{/block}
{/embed}
Les modèles intégrés n'ont pas accès aux variables du contexte actif, mais ils ont accès aux variables globales.
Avec {embed}
vous pouvez insérer non seulement des modèles mais aussi d'autres blocs, ainsi l'exemple
précédent pourrait être écrit comme ceci :
{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 bloc ou d'un nom de fichier,
ajoutez le mot-clé block
ou file
:
{embed block $name} ... {/embed}
Cas d'utilisation
Il existe différents types d'héritage et de réutilisation du code dans Latte. Résumons les principaux concepts pour plus de clarté :
{include template}
Use Case: Utilisation de header.latte
& footer.latte
dans 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
&
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
&
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 : Une fonction qui récupère des variables et produit des balises.
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 : Incorporation de pagination.latte
dans product.table.latte
&
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}