Komponenty a jejich (ne)vykreslení

joe
Člen | 313
+
0
-

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 divu, 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)

joe
Člen | 313
+
0
-

To spolu moc nesouvisí.

Kamil Valenta
Člen | 752
+
0
-

Proč ne?

Martk
Člen | 651
+
0
-

Stačilo by použít

<div n:ifcontent>

https://latte.nette.org/cs/tags#…

Marek Bartoš
Nette Blogger | 1146
+
0
-

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)

joe
Člen | 313
+
0
-

@Mabar To můžu, ale proč mám vlastně vytvářet instanci komponenty, abych uvnitř ní zjistil, že ji nepotřebuju? Informaci, že ji nepotřebuju už vím přece před jejím vytvořením.

Marek Bartoš
Nette Blogger | 1146
+
+2
-

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
+
0
-

Kamil Valenta napsal(a):

Proč ne?

A jak by to tedy konkrétně vypadalo?

joe
Člen | 313
+
0
-

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
+
0
-

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 | 752
+
0
-

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
+
0
-

@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.

dakur
Člen | 493
+
0
-

A pokud chceš mít „jednošmahové“ použití tak, aby se to celé mohlo dít opakovaně ve více presenterech, tak to celé ještě zabal do jedné komponenty, která bude brát $articles a rozhodovat, zda se vykreslí <div> a ArticleList nebo ne.

Editoval dakur (3. 3. 2021 15:41)

Marek Bartoš
Nette Blogger | 1146
+
+1
-

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
+
0
-

@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 | 1146
+
0
-

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
+
0
-

@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.

Ifovat 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 | 1146
+
0
-

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 | 1146
+
+1
-

@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
+
0
-

@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
+
0
-

@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.

Pepiik
Člen | 10
+
0
-

Osobně bych si jednoduše do šablony předal proměnnou $articles a tam si dal podmínku na počet.

Jestli ti jde o další nápady, tak v action nastavit jiný view rovnou bez té komponenty.

joe
Člen | 313
+
0
-

@Pepiik Je to takový jednoduchý efektivní řešení, jen hodně ukecaný. Zajímalo mě, jak takové situace řeší ostatní.

Ten nápad s jinou šablonou zní zajímavě ;-)

turbo80
Člen | 50
+
0
-

@joe
Ahoj prosim te vim ze to nebude patrit k tve otazce.
Jaky ma smysl nevykreslit componentu, kdyz je to podle nazvu pro vypis clanku?
Potrebuji navstevniku webu rict ze nejsou zadne clank…

Diky za vysvetleni.

Editoval turbo80 (8. 3. 2021 10:34)