Š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/
└── Presentation/
    └── 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/
└── Presentation/
    ├── @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 zápisem do $this->template. V šabloně jsou pak dostupné jako lokální proměnné:

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

Pokud chcete, aby se hodnota určité property automaticky předala do šablony jako proměnná, označte ji atributem #[TemplateVariable] a viditelností public:

use Nette\Application\Attributes\TemplateVariable;

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	#[TemplateVariable]
	public string $siteName = 'Můj blog';
}

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.

Typově bezpečné šablony

Při vývoji robustních aplikací je užitečné explicitně nadefinovat, jaké proměnné šablona očekává a jakého jsou typu. Získáte tak typovou kontrolu v PHP, chytré našeptávání v IDE a schopnost statické analýzy odhalovat chyby.

Jak takový výčet nadefinovat? Jednoduše v podobě třídy s properties reprezentujícími proměnné šablony. 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. PHP tak bude při zápisu kontrolovat deklarované typy.

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

Našeptávání můžete využít i přímo v šablonách. Stačí do PhpStorm nainstalovat plugin pro Latte a uvést na začátek šablony název třídy parametrů šablony, více v článku Latte: jak na typový systém:

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

Totéž platí i pro komponenty. Stačí dodržet jmennou konvenci a pro komponentu např. FifteenControl vytvořit třídu parametrů FifteenTemplate.

Pokud potřebujete použít jinou třídu parametrů, využijte metodu createTemplate():

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

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 a další prvky. K dispozici jsou tři způsoby, jak to udělat, od nejrychlejších ad-hoc řešení až po architektonický přístup pro celou aplikaci.

Ad-hoc v metodách presenteru

Nejrychlejší způsob je přidat filtr nebo funkci přímo v kódu presenteru či komponenty. V presenteru je k tomu vhodná metoda beforeRender() nebo render<View>():

protected function beforeRender(): void
{
	// přidání filtru
	$this->template->addFilter('money', fn($val) => round($val) . ' Kč');

	// přidání funkce
	$this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6);
}

V šabloně pak:

<p>Cena: {$price|money}</p>

{if isWeekend($now)} ... {/if}

Pro složitější logiku můžete konfigurovat přímo objekt Latte\Engine:

protected function beforeRender(): void
{
	$latte = $this->template->getLatte();
	$latte->setMigrationWarnings();
}

Pomocí atributů

Elegantní způsob je definovat filtry a funkce jako metody přímo ve třídě parametrů šablony presenteru nebo komponenty a označit je atributy:

class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
{
	#[Latte\Attributes\TemplateFilter]
	public function money(float $val): string
	{
		return round($val) . ' Kč';
	}

	#[Latte\Attributes\TemplateFunction]
	public function isWeekend(DateTimeInterface $date): bool
	{
		return $date->format('N') >= 6
	}
}

Latte automaticky rozpozná a zaregistruje metody označené těmito atributy. Název filtru nebo funkce v šabloně odpovídá názvu metody. Tyto metody nesmí být privátní.

Globálně pomocí Extension

Předchozí způsoby jsou vhodné pro filtry a funkce, které potřebujete jen v konkrétním presenteru nebo komponentě, nikoliv v celé aplikaci. Pro celou aplikaci je nejvhodnější vytvořit si extension. Jde o třídu, která centralizuje všechna rozšíření Latte pro celý projekt. Kusý příklad:

namespace App\Presentation\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()),
			// ...
		];
	}

	private function filterTimeAgoInWords(DateTimeInterface $time): string
	{
		// ...
	}

	// ...
}

Extension zaregistrujeme pomocí konfigurace:

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

Výhodou extension je, že lze využít dependency injection, mít přístup k modelové vrstvě aplikace a všechna rozšíření mít přehledně na jednom místě. Extension umožnuje definovat i vlastní značky, providery, průchody pro Latte kompilátor a další.

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(@Nette\Localization\Translator)

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.