Komentáře
Nahráli jsme blog na webserver a publikovali několik velmi zajímavých příspěvků pomocí Admineru. Lidé čtou náš blog a jsou z něho velmi nadšení. Dostáváme každý den mnoho e-mailů s pochvalami. Ale k čemu je všechna tato chvála, pokud ji máme pouze v e-mailu a nikdo si ji nemůže přečíst? Bylo by lepší, kdyby mohl čtenář článek přímo komentovat, takže by si mohl každý přečíst, jak jsme úžasní.
Pojďme tedy naprogramovat komentáře.
Tvorba nové tabulky
Nažhavíme Adminer a vytvoříme tabulku comments
s těmito sloupci:
id
int, zaškrtneme autoincrement (AI)post_id
, cizí klíč, který odkazuje na tabulkuposts
name
varchar, length 255email
varchar, length 255content
textcreated_at
timestamp
Tabulka by tedy měla vypadat nějak takto:
Nezapomeňte opět použít úložiště InnoDB.
CREATE TABLE `comments` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`post_id` int(11) NOT NULL,
`name` varchar(250) NOT NULL,
`email` varchar(250) NOT NULL,
`content` text NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`)
) ENGINE=InnoDB CHARSET=utf8;
Formulář pro komentování
Prvně musíme vytvořit formulář, který umožní uživatelům příspěvky komentovat. Nette Framework má úžasnou podporu pro formuláře. Můžeme je nakonfigurovat v presenteru a vykreslit v šabloně.
Nette Framework využívá koncept komponent. Komponenta je znovupoužitelná třída, nebo část kódu, který
může být přiložen k jiné komponentě. Dokonce i presenter je komponenta. Každá komponenta je vytvořena
prostřednictvím továrny. Vytvoříme si tedy továrnu pro formulář na komentáře v presenteru
PostPresenter
.
protected function createComponentCommentForm(): Form
{
$form = new Form; // means Nette\Application\UI\Form
$form->addText('name', 'Jméno:')
->setRequired();
$form->addEmail('email', 'E-mail:');
$form->addTextArea('content', 'Komentář:')
->setRequired();
$form->addSubmit('send', 'Publikovat komentář');
return $form;
}
Pojďme si to zase trochu vysvětlit. První řádka vytvoří novou instanci komponenty Form
. Následující
metody připojují HTML inputy do definice tohoto formuláře. ->addText
se vykreslí jako
<input type="text" name="name">
s <label>Jméno:</label>
. Jak již zřejmě správně
odhadujete, tak ->addTextArea
se vykreslí jako <textarea>
a ->addSubmit
jako
<input type="submit">
. Existuje daleko více obdobných metod, ale tyto zatím pro tento formulář stačí.
Více se dočtete v dokumentaci.
Pokud je již formulář definován v presenteru, můžeme ho vykreslit (zobrazit) v šabloně. To uděláme umístěním
značky {control}
na konec šablony, která vykresluje jeden konkrétní příspěvek, do
Post/show.latte
. Protože se komponenta jmenuje commentForm
(název je odvozen od názvu metody
createComponentCommentForm
), značka bude vypadat takto:
...
<h2>Vložte nový příspěvek</h2>
{control commentForm}
Když si nyní zobrazíte stránku s detailem příspěvku, na jeho konci uvidíte nový formulář pro komentáře.
Ukládání do databáze
Už jste zkoušeli formulář vyplnit a odeslat? Asi jste si všimli, že ten formulář vlastně nic nedělá. Musíme připojit callback metodu, která uloží odeslaná data.
Na řádek před return
v továrničce pro komponentu commentForm
umístíme
následující řádek:
$form->onSuccess[] = $this->commentFormSucceeded(...);
Předchozí zápis znamená „po úspěšném odeslání formuláře zavolej metodu commentFormSucceeded
ze
současného presenteru“. Tato metoda však ještě neexistuje. Pojďme ji tedy vytvořit:
private function commentFormSucceeded(\stdClass $data): void
{
$id = $this->getParameter('id');
$this->database->table('comments')->insert([
'post_id' => $id,
'name' => $data->name,
'email' => $data->email,
'content' => $data->content,
]);
$this->flashMessage('Děkuji za komentář', 'success');
$this->redirect('this');
}
Tuto metodu umístíme přímo za továrnu formuláře commentForm
.
Tato nová metoda má jeden argument, což je instance formuláře, který byl odeslán – vytvořen továrnou. Odeslané
hodnoty získáme ve $data
. A následně uložíme data do databázové tabulky comments
.
Ještě jsou zde další dvě metody, které si zaslouží vysvětlit. Redirect metoda doslova přesměrovává zpět na
aktuální stránku. Toto je vhodné udělat po každém odeslání formuláře, pokud obsahoval validní data a callback provedl
operaci tak jak měl. A taky pokud přesměrujeme stránku po odeslání formuláře, neuvidíme dobře známou hlášku
Chcete odeslat data z formuláře znovu?
, kterou občas můžeme v prohlížeči spatřit. (Obecně platí, že po
odeslání formuláře metodou POST
by mělo následovat vždy přesměrování na GET
akci.)
Metoda flashMessage
je pro informování uživatele o výsledku nějaké operace. Protože přesměrováváme,
zpráva nemůže být jednoduše předána šabloně a vykreslena. Proto je zde tato metoda, která si tuto zprávu uloží a
zpřístupní ji při dalším načtení stránky. Flash zprávy jsou vykreslovány v hlavní šabloně
app/UI/@layout.latte
a vypadá to takto:
<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
{$flash->message}
</div>
Jak již víme, tak flash zprávy jsou automaticky předávány do šablony, takže o tom nemusíme moc přemýšlet, zkrátka to funguje. Pro více informací navštivte dokumentaci.
Vykreslování komentářů
Toto je jedna z těch věcí, které si prostě zamilujete. Nette Database má skvělou funkci nazvanou Explorer. Pamatujete si ještě, že tabulky v databázi jsme schválně vytvářeli pomocí InnoDB úložiště? Adminer tak vytvořil něco, čemu se říká cizí klíče, které nám ušetří spoustu práce.
Nette Database Explorer používá cizí klíče pro vyřešení vzájemného vztahu mezi tabulkami a ze znalostí těchto vztahů umí automaticky vytvořit databázové dotazy.
Jak si jistě pamatujete, do šablony jsme předali proměnnou $post
pomocí metody
PostPresenter::renderShow()
a nyní chceme iterovat přes všechny komentáře, které mají hodnotu sloupce
post_id
shodnou s $post->id
. Toho můžeme docílit voláním
$post->related('comments')
. Ano, takhle jednoduše. Podívejme se na výsledný kód:
public function renderShow(int $id): void
{
...
$this->template->post = $post;
$this->template->comments = $post->related('comments')->order('created_at');
}
A šablonu:
...
<h2>Komentáře</h2>
<div class="comments">
{foreach $comments as $comment}
<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
{$comment->name}
</a></b> napsal:</p>
<div>{$comment->content}</div>
{/foreach}
</div>
...
Všimněte si speciálního atributu n:tag-if
. Již víte jak n:atributy
fungují. Pokud k atributu
připojíte předponu tag-
, funkcionalita se aplikuje pouze na HTML tag, ne na jeho obsah. Toto nám umožňuje
udělat ze jména komentátora odkaz pouze v případě, že poskytl svůj e-mail. Tyto dvě řádky jsou identické:
<strong n:tag-if="$important"> Dobrý den! </strong>
{if $important}<strong>{/if} Dobrý den! {if $important}</strong>{/if}