Stránka s příspěvkem
********************
.[perex]
Nyní si vytvoříme další stránku blogu, která bude zobrazovat jeden konkrétní příspěvek.
Musíme si vytvořit novou render metodu, která získá jeden konkrétní článek a předá jej do šablony. Mít tuto metodu v `HomePresenter` není moc hezké, protože se bavíme o článku, a ne o úvodní stránce. Vytvořme si tedy `PostPresenter` v `app/Presentation/Post/`. Tento presenter se také potřebuje připojit k databázi, takže zde opět napíšeme konstruktor, který bude vyžadovat databázové připojení.
`PostPresenter` by mohl tedy vypadat takto:
```php .{file:app/Presentation/Post/PostPresenter.php}
<?php
namespace App\Presentation\Post;
use Nette;
use Nette\Application\UI\Form;
final class PostPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function renderShow(int $id): void
{
$this->template->post = $this->database
->table('posts')
->get($id);
}
}
```
Nesmíme zapomenout uvést správný namespace `App\Presentation\Post`, který podléhá nastavení [mapování presenterů |https://github.com/nette-examples/quickstart/blob/v4.0/config/common.neon#L6-L7].
Metoda `renderShow` vyžaduje jeden argument - ID jednoho konkrétního článku, který má být zobrazen. Poté tento článek načte z databáze a předá ho do šablony.
Do šablony `Home/default.latte` vložíme odkaz na akci `Post:show`.
```latte .{file:app/Presentation/Home/default.latte}
...
<h2><a href="{link Post:show, $post->id}">{$post->title}</a></h2>
...
```
Značka `{link}` generuje URL adresu, která směřuje na akci `Post:show`. Také předává ID příspěvku jako argument.
Totéž můžeme zapsat zkráceně pomocí n:atributu:
```latte .{file:app/Presentation/Home/default.latte}
...
<h2><a n:href="Post:show, $post->id">{$post->title}</a></h2>
...
```
Atribut `n:href` je obdobou tagu `{link}`.
Pro akci `Post:show` však ještě neexistuje šablona. Můžeme si vyzkoušet otevřít odkaz na tento příspěvek. [Tracy |tracy:] zobrazí chybu, protože šablona `Post/show.latte` ještě neexistuje. Pokud vidíte jiné chybové hlášení, tak pravděpodobně budete muset zapnout `mod_rewrite` na webserveru.
Vytvoříme tedy šablonu `Post/show.latte` s tímto obsahem:
```latte .{file:app/Presentation/Post/show.latte}
{block content}
<p><a n:href="Home:default">← zpět na výpis příspěvků</a></p>
<div class="date">{$post->created_at|date:'F j, Y'}</div>
<h1 n:block="title">{$post->title}</h1>
<div class="post">{$post->content}</div>
```
Nyní si projdeme jednotlivé části šablony.
První řádka začíná definicí bloku s názvem "content" stejně jako tomu bylo na úvodní stránce. Tento blok bude opět zobrazen v hlavní šabloně. Jak vidíte, chybí koncová značka `{/block}`. Ta je totiž nepovinná.
Na další řádce je odkaz zpět na výpis článků blogu, takže se uživatel může jednoduše pohybovat mezi výpisem článků a jedním konkrétním. Protože používáme atribut `n:href`, Nette se samo postará o generování odkazů. Odkaz směřuje na akci `default` presenteru `Home` (můžeme napsat také `n:href="Home:"`, protože akce s názvem `default` může být vynechána, doplní se automaticky).
Třetí řádka formátuje výpis data pomocí filtru, který už známe.
Čtvrtá řádka zobrazuje *titulek* blogu v HTML tagu `<h1>`. Tento tag obsahuje atribut, který možná neznáte (`n:block="title"`). Uhodnete, co dělá? Pokud jste četli předchozí část pozorně, tak již víte, že se jedná o `n:atribut`. Toto je jejich další příklad, který je ekvivalentní k:
```latte
{block title}<h1>{$post->title}</h1>{/block}
```
Jednoduše řečeno, tento blok předefinovává blok s názvem `title`. Tento blok je již definován v hlavní *layout* šabloně (`/app/Presentation/@layout.latte:11`) a stejně jako u překrývání metod v OOP se tento blok v hlavní šabloně překryje. Takže `<title>` stránky nyní obsahuje titulek zobrazeného příspěvku a stačilo nám k tomu použít pouze jeden jednoduchý atribut `n:block="title"`. Skvělé, že?
Pátá a poslední řádka šablony zobrazuje celý obsah jednoho konkrétního příspěvku.
Kontrola ID příspěvku
=====================
Co se stane, když někdo změní ID v URL a vloží nějaké neexistující `id`? Měli bychom uživateli nabídnout pěknou chybu typu "stránka nebyla nalezena". Pozměníme tedy trošku render metodu v `PostPresenter`:
```php .{file:app/Presentation/Post/PostPresenter.php}
public function renderShow(int $id): void
{
$post = $this->database
->table('posts')
->get($id);
if (!$post) {
$this->error('Stránka nebyla nalezena');
}
$this->template->post = $post;
}
```
Pokud nemůže být příspěvek nalezen, zavoláním `$this->error(...)` zobrazíme chybovou stránku 404 se srozumitelnou hláškou. Pozor na to, že ve vývojářském režimu (localhost) tuto chybovou stránku neuvidíte. Místo toho se ukáže Tracy s detaily o výjimce, což je pro vývoj docela výhodné. Chceme-li si nechat zobrazit oba režimy, stačí pouze změnit argument metody `setDebugMode` v souboru `Bootstrap.php`.
Shrnutí
=======
Máme databázi s příspěvky a webovou aplikaci, která má dva pohledy - první zobrazuje přehled všech příspěvků a druhý zobrazuje jeden konkrétní příspěvek.
{{priority: -1}}
{{maintitle: Nette tutoriál: stránka s příspěvkem a kontrola ID}}
{{description: Část tutoriálu Nette: přidáte stránku s jedním příspěvkem blogu, odkaz na ni a chybu 404, když požadovaný příspěvek neexistuje.}}
Stránka s příspěvkem
Nyní si vytvoříme další stránku blogu, která bude zobrazovat jeden konkrétní příspěvek.
Musíme si vytvořit novou render metodu, která získá jeden konkrétní článek a předá jej do šablony. Mít tuto metodu
v HomePresenter není moc hezké, protože se bavíme o článku, a ne o úvodní stránce. Vytvořme si tedy
PostPresenter v app/Presentation/Post/. Tento presenter se také potřebuje připojit k databázi,
takže zde opět napíšeme konstruktor, který bude vyžadovat databázové připojení.
PostPresenter by mohl tedy vypadat takto:
<?php
namespace App\Presentation\Post;
use Nette;
use Nette\Application\UI\Form;
final class PostPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private Nette\Database\Explorer $database,
) {
}
public function renderShow(int $id): void
{
$this->template->post = $this->database
->table('posts')
->get($id);
}
}
Nesmíme zapomenout uvést správný namespace App\Presentation\Post, který podléhá nastavení mapování presenterů.
Metoda renderShow vyžaduje jeden argument – ID jednoho konkrétního článku, který má být zobrazen. Poté
tento článek načte z databáze a předá ho do šablony.
Do šablony Home/default.latte vložíme odkaz na akci Post:show.
...
<h2><a href="{link Post:show, $post->id}">{$post->title}</a></h2>
...
Značka {link} generuje URL adresu, která směřuje na akci Post:show. Také předává ID
příspěvku jako argument.
Totéž můžeme zapsat zkráceně pomocí n:atributu:
...
<h2><a n:href="Post:show, $post->id">{$post->title}</a></h2>
...
Atribut n:href je obdobou tagu {link}.
Pro akci Post:show však ještě neexistuje šablona. Můžeme si vyzkoušet otevřít odkaz na tento
příspěvek. Tracy zobrazí chybu, protože šablona Post/show.latte
ještě neexistuje. Pokud vidíte jiné chybové hlášení, tak pravděpodobně budete muset zapnout mod_rewrite na
webserveru.
Vytvoříme tedy šablonu Post/show.latte s tímto obsahem:
{block content}
<p><a n:href="Home:default">← zpět na výpis příspěvků</a></p>
<div class="date">{$post->created_at|date:'F j, Y'}</div>
<h1 n:block="title">{$post->title}</h1>
<div class="post">{$post->content}</div>
Nyní si projdeme jednotlivé části šablony.
První řádka začíná definicí bloku s názvem „content“ stejně jako tomu bylo na úvodní stránce. Tento blok bude
opět zobrazen v hlavní šabloně. Jak vidíte, chybí koncová značka {/block}. Ta je totiž nepovinná.
Na další řádce je odkaz zpět na výpis článků blogu, takže se uživatel může jednoduše pohybovat mezi výpisem
článků a jedním konkrétním. Protože používáme atribut n:href, Nette se samo postará o generování
odkazů. Odkaz směřuje na akci default presenteru Home (můžeme napsat také
n:href="Home:", protože akce s názvem default může být vynechána, doplní se automaticky).
Třetí řádka formátuje výpis data pomocí filtru, který už známe.
Čtvrtá řádka zobrazuje titulek blogu v HTML tagu <h1>. Tento tag obsahuje atribut, který
možná neznáte (n:block="title"). Uhodnete, co dělá? Pokud jste četli předchozí část pozorně, tak již
víte, že se jedná o n:atribut. Toto je jejich další příklad, který je ekvivalentní k:
{block title}<h1>{$post->title}</h1>{/block}
Jednoduše řečeno, tento blok předefinovává blok s názvem title. Tento blok je již definován v hlavní
layout šabloně (/app/Presentation/@layout.latte:11) a stejně jako u překrývání metod v OOP se tento
blok v hlavní šabloně překryje. Takže <title> stránky nyní obsahuje titulek zobrazeného příspěvku a
stačilo nám k tomu použít pouze jeden jednoduchý atribut n:block="title". Skvělé, že?
Pátá a poslední řádka šablony zobrazuje celý obsah jednoho konkrétního příspěvku.
Kontrola ID příspěvku
Co se stane, když někdo změní ID v URL a vloží nějaké neexistující id? Měli bychom uživateli
nabídnout pěknou chybu typu „stránka nebyla nalezena“. Pozměníme tedy trošku render metodu v
PostPresenter:
public function renderShow(int $id): void
{
$post = $this->database
->table('posts')
->get($id);
if (!$post) {
$this->error('Stránka nebyla nalezena');
}
$this->template->post = $post;
}
Pokud nemůže být příspěvek nalezen, zavoláním $this->error(...) zobrazíme chybovou stránku 404 se
srozumitelnou hláškou. Pozor na to, že ve vývojářském režimu (localhost) tuto chybovou stránku neuvidíte. Místo toho se
ukáže Tracy s detaily o výjimce, což je pro vývoj docela výhodné. Chceme-li si nechat zobrazit oba režimy, stačí pouze
změnit argument metody setDebugMode v souboru Bootstrap.php.
Shrnutí
Máme databázi s příspěvky a webovou aplikaci, která má dva pohledy – první zobrazuje přehled všech příspěvků
a druhý zobrazuje jeden konkrétní příspěvek.