Jak jednoduše vkládat komponenty do šablony?
- Pavel S.
- Člen | 24
Nette používám už poměrně dlouho, ale teprve nedávno jsem objevil kouzlo komponent. Začal jsem je používat, ale zdá se mi poněkud neobratný následující postup:
- Vytvořím obecnou komponentu
Moje
v app/components - V konkrétním presentru vytvořím metodu
createComponentMyComp
:
public function createComponentMyComp {
return new Moje();
}
- V šabloně pak volám {control myComp}
Všechno takhle funguje, ale určitě obalování komponenty Moje další komponentou MyComp není ideál. Jak nejlíp tohle obejít a odkazovat se na komponenty přímo? Nějak je injectovat do všech presenterů?
Díky za pomoc!
- 22
- Člen | 1478
MyComp
neni komponenta, ale továrna na komponentu.. takže
nerozumím otázce. Instanci komponenty je potřeba nějak vytvořit a vzhledem
k životnímu cyklu presenteru potřebuješ třeba při AJAXU měnit její stav
přes nějaký handler, takže to opravdu nevím, jak by jsi se chtěl
v šabloně, tedy jen v render fázi, s tím vypořádat?
Editoval 22 (20. 8. 2012 15:25)
- Pavel S.
- Člen | 24
Fajn, zkusím to popsat z jiné strany. Jak bych si to představoval:
- Vytvořím obecnou komponentu
Moje
v app/components - V šabloně uvedu
{control moje}
- Presenter se podívá, jestli zná metodu
createComponentMoje
. Nezná, takže se podívá do app/components, jestli existuje komponentaMoje
. Existuje, takže ji použije a vykreslí, aniž bych musel psát ty 3 řádky, kde akorát vytvořím instanci a vrátím ji.
Řešil už někdo takovou situaci? Je na to v Nette nějaký dobrý pattern?
EDIT: Chápu, že komponenta může reagovat na signály, ale v mojem případě je to jen kus HTML kódu, který bych rád definoval jen jednou pro více použití.
Editoval Pavel S. (20. 8. 2012 15:52)
- duke
- Člen | 650
@22 se vyjádřil poněkud nepřesně. myComp je komponenta (resp. její jméno, pod kterým je zaregistrovaná v presenteru) a Moje je typ té komponenty (tj. název třídy). Je třeba si uvědomit, že můžeš v rámci jedné stránky (tj. jednoho presenteru) mít více komponent téhož typu, se kterými potřebuješ pracovat odděleně. K tomu je potřebuješ odlišit jménem. Obecně můžeš chtít každou z nich nějak jinak nakonfigurovat, a proto se hodí, když bude mít každá svou vlastní továrničku.
- 22
- Člen | 1478
@duke: kterážto je typu Moje. .-) to kde
jsi vyčetl? Jinak je to továrna na komponentu, která ti vratí komponentu
s nějakým názvem a pojmenuje ji ve stromu komponent, jak už bylo řečeno,
můžeš mít více instancí jedné komponenty, a pořád se bavíme o jedné
koponentě a jejich 2 instancích s názvem A a B a ne o komponentě A a
B.
@Pavel S.: jinak komponentu vytvořit nejde, na
vložení kus kodu použij {include 'kus_kodu.latte'}
, jak uvedl
@Teyras, bylo by to magické a Nette se magie
zbavuje
Editoval 22 (20. 8. 2012 16:23)
- Filip Procházka
- Moderator | 4668
@Pavel S.: možná se ti to zdá jako obtěžování, ale ručně vypsat všechny komponenty do továrniček, které pak v daném presenteru (jeho šabloně) použiješ, je ten nejlepší možný přístup.
- duke
- Člen | 650
22 napsal:
@duke: kterážto je typu Moje. .-) to kde jsi vyčetl?
Uznávám, že přesněji by bylo „kterážto je typu object, a třída tohoto objektu je v tomto případě ‚Moje‘“.
22 napsal:
Jinak je to továrna na komponentu, která ti vratí komponentu s nějakým názvem a pojmenuje ji ve stromu komponent, jak už bylo řečeno, můžeš mít více instancí jedné komponenty, a pořád se bavíme o jedné koponentě a jejich 2 instancích s názvem A a B a ne o komponentě A a B.
Bavíme se pořád o MyComp, resp. myComp, nikoli o metodě createComponentMyComp, to si prosím uvědom. Továrna je ta metoda, nikoli název MyComp (jak jsi původně trvrdil). MyComp je jméno komponenty (ve smyslu instance), kterou ta továrna vytváří.
Z hlediska typu (resp. třídy objektu) se bavíme o jedné komponentě, z hlediska jména či instance o dvou různých. Obojí pojímání je možné. Např. pojem „strom komponent“ pojímá komponentu jako instanci (strom instancí, nikoli strom tříd). Sám se tedy dopouštíš nejednoznačného vyjadřování (když pojem „strom komponent“ používáš).
Klidně mohu mít 2 komponenty téhož typu, pojmenovat je A a B a pak se na ně odkazovat jako na komponenty A a B. To, že jsou stejného typu (stejné třídy objektu) mě vůbec nemusí zajímat.
- Šaman
- Člen | 2666
Já myslel, že jsem na tohle odpovídal a teď koukám, že ta odpověď byla úplně v jiném vlákně, kde se řešil stejný problém.
Můžeš si udělat supertovárničku přetížením metody
createComponent($name)
. Je to maličko magie, tak s tím opatrně,
doporučovaný postup je mít na každou komponentu továrničku. Já
supertovárničku používám tak, že pokud nemám explicitně vypsanou
továrničku, tak se mi vrátí továrna stejného jména z configu (ze sekce
factories
).
Je možné taky vracet novou instanci $name
, ale často je potřeba
nějaká konfigurace nové komponenty a config ji umožňuje, zatímco
univerzální zápis return new $name;
ji neumožňuje – takže
jsem rychle narazil a přepsal to.
V createComponent($name)
se dá univerzálně vytvořit
komponenta podle parametru $name
. Když máš požadavek na
komponentu FooBar
(třeba protože ji voláš v šabloně), tak se
hledá metoda createComponentFooBar()
a pokud se nenajde, tak se
zavolá createComponent('fooBar')
, která implicitně nic nedělá.
No a tuhle metodu si můžeš přepsat tak, aby to třeba vytvořilo instanci
třídy FooBar
a vrátilo ti ji. Nebo zkrátka cokoliv bys měl
v továrničce.
Pozor, pokud budeš potřebovat dvě různé komponenty stejné třídy –
třeba dvě různá počitadla. Pak potřebuješ každé pojmenovat jinak a tedy
mít dvě továrničky: createComponentCounter1()
a
createComponentCounter2()
. Jinak při druhém použití komponenty
stejného jména se ti jen podruhé vykreslí ta samá (třeba stránkovač nad
tabuklou i pod ní jsou stále stejná komponenta).
Když si následující kód vložíš do BasePresenteru a BaseControl, tak ve všech poděděných presenterech a a komponentách můžeš používat všechny komponenty, které máš zaregistrované v configu jako factories.
<?php
/**
* Supertovárnička na komponenty
* Pokud neexistuje volaná komponenta, zkusí se zavolat továrnička z configu
*
* @param string $name jméno komponenty
* @return \Nette\ComponentModel\IComponent
*/
protected function createComponent($name)
{
$ucname = ucfirst($name);
if ( method_exists($this, "createComponent$ucname") )
{
return parent::createComponent($name);
}
else
{
$factory = "create$name";
return $this->context->$factory();
}
}
?>
P.S. V BaseControl místo $this->context
musíš
použít $this->presenter->context
Editoval Šaman (25. 8. 2012 16:32)
- Vojtěch Dobeš
- Gold Partner | 1316
Podobný přístup vytváří bezpečností díru. Pokud bude například komponenta na smazání článku, a ověření administrátorských práv bude čistě v presenteru, kde je daná komponenta použita (což je zcela v pořádku), a tahle komponenta se bude vytvářet přes kontext… tak je náhle možné sestavit požadavek pracující s touto komponentou i v presenteru, který na ni nemá explicitní továrničku. Protože stačí, aby identifikátor komponenty byl v URL, a Nette komponentu připraví – a tahle magie umožní čistě podle jména ji vytvořit kdekoliv, i mimo potřebné zabezpečení skrze už. práva, přihlášení atd.
- Šaman
- Člen | 2666
To už je na tom, kdo tu továrnu zavedl do kontextu. Jestli je to komponenta jednoúčelově pro jeden presenter, tak myslím, že v konfigu nemá co dělat. A kontrolu práv by si měla dělat přímo metoda která maže/edituje.
Nicméně trocha magie v tom určitě je, o tom není sporu. Ale opakovat spoustu stejných továren v několika presenterech není pěkné a mít v BasePresenteru spousty továren je ještě ošklivější. Máš nějaký nápad, jak z toho ven?
Editoval Šaman (25. 8. 2012 16:59)
- Vojtěch Dobeš
- Gold Partner | 1316
Nevidím v tom souvislost. Nevidím důvod, proč nemít všechny (i „jednoúčelové“) komponenty připravované skrze DIC, s dobrodiním autowiringu atd. Proto může podobný přístup vést k bezpečností díře, pokud si neuvědomím, co vlastně frameworku povoluji.
Ad kontrola práv: která metoda? Metoda modelu? Metoda komponenty? Odpovídající view, ve které se vykresluje rozhraní komponenty? Mně osobně přijde správná poslední varianta, protože mě vede k nejvolnější provázanosti.
- Šaman
- Člen | 2666
@vojtech.dobes: Podle mě si má práva hlídat model, nikoliv presenter, nebo komponenta. Ale presenter, nebo komponenta si mohou práva kontrolovat dobrovolně také, vypíšou hlášku a o nedovolenou operaci se ani nepokusí. Ale model je ten, kdo za tu oprávněnost ručí – presenter jen prezentuje.
@HosipLan: Sorry, ale nechápu. Problém je ten, že například
komponenta košík
se má zobrazit na každé stránce napříč
aplikací. Takže ji hodíme do layoutu společném pro všechny přihlášené
stránky. Ale kam mám napsat továrničku createComponentBasket()
?
V BasePresenteru je ošklivá a špatně dohledatelná a psát ji v několika
presenterech je opakování naprosto stejného kódu.
- Vojtěch Dobeš
- Gold Partner | 1316
Třída zodpovědná za smazání článku zase podle mě vůbec nemá řešit, jestli aktuálně nastavení session a cookies takovou akci dovoluje (session a cookies === kontrola práv), protože je to míchání věcí. Uživateli například mohu užití metody dovolit jen pro jeho články, zatímco administrátor ji může použít na kterýkoliv. Proč bych ale tuhle logiku měl patlat do metody, jejíž jedinou zodpovědností by mělo být smazání článku?
Pozor, presenter je v Nette prostředník mezi view a modelem. Je zodpovědný za víc než za „prezentování“ (pokud to dobře chápu).
- Filip Procházka
- Moderator | 4668
@Šaman Předně narážím na to, že DIC Factories jsou nepoužitelné.
Vykonání jakékoliv akce by mělo přecházet kontrola oprávnění. Takže když vytvářím komponentu, kontroluji jestli ji uživatel může vidět, když zpracovávám signál, kontroluji, jestli má uživatel oprávnění udělat akci, kterou představuje.
Tyhle kontroly můžou být přímo v presenteru, přímo v komponentě, nebo v nějakém prostředníkovi. Každopádně tam být musí!
„Magické“ vytváření komponent tohle trošku stěžuje, ale i tak se to dá napsat tak, aby jsi při vytváření komponenty kontroloval, jestli komponenta může být vytvořena.
Každopádně jsem zastáncem klasického způsobu, kdy továrničky prostě vyjmenuješ v presenterech, kde budou použity (popř, ve společném předkovi), byť je to o pár řádek ukecanější. Až uvidím opravdu pěkné řešení, jak tohle zautomatizovat, tak začnu propagovat to. Zatím jsem ho neviděl :)