Šablony

Nette používá šablonovací systém Latte. Jednak proto, že jde o nejlépe zabezpečený šablonovací systém pro PHP, a zároveň také systém nejintuitivnější. Nemusíte se učit mnoho nového, vystačíte si se znalostí PHP a několika značek.

Je obvyklé, že stránka se složí ze šablony layoutu + šablony dané akce. Takhle třeba může vypadat šablona layoutu, všimněte si bloků {block} a značky {include}:

<!DOCTYPE html>
<html>
<head>
	<title>{block title}My App{/block}</title>
</head>
<body>
	<header>...</header>
	{include content}
	<footer>...</footer>
</body>
</html>

A tohle bude šablona akce:

{block title}Homepage{/block}

{block content}
<h1>Homepage</h1>
...
{/block}

Ta definuje blok content, který se vloží na místo {include content} v layoutu, a také re-definuje blok title, kterým přepíše {block title} v layoutu. Zkuste si představit výsledek.

Hledání šablon

Nemusíte v presenterech uvádět, jaká šablona se má vykreslit, framework cestu odvodí sám a ušetří vám psaní.

Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, jednodušše umístěte šablonu do tohoto adresáře pod jménem akce (resp. view), tj. pro akci default použijte šablonu default.latte:

app/
└── UI/
    └── Home/
        ├── HomePresenter.php
        └── default.latte

Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce templates, uložte ji buď do souboru <Presenter>.<view>.latte nebo <Presenter>/<view>.latte:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── Home.default.latte  ← 1. varianta
        └── Home/
            └── default.latte   ← 2. varianta

Adresář templates může být umístěn také o úroveň výš, tj. na stejné úrovni, jako je adresář s třídami presenterů.

Pokud se šablona nenajde, presenter odpoví chybou 404 – page not found.

View změníte pomocí $this->setView('jineView'). Také lze přímo určit soubor se šablonou pomocí $this->template->setFile('/path/to/template.latte').

Soubory, kde se dohledávají šablony, lze změnit překrytím metody formatTemplateFiles(), která vrací pole možných názvů souborů.

Hledání šablony layoutu

Nette také automaticky dohledává soubor s layoutem.

Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, umístěte layout buď do složky s presenterem, pokud je specifický jen pro něj, nebo o úroveň výš, pokud je společný pro více presenterů:

app/
└── UI/
    ├── @layout.latte           ← společný layout
    └── Home/
        ├── @layout.latte       ← jen pro presenter Home
        ├── HomePresenter.php
        └── default.latte

Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce templates, bude se layout očekávat na těchto místech:

app/
└── Presenters/
    ├── HomePresenter.php
    └── templates/
        ├── @layout.latte       ← společný layout
        ├── Home.@layout.latte  ← jen pro Home, 1. varianta
        └── Home/
            └── @layout.latte   ← jen pro Home, 2. varianta

Pokud se presenter nachází v modulu, bude se dohledávat i o další adresářové úrovně výš, podle zanoření modulu.

Název layoutu lze změnit pomocí $this->setLayout('layoutAdmin') a pak se bude očekávat v souboru @layoutAdmin.latte. Také lze přímo určit soubor se šablonou layoutu pomocí $this->setLayout('/path/to/template.latte').

Pomocí $this->setLayout(false) nebo značky {layout none} uvnitř šablony se dohledávání layoutu vypne.

Soubory, kde se dohledávají šablony layoutu, lze změnit překrytím metody formatLayoutTemplateFiles(), která vrací pole možných názvů souborů.

Proměnné v šabloně

Proměnné do šablony předáváme tak, že je zapíšeme do $this->template a potom je máme k dispozici v šabloně jako lokální proměnné:

$this->template->article = $this->articles->getById($id);

Takto jednoduše můžeme do šablon předat jakékoliv proměnné. Při vývoji robustních aplikací ale bývá užitečnější se omezit. Například tak, že explicitně nadefinujeme výčet proměnných, které šablona očekává, a jejich typů. Díky tomu nám bude moci PHP kontrolovat typy, IDE správně našeptávat a statická analýza odhalovat chyby.

A jak takový výčet nadefinujeme? Jednoduše v podobě třídy a její properties. Pojmenujeme ji podobně jako presenter, jen s Template na konci:

/**
 * @property-read ArticleTemplate $template
 */
class ArticlePresenter extends Nette\Application\UI\Presenter
{
}

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	public Model\Article $article;
	public Nette\Security\User $user;

	// a další proměnné
}

Objekt $this->template v presenteru bude nyní instancí třídy ArticleTemplate. Takže PHP bude při zápisu kontrolovat deklarované typy. A počínaje verzí PHP 8.2 upozorní i na zápis do neexistující proměnné, v předchozích verzích lze téhož dosáhnout použitím traity Nette\SmartObject.

Anotace @property-read je určená pro IDE a statickou analýzu, díky ní bude fungovat našeptávání, viz PhpStorm and code completion for $this⁠-⁠>⁠template.

Luxusu našeptávání si můžete dopřát i v šablonách, stačí do PhpStorm nainstalovat plugin pro Latte a uvést na začátek šablony název třídy, více v článku Latte: jak na typový systém:

