Form Ajax redrawControl a cache createComponent
- Andy3
- Člen | 15
Ahoj, dnes jsem narazil na zajimavy problem pri redrawControl Component.
Komponenty pouzivam, tak jak zde bylo doporucovano(passive View). Komponenty dostavaji pouze datove objekty a v pripadaze, ze je interakce s uzivatelem, tak eventManager.
Problem mi nastal v kosiku pri zmene poctu kusu polozek. Scenar je nasledujuci:
<?php
public function createComponentStepOne()
{
return new \CheckoutModule\Component\StepOne\StepOne($this->cart, $this->eventManager);
}
public function actionStepOne()
{
//$this->cart je jen seznam polozek
$this->cart = $this->facadeCheckout->getCart();
}
?>
stepOne.latte
{block content}
<div n:snippet="stepOne">
{control stepOne}
</div>
Problem nastava pri Ajaxovem pozadavku na zmenu poctu kusu polozky. Pusti se
handle na submit
formu stepOne-productRental-0-formQuantity-submit
<?php
$form->onSuccess[] = function(\Nette\Application\UI\Form $form){
$payload= new \CheckoutModule\Payload\UpdateProductQuantity($this->product, $form->getValues()->quantity);
$this->eventManager->dispatchEvent(Event::UPDATE_PRODUCT_QUANTITY, new \Kdyby\Events\EventArgsList(array($payload)));
//Mam tu referenci.Tusim, ze takhle se v php Eventy asi pouzivat nemaji. Nahrazuji tim asynchronitu z JS. Pokud ma nekdo lepsi napad, rad se pobavim v jinem topicu.
if($payload->hasError()){
//.....
}
};
?>
Tak jak chapu co se deje. Nette, aby mohlo pustit callback z formulare musi vyinstacovat vsechny rodicovske komponenty(Toto se deje na starych datech kdy je pocet „1“). Vsechno probehne v poradku, prida se do DB pocet „2“.
Ted jak donutit Nette aby, mi to prerendrovalo jiz s novymi daty. Jediny zpusob jak toho dosahnout je nasledovne.
<?php
//presenter
public function renderStepOne()
{
//Narazil jsem na foru na podobny problem a jina cesta je to redirectnout. Ale to pri Ajaxu nechcem.
if ($this->isAjax()) {
$this->cart = $this->facadeCheckout->getCart();
$this->removeComponent($this->getComponent('stepOne'));
$this->addComponent($this->createComponentStepOne(), 'stepOne');
$this->redrawControl();
} else {
$this->redirect('this');
}
}
?>
Zkousel jsem i do stavajici komponenty ‚stepOne‘ setnout nova data a pak dat redrawControl(), ale to nemupomuze, protoze render se v komponente ‚stepOne‘ sice pusti i s novymi daty, ale createComponentProductRental() uz ne(a na pozadi to vrati data s poctem kusu „1“, kdyz se to vytvarelo pro formular).
Vec, kterou nechpu je, ze kdyz si vytvorim Event na uspene pridani do BD „afterUpradeCart“ a pustim ho, tak presenter, ktery ho posloucha, tak se to pusti na starych datech, ale implementace je stejna jako u renderStepOne()
<?php
public function afterUpradeCart()
{
if ($this->isAjax()) {
$this->cart = $this->facadeCheckout->getCart();
$this->removeComponent($this->getComponent('stepOne'));
$this->addComponent($this->createComponentStepOne(), 'stepOne');
$this->redrawControl();
} else {
$this->redirect('this');
}
}
?>
Editoval Andy3 (28. 6. 2019 19:02)
- F.Vesely
- Člen | 369
Kdo ti zde doporucoval, abys komponenty pouzival jako „passive View“? Ano, klidne je tak pouzivej, ale pak v nich nepouzivej dynamicke prekreslovani.
Ja mam tedy radsi, kdyz komponente predam jen napriklad ID a ona si data, co potrebuje, sezene sama. Nejak nevidim duvod, proc bych mel pri kazdem pouziti komponenty zjistovat jaka vsechna data potrebuje a predavat ji je.
Kdyz si vezmu, ze v presenteru mam 5 komponent, kazda potrebuje data ze 3 ruznych sluzeb a ja teda musim do presentru natahnout vsech 15 sluzeb… Nedej boze abych pak zjistil, ze v komponente potrebuju data jeste z dalsi sluzby a ve vsech presenterech, kde komponentu pouzivam, tu sluzbu musim pridat a vytahnout z ni data.
- Andy3
- Člen | 15
„Kdo ti zde doporucoval, abys komponenty pouzival jako „passive View“? Ano, klidne je tak pouzivej, ale pak v nich nepouzivej dynamicke prekreslovani.“
Např.: https://forum.nette.org/…t-komponenty. Podobných topicků je tu na fóru více.
„Ja mam tedy radsi, kdyz komponente predam jen napriklad ID a ona si data, co potrebuje, sezene sama. Nejak nevidim duvod, proc bych mel pri kazdem pouziti komponenty zjistovat jaka vsechna data potrebuje a predavat ji je.“
Typický problem. Máš komponentu na náhled položky, ale předáš jí ID položky. Na stránce máš dvacet těchto komponent, které se chovají nezávisle. Co se stane: Dvacet samostaných dostazů do BD. V jiných případech duplicity dotazů.
„Kdyz si vezmu, ze v presenteru mam 5 komponent, kazda potrebuje data ze 3 ruznych sluzeb a ja teda musim do presentru natahnout vsech 15 sluzeb… Nedej boze abych pak zjistil, ze v komponente potrebuju data jeste z dalsi sluzby a ve vsech presenterech, kde komponentu pouzivam, tu sluzbu musim pridat a vytahnout z ni data.“
Ano, presenter dělá to co podle mě dělat má. Na základě požadavku předává view data, které potřebuje pro zobrazení.
Další věc. Jak dostanes do komponenty služby, které data obstarají?
- Přes service locator → antipatern
- Zaregistruješ komponentu jako službu → antipatern
- Uděláš si továrnu pro komponentu a přes ní dodáš služby → Jde, ale co když komponeta zobrazuje data ze tří různých služeb(položka eshopu, půjčovny, odměnová položka,…), které mají stejný interface. Budeš do komponenty dotahovat všechny služby a tam se rozhodovat, kterou použiješ?… (nebo co když kolekce položek bude mixnutá různými typy položek)
Používání tupých komponent bylo v Reactu doporučováno už před lety(teď se to trochu změnilo, protože GraphQL). Ono se to obecně doporučuje při práci s komponentami na front-endu. Základní pricipy jsou stejné a je jedno jestli je to v Reactu nebo Nette, nebo v něčém jiném. V jistém smyslu David hodně předběhl dobu.
Dynamické překreslovaní nesouvisí s passive view.
- F.Vesely
- Člen | 369
- Problem N+1 dotazu se da prece resit, zalezi co pouzivas za DB vrstvu.
- Pises, ze Presenter preda view data, to je spravne chovani. Ty ale data predavas komponente a ta je predava view, to je tam pak skoro ta komponenta uplne k nicemu. Ja komponentu chapu v MVC architekture jako mini Controller, ktery podle stavu nacita data a predava je View, ktere je zobrazuje. Coz se vicemene pise i v tom odkazu, co posilas.
- Do komponenty predavam sluzby pres tovarnu (tvuj bod 3). Ve tvem pripade bych si udelal 3 komponenty, kazda by natahla data a predala View. Pokud by vsechny vypadaly stejne, tak bych jim dal stejnou sablonu.
- Jak jsem psal jiz vyse, pokud chces pouzivat tupe komponenty, tak je pouzivej, ale nesmis pak od nich ocekavat, ze se budou chovat „chytre“. Logicky, pokud tu tupou komponentu vytvoris s datama a pak ji chces vykreslit s jinyma datama, tak ji je musi neco „chytrejsiho“ zmenit a ta zmena se musi probublat do vsech podkomponent.
- Andy3
- Člen | 15
Problém vyřešen.
Špatná implementace presenteru jako listeneru. Snažil jsem se to udělat
přes config.neon. Dochazí zde k problému, že zdánlivě vše funguje, ale
vytvoří se nová instance třídy presenter jako obyčejná služba…
Řešení je registrovat metody presetneru jako listenery dynamicky např.: ve
startup. Vice viz https://pehapkari.cz/…ce-udalosti/.
„Ty ale data predavas komponente a ta je predava view, to je tam pak skoro ta komponenta uplne k nicemu.“ Jo i ne. Má to jednu več co šablony nemají „typovost dat“ a „typ(jméno) komponenty(šablony)“ což je pro mě základ pro přepoužitelnost a víc ani nechci.
Každý použiváme jiný model. To co používam je tupější. Jak píšes vstup je přes rodicě, který to dál rozhazuje. Občas je to oprus. Tvůj přístup s Doctrinou a se synchronností php asi jde udržet na uzdě. V JS jsem to hned opustil. Diky asynchronnitě, stavu a businessu v komponentách se to od určitého množství blbě spravuje natož debuguje.
Editoval Andy3 (24. 6. 2019 20:03)
- Andy3
- Člen | 15
Prisel jsem uz i na to proc se pri redrawControl() nepousti znovu createComponent…(). Je to kvuli callbaku ve formulari. Ve strevech jsem se nestoural, ale duvod je ten ze je potrena si udzet „referenci“ na ten form, aby sli addnout a zobrazit errory.
Events to the rescue. Finalni reseni. Neni to bulletproof, ale nenapada me use-case na to, abych zaroven potreboval fresh data a zobrazit i nejaky error.
<?php
public function onAfterCartUpdated()
{
if ($this->isAjax()) {
$this->cart = $this->facadeCheckout->getCart();
$this->removeComponent($this->getComponent('stepOne'));
$this->addComponent($this->createComponentStepOne(), 'stepOne');
$this->redrawControl();
} else {
$this->redirect('this');
}
}
public function onErrorCartUpdate()
{
if ($this->isAjax()) {
$this->redrawControl();
} else {
$this->redirect('this');
}
}
?>
Editoval Andy3 (28. 6. 2019 20:02)