Komponenty a jejich (ne)vykreslení
- joe
- Člen | 313
Ahoj, přemýšlím jak ideálně řešit situace, kdy nechci vykreslit
komponentu, ale zároveň ani nějaký její HTML obalový element. Jak byste
prosím konkrétně řešili nevypsání komponenty ArticleList
a
jejího nadřazeného div
u, pokud by nebyly k dispozici žádné
články?
class HomepagePresenter {
/** @inject */
public ArticleListFactory $articleListFactory;
/** @inject */
public ArticleRepository $articleRepository;
private array $articles;
public function actionDefault(): void
{
$this->articles = $this->articleRepository->findAll();
}
protected function createComponentArticleList(): ArticleList
{
$articleList = $this->articleListFactory->create($this->articles);
}
}
A v šabloně default.latte by pak bylo něco takového
<!-- tento div nechci vykreslit, pokud komponenta "articleList" nebude mít
taky co vykreslit ($articles bude prázdné pole) -->
<div>
{control articleList}
</div>
Je více cest, jak to řešit, ale zajímá mě, jak se k tomu stavíte a
jak takové a podobné situace konkrétně řešíte a jestli by šlo najít
nějaké vhodné řešení, které by platilo pro všechny komponenty a nemusel
jsem se u každé komponenty rozhodovat, jaké řešení zrovna použít.
Moc díky.
Editoval joe (3. 3. 2021 1:46)
- Marek Bartoš
- Nette Blogger | 1274
V šabloně presenteru máš presenter dostupný pod $control
.
Můžeš tak přistoupit ke komponentě presenteru a dotázat se jí, zda je
možné ji vyrenderovat
{if $control[articleList]->isRenderable()}
{control articleList}
{/if}
class ArticleList extends Control
{
public function isRenderable(): bool
{
//TODO - implement
}
}
Řešení s capture
a ifcontent
mi osobně
nepřijde tak hezké, ale pokud komponenta nevypíše žádný výstup (ani
prázdné html elementy), tak bude fungovat též
Editoval Mabar (3. 3. 2021 12:22)
- Marek Bartoš
- Nette Blogger | 1274
Tak místo toho měj metodu v presenteru? Já přece nevím, za jakých podmínek tvá komponenta něco vykreslí – i při předání dat v konstruktoru může zjistit, že nevypíše nic.
- joe
- Člen | 313
Mabar napsal(a):
Tak místo toho měj metodu v presenteru? Já přece nevím, za jakých podmínek tvá komponenta něco vykreslí – i při předání dat v konstruktoru může zjistit, že nevypíše nic.
Snažím se přijít na obecné řešení, které by bylo jednoduché, efektivní a rychlé. Podmínka nevykreslení je napsaná nahoře v komentáři u HTML.
Nehledám radu, jak to udělat, hledám řešení, jak by to dělali ostatní, abych se měl čím inspirovat.
Metodu v presenteru mít můžu, ideálně ale do šablon moc objektů tahat
nechci a předávat něco do šablony znamená pro mě vytvořit i render
metodu, protože pouze tam bych chtěl mít data, která se do šablony
posílají. A pak už se to dost rozrůstá na takovou situaci, kdy vlastně
potřebuju jen jednoduchý if
.
Editoval joe (3. 3. 2021 13:17)
- David Matějka
- Moderator | 6445
A jak by to tedy konkrétně vypadalo?
{if}
<div>
{capture $output}
{control foo}
{/capture}
{$output}
</div>
{/if trim($output)}
jinak pokud nechceš předávat data do šablony ani inicializovat komponentu, tak ti zbývá plus mínus nula možných řešení :)
- Kamil Valenta
- Člen | 820
Je přeci zcela rozhodující kdo a kdy zjistí, že se nebude nic
vykreslovat.
Pokud to zjistí už presenter, jak je uvedeno v prvním příspěvku, tak to
mohu rovnou v šabloně odpískat.
{if $presenter->articles}
<div>
{control articleList}
</div>
{/if}
S tím, že articles musí mít getter, nebo být public (rozdíly nejsou předmětem vlákna).
Nebo si data obstará až komponenta sama, pak viz @Mabar
Nebo se do toho promítne kdo ví co, pak musíš sledovat vyrenderovaný content, zda není prázdný.
IMHO nejrychlejší bude sahat na articles, protože jen tak se vyhneš instancovaní komponenty a prakticky jen zužitkuješ data, která jsi z modelu už vytáhl.
- dakur
- Člen | 493
@joe Úplně jednoduše:
class HomepagePresenter {
/** @inject */
public ArticleListFactory $articleListFactory;
/** @inject */
public ArticleRepository $articleRepository;
private array $articles;
public function actionDefault(): void
{
$this->articles = $this->articleRepository->findAll();
}
public function renderDefault(): void
{
$this->template->areAnyArticles = \count($this->articles) > 0;
}
protected function createComponentArticleList(): ArticleList
{
$articleList = $this->articleListFactory->create($this->articles);
}
}
<div n:if="$areAnyArticles">
{control articleList}
</div>
Není to vůbec věc té komponenty, ale presenteru.
- Marek Bartoš
- Nette Blogger | 1274
Není to vůbec věc té komponenty, ale presenteru.
Nechápu, proč by nemělo být věcí komponenty možnost informovat, zda něco dovede nebo ne. Komponenta je díky tomu snadno přenositelná. Jít o komponentu datagridu, který má někdy až desítky možností filtrování, tak si data opatří sama a sama by tedy i poskytla informaci, zda nějaká data vypíše.
Editoval Mabar (3. 3. 2021 16:45)
- joe
- Člen | 313
@Mabar Hm, hm, z tohohle pohledu datagridu máš pravdu, ale ten datagrid chceš pořád vypisovat. Asi je teda potřeba si říct, že tato komponenta bude vždycky něco vracet a jiná komponenta vracet nic nebude. A jak se stavíš k tomu instancovat něco, co není vlastně potřeba? :)
@dakur A píšeš to tak? :) Skoro jsem si říkal, že by se mi
celkem hodilo, kdyby továrna vrátila null
, ale nevím jestli to
není proti nějakému návrhu (?, každopádně teď to nejde). Ta tvá ukázka
by se pak mohla jednoduše zkrátit na:
class HomepagePresenter {
/** @inject */
public ArticleListFactory $articleListFactory;
/** @inject */
public ArticleRepository $articleRepository;
protected function createComponentArticleList(): ?ArticleList
{
$articles = $this->articleRepository->findAll();
if (\count($articles) === 0) {
return null;
}
$articleList = $this->articleListFactory->create($articles);
}
}
a v šabloně pak
<div n:if="$presenter->getComponent('articleList') !== null">
{control articleList}
</div>
Volit další obalovou komponentu se mi pak už moc nelíbí, myslím že by nebyla pak moc znovupoužitelná. (Obalové elementy articleList mohou být různé)
@KamilValenta z šablony bych do presenteru moc nesahal, nejspíš by pak nefungoval ani refaktoring kódu a bylo by to těžce dohledatelné. Ale chápu myšlenku.
@DavidMatějka Ten „obrácený“ if
jsem neznal,
doufám že ho nikdy nebudu potřebovat :D
- Marek Bartoš
- Nette Blogger | 1274
A jak se stavíš k tomu instancovat něco, co není vlastně potřeba?
Vytvoření instance samo o sobě stojí tak málo, že není třeba to řešit. Data bys získával tak jako tak, takže ani tahle otázka není důležitá. Závislosti se dají získat přes tzv. accessor – jde o interface s get() metodou zaregistrovaný jako služba – Nette DI z něj vygeneruje třídu, která závislost získá z DIC až když je potřeba – v případech kde se závislost nemusí využít, jako je tenhle, to může slušně optimalizovat výkon. Jen se přiznám, že netuším, jak náročně je kvůli tomu komponentu připojovat do stromu komponent, ale s tím dokážu žít.
z tohohle pohledu datagridu máš pravdu, ale ten datagrid chceš pořád vypisovat
Místo metody isRenderable()
by byla asi vhodnější metoda
hasData()
– datagrid se může vypsat prázdný, ale pokud jde
o hlavní komponentu na stránce, tak by v takovém případě bylo vhodné,
aby presenter vrátít kód 404, než aby crawler zaindexoval prázdnou
stránku.
- dakur
- Člen | 493
@Mabar Jestliže se má komponenta vykreslovat za určitých podmínek
zároveň s <div>
em, který je mimo ni, pak
o tom nemůže rozhodovat komponenta právě proto, že ten
<div>
jí nepatří. Kdyby byl <div>
v ní, pak samozřejmě ano.
@joe Ano, používám. Např. mám komponentu, která vykresluje dlaždici s různými informacemi. Těchto dlaždic mám pak na stránce více a tuto funkcionalitu „seznam dlaždic“ jsem potřeboval ve dvou presenterech – udělal jsem tedy i komponentu pro seznam dlažic, která renderuje X komponent jednotlivých dlaždic.
If
ovat to pomocí pomocné proměnné mi přijde lepší už
z důvodu toho, že to máš typované skrz {templateType}
(pokud
ho používáš) a já osobně jsem zastáncem toho, aby se v šablonách
neprogramovalo – což $presenter->getControl('xxx') !== null
už trochu je.
Editoval dakur (3. 3. 2021 17:18)
- Marek Bartoš
- Nette Blogger | 1274
Tohle ti fungovat nebude
<div n:if="$presenter->getComponent('articleList') !== null">
{control articleList}
</div>
Ale toto ano :)
<div n:if="$presenter->getComponent('articleList', false) !== null">
{control articleList}
</div>
Jen počítej s tím, že kdybys createComponentArticleList
přejmenoval a v latte zapomněl název změnit, tak ti to tíše projde.
- Marek Bartoš
- Nette Blogger | 1274
@dakur
Jestliže se má komponenta vykreslovat za určitých podmínek zároveň s <div>em, který je mimo ni, pak o tom nemůže rozhodovat komponenta
Ona ale nerozhoduje o tom, jestli se vykreslí, jen mi říká zda má co vykreslit. O tom zda se vykreslí rozhoduje podmínka okolo toho divu :)
Editoval Mabar (3. 3. 2021 17:18)
- dakur
- Člen | 493
@Mabar Toho řešení s isRenderable()
jsem si všiml až
teď. 🙃 Jako asi je to jedno, já bych se držel spíš svého řešení,
protože „mi to tak prostě přijde“, ale netrvám na tom.
~~Ale vlastně nevím, proč ten <div>
není součástí
té komponenty ArticleList
, to by mi přišlo zdaleka nejlepší.
🙂~~ Beru zpět, vlastně si dokážu představit, že to dává smysl, když
chci té komponentě nastavit např. spodní odsazení, ale jen v jednom
použití.
Editoval dakur (3. 3. 2021 17:27)
- joe
- Člen | 313
@Mabar Ono ale nejde z továrny vrátit null
, pak to
křičí did not return or create the desired component
. Ale bylo
by to asi takový nestandartní :)
Ok, tak za podmínky, že při instancování komponenty se nebude nic moc složitýho dít, což bych asi ani neočekával, tak se zdá dobrá ta metoda, jestli jsou nějaká data.
Pak je ale trochu složitější, když by komponenta přijímala víc parametrů a na všechny by se pokládal dotaz. V případě, že by už první nic nevrátil, tak bych to musel ifovat v datech a pak tam posílat prázdná data :D No u takového scénáře zase moc nevím…
Nejspíš jedno ideální řešení nebude.