{templateType App\UI\Article\ArticleTemplate}
...

Takto fungují i šablony v komponentách, stačí jen dodržet jmennou konvenci a pro komponentu např. FifteenControl vytvořit třídu šablony FifteenTemplate.

Pokud potřebujete vytvořit $template jako instanci jiné třídy, využijte metodu createTemplate():

public function renderDefault(): void
{
	$template = $this->createTemplate(SpecialTemplate::class);
	$template->foo = 123;
	// ...
	$this->sendTemplate($template);
}

Výchozí proměnné

Presentery a komponenty předávají do šablon několik užitečných proměnných automaticky:

  • $basePath je absolutní URL cesta ke kořenovému adresáři (např. /eshop)
  • $baseUrl je absolutní URL ke kořenovému adresáři (např. http://localhost/eshop)
  • $user je objekt reprezentující uživatele
  • $presenter je aktuální presenter
  • $control je aktuální komponenta nebo presenter
  • $flashes pole zpráv zaslaných funkcí flashMessage()

Pokud používáte vlastní třídu šablony, tyto proměnné se předají, pokud pro ně vytvoříte property.

Vytváření odkazů

V šabloně se vytvářejí odkazy na další presentery & akce tímto způsobem:

<a n:href="Product:show">detail produktu</a>

Atribut n:href je velmi šikovný pro HTML značky <a>. Chceme-li odkaz vypsat jinde, například v textu, použijeme {link}:

Adresa je: {link Home:default}

Více informací najdete v kapitole Vytváření odkazů URL.

Vlastní filtry, značky apod.

Šablonovací systém Latte lze rozšířit o vlastní filtry, funkce, značky apod. Lze tak učinit přímo v metodě render<View> nebo beforeRender():

public function beforeRender(): void
{
	// přidání filtru
	$this->template->addFilter('foo', /* ... */);

	// nebo konfigurujeme přímo objekt Latte\Engine
	$latte = $this->template->getLatte();
	$latte->addFilterLoader(/* ... */);
}

Latte ve verzi 3 nabízí pokročilejší způsob a to vytvoření si extension pro každý webový projekt. Kusý příklad takové třídy:

namespace App\UI\Accessory;

final class LatteExtension extends Latte\Extension
{
	public function __construct(
		private App\Model\Facade $facade,
		private Nette\Security\User $user,
		// ...
	) {
	}

	public function getFilters(): array
	{
		return [
			'timeAgoInWords' => $this->filterTimeAgoInWords(...),
			'money' => $this->filterMoney(...),
			// ...
		];
	}

	public function getFunctions(): array
	{
		return [
			'canEditArticle' =>
				fn($article) => $this->facade->canEditArticle($article, $this->user->getId()),
			// ...
		];
	}

	// ...
}

Zaregistrujeme ji pomocí konfigurace:

latte:
	extensions:
		- App\UI\Accessory\LatteExtension

Překládání

Pokud programujete vícejazyčnou aplikaci, budete nejspíš potřebovat některé texty v šabloně vypsat v různých jazycích. Nette Framework k tomuto účelu definuje rozhraní pro překlad Nette\Localization\Translator, které má jedinou metodu translate(). Ta přijímá zprávu $message, což zpravidla bývá řetězec, a libovolné další parametry. Úkolem je vrátit přeložený řetězec. V Nette není žádná výchozí implementace, můžete si vybrat podle svých potřeb z několika hotových řešeních, které najdete na Componette. V jejich dokumentaci se dozvíte, jak translator konfigurovat.

Šablonám lze nastavit překladač, který si necháme předat, metodou setTranslator():

protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator);
}

Translator je alternativně možné nastavit pomocí konfigurace:

latte:
	extensions:
		- Latte\Essential\TranslatorExtension

Poté lze překladač používat například jako filtr |translate, a to včetně doplňujících parametrů, které se předají metodě translate() (viz foo, bar):

<a href="basket">{='Košík'|translate}</a>
<span>{$item|translate}</span>
<span>{$item|translate, foo, bar}</span>

Nebo jako podtržítkovou značku:

<a href="basket">{_'Košík'}</a>
<span>{_$item}</span>
<span>{_$item, foo, bar}</span>

Pro překlad úseku šablony existuje párová značka {translate} (od Latte 2.11, dříve se používala značka {_}):

<a href="order">{translate}Objednávka{/translate}</a>
<a href="order">{translate foo, bar}Objednávka{/translate}</a>

Translator se standardně volá za běhu při vykreslování šablony. Latte verze 3 ovšem umí všechny statické texty překládat už během kompilace šablony. Tím se ušetří výkon, protože každý řetězec se přeloží jen jednou a výsledný překlad se zapíše do zkompilované podoby. V adresáři s cache tak vznikne více zkompilovaných verzí šablony, jedna pro každý jazyk. K tomu stačí pouze uvést jazyk jako druhý parametr:

protected function beforeRender(): void
{
	// ...
	$this->template->setTranslator($translator, $lang);
}

Statickým textem je myšleno třeba {_'hello'} nebo {translate}hello{/translate}. Nestatické texty, jako třeba {_$foo}, se nadále budou překládat za běhu.