Σχόλια

Ανεβάσαμε το blog στον webserver και δημοσιεύσαμε μερικές πολύ ενδιαφέρουσες αναρτήσεις χρησιμοποιώντας το Adminer. Οι άνθρωποι διαβάζουν το blog μας και είναι πολύ ενθουσιασμένοι με αυτό. Λαμβάνουμε πολλά email με επαίνους κάθε μέρα. Αλλά τι νόημα έχει όλος αυτός ο έπαινος αν τον έχουμε μόνο στο email και κανείς δεν μπορεί να τον διαβάσει; Θα ήταν καλύτερο αν ο αναγνώστης μπορούσε να σχολιάσει απευθείας το άρθρο, ώστε όλοι να μπορούν να διαβάσουν πόσο καταπληκτικοί είμαστε.

Ας προγραμματίσουμε λοιπόν τα σχόλια.

Δημιουργία νέου πίνακα

Θα ενεργοποιήσουμε το Adminer και θα δημιουργήσουμε έναν πίνακα comments με τις ακόλουθες στήλες:

  • id int, επιλέγουμε autoincrement (AI)
  • post_id, ξένο κλειδί που αναφέρεται στον πίνακα posts
  • name varchar, length 255
  • email varchar, length 255
  • content text
  • created_at timestamp

Ο πίνακας θα πρέπει λοιπόν να μοιάζει κάπως έτσι:

Μην ξεχάσετε να χρησιμοποιήσετε ξανά την αποθήκευση 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;

Φόρμα σχολιασμού

Πρώτα, πρέπει να δημιουργήσουμε μια φόρμα που θα επιτρέπει στους χρήστες να σχολιάζουν τις αναρτήσεις. Το Nette Framework έχει εκπληκτική υποστήριξη για φόρμες. Μπορούμε να τις διαμορφώσουμε στον presenter και να τις αποδώσουμε στο template.

Το Nette Framework χρησιμοποιεί την έννοια των components. Ένα component είναι μια επαναχρησιμοποιήσιμη κλάση ή τμήμα κώδικα που μπορεί να επισυναφθεί σε άλλο component. Ακόμη και ο presenter είναι ένα component. Κάθε component δημιουργείται μέσω ενός factory. Θα δημιουργήσουμε λοιπόν ένα factory για τη φόρμα σχολίων στον presenter PostPresenter.

protected function createComponentCommentForm(): Form
{
	$form = new Form; // σημαίνει Nette\Application\UI\Form

	$form->addText('name', 'Όνομα:')
		->setRequired();

	$form->addEmail('email', 'E-mail:');

	$form->addTextArea('content', 'Σχόλιο:')
		->setRequired();

	$form->addSubmit('send', 'Δημοσίευση σχολίου');

	return $form;
}

Ας το εξηγήσουμε λίγο ξανά. Η πρώτη γραμμή δημιουργεί μια νέα παρουσία του component Form. Οι επόμενες μέθοδοι επισυνάπτουν HTML inputs στον ορισμό αυτής της φόρμας. Το ->addText θα αποδοθεί ως <input type="text" name="name"> με <label>Όνομα:</label>. Όπως πιθανώς μαντεύετε σωστά, το ->addTextArea θα αποδοθεί ως <textarea> και το ->addSubmit ως <input type="submit">. Υπάρχουν πολλές παρόμοιες μέθοδοι, αλλά αυτές αρκούν για αυτήν τη φόρμα προς το παρόν. Μπορείτε να διαβάσετε περισσότερα στην τεκμηρίωση.

Εάν η φόρμα έχει ήδη οριστεί στον presenter, μπορούμε να την αποδώσουμε (εμφανίσουμε) στο template. Αυτό γίνεται τοποθετώντας το tag {control} στο τέλος του template που αποδίδει μια συγκεκριμένη ανάρτηση, στο Post/show.latte. Επειδή το component ονομάζεται commentForm (το όνομα προέρχεται από το όνομα της μεθόδου createComponentCommentForm), το tag θα μοιάζει έτσι:

...
<h2>Εισαγάγετε νέα ανάρτηση</h2>

{control commentForm}

Τώρα, αν εμφανίσετε τη σελίδα με τις λεπτομέρειες της ανάρτησης, θα δείτε μια νέα φόρμα σχολίων στο τέλος της.

Αποθήκευση στη βάση δεδομένων

Έχετε ήδη δοκιμάσει να συμπληρώσετε και να υποβάλετε τη φόρμα; Πιθανόν να παρατηρήσατε ότι η φόρμα στην πραγματικότητα δεν κάνει τίποτα. Πρέπει να επισυνάψουμε μια μέθοδο callback που θα αποθηκεύσει τα υποβληθέντα δεδομένα.

Στη γραμμή πριν από το return στο factory για το component commentForm, τοποθετούμε την ακόλουθη γραμμή:

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

