Nette\Application\UI\Control::redirect() neumí přesměrovat jako presenter
- m.brecher
- Generous Backer | 863
Ahoj,
narazil jsem na nejasnost, jak funguje přesměrování komponenty. Testuji přesměrování komponenty na jiný presenter. Komponenta obsahuje formulář s jedním tlačítkem.
class MyComponent extends Nette\Application\UI\Control
{
public function createComponentButton(): Nette\Application\UI\Form
{
$form = new Form;
$form->addSubmit('button','Přesměrovat')->onClick[] = $this->buttonClick(...);
return $form;
}
public function buttonClick(): void
{
$this->redirect(':Homepage:default'); // přesměrovává komponenta - tohle nefunguje
// $this->presenter->redirect(':Homepage:default'); // přesměrovává presenter - tohle funguje
}
}
Obdržel jsem chybovou hlášku:
Nette\InvalidArgumentException
Component name must be non-empty alphanumeric string, '' given.
Zkusil jsem přesměrovat presenter a to funguje. Nicméně bych si rád vyjasnil, jak přesměrování komponenty funguje, dokumentace přesměrování komponent nijak nevysvětluje.
Z chybové hlášky to vypadá, že metoda redirect() komponenty očekává jako parametr jméno komponenty (jiné ?). Asi na jiný presenter přesměrovat nejde, ale jde v rámci přesměrování na aktuální presenter a akci poslat signál jiné komponentě?
Děkuji za případné komentáře.
Editoval m.brecher (17. 9. 2022 23:25)
- Bulldog
- Člen | 110
Pokud se nepletu, tak link generátor v komponentách nezná rodiče.
A tak by to taky mělo být.
Jeden z principů programování je, že máš programovat třídu k jejímu účelu a ne k účelu nadřízených. Respektive když tvoříš třeba formulář, tak by si jej měl vytvořit tak, aby mu bylo fuk, co se děje okolo, ale zajímal se jen o svou práci.
Pokud by třída věděla o svém nadřízeném (kdo ji používá), tak by
s ním byla svázaná a nedala by se použít jinde, než právě v kompozici
s touhle nadřazenou třídou a to je špatně.
Je to jako by sis chtěl udělat kalkulačku, která počítá jen v rukou
konkrétního Jakuba.
Takže komponenty neví (v rámci odkazování – jinak ví, samozřejmě,
presenter je do nich attachnutý, ale nemusí to nutně být jejich
nadřízený) o nadřízených nic. Takže na ně logicky nemohou
odkazovat.
Komponenty tedy odkazují na signály své/signály svých subkomponent.
Pattern pro psaní odkazů vypadá asi takto:
'(componentName:)*signalName'
Tedy následující odkazy budou směřovat na tyto místa: (pokud předpokládáme, že redirect je použit v komponentě)
/*
* Aktuální komponenta a její signál page. Musí existovat funkce s názvem `handlePage`
*/
$this->redirect('page');
/*
* Komponenta search uvnitř této komponenty. Musí být tvořena funkcí `createComponentSearch`
* a musí obsahovat signál change, který tvoříme funkcí `handleChange`
*/
$this->redirect('search:change');
/*
* Komponenta filter uvnitř této komponenty. Musí být tvořena funkcí `createComponentFilter`
* komponenta filter uvnitř sebe musí mít komponentu page tvořenou pomocí `createComponentPage`
* a konečně komponenta page musí obsahovat signál change, který tvoříme funkcí `handleChange`
*/
$this->redirect('filter:page:change');
// atd...
Z výše uvedeného plyne, že pokud uděláš odkaz který vypadá
takto:
':Homepage:default'
tak to vezme první dvojtečku a snaží se ji to resolvnout jako název
podkomponenty, podle výše uvedeného patternu, což má za následek to, že
to jako název podkomponenty vezme prázdný řetězec, protože před
dvojtečkou nic nemáš a vyhodí to tvůj error že název komponenty nemůže
být prázdné jméno.
Tvůj druhý odkaz přes presenter samozřejmě funguje, jelikož presenter
zná vše a umí správně redirectovat.
Použití je ale zcela nevhodné, jelikož je to přesně to, co jsem psal a to
svázání třídy (ve tvém případě komponenty) s presenterem. Takto
nemůžeš použít komponentu v aplikaci, kde třeba presentery
nepoužíváš, nebo v aplikaci, kde nechceš přesměrovat, ale jen
překreslit snippet, udělat nějaké další změny atp., jelikož stihneš
v komponentě přesměrovat dřív, než by se další věci vykonaly…
Správné řešení je použití návrhového vzoru Observer, aby se prostě o redirect/překreslení/další side efekty postaral někdo jiný až na něj přijde řada.
Nejjedodušší implementace je použít přímo tu komponentu jako observer a prostě si předat nějaký callback/pole callbacků:
class MyComponent extends Nette\Application\UI\Control
{
private array $onButtonSubmit = []; // pole pro callbacky
// nebo (callable $callback) pro starší PHPčka
public function addButtonSubmitCallback(\Closure $callback): static
{
$this->onButtonSubmit[] = $callback; // Přidáme callback do pole
return $this;
}
public function createComponentButton(): Nette\Application\UI\Form
{
$form = new Form;
$form->addSubmit('button','Přesměrovat')->onClick[] = $this->buttonClick(...);
return $form;
}
public function buttonClick(): void
{
// tohle zavolá všechny callbacky, mezi nimiž může být klidně presenterovský
// callback s přesměrováním, ale to, co je jejich náplní je komponentě jedno.
// prostě si zavolá callbacky, které jí někdo dal a od koho jsou, nebo co dělají se nestará
// důležité jen je, aby ten, kdo jí je předal je předal správně
Nette\Utils\Arrays::invoke($this->onButtonSubmit);
}
}
a pak v presenteru:
public function createComponentMyComponent(): MyComponent
{
$component = /* tvorba komponenty ideálně továrnou */
// pro starší PHP v tomto případě musí být funkce 'onMyComponentButtonClick' public
//$component->addButtonSubmitCallback([$this, 'onMyComponentButtonClick']);
// Od verze PHP 7
//$component->addButtonSubmitCallback(\Closure::fromCallable([$this, 'onMyComponentButtonClick']));
// Od PHP 8.1 'First class callable syntax'
$component->addButtonSubmitCallback($this->onMyComponentButtonClick(...));
return $component;
}
private function onMyComponentButtonClick(): void
{
$this->redirect(':Homepage:default'); // tohle už je tvůj redirect
// samozřejmě tento callback může dělat cokoliv a je to plně odstíněno od komponenty.
}
- m.brecher
- Generous Backer | 863
@DavidGrudl Ano, kapitolu o odkazech v komponentách v dokumentaci https://doc.nette.org/…eating-links#… jsem četl několikrát i včera, ale nevyvodil jsem z toho, že to platí i pro $component->redirect(). Text v dokumentaci:
„… značka {link} i metody komponent jako je link() a další považují cíl odkazu vždy za název signálu…“
je sice naprosto jasný, ale já vyhledával fulltextem přímo ve stránce „redirect(“ a proto jsem to minul.
Obecně je v dokumentaci lepší jednotlivé položky explicitně vyjmenovat, než obecně popsat, protože nováček v Nette to potřebuje po lopatě. Doplnil bych to do dokumentace i sám, ale budu si muset nastudovat, jak se do dokumentace přispívá.
Editoval m.brecher (18. 9. 2022 17:17)
- David Grudl
- Nette Core | 8218
Je to normální git repozitar na https://github.com/nette/doc, asi by to šlo editovat přímo na Githubu
- m.brecher
- Generous Backer | 863
David Grudl napsal(a):
Je to normální git repozitar na https://github.com/nette/doc, asi by to šlo editovat přímo na Githubu
Opravuji, je to https://github.com/nette/docs