Наследяване и повторно използване на шаблони
Механизмите за повторно използване и наследяване на шаблони ще повишат вашата производителност, тъй като всеки шаблон съдържа само своето уникално съдържание, а повтарящите се елементи и структури се използват повторно. Представяме три концепции: Наследяване на лейаут, Хоризонтално повторно използване и Единично наследяване.
Концепцията за наследяване на шаблони в Latte е подобна на наследяването на класове в PHP. Вие дефинирате родителски шаблон, от който други дъщерни шаблони могат да наследят и да презапишат части от родителския шаблон. Това работи чудесно, когато елементите споделят обща структура. Звучи ли сложно? Не се притеснявайте, много е лесно.
Наследяване на лейаут {layout}
Нека разгледаме наследяването на шаблон за оформление, т.е. лейаут,
директно с пример. Това е родителски шаблон, който ще наречем например
layout.latte
и който дефинира скелета на 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>
Таговете {block}
дефинират три блока, които дъщерните шаблони
могат да запълнят. Тагът block прави само това, че обявява, че това място
може да бъде презаписано от дъщерен шаблон чрез дефиниране на собствен
блок със същото име.
Дъщерният шаблон може да изглежда така:
{layout 'layout.latte'}
{block title}My amazing blog{/block}
{block content}
<p>Welcome to my awesome homepage.</p>
{/block}
Ключът тук е тагът {layout}
. Той казва на Latte, че този шаблон
“разширява” друг шаблон. Когато Latte рендира този шаблон, първо намира
родителския шаблон – в този случай layout.latte
.
В този момент Latte забелязва трите блокови тага в layout.latte
и
заменя тези блокове със съдържанието на дъщерния шаблон. Тъй като
дъщерният шаблон не е дефинирал блока footer, вместо това се използва
съдържанието от родителския шаблон. Съдържанието в тага {block}
в
родителския шаблон винаги се използва като резервно.
Изходът може да изглежда така:
<!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>
В дъщерния шаблон блоковете могат да бъдат поставени само на най-високо ниво или вътре в друг блок, т.е.:
{block content}
<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}
Също така, блок винаги ще бъде създаден, независимо дали
заобикалящото го условие {if}
се оценява като вярно или невярно.
Така че, дори да не изглежда така, този шаблон ще дефинира блока.
{if false}
{block head}
<meta name="robots" content="noindex, follow">
{/block}
{/if}
Ако искате изходът вътре в блока да се показва условно, използвайте следното вместо това:
{block head}
{if $condition}
<meta name="robots" content="noindex, follow">
{/if}
{/block}
Пространството извън блоковете в дъщерния шаблон се изпълнява преди
рендирането на шаблона на лейаута, така че можете да го използвате за
дефиниране на променливи като {var $foo = bar}
и за разпространение на
данни по цялата верига на наследяване:
{layout 'layout.latte'}
{var $robots = noindex}
...
Многостепенно наследяване
Можете да използвате толкова нива на наследяване, колкото са ви необходими. Обичайният начин за използване на наследяването на лейаути е следният тристепенен подход:
- Създайте шаблон
layout.latte
, който съдържа основния скелет на външния вид на сайта. - Създайте шаблон
layout-SECTIONNAME.latte
за всяка секция на вашия сайт. Напримерlayout-news.latte
,layout-blog.latte
и т.н. Всички тези шаблони разширяватlayout.latte
и включват стилове и дизайн, специфични за всяка секция. - Създайте индивидуални шаблони за всеки тип страница, например новинарска статия или публикация в блог. Тези шаблони разширяват съответния шаблон на секцията.
Динамично наследяване
Като име на родителския шаблон може да се използва променлива или произволен PHP израз, така че наследяването може да се държи динамично:
{layout $standalone ? 'minimum.latte' : 'layout.latte'}
Можете също да използвате Latte API за автоматично избиране на шаблон за лейаут.
Съвети
Ето няколко съвета за работа с наследяване на лейаути:
- Ако използвате
{layout}
в шаблон, той трябва да бъде първият таг на шаблона в този шаблон. - Лейаутът може да се търси автоматично (както
например в презентери). В
такъв случай, ако шаблонът не трябва да има лейаут, той го обявява с
тага
{layout none}
. - Тагът
{layout}
има псевдоним{extends}
. - Името на файла на лейаута зависи от loader.
- Можете да имате толкова блокове, колкото искате. Помнете, че дъщерните шаблони не е необходимо да дефинират всички родителски блокове, така че можете да попълните разумни стойности по подразбиране в няколко блока и след това да дефинирате само тези, от които се нуждаете по-късно.
Блокове {block}
Вижте също анонимен {block}
Блокът представлява начин за промяна на начина, по който се рендира определена част от шаблона, но по никакъв начин не се намесва в логиката около него. В следващия пример ще покажем как работи блокът, но също и как не работи:
{foreach $posts as $post}
{block post}
<h1>{$post->title}</h1>
<p>{$post->body}</p>
{/block}
{/foreach}
Ако рендирате този шаблон, резултатът ще бъде абсолютно същият със
или без таговете {block}
. Блоковете имат достъп до променливи от
външни обхвати. Те просто дават възможност да бъдат презаписани от
дъщерен шаблон:
{layout 'parent.Latte'}
{block post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/block}
Сега, при рендиране на дъщерния шаблон, цикълът ще използва блока,
дефиниран в дъщерния шаблон child.Latte
, вместо блока, дефиниран в
parent.Latte
; изпълненият шаблон тогава е еквивалентен на следното:
{foreach $posts as $post}
<article>
<header>{$post->title}</header>
<section>{$post->text}</section>
</article>
{/foreach}
Ако обаче създадем нова променлива вътре в именуван блок или заменим стойността на съществуваща, промяната ще бъде видима само вътре в блока:
{var $foo = 'foo'}
{block post}
{do $foo = 'new value'}
{var $bar = 'bar'}
{/block}
foo: {$foo} // отпечатва: foo
bar: {$bar ?? 'not defined'} // отпечатва: not defined
Съдържанието на блока може да бъде модифицирано с помощта на филтри. Следният пример премахва целия HTML и променя регистъра на буквите:
<title>{block title|stripHtml|capitalize}...{/block}</title>
Тагът може да бъде записан и като n:атрибут:
<article n:block=post>
...
</article>
Локални блокове
Всеки блок презаписва съдържанието на родителския блок със същото име – с изключение на локалните блокове. В класовете това би било нещо като частни методи. По този начин можете да създавате шаблон, без да се притеснявате, че поради съвпадение на имената на блоковете, те ще бъдат презаписани от друг шаблон.
{block local helper}
...
{/block}
Рендиране на блокове {include}
Вижте също {include file}
За да изведете блок на определено място, използвайте тага
{include blockname}
:
<title>{block title}{/block}</title>
<h1>{include title}</h1>
Можете също да изведете блок от друг шаблон:
{include footer from 'main.latte'}
Рендираният блок няма достъп до променливите на активния контекст, освен ако блокът не е дефиниран в същия файл, в който е вмъкнат. Въпреки това, той има достъп до глобалните променливи.
Можете да предавате променливи към блока по този начин:
{include footer, foo: bar, id: 123}
Като име на блока може да се използва променлива или произволен израз
в PHP. В такъв случай, преди променливата добавяме ключовата дума
block
, за да може Latte още по време на компилация да знае, че става
въпрос за блок, а не за вмъкване на
шаблон, чието име също може да бъде в променлива:
{var $name = footer}
{include block $name}
Блокът може да бъде рендиран и вътре в себе си, което е полезно например при рендиране на дървовидна структура:
{define menu, $items}
<ul>
{foreach $items as $item}
<li>
{if is_array($item)}
{include menu, $item}
{else}
{$item}
{/if}
</li>
{/foreach}
</ul>
{/define}
Вместо {include menu, ...}
можем да напишем {include this, ...}
, където
this
означава текущия блок.
Рендираният блок може да бъде модифициран с помощта на филтри. Следният пример премахва целия HTML и променя регистъра на буквите:
<title>{include heading|stripHtml|capitalize}</title>
Родителски блок
Ако трябва да изведете съдържанието на блок от родителския шаблон,
използвайте {include parent}
. Това е полезно, ако искате само да
допълните съдържанието на родителския блок, вместо да го презапишете
напълно.
{block footer}
{include parent}
<a href="https://github.com/nette">GitHub</a>
<a href="https://twitter.com/nettefw">Twitter</a>
{/block}
Дефиниции {define}
Освен блокове, в Latte съществуват и “дефиниции”. В обикновените езици за програмиране бихме ги сравнили с функции. Те са полезни за повторно използване на фрагменти от шаблони, за да не се повтаряте.
Latte се стреми да прави нещата прости, така че по същество дефинициите са същите като блоковете и всичко, което е казано за блоковете, важи и за дефинициите. Те се различават от блоковете по това, че:
- са затворени в тагове
{define}
- рендират се едва когато ги вмъкнете чрез
{include}
- могат да им се дефинират параметри, подобно на функциите в PHP
{block foo}<p>Hello</p>{/block}
{* отпечатва: <p>Hello</p> *}
{define bar}<p>World</p>{/define}
{* не отпечатва нищо *}
{include bar}
{* отпечатва: <p>World</p> *}
Представете си, че имате помощен шаблон с колекция от дефиниции за това как да рисувате 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}
Аргументите винаги са незадължителни със стойност по подразбиране
null
, освен ако не е посочена стойност по подразбиране (тук
'text'
е стойността по подразбиране за $type
). Могат да се
декларират и типове параметри: {define input, string $name, ...}
.
Шаблонът с дефиниции се зарежда с помощта на {import}
. Самите дефиниции се рендират по същия начин като блоковете:
<p>{include input, 'password', null, 'password'}</p>
<p>{include textarea, 'comment'}</p>
Дефинициите нямат достъп до променливите на активния контекст, но имат достъп до глобалните променливи.
Динамични имена на блокове
Latte позволява голяма гъвкавост при дефинирането на блокове, тъй като
името на блока може да бъде произволен PHP израз. Този пример дефинира
три блока с имена hi-Peter
, hi-John
и hi-Mary
:
{foreach [Peter, John, Mary] as $name}
{block "hi-$name"}Hi, I am {$name}.{/block}
{/foreach}
В дъщерния шаблон тогава можем да предефинираме например само един блок:
{block hi-John}Hello. I am {$name}.{/block}
Така изходът ще изглежда по следния начин:
Hi, I am Peter.
Hello. I am John.
Hi, I am Mary.
Проверка за съществуване на блокове
{ifset}
Вижте също {ifset $var}
С помощта на теста {ifset blockname}
проверяваме дали в текущия
контекст съществува блок (или повече блокове):
{ifset footer}
...
{/ifset}
{ifset footer, header, main}
...
{/ifset}
Като име на блока може да се използва променлива или произволен израз
в PHP. В такъв случай, преди променливата добавяме ключовата дума
block
, за да е ясно, че не става въпрос за тест за съществуване на променливи:
{ifset block $name}
...
{/ifset}
Съществуването на блокове се проверява и от функцията hasBlock()
:
{if hasBlock(header) || hasBlock(footer)}
...
{/if}
Съвети
Няколко съвета за работа с блокове:
- Последният блок на най-високо ниво не е необходимо да има затварящ таг (блокът завършва с края на документа). Това опростява писането на дъщерни шаблони, които съдържат един основен блок.
- За по-добра четимост можете да посочите името на блока в тага
{/block}
, например{/block footer}
. Името обаче трябва да съвпада с името на блока. В по-големи шаблони тази техника ще ви помогне да видите кои тагове на блокове се затварят. - В един и същ шаблон не можете директно да дефинирате няколко тага на блокове със същото име. Това обаче може да се постигне с помощта на динамични имена на блокове.
- Можете да използвате n:атрибути за
дефиниране на блокове
като
<h1 n:block=title>Welcome to my awesome homepage</h1>
- Блоковете могат да се използват и без имена, само за прилагане на филтри:
{block|strip} hello {/block}
Хоризонтално повторно използване
{import}
Хоризонталното повторно използване е третият механизъм в Latte за
повторно използване и наследяване. Той позволява зареждане на блокове
от други шаблони. Това е подобно на създаването на файл с помощни
функции в PHP, който след това зареждаме с помощта на require
.
Въпреки че наследяването на лейаута на шаблона е една от най-мощните функции на Latte, то е ограничено до просто наследяване – шаблонът може да разшири само един друг шаблон. Хоризонталното повторно използване е начин за постигане на множествено наследяване.
Нека имаме файл с дефиниции на блокове:
{block sidebar}...{/block}
{block menu}...{/block}
С помощта на командата {import}
импортираме всички блокове и Дефиниции, дефинирани в blocks.latte
, в друг шаблон:
{import 'blocks.latte'}
{* сега могат да се използват блоковете sidebar и menu *}
Ако импортирате блокове в родителския шаблон (т.е. използвате
{import}
в layout.latte
), блоковете ще бъдат достъпни и във всички
дъщерни шаблони, което е много практично.
Шаблонът, предназначен за импортиране (напр. blocks.latte
), не трябва
да разширява друг шаблон, т.е. да използва
{layout}
. Въпреки това, той може да импортира други шаблони.
Тагът {import}
трябва да бъде първият таг на шаблона след
{layout}
. Името на шаблона може да бъде произволен PHP израз:
{import $ajax ? 'ajax.latte' : 'not-ajax.latte'}
В шаблона можете да използвате толкова команди {import}
, колкото
искате. Ако два импортирани шаблона дефинират един и същ блок, печели
първият. Най-висок приоритет обаче има основният шаблон, който може да
презапише всеки импортиран блок.
Съдържанието на презаписаните блокове може да бъде запазено, като вмъкнем блока по същия начин, по който се вмъква родителски блок:
{layout 'layout.latte'}
{import 'blocks.latte'}
{block sidebar}
{include parent}
{/block}
{block title}...{/block}
{block content}...{/block}
В този пример {include parent}
извиква блока sidebar
от шаблона
blocks.latte
.
Единично наследяване {embed}
Единичното наследяване разширява идеята за наследяване на лейаути до нивото на фрагменти от съдържание. Докато наследяването на лейаути работи със “скелета на документа”, който се оживява от дъщерни шаблони, единичното наследяване ви позволява да създавате скелети за по-малки единици съдържание и да ги използвате повторно навсякъде, където искате.
При единичното наследяване ключът е тагът {embed}
. Той комбинира
поведението на {include}
и {layout}
. Позволява ви да вмъкнете
съдържанието на друг шаблон или блок и по избор да предавате
променливи, точно както при {include}
. Също така ви позволява да
презапишете всеки блок, дефиниран вътре във вмъкнатия шаблон, както
при използване на {layout}
.
Например, нека използваме елемент акордеон. Да разгледаме скелета на
елемента, съхранен в шаблона collapsible.latte
:
<section class="collapsible {$modifierClass}">
<h4 class="collapsible__title">
{block title}{/block}
</h4>
<div class="collapsible__content">
{block content}{/block}
</div>
</section>
Таговете {block}
дефинират два блока, които дъщерните шаблони
могат да запълнят. Да, както в случая с родителския шаблон при
наследяването на лейаути. Виждате също променливата $modifierClass
.
Нека използваме нашия елемент в шаблона. Тук идва ред на {embed}
.
Това е изключително мощен таг, който ни позволява да правим всичко: да
вмъкваме съдържанието на шаблона на елемента, да добавяме променливи
към него и да добавяме блокове със собствен 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}
Изходът може да изглежда така:
<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>
Блоковете вътре във вмъкнатите тагове образуват отделен слой,
независим от другите блокове. Следователно те могат да имат същото име
като блок извън вмъкването и не се влияят по никакъв начин. С помощта на
тага include вътре в таговете {embed}
можете да
вмъквате блокове, създадени тук, блокове от вмъкнатия шаблон (които
не са локални), както и блокове от основния
шаблон, които от своя страна са локални. Можете също да импортирате блокове от други файлове:
{block outer}…{/block}
{block local hello}…{/block}
{embed 'collapsible.latte', modifierClass: my-style}
{import 'blocks.latte'}
{block inner}…{/block}
{block title}
{include inner} {* работи, блокът е дефиниран вътре в embed *}
{include hello} {* работи, блокът е локален в този шаблон *}
{include content} {* работи, блокът е дефиниран във вмъкнатия шаблон *}
{include aBlockDefinedInImportedTemplate} {* работи *}
{include outer} {* не работи! - блокът е във външния слой *}
{/block}
{/embed}
Вмъкнатите шаблони нямат достъп до променливите на активния контекст, но имат достъп до глобалните променливи.
С помощта на {embed}
могат да се вмъкват не само шаблони, но и
други блокове, така че предишният пример може да бъде записан по
следния начин:
{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}
Ако предадем израз на {embed}
и не е ясно дали това е име на блок
или файл, добавяме ключовата дума block
или file
:
{embed block $name} ... {/embed}
Случаи на употреба
В Latte съществуват различни типове наследяване и повторно използване на код. Нека обобщим основните концепции за по-голяма яснота:
{include template}
Случай на употреба: Използване на header.latte
и footer.latte
вътре в 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}
Случай на употреба: Разширяване на layout.latte
вътре в
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}
Случай на употреба: sidebar.latte
в 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}
Случай на употреба: Функции, на които предаваме променливи и те рендират нещо.
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}
Случай на употреба: Вмъкване на pagination.latte
в
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}