Model komponentów
Ważnym pojęciem w Nette jest komponent. Do stron wstawiamy wizualne interaktywne komponenty; formularze i wszystkie ich elementy
również są komponentami. Dwie podstawowe klasy, po których dziedziczą wszystkie te komponenty, są częścią pakietu
nette/component-model i odpowiadają za tworzenie hierarchii drzewa komponentów.
Component
Nette\ComponentModel\Component
to wspólny przodek wszystkich komponentów. Zawiera metodę getName() zwracającą nazwę komponentu i metodę
getParent() zwracającą jego rodzica. Oba można ustawić metodą setParent(): pierwszym parametrem
jest rodzic, a drugim nazwa komponentu.
lookup (?string $type, bool $throw=true): ?Component
Szuka w górę hierarchii obiektu pożądanej klasy albo interfejsu. Na przykład
$component->lookup(Nette\Application\UI\Presenter::class) zwraca presenter, jeśli komponent jest do niego
podłączony, nawet przez kilka poziomów. Jeśli pasujący obiekt nie zostanie znaleziony, rzuca wyjątek; przekaż jako drugi
argument false, żeby zamiast tego zwrócić null. Jeśli przekażesz jako $type wartość
null, metoda szuka najwyższego komponentu w drzewie, czyli korzenia bez rodzica.
lookupPath (?string $type=null, bool $throw=true): ?string
Zwraca tak zwaną ścieżkę, czyli ciąg powstały z połączenia nazw wszystkich komponentów na drodze między bieżącym
komponentem a komponentem szukanym. Na przykład $component->lookupPath(Nette\Application\UI\Presenter::class)
zwraca unikalny identyfikator komponentu względem presentera. Gdy $type to null (albo jest pominięty),
ścieżka mierzona jest do korzenia drzewa.
Container
Nette\ComponentModel\Container
to komponent rodzicielski, czyli komponent zawierający dzieci i tworzący tym samym strukturę drzewiastą. Ma metody do
łatwego dodawania, pobierania i usuwania obiektów. Jest przodkiem na przykład formularza albo klas Control i
Presenter. Potomkowie używający traitu ArrayAccess (jak Control i Presenter)
pozwalają też sięgać po dzieci zapisem tablicowym, np. $container['child'].
addComponent (Component $component, ?string $name, ?string $insertBefore=null): static
Dodaje komponent do kontenera jako dziecko. Jeśli $name to null, używana jest własna nazwa
komponentu. Za pomocą opcjonalnego $insertBefore, czyli nazwy istniejącego dziecka, nowy komponent wstawiany jest
tuż przed nim; w przeciwnym razie dołączany jest na koniec. Metoda zwraca sam kontener, więc wywołania można łączyć w
łańcuch.
removeComponent (Component $component): void
Usuwa komponent potomny z kontenera.
getComponent (string $name): ?Component
Zwraca komponent. Próba pobrania niezdefiniowanego dziecka wywołuje metodę fabrykującą
createComponent($name). Metoda createComponent($name) wywołuje w bieżącym komponencie metodę
createComponent<nazwa komponentu>, przekazując nazwę komponentu jako parametr. Utworzony komponent dodawany
jest następnie do bieżącego komponentu jako jego dziecko. Metody te nazywamy fabrykami komponentów i można je implementować
w klasach dziedziczących po Container.
getComponents(): IComponent[]
Zwraca bezpośrednich potomków jako tablicę; klucze zawierają nazwy tych komponentów. Żeby pobrać rekurencyjnie całe
poddrzewo, użyj getComponentTree(), opcjonalnie w połączeniu z array_filter() do filtrowania po
typie. (Parametry $deep i $filterType znane ze starszych wersji zostały usunięte w wersji 4.0.)
getComponentTree(): list<IComponent>
Pobiera całą hierarchię komponentów, wraz ze wszystkimi zagnieżdżonymi komponentami potomnymi, jako tablicę indeksowaną. Przeszukiwanie odbywa się w głąb.
Monitorowanie przodków
Model komponentów Nette pozwala bardzo dynamicznie pracować z drzewem (możemy usuwać, przenosić, dodawać komponenty), więc błędem byłoby poleganie na tym, że po utworzeniu komponentu rodzic, rodzic rodzica itd. są znani natychmiast (w konstruktorze). Zwykle przy tworzeniu komponentu rodzic w ogóle nie jest znany.
Jak komponent może dowiedzieć się o momencie, w którym zostaje podłączony pod presenter albo pod dowolnego innego
przodka danego typu? Obserwowanie bezpośredniego rodzica nie wystarczy, bo połączenie może nastąpić wyżej w drzewie, na
przykład gdy podłączany jest rodzic rodzica. Do tego służy metoda monitor($type, $attached,
$detached): komponent deklaruje, że chce być powiadamiany zawsze, gdy nad nim w drzewie pojawi się przodek klasy albo
interfejsu $type albo gdy z niego zniknie. Komponent może monitorować dowolną liczbę typów; callback
$attached uruchamia się, gdy pasujący przodek się podłączy, i otrzymuje tego przodka jako argument, a
$detached uruchamia się, gdy się odłączy. Monitorowanie można znowu zatrzymać przez
unmonitor($type).
Powiadomienia podążają za strukturą drzewa. Przy dołączaniu przodek powiadamiany jest przed swoimi potomkami (z góry na dół), więc rodzic może najpierw przygotować wspólny stan albo nawet usunąć dziecko, zanim uruchomi się jego własny callback. Przy odłączaniu kolejność jest odwrotna: najpierw powiadamiani są potomkowie. Callbacki są też deduplikowane, więc ten sam callback nigdy nie jest wywoływany dwa razy dla tego samego obiektu. Uzasadnienie tego zachowania znajdziesz we wpisie na blogu o wersji 4.0.
Dla lepszego zrozumienia oto przykład: klasa UploadControl, reprezentująca element formularza do wysyłania
plików w Nette Forms, musi ustawić formularzowi atrybut enctype na multipart/form-data. W momencie
utworzenia obiektu może jednak nie być podłączona do żadnego formularza. W którym więc momencie formularz zmodyfikować?
Rozwiązanie jest proste: żądanie monitorowania składa się w konstruktorze:
class UploadControl extends Nette\Forms\Controls\BaseControl
{
public function __construct($label)
{
$this->monitor(Nette\Forms\Form::class, function ($form): void {
$form->setHtmlAttribute('enctype', 'multipart/form-data');
});
// ...
}
// ...
}
a gdy tylko formularz stanie się dostępny, callback zostanie wywołany.
Jeśli aktualizujesz do nowszej wersji, zajrzyj na stronę aktualizacji.