Διαδραστικά Components
Τα components είναι ανεξάρτητα, επαναχρησιμοποιήσιμα αντικείμενα που ενσωματώνουμε σε σελίδες. Μπορεί να είναι φόρμες, datagrids, δημοσκοπήσεις, στην πραγματικότητα οτιδήποτε έχει νόημα να χρησιμοποιείται επανειλημμένα. Θα δείξουμε:
- πώς να χρησιμοποιείτε τα components;
- πώς να τα γράφετε;
- τι είναι τα signals;
Το Nette έχει ενσωματωμένο ένα σύστημα components. Κάτι παρόμοιο μπορεί να θυμούνται οι παλαιότεροι από τα Delphi ή τα ASP.NET Web Forms, ενώ κάτι παρόμοιο αποτελεί τη βάση του React ή του Vue.js. Ωστόσο, στον κόσμο των PHP frameworks, πρόκειται για ένα μοναδικό χαρακτηριστικό.
Τα components επηρεάζουν θεμελιωδώς την προσέγγιση στην ανάπτυξη εφαρμογών. Μπορείτε να συνθέτετε σελίδες από προκατασκευασμένες μονάδες. Χρειάζεστε ένα datagrid στη διαχείριση; Θα το βρείτε στο Componette, ένα αποθετήριο open-source πρόσθετων (όχι μόνο components) για το Nette, και απλά το ενσωματώνετε στον presenter.
Μπορείτε να ενσωματώσετε οποιονδήποτε αριθμό components σε έναν presenter. Και σε ορισμένα components, μπορείτε να ενσωματώσετε άλλα components. Αυτό δημιουργεί ένα δέντρο components, του οποίου η ρίζα είναι ο presenter.
Μέθοδοι Εργοστασίου
Πώς ενσωματώνονται και στη συνέχεια χρησιμοποιούνται τα components στον presenter; Συνήθως μέσω factory μεθόδων.
Ένα factory component είναι ένας κομψός τρόπος δημιουργίας components μόνο όταν
είναι πραγματικά απαραίτητα (lazy / on demand). Η όλη μαγεία έγκειται στην
υλοποίηση μιας μεθόδου με το όνομα createComponent<Name>()
, όπου
<Name>
είναι το όνομα του component που δημιουργείται, και η οποία
δημιουργεί και επιστρέφει το component.
class DefaultPresenter extends Nette\Application\UI\Presenter
{
protected function createComponentPoll(): PollControl
{
$poll = new PollControl;
$poll->items = $this->item;
return $poll;
}
}
Χάρη στο γεγονός ότι όλα τα components δημιουργούνται σε ξεχωριστές μεθόδους, ο κώδικας γίνεται πιο ευανάγνωστος.
Τα ονόματα των components ξεκινούν πάντα με μικρό γράμμα, παρόλο που γράφονται με κεφαλαίο στο όνομα της μεθόδου.
Δεν καλούμε ποτέ απευθείας τα factories, καλούνται μόνα τους την πρώτη φορά που χρησιμοποιούμε το component. Χάρη σε αυτό, το component δημιουργείται τη σωστή στιγμή και μόνο όταν είναι πραγματικά απαραίτητο. Αν δεν χρησιμοποιήσουμε το component (για παράδειγμα, κατά τη διάρκεια μιας αίτησης AJAX όπου μεταδίδεται μόνο ένα μέρος της σελίδας, ή κατά την προσωρινή αποθήκευση του template), δεν δημιουργείται καθόλου και εξοικονομούμε απόδοση του διακομιστή.
// προσπελάζουμε το component και αν είναι η πρώτη φορά,
// καλείται η createComponentPoll() η οποία το δημιουργεί
$poll = $this->getComponent('poll');
// εναλλακτική σύνταξη: $poll = $this['poll'];
Στο template, είναι δυνατό να αποδοθεί ένα component χρησιμοποιώντας την ετικέτα {control}. Επομένως, δεν χρειάζεται να μεταβιβάζετε χειροκίνητα τα components στο template.
<h2>Ψηφίστε</h2>
{control poll}
Hollywood Style
Τα components χρησιμοποιούν συνήθως μια φρέσκια τεχνική, την οποία μας αρέσει να αποκαλούμε Hollywood style. Σίγουρα γνωρίζετε τη φράση που ακούν τόσο συχνά οι συμμετέχοντες σε οντισιόν ταινιών: «Μην μας καλέσετε, θα σας καλέσουμε εμείς». Και ακριβώς περί αυτού πρόκειται.
Στο Nette, αντί να πρέπει συνεχώς να ρωτάτε κάτι («υποβλήθηκε η φόρμα;», «ήταν έγκυρη;» ή «πάτησε ο χρήστης αυτό το κουμπί;»), λέτε στο framework «όταν συμβεί αυτό, κάλεσε αυτή τη μέθοδο» και αφήνετε την υπόλοιπη δουλειά σε αυτό. Αν προγραμματίζετε σε JavaScript, αυτό το στυλ προγραμματισμού σας είναι οικείο. Γράφετε συναρτήσεις που καλούνται όταν συμβεί ένα συγκεκριμένο γεγονός. Και η γλώσσα τους μεταβιβάζει τις κατάλληλες παραμέτρους.
Αυτό αλλάζει εντελώς την οπτική γωνία της συγγραφής εφαρμογών. Όσο περισσότερες εργασίες μπορείτε να αφήσετε στο framework, τόσο λιγότερη δουλειά έχετε εσείς. Και τόσο λιγότερα πράγματα μπορείτε, για παράδειγμα, να παραλείψετε.
Γράφοντας ένα Component
Με τον όρο component, συνήθως εννοούμε έναν απόγονο της κλάσης Nette\Application\UI\Control. (Θα ήταν
πιο ακριβές να χρησιμοποιούμε τον όρο «controls», αλλά οι «έλεγχοι» έχουν
εντελώς διαφορετική σημασία στα Ελληνικά και ο όρος «components» έχει
επικρατήσει.) Ο ίδιος ο presenter Nette\Application\UI\Presenter είναι,
παρεμπιπτόντως, επίσης απόγονος της κλάσης Control
.
use Nette\Application\UI\Control;
class PollControl extends Control
{
}
Απόδοση
Γνωρίζουμε ήδη ότι για την απόδοση ενός component χρησιμοποιείται η
ετικέτα {control componentName}
. Αυτή στην πραγματικότητα καλεί τη μέθοδο
render()
του component, στην οποία φροντίζουμε για την απόδοση. Έχουμε
στη διάθεσή μας, ακριβώς όπως στον presenter, ένα Latte template στη μεταβλητή
$this->template
, στην οποία μεταβιβάζουμε παραμέτρους. Σε αντίθεση με
τον presenter, πρέπει να καθορίσουμε το αρχείο με το template και να το αφήσουμε
να αποδοθεί:
public function render(): void
{
// εισάγουμε κάποιες παραμέτρους στο template
$this->template->param = $value;
// και το αποδίδουμε
$this->template->render(__DIR__ . '/poll.latte');
}
Η ετικέτα {control}
επιτρέπει τη μεταβίβαση παραμέτρων στη μέθοδο
render()
:
{control poll $id, $message}
public function render(int $id, string $message): void
{
// ...
}
Μερικές φορές, ένα component μπορεί να αποτελείται από πολλά μέρη που
θέλουμε να αποδώσουμε ξεχωριστά. Για καθένα από αυτά, δημιουργούμε τη
δική του μέθοδο απόδοσης, εδώ στο παράδειγμα, για παράδειγμα,
renderPaginator()
:
public function renderPaginator(): void
{
// ...
}
Και στο template, την καλούμε στη συνέχεια χρησιμοποιώντας:
{control poll:paginator}
Για καλύτερη κατανόηση, είναι καλό να γνωρίζουμε πώς μεταφράζεται αυτή η ετικέτα σε PHP.
{control poll}
{control poll:paginator 123, 'hello'}
μεταφράζεται ως:
$control->getComponent('poll')->render();
$control->getComponent('poll')->renderPaginator(123, 'hello');
Η μέθοδος getComponent()
επιστρέφει το component poll
και πάνω σε
αυτό το component καλεί τη μέθοδο render()
, ή renderPaginator()
αν έχει
καθοριστεί διαφορετικός τρόπος απόδοσης στην ετικέτα μετά την άνω και
κάτω τελεία.
Προσοχή, αν εμφανιστεί οπουδήποτε στις παραμέτρους το
=>
, όλες οι παράμετροι θα συσκευαστούν σε έναν πίνακα και θα
μεταβιβαστούν ως το πρώτο όρισμα:
{control poll, id: 123, message: 'hello'}
μεταφράζεται ως:
$control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']);
Απόδοση υπο-component:
{control cartControl-someForm}
μεταφράζεται ως:
$control->getComponent("cartControl-someForm")->render();
Τα components, όπως και οι presenters, μεταβιβάζουν αυτόματα αρκετές χρήσιμες μεταβλητές στα templates:
$basePath
είναι η απόλυτη διαδρομή URL προς τον ριζικό κατάλογο (π.χ./eshop
)$baseUrl
είναι η απόλυτη URL προς τον ριζικό κατάλογο (π.χ.http://localhost/eshop
)$user
είναι το αντικείμενο που αντιπροσωπεύει τον χρήστη$presenter
είναι ο τρέχων presenter$control
είναι το τρέχον component$flashes
array μηνυμάτων που στάλθηκαν από τη συνάρτησηflashMessage()
Σήμα
Γνωρίζουμε ήδη ότι η πλοήγηση σε μια εφαρμογή Nette βασίζεται στη
σύνδεση ή την ανακατεύθυνση σε ζεύγη Presenter:action
. Αλλά τι γίνεται
αν θέλουμε απλώς να εκτελέσουμε μια ενέργεια στην τρέχουσα σελίδα;
Για παράδειγμα, να αλλάξουμε τη διάταξη των στηλών σε έναν πίνακα; να
διαγράψουμε ένα στοιχείο; να αλλάξουμε σε φωτεινή/σκοτεινή λειτουργία;
να υποβάλουμε μια φόρμα; να ψηφίσουμε σε μια δημοσκόπηση; κ.λπ.
Αυτό το είδος αιτήματος ονομάζεται signal. Και όπως οι ενέργειες καλούν
τις μεθόδους action<Action>()
ή render<Action>()
, τα signals καλούν
τις μεθόδους handle<Signal>()
. Ενώ ο όρος ενέργεια (ή view) σχετίζεται
καθαρά μόνο με τους presenters, τα signals αφορούν όλα τα components. Και επομένως και
τους presenters, επειδή το UI\Presenter
είναι απόγονος του UI\Control
.
public function handleClick(int $x, int $y): void
{
// ... επεξεργασία του signal ...
}
Έναν σύνδεσμο που καλεί ένα signal τον δημιουργούμε με τον συνηθισμένο
τρόπο, δηλαδή στο template με το χαρακτηριστικό n:href
ή την ετικέτα
{link}
, στον κώδικα με τη μέθοδο link()
. Περισσότερα στο
κεφάλαιο Δημιουργία
συνδέσμων URL.
<a n:href="click! $x, $y">κάντε κλικ εδώ</a>
Ένα signal καλείται πάντα στον τρέχοντα presenter και action, δεν είναι δυνατό να το καλέσετε σε άλλο presenter ή άλλη action.
Ένα signal προκαλεί λοιπόν την επαναφόρτωση της σελίδας ακριβώς όπως στην αρχική αίτηση, απλώς επιπλέον καλεί τη μέθοδο χειρισμού του signal με τις κατάλληλες παραμέτρους. Αν η μέθοδος δεν υπάρχει, δημιουργείται μια εξαίρεση Nette\Application\UI\BadSignalException, η οποία εμφανίζεται στον χρήστη ως σελίδα σφάλματος 403 Forbidden.
Snippets και AJAX
Τα signals μπορεί να σας θυμίζουν λίγο το AJAX: handlers που καλούνται στην τρέχουσα σελίδα. Και έχετε δίκιο, τα signals καλούνται πράγματι συχνά μέσω AJAX και στη συνέχεια μεταφέρουμε στο πρόγραμμα περιήγησης μόνο τα αλλαγμένα τμήματα της σελίδας. Δηλαδή τα λεγόμενα snippets. Περισσότερες πληροφορίες θα βρείτε στη σελίδα αφιερωμένη στο AJAX.
Flash Μηνύματα
Ένα component έχει το δικό του χώρο αποθήκευσης flash μηνυμάτων, ανεξάρτητο από τον presenter. Πρόκειται για μηνύματα που, για παράδειγμα, ενημερώνουν για το αποτέλεσμα μιας λειτουργίας. Ένα σημαντικό χαρακτηριστικό των flash μηνυμάτων είναι ότι είναι διαθέσιμα στο template ακόμη και μετά από ανακατεύθυνση. Ακόμη και μετά την εμφάνισή τους, παραμένουν ενεργά για άλλα 30 δευτερόλεπτα – για παράδειγμα, σε περίπτωση που ο χρήστης ανανεώσει τη σελίδα λόγω σφάλματος μετάδοσης – το μήνυμα δεν εξαφανίζεται αμέσως.
Η αποστολή γίνεται από τη μέθοδο flashMessage. Η
πρώτη παράμετρος είναι το κείμενο του μηνύματος ή ένα αντικείμενο
stdClass
που αντιπροσωπεύει το μήνυμα. Η προαιρετική δεύτερη
παράμετρος είναι ο τύπος του (error, warning, info κ.λπ.). Η μέθοδος
flashMessage()
επιστρέφει μια παρουσία του flash μηνύματος ως
αντικείμενο stdClass
, στο οποίο μπορούν να προστεθούν περαιτέρω
πληροφορίες.
$this->flashMessage('Το στοιχείο διαγράφηκε.');
$this->redirect(/* ... */); // και ανακατευθύνουμε
Στο template, αυτά τα μηνύματα είναι διαθέσιμα στη μεταβλητή $flashes
ως αντικείμενα stdClass
, τα οποία περιέχουν τις ιδιότητες
message
(κείμενο μηνύματος), type
(τύπος μηνύματος) και μπορούν
να περιέχουν τις ήδη αναφερθείσες πληροφορίες χρήστη. Τα αποδίδουμε,
για παράδειγμα, ως εξής:
{foreach $flashes as $flash}
<div class="flash {$flash->type}">{$flash->message}</div>
{/foreach}
Ανακατεύθυνση μετά από Σήμα
Μετά την επεξεργασία ενός signal component, συχνά ακολουθεί ανακατεύθυνση. Είναι μια παρόμοια κατάσταση με τις φόρμες – μετά την υποβολή τους, ανακατευθύνουμε επίσης, ώστε η ανανέωση της σελίδας στο πρόγραμμα περιήγησης να μην προκαλέσει εκ νέου υποβολή των δεδομένων.
$this->redirect('this') // ανακατευθύνει στον τρέχοντα presenter και action
Επειδή ένα component είναι ένα επαναχρησιμοποιήσιμο στοιχείο και συνήθως
δεν θα πρέπει να έχει άμεση σύνδεση με συγκεκριμένους presenters, οι μέθοδοι
redirect()
και link()
ερμηνεύουν αυτόματα την παράμετρο ως signal
του component:
$this->redirect('click') // ανακατευθύνει στο signal 'click' του ίδιου component
Αν χρειαστεί να ανακατευθύνετε σε άλλο presenter ή ενέργεια, μπορείτε να το κάνετε μέσω του presenter:
$this->getPresenter()->redirect('Product:show'); // ανακατευθύνει σε άλλο presenter/action
Persistent Παράμετροι
Οι persistent παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης στα components μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα στη session, μεταφέρονται στη διεύθυνση URL. Και αυτό γίνεται εντελώς αυτόματα, συμπεριλαμβανομένων των συνδέσμων που δημιουργούνται σε άλλα components στην ίδια σελίδα.
Έχετε, για παράδειγμα, ένα component για τη σελιδοποίηση περιεχομένου.
Μπορεί να υπάρχουν πολλά τέτοια components σε μια σελίδα. Και θέλουμε, μετά
το κλικ σε έναν σύνδεσμο, όλα τα components να παραμείνουν στην τρέχουσα
σελίδα τους. Γι' αυτό, κάνουμε τον αριθμό σελίδας (page
) μια persistent
παράμετρο.
Η δημιουργία μιας persistent παραμέτρου στο Nette είναι εξαιρετικά απλή.
Αρκεί να δημιουργήσετε μια δημόσια property και να την επισημάνετε με ένα
attribute: (παλαιότερα χρησιμοποιούνταν το /** @persistent */
)
use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική
class PaginatingControl extends Control
{
#[Persistent]
public int $page = 1; // πρέπει να είναι public
}
Συνιστούμε να καθορίσετε τον τύπο δεδομένων για την property (π.χ.
int
) και μπορείτε επίσης να καθορίσετε μια προεπιλεγμένη τιμή. Οι
τιμές των παραμέτρων μπορούν να επικυρωθούν.
Κατά τη δημιουργία ενός συνδέσμου, η τιμή της persistent παραμέτρου μπορεί να αλλάξει:
<a n:href="this page: $page + 1">επόμενο</a>
Ή μπορεί να επαναφερθεί, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Στη συνέχεια, θα πάρει την προεπιλεγμένη της τιμή:
<a n:href="this page: null">επαναφορά</a>
Persistent Components
Όχι μόνο οι παράμετροι, αλλά και τα components μπορούν να είναι persistent. Σε
ένα τέτοιο component, οι persistent παράμετροί του μεταφέρονται ακόμη και μεταξύ
διαφορετικών actions του presenter ή μεταξύ πολλών presenters. Σημειώνουμε τα persistent
components με μια annotation στην κλάση του presenter. Για παράδειγμα, έτσι
σημειώνουμε τα components calendar
και poll
:
/**
* @persistent(calendar, poll)
*/
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}
Τα υπο-components μέσα σε αυτά τα components δεν χρειάζεται να σημειωθούν, γίνονται επίσης persistent.
Στην PHP 8, μπορείτε επίσης να χρησιμοποιήσετε attributes για να σημειώσετε τα persistent components:
use Nette\Application\Attributes\Persistent;
#[Persistent('calendar', 'poll')]
class DefaultPresenter extends Nette\Application\UI\Presenter
{
}
Components με Εξαρτήσεις
Πώς να δημιουργήσετε components με εξαρτήσεις χωρίς να «μολύνετε» τους presenters που θα τα χρησιμοποιήσουν; Χάρη στις έξυπνες ιδιότητες του DI container στο Nette, όπως και με τη χρήση κλασικών υπηρεσιών, μπορείτε να αφήσετε το μεγαλύτερο μέρος της δουλειάς στο framework.
Ας πάρουμε ως παράδειγμα ένα component που έχει εξάρτηση από την υπηρεσία
PollFacade
:
class PollControl extends Control
{
public function __construct(
private int $id, // Id της δημοσκόπησης για την οποία δημιουργούμε το component
private PollFacade $facade,
) {
}
public function handleVote(int $voteId): void
{
$this->facade->vote($id, $voteId);
// ...
}
}
Αν γράφαμε μια κλασική υπηρεσία, δεν θα υπήρχε πρόβλημα. Ο DI container θα
φρόντιζε αόρατα για τη μεταβίβαση όλων των εξαρτήσεων. Αλλά με τα
components, συνήθως τα χειριζόμαστε δημιουργώντας μια νέα παρουσία τους
απευθείας στον presenter στις factory μεθόδους
createComponent…()
. Αλλά η μεταβίβαση όλων των εξαρτήσεων όλων των
components στον presenter, για να τις μεταβιβάσουμε στη συνέχεια στα components, είναι
δυσκίνητη. Και πόσος γραμμένος κώδικας…
Το λογικό ερώτημα είναι, γιατί απλά δεν καταχωρούμε το component ως
κλασική υπηρεσία, δεν το μεταβιβάζουμε στον presenter και στη συνέχεια δεν
το επιστρέφουμε στη μέθοδο createComponent…()
? Αυτή η προσέγγιση είναι
όμως ακατάλληλη, επειδή θέλουμε να έχουμε τη δυνατότητα να
δημιουργούμε το component ακόμη και πολλές φορές.
Η σωστή λύση είναι να γράψουμε ένα factory για το component, δηλαδή μια κλάση που θα μας δημιουργήσει το component:
class PollControlFactory
{
public function __construct(
private PollFacade $facade,
) {
}
public function create(int $id): PollControl
{
return new PollControl($id, $this->facade);
}
}
Καταχωρούμε αυτό το factory στο container μας στη διαμόρφωση:
services:
- PollControlFactory
και τέλος το χρησιμοποιούμε στον presenter μας:
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 μπορεί να δημιουργήσει τέτοια απλά factories, οπότε αντί για ολόκληρο τον κώδικά του, αρκεί να γράψουμε μόνο το interface του:
interface PollControlFactory
{
public function create(int $id): PollControl;
}
Και αυτό είναι όλο. Το Nette υλοποιεί εσωτερικά αυτό το interface και το
μεταβιβάζει στον presenter, όπου μπορούμε ήδη να το χρησιμοποιήσουμε.
Προσθέτει μαγικά στην component μας την παράμετρο $id
και την
παρουσία της κλάσης PollFacade
.
Components σε Βάθος
Τα components στην Nette Application είναι επαναχρησιμοποιήσιμα μέρη μιας διαδικτυακής εφαρμογής που ενσωματώνουμε σε σελίδες και στα οποία, άλλωστε, είναι αφιερωμένο ολόκληρο αυτό το κεφάλαιο. Ποιες ακριβώς δυνατότητες έχει ένα τέτοιο component;
- μπορεί να αποδοθεί σε ένα template
- γνωρίζει ποιο μέρος του πρέπει να αποδώσει κατά τη διάρκεια μιας αίτησης AJAX (snippets)
- έχει τη δυνατότητα να αποθηκεύει την κατάστασή του στη διεύθυνση URL (persistent παράμετροι)
- έχει τη δυνατότητα να αντιδρά στις ενέργειες του χρήστη (signals)
- δημιουργεί μια ιεραρχική δομή (όπου η ρίζα είναι ο presenter)
Κάθε μία από αυτές τις λειτουργίες παρέχεται από κάποια από τις κλάσεις της γραμμής κληρονομικότητας. Η απόδοση (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 }
Κύκλος Ζωής του Component
Επικύρωση Persistent Παραμέτρων
Οι τιμές των persistent παραμέτρων που λαμβάνονται
από τη διεύθυνση URL γράφονται στις properties από τη μέθοδο loadState()
.
Αυτή ελέγχει επίσης εάν ο τύπος δεδομένων που καθορίζεται στην property
αντιστοιχεί, διαφορετικά απαντά με σφάλμα 404 και η σελίδα δεν
εμφανίζεται.
Ποτέ μην εμπιστεύεστε τυφλά τις persistent παραμέτρους, επειδή μπορούν
εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση 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 properties,
γίνεται από τη μέθοδο saveState()
.
Σήματα σε Βάθος
Ένα signal προκαλεί την επαναφόρτωση της σελίδας ακριβώς όπως στην
αρχική αίτηση (εκτός από την περίπτωση που καλείται μέσω AJAX) και καλεί
τη μέθοδο signalReceived($signal)
, της οποίας η προεπιλεγμένη υλοποίηση
στην κλάση Nette\Application\UI\Component
προσπαθεί να καλέσει μια μέθοδο που
αποτελείται από τις λέξεις handle{signal}
. Η περαιτέρω επεξεργασία
εξαρτάται από το συγκεκριμένο αντικείμενο. Τα αντικείμενα που
κληρονομούν από το Component
(δηλαδή Control
και Presenter
)
αντιδρούν προσπαθώντας να καλέσουν τη μέθοδο handle{signal}
με τις
κατάλληλες παραμέτρους.
Με άλλα λόγια: λαμβάνεται ο ορισμός της συνάρτησης handle{signal}
και
όλες οι παράμετροι που ήρθαν με την αίτηση, και στα ορίσματα
αντιστοιχίζονται οι παράμετροι από τη διεύθυνση URL με βάση το όνομα και
γίνεται προσπάθεια κλήσης της συγκεκριμένης μεθόδου. Για παράδειγμα,
ως παράμετρος $id
μεταβιβάζεται η τιμή από την παράμετρο id
στη διεύθυνση URL, ως $something
μεταβιβάζεται το something
από τη
διεύθυνση URL, κ.λπ. Και αν η μέθοδος δεν υπάρχει, η μέθοδος
signalReceived
δημιουργεί μια εξαίρεση.
Ένα signal μπορεί να ληφθεί από οποιοδήποτε component, presenter ή αντικείμενο
που υλοποιεί το interface SignalReceiver
και είναι συνδεδεμένο στο δέντρο
των components.
Οι κύριοι παραλήπτες signals θα είναι οι Presenters
και τα οπτικά components
που κληρονομούν από το Control
. Ένα signal προορίζεται να χρησιμεύσει
ως ένδειξη για ένα αντικείμενο ότι πρέπει να κάνει κάτι – μια
δημοσκόπηση πρέπει να καταμετρήσει μια ψήφο από έναν χρήστη, ένα μπλοκ
με ειδήσεις πρέπει να επεκταθεί και να εμφανίσει διπλάσιες ειδήσεις,
μια φόρμα υποβλήθηκε και πρέπει να επεξεργαστεί τα δεδομένα, και ούτω
καθεξής.
Η διεύθυνση URL για ένα signal δημιουργείται χρησιμοποιώντας τη μέθοδο Component::link(). Ως
παράμετρο $destination
μεταβιβάζουμε τη συμβολοσειρά {signal}!
και ως $args
έναν πίνακα ορισμάτων που θέλουμε να μεταβιβάσουμε
στο signal. Το signal καλείται πάντα στον τρέχοντα presenter και action με τις
τρέχουσες παραμέτρους, οι παράμετροι του signal απλώς προστίθενται.
Επιπλέον, προστίθεται αμέσως στην αρχή η παράμετρος ?do
, η οποία
καθορίζει το signal.
Η μορφή του είναι είτε {signal}
, είτε {signalReceiver}-{signal}
. Το
{signalReceiver}
είναι το όνομα του component στον presenter. Γι' αυτό δεν μπορεί
να υπάρχει παύλα στο όνομα του component – χρησιμοποιείται για να
διαχωρίσει το όνομα του component και του signal, ωστόσο είναι δυνατό να
ενσωματωθούν έτσι πολλά components.
Η μέθοδος isSignalReceiver()
ελέγχει εάν το component (πρώτο όρισμα) είναι ο παραλήπτης του signal (δεύτερο
όρισμα). Μπορούμε να παραλείψουμε το δεύτερο όρισμα – τότε ελέγχει εάν
το component είναι ο παραλήπτης οποιουδήποτε signal. Ως δεύτερη παράμετρο,
μπορείτε να καθορίσετε true
για να επαληθεύσετε εάν ο παραλήπτης
δεν είναι μόνο το αναφερόμενο component, αλλά και οποιοσδήποτε
απόγονός του.
Σε οποιαδήποτε φάση πριν από το handle{signal}
, μπορούμε να
εκτελέσουμε το signal χειροκίνητα καλώντας τη μέθοδο processSignal(), η
οποία αναλαμβάνει τη διαχείριση του signal – παίρνει το component που έχει
οριστεί ως παραλήπτης του signal (αν δεν έχει οριστεί παραλήπτης signal,
είναι ο ίδιος ο presenter) και του στέλνει το signal.
Παράδειγμα:
if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) {
$this->processSignal();
}
Με αυτόν τον τρόπο, το signal εκτελείται πρόωρα και δεν θα κληθεί ξανά.