Vznik (nebo rozšíření?) životního cyklu komponenty
- Patrik Votoček
- Člen | 2221
Proč?
Zjistil jsem že vpodstatě nemám žádnou komponentu bez šablon. Jelikož jsem líný tak mě nebavilo pořád ručně nastavovat šablonu která se má použít a vyrenderovat ji. Tak jsem si udělal takovou berličku která mě ale začíná taky vadit.
Standardní řešení
class FooControl extends \Nette\Application\UI\Control
{
public function render($foo)
{
$this->template->foo = $foo;
$this->template->setFile(__DIR__ . "/FooControl.latte");
$this->template->render();
}
}
Řešení s berličkou
class FooControl extends \Nella\Application\UI\Control
{
public function render($foo)
{
$this->template->foo = $foo;
$this->_render(__METHOD__);
}
}
Implementoval jsem si totiž Control::formatTemplateFiles
který
má vpodstatě stejnou funkci jako
Presenter::formatTemplateFiles
.
Navrhované řešení
namespace Nette\Application\UI;
class Control ...
{
/** @var string */
protected $view;
public function run($view = NULL, array $args = array())
{
$this->view = $view;
callback($this, 'render' . $view ? ucfirst($view) : "")->invokeArgs($args);
}
}
namespace App;
class BaseControl extends \Nette\Application\UI\Control
{
protected beforeRender()
{
$file = $this->formatTemplateFile($this->view);
$this->template->setFile($file);
}
public function run($view = NULL, array $args = array())
{
$this->view = $view;
$this->beforeRender();
callback($this, 'render' . $view ? ucfirst($view) : "")->invokeArgs($args);
$this->template->render();
}
}
{control foo:bar "test"}
by pak volal
$control->getComponent('foo')->run('bar', array("test"))
beforeRender a afterRender můžou být přímo v nette
není to ale nutné. Každý si je totiž může doimplementovat sám ve
vlastním BaseControlu. Za klíčovou ale považuji metodu
Control::run
.
Díky přepracovaným makrům není zas takový problém tohoto chování docílit už dneska. Myslím si ale že by to využilo více lidí.
PS: psáno z hlavy a bez IDE takže tam jsou možná bugy. :-)
Nápady / Připomínky / Souhlas / Nesouhlas?
- Ondřej Mirtes
- Člen | 1536
Komponenty jsou na tohle nepoužitelné. Hodí se na věci, které využiju napříč projekty, tzn. WebLoader, DataGrid apod. Těchto komponent je ale pár a většinou je za mě napíšou jiní :)
Využít komponentu jen pro šablonu je příliš psaní (proč bych měl
psát třídu, když chci jen zobrazit nějaký blok HTML na více
místech?) – obvykle stačí nějaké {include sablona.latte}
v šabloně.
Tímhle RFC se z komponenty snažíš udělat Presenter. Ale proč? Proč nevyužít presentery? Pohrávám si s myšlenkou začít využívat presentery ne jako třídy (tedy jak je to teď ve frameworku), ale jako objekty. Tedy že budou univerzálně řešit jeden úkol (např. CRUD v administraci) a pomocí setterů mu nastavím nějaké detaily, např. pro jakou entitu má ten CRUD řešit, jaké šablony má použít apod.
Více identifikátorů presenterů jako „Front:Homepage“ nebo „Front:Contact“ by pak mohlo směřovat do té samé třídy, ale s různým nastavením.
K ověření tohoto nápadu se dostanu v Mediu během následujících týdnů, tak pak poreferuju, případně něco řeknu na Poslední sobotě.
- newPOPE
- Člen | 648
Fajn napad, suhlas s @Nox a ano ani mna to nebavi :-) no skor ako len
run
by som skor bol za zivotny cyklus aky ma Presenter… (mozno
trocha osekany) mohlo by to miast preco ma komponenta iny ziv. cyklus ked aj
Presenter je typ komponenty. (moj subj. nazor)
mozno nieco taketo:
init()
//startup()handleFoo($args)
renderFoo($args)
finish(...)
//shutdown()
startup
a shutdown
skor asi stylom ala Presenter…
(zvyk je zvyk)
- Patrik Votoček
- Člen | 2221
Ondřej Mirtes napsal(a):
Komponenty jsou na tohle nepoužitelné. Hodí se na věci, které využiju napříč projekty, tzn. WebLoader, DataGrid apod. Těchto komponent je ale pár a většinou je za mě napíšou jiní :)
Nesouhlasím. Jsou to všechny komponenty které mám v projektu.
Využít komponentu jen pro šablonu je příliš psaní (proč bych měl psát třídu, když chci jen zobrazit nějaký blok HTML na více místech?) – obvykle stačí nějaké
{include sablona.latte}
v šabloně.
Nevím jak jsi přišel na využití komponenty jako „snippetu“ šablony použitelného kdekoli. Nic takové jsem nepsal a ani tak komponenty nepoužívám. Doopravdy používám komponenty jako komponenty.
Tímhle RFC se z komponenty snažíš udělat Presenter. Ale proč?
Nesnažím. Snažím se pouze o jednodušší práci se šablonamy v komponentách.
Proč nevyužít presentery?
Protože řeším komponenty a né presentery (presenter je moc velký „moloch“).
Pohrávám si s myšlenkou začít využívat presentery ne jako třídy (tedy jak je to teď ve frameworku), ale jako objekty. Tedy že budou univerzálně řešit jeden úkol (např. CRUD v administraci) a pomocí setterů mu nastavím nějaké detaily, např. pro jakou entitu má ten CRUD řešit, jaké šablony má použít apod.
Tohle jsem moc nepobral. Protože buď to je to jak presentery používám už teď. Nebo se snažíš používat presentery právě na to na co se více hodí komponenty.
Jen pro upřesnění mám třeba komponentu na komentáře kterou můžu připojit k libovolné stránce.
- Nejedná se o pouhou šablonu – formulář pro přidání komentáře
- Jako presenter to nevyužiju protože to pak nemůžu připojit k libovolné stránce (která je presenterem)
Takových (podobných) komponent mám v aplikaci aktuálně asi 30.
Jan Tvrdík napsal(a):
Automatické nastavování cesty k šabloně jsem vyřešil docela snadno.
Tohle je použitelné pouze do chvíle kdy máš v komponentě pouze jeden „view“.
Mám například komponentu třeba na slidery (takovej ten blibinec jak
mění a prohazuje obrázky).
Má metodu „root“ view při kterém se vykreslí a edit view ve kterém se
spravuje (přídávají / ubírají obrázky atd.).
EDIT: upravil jsem první post aby bylo jasné že nejde primárně
o beforeRender a afterRender ale o Control::run
- Filip Procházka
- Moderator | 4668
Pokud potřebuješ životní cyklus, pak na to už přece berličky hotové
máme. Jediné co jsi udělal navíc, tak přejmenoval metodu
render
na run
a tím z toho děláš v podstatě
okroužlaný Presenter
a mně se to vubec nelíbí.
Nevidím v tom přínos.
- Tharos
- Člen | 1030
Ondřej Mirtes napsal(a):
Tímhle RFC se z komponenty snažíš udělat Presenter. Ale proč? Proč nevyužít presentery? Pohrávám si s myšlenkou začít využívat presentery ne jako třídy (tedy jak je to teď ve frameworku), ale jako objekty. Tedy že budou univerzálně řešit jeden úkol (např. CRUD v administraci) a pomocí setterů mu nastavím nějaké detaily, např. pro jakou entitu má ten CRUD řešit, jaké šablony má použít apod.
Více identifikátorů presenterů jako „Front:Homepage“ nebo „Front:Contact“ by pak mohlo směřovat do té samé třídy, ale s různým nastavením.
Tuto myšlenku tady rozebíral HosipLan nějaký ten pátek zpátky (tak půl roku?). Zhruba v té době to napadlo nezávisle i mě (a ono se asi k této myšlence dopracuje každý, komu nestačí standardní komponenty a přirozeně hledá v presenteru inspiraci). Tenkrát mi to nedalo a taky jsem v tomto směru podnikl pár experimentů. Výsledek shrnu do prosté věty: nebylo to ono. Prostě je to znásilňování presenteru a jeho využívání k něčemu, k čemu není navržen. Připadá mi to nečisté a je to totální WTF pro běžného Nette vývojáře, který se k Tvému projektu dostane.
Jestli se vám to ale podaří naimplementovat k vaší plné spokojenosti, proti gustu samozřejmě žádná a nechť to slouží.
- Ondřej Mirtes
- Člen | 1536
Jde mi právě o komponenty s více „views“. Jsou vlastně dva způsoby, jak s nimi pracovat:
- Vykreslit komponentu přes
{control komponenta}
a střídat views přes signály. Vyžaduje to minimální práci ze strany presenteru, ale aby vše fungovalo korektně, je potřeba hodně hackování v komponentě, pokud má např. odesílat nějaký formulář. - Pro každé view komponenty vytvořit zvláštní view presenteru, kde se
bude komponenta vykreslovat přes
{control komponenta:view}
. Tohle vyžaduje více práce ze strany presenteru, duplikování kódu a je to nehezké.
Tento druh komponent stejně nejde napsat „univerzálně“ a je tam značný coupling s presentery – v komponentě po odeslání formuláře obvykle potřebuju někam redirectovat – buďto tu lokaci tam nastavím přes setter, což opět zvyšuje úsilí ze strany presenteru, anebo do redirect() uved natvrdo cestu k nějakému presenteru, což zase provazuje komponentu s konkrétní aplikací a nemůžu ji přesunout jinam.
Prostě pokud máte komponentu, kterou vykreslujete v akci presenteru, jejíž jediným účelem je vykreslení právě této komponenty, je to jasný kandidát na „znovupoužitelný presenter“.
Nepřipadá mi to nečisté, jde jen o to, jaký presenter a jak nakonfigurovaný vrátí PresenterFactory na určitý identifikátor. Pokud si implementuji vlastní factory, která bude presentery vracet jako nakonfigurované služby z DI kontejneru, nevidím v tom problém. Django má také „generic views“, což je přesně toto.
Odstraňování duplikovaného kódu se v presenterech dělá dost špatně – můžu nějakou společnou funkčnost vyřadit do předka, ale pokud je těchto požadovaných funkčností víc, skončím u nějaké šílené hierarchie nebo u toho, že všechno narvu do BasePresenteru a některé presentery si s sebou potáhnou věci, které nepotřebují. A využít na to komponenty mi přijde neohrabané z důvodů uvedených výše. Proto se na to chci podívat úplně z jiného úhlu pohledu a nezavádět nějaký životní cyklus do komponenty, když mi to přijde jen jako obcházení problému.
- Ondřej Mirtes
- Člen | 1536
Abychom se bavili konkrétněji, zkuste mi navrhnout řešení, pokud jsou známy tyto skutečnosti a požadavky:
- Mám administraci. V té spravuji v několika sekcích různé entity. Tyto entity mohou mít na sebe navázané fotografie (1:N).
- U všech entit chci spravovat přiřazené fotografie. Tedy CRUD – upload nové, seznam aktuálních (v DataGridu), detail fotografie, editace (úprava názvu či náhrada obrázku za nový) a smazání (ConfirmationDialog-em z DataGridu a detailu fotky).
- V administraci mám dohromady 6 druhů entit. 4 z nich mají takto navázané fotografie, jiné 4 mají podobně navázaná videa.
- Nechci mít nikde duplikovaný žádný kód.
- Nechci si v předcích (BasePresenter) s sebou tahat žádné zbytečnosti.
Zajímá mě, jak byste řešili CRUD fotografií.
- Honza Marek
- Člen | 1664
Nechápu tu motivaci. Jestli jsem to dobře pochopil, tak cílem je spojit dnešních několik komponent do jedné třídy. A ještě zařídit aby se s tim potýkali i ti, kdo o to nemají zájem.
- Ani
- Člen | 226
Ondřej Mirtes:
Třeba já mám komponentu File ta má view upload a browser. Tj. zajišťuje nahrávání a procházení souborů. Navíc má možnost si navěsit různé callbacky, tj. při kliknutí na obrázek/soubor v browseru, při nahrání na server.
Pak mám komponentu pro galerie ta umožňuje render normálně na web. Render na editaci (přidávání z fotek na serveru a nahrávání nových). To se dělá tak že v ní vytvořím komponentu File a navěsím si na callback při kliknutí, ať se připojí ke galerii. To samé se může udělat po nahrání.
Žádný kód duplikovat nemusíš, stačí mít ty komponenty napsané tak aby šli věšet do sebe.
- Patrik Votoček
- Člen | 2221
HosipLan napsal(a):
Pokud potřebuješ životní cyklus, pak na to už přece berličky hotové máme.
Vím o tomhle řešení ale já nehledám berličku
snažím se o to aby nebyla berlička potřeba. Při pohledu do render
metody se mě chce blinkat (takového ohýbání pro nic – to už se více
spokojím s $this->_render(__METHOD__)
).
Jediné co jsi udělal navíc, tak přejmenoval metodu
render
narun
a tím z toho děláš v podstatě okroužlanýPresenter
a mně se to vubec nelíbí.
Tím že ten tvůj „bastl“ převedu na čitelnější kód (a znatelně kratší) z toho udělám presenter? A to jak přesně?
Ondřej Mirtes napsal(a):
- Pro každé view komponenty vytvořit zvláštní view presenteru, kde se bude komponenta vykreslovat přes
{control komponenta:view}
. Tohle vyžaduje více práce ze strany presenteru, duplikování kódu a je to nehezké.
Používám tohle už dlouho a nepozoruju žádné duplikování kódu. (Ani CPD :-) )
Tento druh komponent stejně nejde napsat „univerzálně“ a je tam značný coupling s presentery – v komponentě po odeslání formuláře obvykle potřebuju někam redirectovat – buďto tu lokaci tam nastavím přes setter, což opět zvyšuje úsilí ze strany presenteru, anebo do redirect() uved natvrdo cestu k nějakému presenteru, což zase provazuje komponentu s konkrétní aplikací a nemůžu ji přesunout jinam.
Díky zavedeným konvencím ani tohle nepozoruju. V 99% případů přesměrovávám na „this“.
Nepřipadá mi to nečisté, jde jen o to, jaký presenter a jak nakonfigurovaný vrátí PresenterFactory na určitý identifikátor. Pokud si implementuji vlastní factory, která bude presentery vracet jako nakonfigurované služby z DI kontejneru, nevidím v tom problém. Django má také „generic views“, což je přesně toto.
Chápu to dobře že tedy budeš stránku skládat z více presenterů? To mě připadá jako šílený overhead oproti mnou využívaného přístupu pomocí komponent.
Ale abych to pochopil zkus popsat jak by jsi řešil právě mnou zmiňované
komentáře.
Já to řeším tak že mám komponentu která má render který
vykresluje komentáře a mám v komponentě formulář který vykresluji při
vykreslování komentářů. Po odeslání komentáře přidám flash message
komponentě (zobrazí se tedy v komponentě a na stránce – je hned jasná
souvislost) a přesmeruju na „this“. Komponenta se dá připojit
k jakékoli stránce.
Záměrně neodpovídám na tvůj komentář jak bych řešil CRUD fotogalerie protože to je rozsáhlé (odpověď si nechám na později / jinam).
- Ondřej Mirtes
- Člen | 1536
Chápu to dobře že tedy budeš stránku skládat z více presenterů? To mě připadá jako šílený overhead oproti mnou využívaného přístupu pomocí komponent.
Ne, říkám, že to je pro případy, kdy jediný účel akcí presenterů je vykreslit „view“ nějaké komponenty.
Na tvoje komentáře se v pohodě hodí normální komponenta v Nette, je to jednoduchý případ :) Jedno render(), jeden formulář. Pokud by se ale staly složitějšími, tedy např. jako na Zdrojáku (více views – celá stránkovaná diskuse, zobrazení jednoho vlákna, zobrazení jednoho příspěvku) a navíc bych na každém tom view chtěl mít zobrazený formulář pro nový příspěvek v relevantním kontextu, už by to tak jednoznačné nebylo a vedlo by to opět na znovupoužitelné presentery (protože diskuse by byla pravděpodobně jediná věc, která se na dané stránce zobrazuje a chtěli bychom v ní mít hezká URL).
Můj příklad s CRUDem fotek taky směřuje ke znovupoužitelným presenterům.
- Patrik Votoček
- Člen | 2221
Ondřej Mirtes napsal(a):
Ne, říkám, že to je pro případy, kdy jediný účel akcí presenterů je vykreslit „view“ nějaké komponenty.
Jak jsem už psal toho se tato diskuse netýká (v tomhle s tebou souhlasím).
Na tvoje komentáře se v pohodě hodí normální komponenta v Nette, je to jednoduchý případ :)
Stejným případem je mnou zmiňovaný slider který má ale už 2 „view“. A jak už jsem taky psal tak takových více či méně jednoduchých komponent mám v pojektu aktuálně 30 (a určitě se nejedná o konečné číslo).
Můj příklad s CRUDem fotek taky směřuje ke znovupoužitelným presenterům.
Už to konečně chápu… :-)
Ale jak se snažím celou diskusi vysvětlovat tak o to se mě nejedná jedná se mě skutečně o „jednoduché“ komponenty (ale né zas tak jednoduché aby pouze vykreslovali šablonu).
Jak to tak tady čtu tak jsem možná měl do titulku vlákna napsat „zjednodušení (nebo automatizace) používání šablon v komponentách“.
- Jendaaa
- Člen | 21
Stejným případem je mnou zmiňovaný slider který má ale už 2 „view“. A jak už jsem taky psal tak takových více či méně jednoduchých komponent mám v pojektu aktuálně 30 (a určitě se nejedná o konečné číslo).
Trochu OT, ale mohl by si mi dát příklady dalších komponent, které na webu používáš? Zatím tlačím většinu věcí do presenterů, tak mi přijde 30 docela velké číslo…
- Filip Procházka
- Moderator | 4668
Nejlíp úplně všechno :) Ale když se zeptáš konkrétně (a nejlépe ve vlastním vlákně) tak ti určitě někdo poradí a třeba se u toho zvládneme i pohádat, aspoň bude sranda :) V lepším případě ti při psaní toho příspěvku dojde, co se hodí na komponentu a co ne a pak se nebudeš muset ani ptát :)
- Patrik Votoček
- Člen | 2221
Rád bych znovu otevřel diskusi k tomuto RFC.
Pokud máme možnost používat v komponentách více view (jako že tu možnost máme) hodilo by se vzhledem ke DRY mít možnost ovlivnit jednoduše společné části všech view. A to tak aby nám byl znám název view kterého se změny týkají (například kvůli výše zmiňovanému automatickému dosazování šablon).