Η προηγούμενη σύνταξη σημαίνει “μετά την επιτυχή υποβολή της φόρμας, καλέστε τη μέθοδο commentFormSucceeded από τον τρέχοντα presenter”. Ωστόσο, αυτή η μέθοδος δεν υπάρχει ακόμα. Ας τη δημιουργήσουμε λοιπόν:

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('Ευχαριστώ για το σχόλιο', 'success');
	$this->redirect('this');
}

Θα τοποθετήσουμε αυτήν τη μέθοδο ακριβώς μετά το factory της φόρμας commentForm.

Αυτή η νέα μέθοδος έχει ένα όρισμα, το οποίο είναι μια παρουσία της φόρμας που υποβλήθηκε – δημιουργήθηκε από το factory. Λαμβάνουμε τις υποβληθείσες τιμές στο $data. Και στη συνέχεια αποθηκεύουμε τα δεδομένα στον πίνακα της βάσης δεδομένων comments.

Υπάρχουν ακόμα δύο μέθοδοι που αξίζουν εξήγηση. Η μέθοδος redirect κυριολεκτικά ανακατευθύνει πίσω στην τρέχουσα σελίδα. Αυτό είναι καλό να γίνεται μετά από κάθε υποβολή φόρμας, εάν περιείχε έγκυρα δεδομένα και το callback εκτέλεσε τη λειτουργία όπως έπρεπε. Επίσης, εάν ανακατευθύνουμε τη σελίδα μετά την υποβολή της φόρμας, δεν θα δούμε το γνωστό μήνυμα Θέλετε να υποβάλετε ξανά τα δεδομένα της φόρμας;, το οποίο μερικές φορές μπορούμε να δούμε στον browser. (Γενικά, μετά την υποβολή μιας φόρμας με τη μέθοδο POST, θα πρέπει πάντα να ακολουθεί ανακατεύθυνση σε μια GET action.)

Η μέθοδος flashMessage είναι για την ενημέρωση του χρήστη σχετικά με το αποτέλεσμα κάποιας λειτουργίας. Επειδή ανακατευθύνουμε, το μήνυμα δεν μπορεί απλά να περάσει στο template και να αποδοθεί. Γι' αυτό υπάρχει αυτή η μέθοδος, η οποία αποθηκεύει αυτό το μήνυμα και το καθιστά διαθέσιμο στην επόμενη φόρτωση της σελίδας. Τα flash μηνύματα αποδίδονται στο κύριο template app/Presentation/@layout.latte και μοιάζουν κάπως έτσι:

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

Όπως ήδη γνωρίζουμε, τα flash μηνύματα περνούν αυτόματα στο template, οπότε δεν χρειάζεται να το σκεφτόμαστε πολύ, απλά λειτουργεί. Για περισσότερες πληροφορίες επισκεφθείτε την τεκμηρίωση.

Απόδοση σχολίων

Αυτό είναι ένα από τα πράγματα που απλά θα λατρέψετε. Η Nette Database έχει μια εξαιρετική λειτουργία που ονομάζεται Explorer. Θυμάστε ακόμα ότι δημιουργήσαμε τους πίνακες στη βάση δεδομένων χρησιμοποιώντας την αποθήκευση InnoDB; Το Adminer δημιούργησε έτσι κάτι που ονομάζεται ξένα κλειδιά, τα οποία θα μας γλιτώσουν από πολλή δουλειά.

Η Nette Database Explorer χρησιμοποιεί ξένα κλειδιά για να επιλύσει την αμοιβαία σχέση μεταξύ των πινάκων και από τη γνώση αυτών των σχέσεων μπορεί να δημιουργήσει αυτόματα ερωτήματα βάσης δεδομένων.

Όπως σίγουρα θυμάστε, περάσαμε τη μεταβλητή $post στο template χρησιμοποιώντας τη μέθοδο PostPresenter::renderShow() και τώρα θέλουμε να επαναλάβουμε όλα τα σχόλια που έχουν την τιμή της στήλης post_id ίδια με το $post->id. Μπορούμε να το πετύχουμε καλώντας $post->related('comments'). Ναι, τόσο απλά. Ας δούμε τον τελικό κώδικα:

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

Και το template:

...
<h2>Σχόλια</h2>

<div class="comments">
	{foreach $comments as $comment}
		<p><b><a href="mailto:{$comment->email}" n:tag-if="$comment->email">
			{$comment->name}
		</a></b> έγραψε:</p> {* Translate "napsal:" *}

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

Παρατηρήστε το ειδικό attribute n:tag-if. Ήδη ξέρετε πώς λειτουργούν τα n:attributes. Αν επισυνάψετε το πρόθεμα tag- στο attribute, η λειτουργικότητα εφαρμόζεται μόνο στο HTML tag, όχι στο περιεχόμενό του. Αυτό μας επιτρέπει να κάνουμε το όνομα του σχολιαστή σύνδεσμο μόνο στην περίπτωση που παρείχε το email του. Αυτές οι δύο γραμμές είναι πανομοιότυπες:

<strong n:tag-if="$important"> Καλημέρα! </strong>

{if $important}<strong>{/if} Καλημέρα! {if $important}</strong>{/if}