Успадкування шаблонів і можливість повторного використання

Механізми повторного використання та успадкування шаблонів підвищують вашу продуктивність, оскільки кожен шаблон містить тільки унікальний вміст, а повторювані елементи та структури використовуються повторно. Ми представляємо три концепції: успадкування макета, горизонтальне повторне використання і успадкування одиниць.

Концепція успадкування шаблонів 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}&copy; 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 рендерить цей шаблон, спершу він знаходить батька – у цьому випадку layout.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">
		&copy; Copyright 2008
	</div>
</body>
</html>

У дочірньому шаблоні блоки можуть розташовуватися тільки або на верхньому рівні, або всередині іншого блоку, тобто:

{block content}
	<h1>{block title}Welcome to my awesome homepage{/block}</h1>
{/block}

Крім того, блок завжди буде створюватися в незалежно від того, чи буде оточуюча {if} умова оцінена як true або false. Всупереч тому, що ви можете подумати, цей шаблон дійсно визначає блок.

{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}

...

Багаторівневе успадкування

Ви можете використовувати стільки рівнів успадкування, скільки необхідно. Одним із поширених способів використання успадкування макетів є наступний трирівневий підхід:

  1. Створіть шаблон layout.latte, у якому зберігатиметься основний зовнішній вигляд вашого сайту.
  2. Створіть шаблон layout-SECTIONNAME.latte для кожного розділу вашого сайту. Наприклад, layout-news.latte, layout-blog.latte тощо. Усі ці шаблони розширюють layout.latte і включають стилі/дизайн для кожного розділу.
  3. Створіть окремі шаблони для кожного типу сторінки, наприклад, для новинної статті або запису в блозі. Ці шаблони розширюють відповідний шаблон розділу.

Динамічне успадкування макета

Ви можете використовувати змінну або будь-який вираз PHP як ім'я батьківського шаблону, отже, успадкування може поводитися динамічно:

{layout $standalone ? 'minimum.latte' : 'layout.latte'}

Ви також можете використовувати Latte API для автоматичного вибору шаблону компонування.

Поради

Ось кілька порад щодо роботи зі спадкуванням макета:

  • Якщо ви використовуєте {layout} у шаблоні, він має бути першим тегом шаблону в цьому шаблоні.
  • Макет можна шукати автоматично (як у презентаторах). У цьому випадку, якщо шаблон не повинен мати макет, він вкаже на це тегом {layout none}.
  • Тег {layout} має псевдонім {extends}.
  • Ім'я файлу розширеного шаблону залежить від завантажувача шаблонів.
  • Ви можете мати стільки блоків, скільки хочете. Пам'ятайте, що дочірні шаблони не зобов'язані визначати всі батьківські блоки, тому ви можете заповнити розумні значення за замовчуванням у кількох блоках, а потім визначити лише ті, які вам потрібні пізніше.

Блоки {block}

Див. також анонімні {block}

Блок дає можливість змінити відображення певної частини шаблону, але ніяк не втручається в навколишню логіку. Давайте розглянемо наступний приклад, щоб проілюструвати, як працює блок і, що важливіше, як він не працює:

{foreach $posts as $post}
{block post}
	<h1>{$post->title}</h1>
	<p>{$post->body}</p>
{/block}
{/foreach}

Якщо ви відобразите цей шаблон, результат буде точно таким самим із тегами блоку або без них. Блоки мають доступ до змінних із зовнішніх діапазонів. Це просто спосіб зробити його перевизначеним для дочірнього шаблону:

{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}                  // prints: foo
bar: {$bar ?? 'not defined'} // prints: not defined

Вміст блоку може бути змінено за допомогою фільтрів. У наступному прикладі видаляється весь HTML і наводиться заголовок:

<title>{block title|stripHtml|capitalize}...{/block}</title>

Тег також може бути записаний як n:attribute:

<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 перед змінною, щоб під час компіляції було відомо, що це блок, а не вставка шаблону, ім'я якого також може бути у змінній:

{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 намагається зробити все просто, тому в основному визначення – це те ж саме, що і блоки, і все, що сказано про блоки, також стосується і визначень. Цим вони відрізняються від блоків:

  1. вони укладені в теги {define}
  2. вони рендерингуються тільки тоді, коли вставлені через {include}
  3. для них можна визначати параметри, як для функцій у 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> *}

Уявіть, що у вас є допоміжний шаблон з набором визначень того, як малювати 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:attributes для визначення таких блоків, як <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'}

{* тепер можна використовувати бічну панель і блоки меню *}

Якщо ви імпортуєте блоки з батьківського шаблону (тобто використовуєте {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>

Блоки всередині тегів embed утворюють окремий шар, незалежний від інших блоків. Тому вони можуть мати те саме ім'я, що й блок поза embed, і ніяк на них не впливають. Використовуючи тег 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}

Use Case: Використання 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}
версію: 3.0