Komentarji

Blog smo naložili na spletni strežnik in z uporabo Adminerja objavili nekaj zelo zanimivih prispevkov. Ljudje berejo naš blog in so nad njim zelo navdušeni. Vsak dan prejemamo veliko e-poštnih sporočil s pohvalami. Toda kaj nam pomaga vsa ta pohvala, če jo imamo samo v e-pošti in je nihče ne more prebrati? Bolje bi bilo, če bi bralec lahko neposredno komentiral članek, tako da bi lahko vsak prebral, kako čudoviti smo.

Torej, programirajmo komentarje.

Ustvarjanje nove tabele

Zaženimo Adminer in ustvarimo tabelo comments z naslednjimi stolpci:

  • id int, označimo autoincrement (AI)
  • post_id, tuji ključ, ki se nanaša na tabelo posts
  • name varchar, length 255
  • email varchar, length 255
  • content text
  • created_at timestamp

Tabela bi torej morala izgledati nekako takole:

Ne pozabite ponovno uporabiti shrambe 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;

Obrazec za komentiranje

Najprej moramo ustvariti obrazec, ki bo uporabnikom omogočal komentiranje prispevkov. Nette Framework ima odlično podporo za obrazce. Lahko jih konfiguriramo v presenterju in izrišemo v predlogi.

Nette Framework uporablja koncept komponent. Komponenta je ponovno uporabljiv razred ali del kode, ki ga je mogoče priložiti drugi komponenti. Celo presenter je komponenta. Vsaka komponenta je ustvarjena preko tovarne. Torej bomo ustvarili tovarno za obrazec za komentarje v presenterju PostPresenter.

protected function createComponentCommentForm(): Form
{
	$form = new Form; // pomeni Nette\Application\UI\Form

	$form->addText('name', 'Ime:')
		->setRequired();

	$form->addEmail('email', 'E-pošta:');

	$form->addTextArea('content', 'Komentar:')
		->setRequired();

	$form->addSubmit('send', 'Objavi komentar');

	return $form;
}

Pojasnimo si to malo bolje. Prva vrstica ustvari novo instanco komponente Form. Naslednje metode priključijo HTML vnose v definicijo tega obrazca. ->addText se bo izrisal kot <input type="text" name="name"> z <label>Ime:</label>. Kot verjetno že pravilno ugibate, se ->addTextArea izriše kot <textarea> in ->addSubmit kot <input type="submit">. Obstaja še veliko podobnih metod, vendar te zaenkrat za ta obrazec zadostujejo. Več si lahko preberete v dokumentaciji.

Če je obrazec že definiran v presenterju, ga lahko izrišemo (prikažemo) v predlogi. To storimo tako, da na konec predloge, ki izrisuje en konkreten prispevek, v Post/show.latte postavimo oznako {control}. Ker se komponenta imenuje commentForm (ime izhaja iz imena metode createComponentCommentForm), bo oznaka izgledala takole:

...
<h2>Vnesite nov komentar</h2>

{control commentForm}

Če si zdaj ogledate stran s podrobnostmi prispevka, boste na njenem koncu videli nov obrazec za komentarje.

Shranjevanje v podatkovno bazo

Ste že poskusili izpolniti obrazec in ga poslati? Verjetno ste opazili, da obrazec pravzaprav nič ne naredi. Povezati moramo callback metodo, ki bo shranila poslana podatke.

Na vrstico pred return v tovarni za komponento commentForm postavimo naslednjo vrstico:

$form->onSuccess[] = $this->commentFormSucceeded(...);

Prejšnji zapis pomeni “po uspešnem pošiljanju obrazca pokliči metodo commentFormSucceeded iz trenutnega presenterja”. Ta metoda pa še ne obstaja. Ustvarimo jo torej:

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('Hvala za komentar', 'success');
	$this->redirect('this');
}

To metodo postavimo neposredno za tovarno obrazca commentForm.

Ta nova metoda ima en argument, ki je instanca obrazca, ki je bil poslan – ustvarjen s tovarno. Poslane vrednosti dobimo v $data. Nato shranimo podatke v podatkovno tabelo comments.

Tu sta še dve metodi, ki si zaslužita pojasnilo. Metoda redirect dobesedno preusmeri nazaj na trenutno stran. To je primerno storiti po vsakem pošiljanju obrazca, če je vseboval veljavne podatke in je callback izvedel operacijo, kot je bilo predvideno. Prav tako, če stran preusmerimo po pošiljanju obrazca, ne bomo videli dobro znanega sporočila Želite ponovno poslati podatke iz obrazca?, ki ga včasih lahko vidimo v brskalniku. (Na splošno velja, da bi po pošiljanju obrazca z metodo POST vedno morala slediti preusmeritev na GET akcijo.)

Metoda flashMessage je namenjena obveščanju uporabnika o rezultatu neke operacije. Ker preusmerjamo, sporočila ni mogoče preprosto predati predlogi in ga izrisati. Zato je tu ta metoda, ki si to sporočilo shrani in ga naredi dostopnega ob naslednjem nalaganju strani. Flash sporočila se izrisujejo v glavni predlogi app/Presentation/@layout.latte in izgledajo takole:

<div n:foreach="$flashes as $flash" n:class="flash, $flash->type">
	{$flash->message}
</div>

Kot že vemo, se flash sporočila samodejno predajo predlogi, zato o tem ni treba veliko razmišljati, preprosto deluje. Za več informacij obiščite dokumentacijo.

Izrisovanje komentarjev

To je ena tistih stvari, ki jih boste preprosto vzljubili. Nette Database ima odlično funkcijo imenovano Explorer. Se še spomnite, da smo tabele v podatkovni bazi namenoma ustvarjali z InnoDB shrambo? Adminer je tako ustvaril nekaj, čemur se reče tuji ključi, ki nam bodo prihranili veliko dela.

Nette Database Explorer uporablja tuje ključe za reševanje medsebojnih odnosov med tabelami in na podlagi poznavanja teh odnosov zna samodejno ustvariti podatkovne poizvedbe.

Kot se zagotovo spomnite, smo v predlogo predali spremenljivko $post z metodo PostPresenter::renderShow() in zdaj želimo iterirati skozi vse komentarje, ki imajo vrednost stolpca post_id enako $post->id. To lahko dosežemo s klicem $post->related('comments'). Da, tako preprosto. Poglejmo si končno kodo:

public function renderShow(int $id): void
{
	...
	$this->template->post = $post;
	$this->template->comments = $post->related('comments')->order('created_at');
}

In predlogo:

...
<h2>Komentarji</h2>

<div class="comments">
	{foreach $comments as $comment}
		<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
			{$comment->name}
		</a></b> napisal:</p>

		<div>{$comment->content}</div>
	{/foreach}
</div>
...

Opazite poseben atribut n:tag-if. Že veste, kako delujejo n:atributi. Če atributu dodate predpono tag-, se funkcionalnost uporabi samo za HTML oznako, ne pa za njeno vsebino. To nam omogoča, da iz imena komentatorja naredimo povezavo samo v primeru, da je navedel svoj e-poštni naslov. Ti dve vrstici sta identični:

<strong n:tag-if="$important"> Dober dan! </strong>

{if $important}<strong>{/if} Dober dan! {if $important}</strong>{/if}