Nette Documentation Preview

syntax
Διαδραστικά στοιχεία
********************

<div class=perex>

Τα συστατικά είναι ξεχωριστά επαναχρησιμοποιήσιμα αντικείμενα που τοποθετούμε σε σελίδες. Μπορεί να είναι φόρμες, datagrids, δημοσκοπήσεις, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. Θα δείξουμε:

- Πώς να χρησιμοποιήσετε τα συστατικά;
- πώς να τα γράψετε;
- Τι είναι τα σήματα;

</div>

Η Nette διαθέτει ένα ενσωματωμένο σύστημα συστατικών. Οι παλαιότεροι από εσάς μπορεί να θυμάστε κάτι παρόμοιο από τους Delphi ή τις ASP.NET Web Forms. Το React ή το Vue.js είναι χτισμένο πάνω σε κάτι εξ αποστάσεως παρόμοιο. Ωστόσο, στον κόσμο των πλαισίων PHP, αυτό είναι ένα εντελώς μοναδικό χαρακτηριστικό.

Ταυτόχρονα, τα συστατικά αλλάζουν ριζικά την προσέγγιση στην ανάπτυξη εφαρμογών. Μπορείτε να συνθέσετε σελίδες από προπαρασκευασμένες μονάδες. Χρειάζεστε πλέγμα δεδομένων στη διαχείριση; Μπορείτε να το βρείτε στο [Componette |https://componette.org/search/component], ένα αποθετήριο πρόσθετων στοιχείων ανοικτού κώδικα (όχι μόνο συστατικών) για το Nette, και απλά να το επικολλήσετε στον παρουσιαστή.

Μπορείτε να ενσωματώσετε οποιονδήποτε αριθμό συστατικών στον παρουσιαστή. Και μπορείτε να εισαγάγετε άλλα συστατικά σε ορισμένα συστατικά. Έτσι δημιουργείται ένα δέντρο συστατικών με ρίζα τον παρουσιαστή.


Μέθοδοι εργοστασίου .[#toc-factory-methods]
===========================================

Πώς τοποθετούνται και στη συνέχεια χρησιμοποιούνται τα στοιχεία στον παρουσιαστή; Συνήθως με τη χρήση μεθόδων εργοστασίου.

Το εργοστάσιο συστατικών είναι ένας κομψός τρόπος να δημιουργούνται συστατικά μόνο όταν πραγματικά χρειάζονται (lazy / on-demand). Όλη η μαγεία βρίσκεται στην υλοποίηση μιας μεθόδου που ονομάζεται `createComponent<Name>()`, όπου `<Name>` είναι το όνομα του συστατικού, που θα δημιουργήσει και θα επιστρέψει.

```php .{file:DefaultPresenter.php}
class DefaultPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentPoll(): PollControl
	{
		$poll = new PollControl;
		$poll->items = $this->item;
		return $poll;
	}
}
```

Επειδή όλα τα συστατικά δημιουργούνται σε ξεχωριστές μεθόδους, ο κώδικας είναι πιο καθαρός και πιο ευανάγνωστος.

.[note]
Τα ονόματα των συστατικών αρχίζουν πάντα με πεζό γράμμα, αν και στο όνομα της μεθόδου γράφονται με κεφαλαίο.

Δεν καλούμε ποτέ απευθείας τα εργοστάσια, καλούνται αυτόματα, όταν χρησιμοποιούμε συστατικά για πρώτη φορά. Χάρη σε αυτό, ένα συστατικό δημιουργείται την κατάλληλη στιγμή και μόνο αν πραγματικά χρειάζεται. Αν δεν θα χρησιμοποιούσαμε το συστατικό (για παράδειγμα σε κάποια αίτηση AJAX, όπου επιστρέφουμε μόνο ένα μέρος της σελίδας, ή όταν τα μέρη είναι αποθηκευμένα στην προσωρινή μνήμη), δεν θα δημιουργούνταν καν και θα εξοικονομούσαμε την απόδοση του διακομιστή.

```php .{file:DefaultPresenter.php}
// έχουμε πρόσβαση στο στοιχείο και αν ήταν η πρώτη φορά,
// καλεί την createComponentPoll() για να το δημιουργήσει
$poll = $this->getComponent('poll');
// εναλλακτική σύνταξη: $poll = $this['poll'],
```

Στο πρότυπο, μπορείτε να αποδώσετε ένα συστατικό χρησιμοποιώντας την ετικέτα [{control} |#Rendering]. Έτσι, δεν χρειάζεται να περνάτε χειροκίνητα τα συστατικά στο πρότυπο.

```latte
<h2>Please Vote</h2>

{control poll}
```


Στυλ Hollywood .[#toc-hollywood-style]
======================================

Τα στοιχεία χρησιμοποιούν συνήθως μια δροσερή τεχνική, την οποία αποκαλούμε στυλ Χόλιγουντ. Σίγουρα γνωρίζετε το κλισέ που ακούνε συχνά οι ηθοποιοί στα κάστινγκ: "Μη μας καλέσετε, θα σας καλέσουμε εμείς". Και περί αυτού πρόκειται.

Στη Nette, αντί να χρειάζεται να κάνετε συνεχώς ερωτήσεις ("υποβλήθηκε η φόρμα;", "ήταν έγκυρη;" ή "πάτησε κανείς αυτό το κουμπί;"), λέτε στο πλαίσιο "όταν συμβεί αυτό, καλέστε αυτή τη μέθοδο" και αφήνετε την περαιτέρω εργασία σε αυτό. Αν προγραμματίζετε σε JavaScript, είστε εξοικειωμένοι με αυτό το στυλ προγραμματισμού. Γράφετε συναρτήσεις που καλούνται όταν συμβαίνει ένα συγκεκριμένο γεγονός. Και η μηχανή περνάει τις κατάλληλες παραμέτρους σε αυτές.

Αυτό αλλάζει εντελώς τον τρόπο με τον οποίο γράφετε εφαρμογές. Όσο περισσότερες εργασίες μπορείτε να αναθέσετε στο πλαίσιο, τόσο λιγότερη δουλειά έχετε. Και τόσο λιγότερο μπορείτε να ξεχάσετε.


Πώς να γράψετε ένα συστατικό .[#toc-how-to-write-a-component]
=============================================================

Με τον όρο συστατικό συνήθως εννοούμε απογόνους της κλάσης [api:Nette\Application\UI\Control]. Ο ίδιος ο παρουσιαστής [api:Nette\Application\UI\Presenter] είναι επίσης απόγονος της κλάσης `Control`.

```php .{file:PollControl.php}
use Nette\Application\UI\Control;

class PollControl extends Control
{
}
```


Παρουσίαση .[#toc-rendering]
============================

Γνωρίζουμε ήδη ότι η ετικέτα `{control componentName}` χρησιμοποιείται για τη σχεδίαση ενός στοιχείου. Στην πραγματικότητα καλεί τη μέθοδο `render()` του συστατικού, στην οποία αναλαμβάνουμε την απόδοση. Έχουμε, όπως ακριβώς και στον παρουσιαστή, ένα [πρότυπο Latte |templates] στη μεταβλητή `$this->template`, στην οποία περνάμε τις παραμέτρους. Σε αντίθεση με τη χρήση σε έναν παρουσιαστή, πρέπει να καθορίσουμε ένα αρχείο προτύπου και να το αφήσουμε να κάνει render:

```php .{file:PollControl.php}
public function render(): void
{
	// θα βάλουμε κάποιες παραμέτρους στο πρότυπο
	$this->template->param = $value;
	// και θα το σχεδιάσουμε
	$this->template->render(__DIR__ . '/poll.latte');
}
```

Η ετικέτα `{control}` επιτρέπει να περάσουμε παραμέτρους στη μέθοδο `render()`:

```latte
{control poll $id, $message}
```

```php .{file:PollControl.php}
public function render(int $id, string $message): void
{
	// ...
}
```

Μερικές φορές ένα συστατικό μπορεί να αποτελείται από διάφορα μέρη που θέλουμε να απεικονίσουμε ξεχωριστά. Για κάθε ένα από αυτά θα δημιουργήσουμε τη δική μας μέθοδο απόδοσης, εδώ είναι για παράδειγμα το `renderPaginator()`:

```php .{file:PollControl.php}
public function renderPaginator(): void
{
	// ...
}
```

Και στο πρότυπο στη συνέχεια την καλούμε χρησιμοποιώντας:

```latte
{control poll:paginator}
```

Για καλύτερη κατανόηση είναι καλό να γνωρίζουμε πώς η ετικέτα μεταφράζεται σε κώδικα PHP.

```latte
{control poll}
{control poll:paginator 123, 'hello'}
```

Αυτό μεταγλωττίζεται σε:

```php
$control->getComponent('poll')->render();
$control->getComponent('poll')->renderPaginator(123, 'hello');
```

`getComponent()` `poll` και στη συνέχεια καλείται η μέθοδος `render()` ή `renderPaginator()`, αντίστοιχα, σε αυτό.

.[caution]
Εάν οπουδήποτε στο τμήμα παραμέτρων χρησιμοποιείται **`=>`**, όλες οι παράμετροι θα περιτυλιχθούν με έναν πίνακα και θα περάσουν ως πρώτο όρισμα:

```latte
{control poll, id: 123, message: 'hello'}
```

compiles to:

```php
$control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']);
```

Απόδοση του υπο-στοιχείου:

```latte
{control cartControl-someForm}
```

μεταγλωττίζει σε:

```php
$control->getComponent("cartControl-someForm")->render();
```

Τα συστατικά, όπως οι παρουσιαστές, μεταβιβάζουν αυτόματα διάφορες χρήσιμες μεταβλητές στα πρότυπα:

- `$basePath` είναι μια απόλυτη διαδρομή URL στο root dir (για παράδειγμα `/CD-collection`)
- `$baseUrl` είναι μια απόλυτη διεύθυνση URL στο root dir (για παράδειγμα `http://localhost/CD-collection`)
- `$user` είναι ένα αντικείμενο [που αντιπροσωπεύει τον χρήστη |security:authentication]
- `$presenter` είναι ο τρέχων παρουσιαστής
- `$control` είναι το τρέχον συστατικό
- `$flashes` κατάλογος [μηνυμάτων |#flash-messages] που αποστέλλονται από τη μέθοδο `flashMessage()`


Σήμα .[#toc-signal]
===================

Γνωρίζουμε ήδη ότι η πλοήγηση στην εφαρμογή Nette συνίσταται στη σύνδεση ή την ανακατεύθυνση σε ζεύγη `Presenter:action`. Τι γίνεται όμως αν θέλουμε απλώς να εκτελέσουμε μια ενέργεια στην **τρέχουσα σελίδα**; Για παράδειγμα, να αλλάξουμε τη σειρά ταξινόμησης της στήλης στον πίνακα, να διαγράψουμε στοιχείο, να αλλάξουμε τη λειτουργία φωτός/σκοταδιού, να υποβάλουμε τη φόρμα, να ψηφίσουμε στην ψηφοφορία κ.λπ.

Αυτός ο τύπος αιτήματος ονομάζεται σήμα. Και όπως οι ενέργειες επικαλούνται μεθόδους `action<Action>()` ή `render<Action>()`, τα σήματα καλούν μεθόδους `handle<Signal>()`. Ενώ η έννοια της ενέργειας (ή της προβολής) αφορά μόνο τους παρουσιαστές, τα σήματα εφαρμόζονται σε όλα τα στοιχεία. Και επομένως και στους παρουσιαστές, επειδή το `UI\Presenter` είναι απόγονος του `UI\Control`.

```php
public function handleClick(int $x, int $y): void
{
	// ... επεξεργασία σήματος ...
}
```

Ο σύνδεσμος που καλεί το σήμα δημιουργείται με τον συνήθη τρόπο, δηλαδή στο πρότυπο με το χαρακτηριστικό `n:href` ή την ετικέτα `{link}`, στον κώδικα με τη μέθοδο `link()`. Περισσότερα στο κεφάλαιο [Δημιουργία συνδέσμων URL |creating-links#Links to Signal].

```latte
<a n:href="click! $x, $y">click here</a>
```

Το σήμα καλείται πάντα στον τρέχοντα παρουσιαστή και προβολή, επομένως δεν είναι δυνατή η σύνδεση με το σήμα σε διαφορετικό παρουσιαστή/δράση.

Έτσι, το σήμα προκαλεί την επαναφόρτωση της σελίδας με τον ίδιο ακριβώς τρόπο όπως στην αρχική αίτηση, μόνο που επιπλέον καλεί τη μέθοδο χειρισμού του σήματος με τις κατάλληλες παραμέτρους. Εάν η μέθοδος δεν υπάρχει, δημιουργείται η εξαίρεση [api:Nette\Application\UI\BadSignalException], η οποία εμφανίζεται στο χρήστη ως σελίδα σφάλματος 403 Forbidden.


Αποσπάσματα και AJAX .[#toc-snippets-and-ajax]
==============================================

Τα σήματα μπορεί να σας θυμίζουν λίγο AJAX: χειριστές που καλούνται στην τρέχουσα σελίδα. Και έχετε δίκιο, τα σήματα καλούνται πραγματικά συχνά με τη χρήση AJAX, και στη συνέχεια μεταδίδουμε μόνο τα αλλαγμένα τμήματα της σελίδας στο πρόγραμμα περιήγησης. Ονομάζονται αποσπάσματα. Περισσότερες πληροφορίες μπορείτε να βρείτε [στη σελίδα σχετικά με το AJAX |ajax].


Μηνύματα Flash .[#toc-flash-messages]
=====================================

Ένα στοιχείο έχει τη δική του αποθήκευση μηνυμάτων flash ανεξάρτητα από τον παρουσιαστή. Πρόκειται για μηνύματα που, για παράδειγμα, ενημερώνουν για το αποτέλεσμα της λειτουργίας. Ένα σημαντικό χαρακτηριστικό των μηνυμάτων flash είναι ότι είναι διαθέσιμα στο πρότυπο ακόμη και μετά την ανακατεύθυνση. Ακόμη και μετά την εμφάνισή τους, θα παραμείνουν ζωντανά για άλλα 30 δευτερόλεπτα - για παράδειγμα, σε περίπτωση που ο χρήστης θα ανανεώσει ακούσια τη σελίδα - το μήνυμα δεν θα χαθεί.

Η αποστολή γίνεται με τη μέθοδο [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Η πρώτη παράμετρος είναι το κείμενο του μηνύματος ή το αντικείμενο `stdClass` που αντιπροσωπεύει το μήνυμα. Η προαιρετική δεύτερη παράμετρος είναι ο τύπος του (σφάλμα, προειδοποίηση, πληροφορία κ.λπ.). Η μέθοδος `flashMessage()` επιστρέφει ένα instance του flash message ως αντικείμενο stdClass στο οποίο μπορείτε να περάσετε πληροφορίες.

```php
$this->flashMessage('Item was deleted.');
$this->redirect(/* ... */); // και ανακατεύθυνση
```

Στο πρότυπο, τα μηνύματα αυτά είναι διαθέσιμα στη μεταβλητή `$flashes` ως αντικείμενα `stdClass`, τα οποία περιέχουν τις ιδιότητες `message` (κείμενο μηνύματος), `type` (τύπος μηνύματος) και μπορούν να περιέχουν τις ήδη αναφερθείσες πληροφορίες χρήστη. Τα σχεδιάζουμε ως εξής:

```latte
{foreach $flashes as $flash}
	<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
```


Μόνιμες παράμετροι .[#toc-persistent-parameters]
================================================

Οι μόνιμες παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης των στοιχείων μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα συνόδου, μεταφέρονται στη διεύθυνση URL. Και μεταφέρονται αυτόματα, συμπεριλαμβανομένων των συνδέσμων που δημιουργούνται σε άλλα στοιχεία στην ίδια σελίδα.

Για παράδειγμα, έχετε ένα στοιχείο σελιδοποίησης περιεχομένου. Μπορεί να υπάρχουν πολλά τέτοια στοιχεία σε μια σελίδα. Και θέλετε όλα τα συστατικά να παραμένουν στην τρέχουσα σελίδα τους όταν κάνετε κλικ στο σύνδεσμο. Επομένως, κάνουμε τον αριθμό σελίδας (`page`) μια μόνιμη παράμετρο.

Η δημιουργία μιας μόνιμης παραμέτρου είναι εξαιρετικά εύκολη στη Nette. Απλά δημιουργήστε μια δημόσια ιδιότητα και επισημάνετέ την με το χαρακτηριστικό: (προηγουμένως χρησιμοποιούνταν το `/** @persistent */` )

```php
use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική

class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1; // πρέπει να είναι δημόσια
}
```

Συνιστούμε να συμπεριλάβετε τον τύπο δεδομένων (π.χ. `int`) μαζί με την ιδιότητα και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Validation of Persistent Parameters].

Μπορείτε να αλλάξετε την τιμή μιας μόνιμης παραμέτρου κατά τη δημιουργία ενός συνδέσμου:

```latte
<a n:href="this page: $page + 1">next</a>
```

Ή μπορεί να *επαναρυθμιστεί*, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Τότε θα πάρει την προεπιλεγμένη τιμή της:

```latte
<a n:href="this page: null">reset</a>
```


Εμμένουσες συνιστώσες .[#toc-persistent-components]
===================================================

Όχι μόνο οι παράμετροι αλλά και τα συστατικά μπορούν να είναι μόνιμα. Οι μόνιμες παράμετροι τους μεταφέρονται επίσης μεταξύ διαφορετικών ενεργειών ή μεταξύ διαφορετικών παρουσιαστών. Χαρακτηρίζουμε τα μόνιμα συστατικά με αυτές τις σημειώσεις για την κλάση presenter. Για παράδειγμα, εδώ επισημαίνουμε τα συστατικά `calendar` και `poll` ως εξής:

```php
/**
 * @persistent(calendar, poll)
 */
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}
```

Δεν χρειάζεται να επισημάνετε τα υποστοιχεία ως μόνιμα, είναι μόνιμα αυτόματα.

Στην PHP 8, μπορείτε επίσης να χρησιμοποιήσετε χαρακτηριστικά για να επισημάνετε μόνιμα συστατικά:

```php
use Nette\Application\Attributes\Persistent;

#[Persistent('calendar', 'poll')]
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}
```


Εξαρτήματα με εξαρτήσεις .[#toc-components-with-dependencies]
=============================================================

Πώς να δημιουργήσετε συστατικά με εξαρτήσεις χωρίς να "μπερδέψετε" τους παρουσιαστές που θα τα χρησιμοποιήσουν; Χάρη στα έξυπνα χαρακτηριστικά του DI container στο Nette, όπως και με τη χρήση παραδοσιακών υπηρεσιών, μπορούμε να αφήσουμε το μεγαλύτερο μέρος της δουλειάς στο πλαίσιο.

Ας πάρουμε ως παράδειγμα ένα συστατικό που έχει εξάρτηση από την υπηρεσία `PollFacade`:

```php
class PollControl extends Control
{
	public function __construct(
		private int $id, // Id μιας δημοσκόπησης, για την οποία δημιουργείται το στοιχείο
		private PollFacade $facade,
	) {
	}

	public function handleVote(int $voteId): void
	{
		$this->facade->vote($id, $voteId);
		// ...
	}
}
```

Αν γράφαμε μια κλασική υπηρεσία, δεν θα υπήρχε λόγος ανησυχίας. Το DI container θα φρόντιζε αόρατα να περάσει όλες τις εξαρτήσεις. Αλλά συνήθως χειριζόμαστε τα συστατικά δημιουργώντας μια νέα τους περίπτωση απευθείας στον παρουσιαστή σε [μεθόδους εργοστασίου |#factory methods] `createComponent...()`. Αλλά το πέρασμα όλων των εξαρτήσεων όλων των συστατικών στον presenter για να τις περάσουμε στη συνέχεια στα συστατικά είναι δυσκίνητο. Και η ποσότητα του κώδικα που γράφεται...

Το λογικό ερώτημα είναι, γιατί δεν καταχωρούμε απλώς το συστατικό ως κλασική υπηρεσία, να το περάσουμε στον παρουσιαστή και στη συνέχεια να το επιστρέψουμε στη μέθοδο `createComponent...()`; Αλλά αυτή η προσέγγιση είναι ακατάλληλη, επειδή θέλουμε να μπορούμε να δημιουργήσουμε το συστατικό πολλές φορές.

Η σωστή λύση είναι να γράψουμε ένα εργοστάσιο για το συστατικό, δηλαδή μια κλάση που δημιουργεί το συστατικό για εμάς:

```php
class PollControlFactory
{
	public function __construct(
		private PollFacade $facade,
	) {
	}

	public function create(int $id): PollControl
	{
		return new PollControl($id, $this->facade);
	}
}
```

Τώρα εγγράφουμε την υπηρεσία μας στο DI container στη διαμόρφωση:

```neon
services:
	- PollControlFactory
```

Τέλος, θα χρησιμοποιήσουμε αυτό το εργοστάσιο στον παρουσιαστή μας:

```php
class PollPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private PollControlFactory $pollControlFactory,
	) {
	}

	protected function createComponentPollControl(): PollControl
	{
		$pollId = 1; // μπορούμε να περάσουμε την παράμετρο μας
		return $this->pollControlFactory->create($pollId);
	}
}
```

Το σπουδαίο είναι ότι το Nette DI μπορεί να [δημιουργήσει |dependency-injection:factory] τέτοια απλά εργοστάσια, οπότε αντί να γράψετε ολόκληρο τον κώδικα, χρειάζεται απλώς να γράψετε τη διεπαφή του:

```php
interface PollControlFactory
{
	public function create(int $id): PollControl;
}
```

Αυτό είναι όλο. Η Nette υλοποιεί εσωτερικά αυτή τη διεπαφή και την εγχέει στον παρουσιαστή μας, όπου μπορούμε να τη χρησιμοποιήσουμε. Επίσης, περνάει μαγικά την παράμετρο `$id` και την περίπτωση της κλάσης `PollFacade` στο στοιχείο μας.


Συστατικά σε βάθος .[#toc-components-in-depth]
==============================================

Τα συστατικά σε μια εφαρμογή Nette είναι τα επαναχρησιμοποιήσιμα μέρη μιας διαδικτυακής εφαρμογής που ενσωματώνουμε σε σελίδες, τα οποία αποτελούν το αντικείμενο αυτού του κεφαλαίου. Ποιες ακριβώς είναι οι δυνατότητες ενός τέτοιου συστατικού;

1) είναι δυνατό να αποδοθεί σε ένα πρότυπο
2) γνωρίζει [ποιο μέρος του εαυτού του |ajax#snippets] να αποδώσει κατά τη διάρκεια μιας αίτησης AJAX (αποσπάσματα)
3) έχει τη δυνατότητα να αποθηκεύει την κατάστασή του σε μια διεύθυνση URL (μόνιμες παράμετροι)
4) έχει τη δυνατότητα να ανταποκρίνεται σε ενέργειες του χρήστη (σήματα)
5) δημιουργεί μια ιεραρχική δομή (όπου η ρίζα είναι ο παρουσιαστής)

Κάθε μία από αυτές τις λειτουργίες χειρίζεται μία από τις κλάσεις της κληρονομικής γραμμής. Την απόδοση (1 + 2) χειρίζεται η κλάση [api:Nette\Application\UI\Control], την ενσωμάτωση στον [κύκλο ζωής |presenters#life-cycle-of-presenter] (3, 4) η κλάση [api:Nette\Application\UI\Component] και τη δημιουργία της ιεραρχικής δομής (5) οι κλάσεις [Container και Component |component-model:].

```
Nette\ComponentModel\Component  { IComponent }
|
+- Nette\ComponentModel\Container  { IContainer }
	|
	+- Nette\Application\UI\Component  { SignalReceiver, StatePersistent }
		|
		+- Nette\Application\UI\Control  { Renderable }
			|
			+- Nette\Application\UI\Presenter  { IPresenter }
```


Κύκλος ζωής του συστατικού .[#toc-life-cycle-of-component]
----------------------------------------------------------

[* lifecycle-component.svg *] *** *Κύκλος ζωής του συστατικού* .<>


Επικύρωση μόνιμων παραμέτρων .[#toc-validation-of-persistent-parameters]
------------------------------------------------------------------------

Οι τιμές των [μόνιμων παραμέτρων |#persistent parameters] που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες με τη μέθοδο `loadState()`. Ελέγχει επίσης αν ο τύπος δεδομένων που έχει καθοριστεί για την ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί.

Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, επειδή μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν ο αριθμός σελίδας `$this->page` είναι μεγαλύτερος από 0. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο `loadState()` που αναφέρθηκε παραπάνω:

```php
class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1;

	public function loadState(array $params): void
	{
		parent::loadState($params); // εδώ ορίζεται η σελίδα $this->page
		// ακολουθεί τον έλεγχο της τιμής του χρήστη:
		if ($this->page < 1) {
			$this->error();
		}
	}
}
```

Η αντίθετη διαδικασία, δηλαδή η συλλογή τιμών από persistent properites, αντιμετωπίζεται από τη μέθοδο `saveState()`.


Σήματα σε βάθος .[#toc-signals-in-depth]
----------------------------------------

Ένα σήμα προκαλεί επαναφόρτωση της σελίδας όπως και το αρχικό αίτημα (με εξαίρεση το AJAX) και καλεί τη μέθοδο `signalReceived($signal)` της οποίας η προεπιλεγμένη υλοποίηση στην κλάση `Nette\Application\UI\Component` προσπαθεί να καλέσει μια μέθοδο που αποτελείται από τις λέξεις `handle{Signal}`. Η περαιτέρω επεξεργασία βασίζεται στο συγκεκριμένο αντικείμενο. Τα αντικείμενα που είναι απόγονοι του `Component` (δηλ. `Control` και `Presenter`) προσπαθούν να καλέσουν το `handle{Signal}` με σχετικές παραμέτρους.

Με άλλα λόγια: λαμβάνεται ο ορισμός της μεθόδου `handle{Signal}` και όλες οι παράμετροι που ελήφθησαν στην αίτηση αντιστοιχίζονται με τις παραμέτρους της μεθόδου. Αυτό σημαίνει ότι η παράμετρος `id` από τη διεύθυνση URL αντιστοιχίζεται με την παράμετρο της μεθόδου `$id`, η `something` με την `$something` κ.ο.κ. Και αν η μέθοδος δεν υπάρχει, η μέθοδος `signalReceived` πετάει [μια εξαίρεση |api:Nette\Application\UI\BadSignalException].

Το σήμα μπορεί να ληφθεί από οποιοδήποτε συστατικό, παρουσιαστή αντικειμένου που υλοποιεί τη διεπαφή `SignalReceiver` εάν είναι συνδεδεμένο με το δέντρο συστατικών.

Οι κύριοι δέκτες σημάτων είναι το `Presenters` και τα οπτικά συστατικά που επεκτείνουν το `Control`. Ένα σήμα είναι ένα σημάδι για ένα αντικείμενο ότι πρέπει να κάνει κάτι - η δημοσκόπηση μετράει την ψήφο του χρήστη, το πλαίσιο με τις ειδήσεις πρέπει να ξεδιπλωθεί, η φόρμα στάλθηκε και πρέπει να επεξεργαστεί τα δεδομένα κ.ο.κ.

Η διεύθυνση URL για το σήμα δημιουργείται με τη χρήση της μεθόδου [Component::link() |api:Nette\Application\UI\Component::link()]. Ως παράμετρος `$destination` περνάμε το string `{signal}!` και ως `$args` έναν πίνακα με τα ορίσματα που θέλουμε να περάσουμε στον χειριστή του σήματος. Οι παράμετροι του σήματος συνδέονται με τη διεύθυνση URL του τρέχοντος παρουσιαστή/προβολής. **Η παράμετρος `?do` στη διεύθυνση URL καθορίζει το σήμα που καλείται.**

Η μορφή της είναι `{signal}` ή `{signalReceiver}-{signal}`. `{signalReceiver}` είναι το όνομα του στοιχείου στον παρουσιαστή. Αυτός είναι ο λόγος για τον οποίο η παύλα (ανακριβώς παύλα) δεν μπορεί να υπάρχει στο όνομα των συστατικών - χρησιμοποιείται για να χωρίσει το όνομα του συστατικού και του σήματος, αλλά είναι δυνατόν να συνθέσετε πολλά συστατικά.

Η μέθοδος [isSignalReceiver() |api:Nette\Application\UI\Presenter::isSignalReceiver()] επαληθεύει αν ένα συστατικό (πρώτο όρισμα) είναι δέκτης ενός σήματος (δεύτερο όρισμα). Το δεύτερο όρισμα μπορεί να παραλειφθεί - τότε διαπιστώνει αν το συστατικό είναι δέκτης οποιουδήποτε σήματος. Εάν το δεύτερο όρισμα είναι `true`, ελέγχει εάν το συστατικό ή οι απόγονοί του είναι δέκτες ενός σήματος.

Σε οποιαδήποτε φάση που προηγείται του `handle{Signal}` μπορεί να εκτελεστεί το σήμα χειροκίνητα καλώντας τη μέθοδο [processSignal() |api:Nette\Application\UI\Presenter::processSignal()] η οποία αναλαμβάνει την ευθύνη για την εκτέλεση του σήματος. Παίρνει το συστατικό δέκτη (αν δεν έχει οριστεί είναι ο ίδιος ο παρουσιαστής) και του στέλνει το σήμα.

Παράδειγμα:

```php
if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {
	$this->processSignal();
}
```

Το σήμα εκτελείται πρόωρα και δεν θα κληθεί ξανά.

Διαδραστικά στοιχεία

Τα συστατικά είναι ξεχωριστά επαναχρησιμοποιήσιμα αντικείμενα που τοποθετούμε σε σελίδες. Μπορεί να είναι φόρμες, datagrids, δημοσκοπήσεις, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. Θα δείξουμε:

  • Πώς να χρησιμοποιήσετε τα συστατικά;
  • πώς να τα γράψετε;
  • Τι είναι τα σήματα;

Η Nette διαθέτει ένα ενσωματωμένο σύστημα συστατικών. Οι παλαιότεροι από εσάς μπορεί να θυμάστε κάτι παρόμοιο από τους Delphi ή τις ASP.NET Web Forms. Το React ή το Vue.js είναι χτισμένο πάνω σε κάτι εξ αποστάσεως παρόμοιο. Ωστόσο, στον κόσμο των πλαισίων PHP, αυτό είναι ένα εντελώς μοναδικό χαρακτηριστικό.

Ταυτόχρονα, τα συστατικά αλλάζουν ριζικά την προσέγγιση στην ανάπτυξη εφαρμογών. Μπορείτε να συνθέσετε σελίδες από προπαρασκευασμένες μονάδες. Χρειάζεστε πλέγμα δεδομένων στη διαχείριση; Μπορείτε να το βρείτε στο Componette, ένα αποθετήριο πρόσθετων στοιχείων ανοικτού κώδικα (όχι μόνο συστατικών) για το Nette, και απλά να το επικολλήσετε στον παρουσιαστή.

Μπορείτε να ενσωματώσετε οποιονδήποτε αριθμό συστατικών στον παρουσιαστή. Και μπορείτε να εισαγάγετε άλλα συστατικά σε ορισμένα συστατικά. Έτσι δημιουργείται ένα δέντρο συστατικών με ρίζα τον παρουσιαστή.

Μέθοδοι εργοστασίου

Πώς τοποθετούνται και στη συνέχεια χρησιμοποιούνται τα στοιχεία στον παρουσιαστή; Συνήθως με τη χρήση μεθόδων εργοστασίου.

Το εργοστάσιο συστατικών είναι ένας κομψός τρόπος να δημιουργούνται συστατικά μόνο όταν πραγματικά χρειάζονται (lazy / on-demand). Όλη η μαγεία βρίσκεται στην υλοποίηση μιας μεθόδου που ονομάζεται createComponent<Name>(), όπου <Name> είναι το όνομα του συστατικού, που θα δημιουργήσει και θα επιστρέψει.

class DefaultPresenter extends Nette\Application\UI\Presenter
{
	protected function createComponentPoll(): PollControl
	{
		$poll = new PollControl;
		$poll->items = $this->item;
		return $poll;
	}
}

Επειδή όλα τα συστατικά δημιουργούνται σε ξεχωριστές μεθόδους, ο κώδικας είναι πιο καθαρός και πιο ευανάγνωστος.

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

Δεν καλούμε ποτέ απευθείας τα εργοστάσια, καλούνται αυτόματα, όταν χρησιμοποιούμε συστατικά για πρώτη φορά. Χάρη σε αυτό, ένα συστατικό δημιουργείται την κατάλληλη στιγμή και μόνο αν πραγματικά χρειάζεται. Αν δεν θα χρησιμοποιούσαμε το συστατικό (για παράδειγμα σε κάποια αίτηση AJAX, όπου επιστρέφουμε μόνο ένα μέρος της σελίδας, ή όταν τα μέρη είναι αποθηκευμένα στην προσωρινή μνήμη), δεν θα δημιουργούνταν καν και θα εξοικονομούσαμε την απόδοση του διακομιστή.

// έχουμε πρόσβαση στο στοιχείο και αν ήταν η πρώτη φορά,
// καλεί την createComponentPoll() για να το δημιουργήσει
$poll = $this->getComponent('poll');
// εναλλακτική σύνταξη: $poll = $this['poll'],

Στο πρότυπο, μπορείτε να αποδώσετε ένα συστατικό χρησιμοποιώντας την ετικέτα {control}. Έτσι, δεν χρειάζεται να περνάτε χειροκίνητα τα συστατικά στο πρότυπο.

<h2>Please Vote</h2>

{control poll}

Στυλ Hollywood

Τα στοιχεία χρησιμοποιούν συνήθως μια δροσερή τεχνική, την οποία αποκαλούμε στυλ Χόλιγουντ. Σίγουρα γνωρίζετε το κλισέ που ακούνε συχνά οι ηθοποιοί στα κάστινγκ: „Μη μας καλέσετε, θα σας καλέσουμε εμείς“. Και περί αυτού πρόκειται.

Στη Nette, αντί να χρειάζεται να κάνετε συνεχώς ερωτήσεις („υποβλήθηκε η φόρμα;“, „ήταν έγκυρη;“ ή „πάτησε κανείς αυτό το κουμπί;“), λέτε στο πλαίσιο „όταν συμβεί αυτό, καλέστε αυτή τη μέθοδο“ και αφήνετε την περαιτέρω εργασία σε αυτό. Αν προγραμματίζετε σε JavaScript, είστε εξοικειωμένοι με αυτό το στυλ προγραμματισμού. Γράφετε συναρτήσεις που καλούνται όταν συμβαίνει ένα συγκεκριμένο γεγονός. Και η μηχανή περνάει τις κατάλληλες παραμέτρους σε αυτές.

Αυτό αλλάζει εντελώς τον τρόπο με τον οποίο γράφετε εφαρμογές. Όσο περισσότερες εργασίες μπορείτε να αναθέσετε στο πλαίσιο, τόσο λιγότερη δουλειά έχετε. Και τόσο λιγότερο μπορείτε να ξεχάσετε.

Πώς να γράψετε ένα συστατικό

Με τον όρο συστατικό συνήθως εννοούμε απογόνους της κλάσης Nette\Application\UI\Control. Ο ίδιος ο παρουσιαστής Nette\Application\UI\Presenter είναι επίσης απόγονος της κλάσης Control.

use Nette\Application\UI\Control;

class PollControl extends Control
{
}

Παρουσίαση

Γνωρίζουμε ήδη ότι η ετικέτα {control componentName} χρησιμοποιείται για τη σχεδίαση ενός στοιχείου. Στην πραγματικότητα καλεί τη μέθοδο render() του συστατικού, στην οποία αναλαμβάνουμε την απόδοση. Έχουμε, όπως ακριβώς και στον παρουσιαστή, ένα πρότυπο Latte στη μεταβλητή $this->template, στην οποία περνάμε τις παραμέτρους. Σε αντίθεση με τη χρήση σε έναν παρουσιαστή, πρέπει να καθορίσουμε ένα αρχείο προτύπου και να το αφήσουμε να κάνει render:

public function render(): void
{
	// θα βάλουμε κάποιες παραμέτρους στο πρότυπο
	$this->template->param = $value;
	// και θα το σχεδιάσουμε
	$this->template->render(__DIR__ . '/poll.latte');
}

Η ετικέτα {control} επιτρέπει να περάσουμε παραμέτρους στη μέθοδο render():

{control poll $id, $message}
public function render(int $id, string $message): void
{
	// ...
}

Μερικές φορές ένα συστατικό μπορεί να αποτελείται από διάφορα μέρη που θέλουμε να απεικονίσουμε ξεχωριστά. Για κάθε ένα από αυτά θα δημιουργήσουμε τη δική μας μέθοδο απόδοσης, εδώ είναι για παράδειγμα το renderPaginator():

public function renderPaginator(): void
{
	// ...
}

Και στο πρότυπο στη συνέχεια την καλούμε χρησιμοποιώντας:

{control poll:paginator}

Για καλύτερη κατανόηση είναι καλό να γνωρίζουμε πώς η ετικέτα μεταφράζεται σε κώδικα PHP.

{control poll}
{control poll:paginator 123, 'hello'}

Αυτό μεταγλωττίζεται σε:

$control->getComponent('poll')->render();
$control->getComponent('poll')->renderPaginator(123, 'hello');

getComponent() poll και στη συνέχεια καλείται η μέθοδος render() ή renderPaginator(), αντίστοιχα, σε αυτό.

Εάν οπουδήποτε στο τμήμα παραμέτρων χρησιμοποιείται =>, όλες οι παράμετροι θα περιτυλιχθούν με έναν πίνακα και θα περάσουν ως πρώτο όρισμα:

{control poll, id: 123, message: 'hello'}

compiles to:

$control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']);

Απόδοση του υπο-στοιχείου:

{control cartControl-someForm}

μεταγλωττίζει σε:

$control->getComponent("cartControl-someForm")->render();

Τα συστατικά, όπως οι παρουσιαστές, μεταβιβάζουν αυτόματα διάφορες χρήσιμες μεταβλητές στα πρότυπα:

  • $basePath είναι μια απόλυτη διαδρομή URL στο root dir (για παράδειγμα /CD-collection)
  • $baseUrl είναι μια απόλυτη διεύθυνση URL στο root dir (για παράδειγμα http://localhost/CD-collection)
  • $user είναι ένα αντικείμενο που αντιπροσωπεύει τον χρήστη
  • $presenter είναι ο τρέχων παρουσιαστής
  • $control είναι το τρέχον συστατικό
  • $flashes κατάλογος μηνυμάτων που αποστέλλονται από τη μέθοδο flashMessage()

Σήμα

Γνωρίζουμε ήδη ότι η πλοήγηση στην εφαρμογή Nette συνίσταται στη σύνδεση ή την ανακατεύθυνση σε ζεύγη Presenter:action. Τι γίνεται όμως αν θέλουμε απλώς να εκτελέσουμε μια ενέργεια στην τρέχουσα σελίδα; Για παράδειγμα, να αλλάξουμε τη σειρά ταξινόμησης της στήλης στον πίνακα, να διαγράψουμε στοιχείο, να αλλάξουμε τη λειτουργία φωτός/σκοταδιού, να υποβάλουμε τη φόρμα, να ψηφίσουμε στην ψηφοφορία κ.λπ.

Αυτός ο τύπος αιτήματος ονομάζεται σήμα. Και όπως οι ενέργειες επικαλούνται μεθόδους action<Action>() ή render<Action>(), τα σήματα καλούν μεθόδους handle<Signal>(). Ενώ η έννοια της ενέργειας (ή της προβολής) αφορά μόνο τους παρουσιαστές, τα σήματα εφαρμόζονται σε όλα τα στοιχεία. Και επομένως και στους παρουσιαστές, επειδή το UI\Presenter είναι απόγονος του UI\Control.

public function handleClick(int $x, int $y): void
{
	// ... επεξεργασία σήματος ...
}

Ο σύνδεσμος που καλεί το σήμα δημιουργείται με τον συνήθη τρόπο, δηλαδή στο πρότυπο με το χαρακτηριστικό n:href ή την ετικέτα {link}, στον κώδικα με τη μέθοδο link(). Περισσότερα στο κεφάλαιο Δημιουργία συνδέσμων URL.

<a n:href="click! $x, $y">click here</a>

Το σήμα καλείται πάντα στον τρέχοντα παρουσιαστή και προβολή, επομένως δεν είναι δυνατή η σύνδεση με το σήμα σε διαφορετικό παρουσιαστή/δράση.

Έτσι, το σήμα προκαλεί την επαναφόρτωση της σελίδας με τον ίδιο ακριβώς τρόπο όπως στην αρχική αίτηση, μόνο που επιπλέον καλεί τη μέθοδο χειρισμού του σήματος με τις κατάλληλες παραμέτρους. Εάν η μέθοδος δεν υπάρχει, δημιουργείται η εξαίρεση Nette\Application\UI\BadSignalException, η οποία εμφανίζεται στο χρήστη ως σελίδα σφάλματος 403 Forbidden.

Αποσπάσματα και AJAX

Τα σήματα μπορεί να σας θυμίζουν λίγο AJAX: χειριστές που καλούνται στην τρέχουσα σελίδα. Και έχετε δίκιο, τα σήματα καλούνται πραγματικά συχνά με τη χρήση AJAX, και στη συνέχεια μεταδίδουμε μόνο τα αλλαγμένα τμήματα της σελίδας στο πρόγραμμα περιήγησης. Ονομάζονται αποσπάσματα. Περισσότερες πληροφορίες μπορείτε να βρείτε στη σελίδα σχετικά με το AJAX.

Μηνύματα Flash

Ένα στοιχείο έχει τη δική του αποθήκευση μηνυμάτων flash ανεξάρτητα από τον παρουσιαστή. Πρόκειται για μηνύματα που, για παράδειγμα, ενημερώνουν για το αποτέλεσμα της λειτουργίας. Ένα σημαντικό χαρακτηριστικό των μηνυμάτων flash είναι ότι είναι διαθέσιμα στο πρότυπο ακόμη και μετά την ανακατεύθυνση. Ακόμη και μετά την εμφάνισή τους, θα παραμείνουν ζωντανά για άλλα 30 δευτερόλεπτα – για παράδειγμα, σε περίπτωση που ο χρήστης θα ανανεώσει ακούσια τη σελίδα – το μήνυμα δεν θα χαθεί.

Η αποστολή γίνεται με τη μέθοδο flashMessage. Η πρώτη παράμετρος είναι το κείμενο του μηνύματος ή το αντικείμενο stdClass που αντιπροσωπεύει το μήνυμα. Η προαιρετική δεύτερη παράμετρος είναι ο τύπος του (σφάλμα, προειδοποίηση, πληροφορία κ.λπ.). Η μέθοδος flashMessage() επιστρέφει ένα instance του flash message ως αντικείμενο stdClass στο οποίο μπορείτε να περάσετε πληροφορίες.

$this->flashMessage('Item was deleted.');
$this->redirect(/* ... */); // και ανακατεύθυνση

Στο πρότυπο, τα μηνύματα αυτά είναι διαθέσιμα στη μεταβλητή $flashes ως αντικείμενα stdClass, τα οποία περιέχουν τις ιδιότητες message (κείμενο μηνύματος), type (τύπος μηνύματος) και μπορούν να περιέχουν τις ήδη αναφερθείσες πληροφορίες χρήστη. Τα σχεδιάζουμε ως εξής:

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

Μόνιμες παράμετροι

Οι μόνιμες παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης των στοιχείων μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα συνόδου, μεταφέρονται στη διεύθυνση URL. Και μεταφέρονται αυτόματα, συμπεριλαμβανομένων των συνδέσμων που δημιουργούνται σε άλλα στοιχεία στην ίδια σελίδα.

Για παράδειγμα, έχετε ένα στοιχείο σελιδοποίησης περιεχομένου. Μπορεί να υπάρχουν πολλά τέτοια στοιχεία σε μια σελίδα. Και θέλετε όλα τα συστατικά να παραμένουν στην τρέχουσα σελίδα τους όταν κάνετε κλικ στο σύνδεσμο. Επομένως, κάνουμε τον αριθμό σελίδας (page) μια μόνιμη παράμετρο.

Η δημιουργία μιας μόνιμης παραμέτρου είναι εξαιρετικά εύκολη στη Nette. Απλά δημιουργήστε μια δημόσια ιδιότητα και επισημάνετέ την με το χαρακτηριστικό: (προηγουμένως χρησιμοποιούνταν το /** @persistent */ )

use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική

class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1; // πρέπει να είναι δημόσια
}

Συνιστούμε να συμπεριλάβετε τον τύπο δεδομένων (π.χ. int) μαζί με την ιδιότητα και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να επικυρωθούν.

Μπορείτε να αλλάξετε την τιμή μιας μόνιμης παραμέτρου κατά τη δημιουργία ενός συνδέσμου:

<a n:href="this page: $page + 1">next</a>

Ή μπορεί να επαναρυθμιστεί, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Τότε θα πάρει την προεπιλεγμένη τιμή της:

<a n:href="this page: null">reset</a>

Εμμένουσες συνιστώσες

Όχι μόνο οι παράμετροι αλλά και τα συστατικά μπορούν να είναι μόνιμα. Οι μόνιμες παράμετροι τους μεταφέρονται επίσης μεταξύ διαφορετικών ενεργειών ή μεταξύ διαφορετικών παρουσιαστών. Χαρακτηρίζουμε τα μόνιμα συστατικά με αυτές τις σημειώσεις για την κλάση presenter. Για παράδειγμα, εδώ επισημαίνουμε τα συστατικά calendar και poll ως εξής:

/**
 * @persistent(calendar, poll)
 */
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}

Δεν χρειάζεται να επισημάνετε τα υποστοιχεία ως μόνιμα, είναι μόνιμα αυτόματα.

Στην PHP 8, μπορείτε επίσης να χρησιμοποιήσετε χαρακτηριστικά για να επισημάνετε μόνιμα συστατικά:

use Nette\Application\Attributes\Persistent;

#[Persistent('calendar', 'poll')]
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}

Εξαρτήματα με εξαρτήσεις

Πώς να δημιουργήσετε συστατικά με εξαρτήσεις χωρίς να „μπερδέψετε“ τους παρουσιαστές που θα τα χρησιμοποιήσουν; Χάρη στα έξυπνα χαρακτηριστικά του DI container στο Nette, όπως και με τη χρήση παραδοσιακών υπηρεσιών, μπορούμε να αφήσουμε το μεγαλύτερο μέρος της δουλειάς στο πλαίσιο.

Ας πάρουμε ως παράδειγμα ένα συστατικό που έχει εξάρτηση από την υπηρεσία PollFacade:

class PollControl extends Control
{
	public function __construct(
		private int $id, // Id μιας δημοσκόπησης, για την οποία δημιουργείται το στοιχείο
		private PollFacade $facade,
	) {
	}

	public function handleVote(int $voteId): void
	{
		$this->facade->vote($id, $voteId);
		// ...
	}
}

Αν γράφαμε μια κλασική υπηρεσία, δεν θα υπήρχε λόγος ανησυχίας. Το DI container θα φρόντιζε αόρατα να περάσει όλες τις εξαρτήσεις. Αλλά συνήθως χειριζόμαστε τα συστατικά δημιουργώντας μια νέα τους περίπτωση απευθείας στον παρουσιαστή σε μεθόδους εργοστασίου createComponent...(). Αλλά το πέρασμα όλων των εξαρτήσεων όλων των συστατικών στον presenter για να τις περάσουμε στη συνέχεια στα συστατικά είναι δυσκίνητο. Και η ποσότητα του κώδικα που γράφεται…

Το λογικό ερώτημα είναι, γιατί δεν καταχωρούμε απλώς το συστατικό ως κλασική υπηρεσία, να το περάσουμε στον παρουσιαστή και στη συνέχεια να το επιστρέψουμε στη μέθοδο createComponent...(); Αλλά αυτή η προσέγγιση είναι ακατάλληλη, επειδή θέλουμε να μπορούμε να δημιουργήσουμε το συστατικό πολλές φορές.

Η σωστή λύση είναι να γράψουμε ένα εργοστάσιο για το συστατικό, δηλαδή μια κλάση που δημιουργεί το συστατικό για εμάς:

class PollControlFactory
{
	public function __construct(
		private PollFacade $facade,
	) {
	}

	public function create(int $id): PollControl
	{
		return new PollControl($id, $this->facade);
	}
}

Τώρα εγγράφουμε την υπηρεσία μας στο DI container στη διαμόρφωση:

services:
	- PollControlFactory

Τέλος, θα χρησιμοποιήσουμε αυτό το εργοστάσιο στον παρουσιαστή μας:

class PollPresenter extends Nette\Application\UI\Presenter
{
	public function __construct(
		private PollControlFactory $pollControlFactory,
	) {
	}

	protected function createComponentPollControl(): PollControl
	{
		$pollId = 1; // μπορούμε να περάσουμε την παράμετρο μας
		return $this->pollControlFactory->create($pollId);
	}
}

Το σπουδαίο είναι ότι το Nette DI μπορεί να δημιουργήσει τέτοια απλά εργοστάσια, οπότε αντί να γράψετε ολόκληρο τον κώδικα, χρειάζεται απλώς να γράψετε τη διεπαφή του:

interface PollControlFactory
{
	public function create(int $id): PollControl;
}

Αυτό είναι όλο. Η Nette υλοποιεί εσωτερικά αυτή τη διεπαφή και την εγχέει στον παρουσιαστή μας, όπου μπορούμε να τη χρησιμοποιήσουμε. Επίσης, περνάει μαγικά την παράμετρο $id και την περίπτωση της κλάσης PollFacade στο στοιχείο μας.

Συστατικά σε βάθος

Τα συστατικά σε μια εφαρμογή Nette είναι τα επαναχρησιμοποιήσιμα μέρη μιας διαδικτυακής εφαρμογής που ενσωματώνουμε σε σελίδες, τα οποία αποτελούν το αντικείμενο αυτού του κεφαλαίου. Ποιες ακριβώς είναι οι δυνατότητες ενός τέτοιου συστατικού;

  1. είναι δυνατό να αποδοθεί σε ένα πρότυπο
  2. γνωρίζει ποιο μέρος του εαυτού του να αποδώσει κατά τη διάρκεια μιας αίτησης AJAX (αποσπάσματα)
  3. έχει τη δυνατότητα να αποθηκεύει την κατάστασή του σε μια διεύθυνση URL (μόνιμες παράμετροι)
  4. έχει τη δυνατότητα να ανταποκρίνεται σε ενέργειες του χρήστη (σήματα)
  5. δημιουργεί μια ιεραρχική δομή (όπου η ρίζα είναι ο παρουσιαστής)

Κάθε μία από αυτές τις λειτουργίες χειρίζεται μία από τις κλάσεις της κληρονομικής γραμμής. Την απόδοση (1 + 2) χειρίζεται η κλάση Nette\Application\UI\Control, την ενσωμάτωση στον κύκλο ζωής (3, 4) η κλάση Nette\Application\UI\Component και τη δημιουργία της ιεραρχικής δομής (5) οι κλάσεις Container και Component.

Nette\ComponentModel\Component  { IComponent }
|
+- Nette\ComponentModel\Container  { IContainer }
	|
	+- Nette\Application\UI\Component  { SignalReceiver, StatePersistent }
		|
		+- Nette\Application\UI\Control  { Renderable }
			|
			+- Nette\Application\UI\Presenter  { IPresenter }

Κύκλος ζωής του συστατικού

Κύκλος ζωής του συστατικού

Επικύρωση μόνιμων παραμέτρων

Οι τιμές των μόνιμων παραμέτρων που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες με τη μέθοδο loadState(). Ελέγχει επίσης αν ο τύπος δεδομένων που έχει καθοριστεί για την ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί.

Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, επειδή μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν ο αριθμός σελίδας $this->page είναι μεγαλύτερος από 0. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο loadState() που αναφέρθηκε παραπάνω:

class PaginatingControl extends Control
{
	#[Persistent]
	public int $page = 1;

	public function loadState(array $params): void
	{
		parent::loadState($params); // εδώ ορίζεται η σελίδα $this->page
		// ακολουθεί τον έλεγχο της τιμής του χρήστη:
		if ($this->page < 1) {
			$this->error();
		}
	}
}

Η αντίθετη διαδικασία, δηλαδή η συλλογή τιμών από persistent properites, αντιμετωπίζεται από τη μέθοδο saveState().

Σήματα σε βάθος

Ένα σήμα προκαλεί επαναφόρτωση της σελίδας όπως και το αρχικό αίτημα (με εξαίρεση το AJAX) και καλεί τη μέθοδο signalReceived($signal) της οποίας η προεπιλεγμένη υλοποίηση στην κλάση Nette\Application\UI\Component προσπαθεί να καλέσει μια μέθοδο που αποτελείται από τις λέξεις handle{Signal}. Η περαιτέρω επεξεργασία βασίζεται στο συγκεκριμένο αντικείμενο. Τα αντικείμενα που είναι απόγονοι του Component (δηλ. Control και Presenter) προσπαθούν να καλέσουν το handle{Signal} με σχετικές παραμέτρους.

Με άλλα λόγια: λαμβάνεται ο ορισμός της μεθόδου handle{Signal} και όλες οι παράμετροι που ελήφθησαν στην αίτηση αντιστοιχίζονται με τις παραμέτρους της μεθόδου. Αυτό σημαίνει ότι η παράμετρος id από τη διεύθυνση URL αντιστοιχίζεται με την παράμετρο της μεθόδου $id, η something με την $something κ.ο.κ. Και αν η μέθοδος δεν υπάρχει, η μέθοδος signalReceived πετάει μια εξαίρεση.

Το σήμα μπορεί να ληφθεί από οποιοδήποτε συστατικό, παρουσιαστή αντικειμένου που υλοποιεί τη διεπαφή SignalReceiver εάν είναι συνδεδεμένο με το δέντρο συστατικών.

Οι κύριοι δέκτες σημάτων είναι το Presenters και τα οπτικά συστατικά που επεκτείνουν το Control. Ένα σήμα είναι ένα σημάδι για ένα αντικείμενο ότι πρέπει να κάνει κάτι – η δημοσκόπηση μετράει την ψήφο του χρήστη, το πλαίσιο με τις ειδήσεις πρέπει να ξεδιπλωθεί, η φόρμα στάλθηκε και πρέπει να επεξεργαστεί τα δεδομένα κ.ο.κ.

Η διεύθυνση URL για το σήμα δημιουργείται με τη χρήση της μεθόδου Component::link(). Ως παράμετρος $destination περνάμε το string {signal}! και ως $args έναν πίνακα με τα ορίσματα που θέλουμε να περάσουμε στον χειριστή του σήματος. Οι παράμετροι του σήματος συνδέονται με τη διεύθυνση URL του τρέχοντος παρουσιαστή/προβολής. Η παράμετρος ?do στη διεύθυνση URL καθορίζει το σήμα που καλείται.

Η μορφή της είναι {signal} ή {signalReceiver}-{signal}. {signalReceiver} είναι το όνομα του στοιχείου στον παρουσιαστή. Αυτός είναι ο λόγος για τον οποίο η παύλα (ανακριβώς παύλα) δεν μπορεί να υπάρχει στο όνομα των συστατικών – χρησιμοποιείται για να χωρίσει το όνομα του συστατικού και του σήματος, αλλά είναι δυνατόν να συνθέσετε πολλά συστατικά.

Η μέθοδος isSignalReceiver() επαληθεύει αν ένα συστατικό (πρώτο όρισμα) είναι δέκτης ενός σήματος (δεύτερο όρισμα). Το δεύτερο όρισμα μπορεί να παραλειφθεί – τότε διαπιστώνει αν το συστατικό είναι δέκτης οποιουδήποτε σήματος. Εάν το δεύτερο όρισμα είναι true, ελέγχει εάν το συστατικό ή οι απόγονοί του είναι δέκτες ενός σήματος.

Σε οποιαδήποτε φάση που προηγείται του handle{Signal} μπορεί να εκτελεστεί το σήμα χειροκίνητα καλώντας τη μέθοδο processSignal() η οποία αναλαμβάνει την ευθύνη για την εκτέλεση του σήματος. Παίρνει το συστατικό δέκτη (αν δεν έχει οριστεί είναι ο ίδιος ο παρουσιαστής) και του στέλνει το σήμα.

Παράδειγμα:

if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {
	$this->processSignal();
}

Το σήμα εκτελείται πρόωρα και δεν θα κληθεί ξανά.