Dvojnásobné zpracování widgetu

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
xuffo
Člen | 2
+
0
-

Zdravím, už několik dní se lopotím s následujícím problémem. Používám WebLoader, který mám integrovaný do BasePresenteru v podobě továrniček createComponentCss() a createComponentJs(). V layoutu potom volám obě zmíněné komponenty pomocí makra {control …}:

<head>
	(...)

	{control css}
	{control js}
</head>

Chci docílit toho, aby si komponenty (např. DataGrid, DatePicker apod.) automaticky načítaly svůj JavaScript a CSS, jde mi tedy o jakýsi automatický „lazy loading“ skriptů a stylů. Např. pokud se na stránce vyskytuje komponenta DataGrid, automaticky se načtou do WebLoaderu potřebné soubory datagrid.css a datagrid.js. Naopak, pokud se na stránce DataGrid nevyskytuje, zmíněné soubory se nenačtou.

Ve třídě DataGrid mám tedy přidanou následující metodu:

<?php
public function attached($obj)
{
	$obj->getComponent('css')->addFiles($this->css);
	$obj->getComponent('js')->addFiles($this->js);
}
?>

Tato metoda se volá po připojení komponenty k presenteru, tedy jestli to dobře chápu, po zpracování odpovídajícího makra {control …} (volajícího DataGrid) v šabloně. Metoda zajišťuje načtení potřebných souborů do WebLoaderu.

A teď se dostávám k jádru pudla. Zatím se mi nepodařilo nijak dosáhnout, aby vše fungovalo jak si představuji, neboť v době, kdy se renderují {control css} a {control js} v layoutu, ještě není zpracován widget s DataGridem ve view, a tak k volání metody attached (a tím pádem přidání potřebných souborů do WebLoaderu) dochází pozdě. V layoutu se tedy takto načtené soubory neobjeví.

Zkusil jsem tedy {control css} a {control js} v layoutu posunout až na konec, označit jako bloky a tyto bloky vložit na původní místo do hlavičky pomocí {include …}:

@{block #layout}
(...)
<head>
	(...)

	{include #css}
	{include #js}
</head>

(...)

</body>
</html>
@{/block}

{block #css}
	{control css}
{/block}

{block #js}
	{control js}
{/block}

A došel jsem k následujicím poznatkům. Oba WebLoaderové widgety jsou volány 2×, nejprve v místě, kde je {include #css} a {include #js} a podruhé dole na konci stránky uvnitř {block #css} a {block #js}. V místě prvního volání však stále není připojena komponenta DataGrid, takže tam se potřebné soubory nenačtou, načtou se až na druhý pokus na konci stránky, kde je mi to ale už k ničemu. Navíc na konci stránky ani nechci widgety vypisovat, ale pouze načíst, což makro {block …} zřejmě neumožňuje.

Poradíte mi někdo prosím, co s tím a zda to má vůbec řešení? Jak dosáhnout, aby se makra {control css} a {control js} provedla až po vyrenderování view, ale jejich výstup se vložil na správné místo, tedy do sekce <head> … </head> v layoutu?

Editoval xuffo (26. 9. 2009 0:18)

xificurk
Člen | 121
+
0
-

Jo, tohle taky řeším… sice mám místo WebLoaderu vlastní komponentu a soubory připojuju až v šabloně, ale problém je stejný.

Myslím, že informace o tom, jaké soubory s kaskádovými styly a javascripty potřebuje daný presenter/widget, patří do šablony, ne do presenteru. V případě šablony presenteru to už lze udělat – stačí kód, který zajišťuje připojení příslušných souborů dát mimo {block #content}, ten se vykoná před samotným vykreslením stránky a všechno jede, jak má.

Widgety ale v tomto představují problém, protože v jejich šabloně není způsob, jak zapsat kód, který by se měl vykonat „před vykreslením“. To znamená, že potřebná data pro vykreslení hlavičky dokumentu jsou dostupná až po vykreslení všech widgetů, což je typicky na konci dokumentu :-)

Existuje workaround, který ale pokládám za moc moc škaredý™ a tím je znásilnění {capture}:

{*@layout.phtml*}

<html>

<head>
  <title>Úderný titulek</title>

{capture $layout}
</head>

<body>
  <h1>Mega hyper projekt</h1>
  {include #content}
  {control StrasneMocBychChtelJsACssVHlavicce}
</body>

</html>
{/capture}

{control CssVHlavicce}
{control JsVHlavicce}
{!$layout}
Ondřej Mirtes
Člen | 1536
+
0
-

A co využít toho, že WebLoader umožňuje zapisování požadovaných souborů až v hlavičce, jako další parametr {widget}?

Popisoval jsem to tady:

https://forum.nette.org/…iewtopic.php?…

Ne nadarmo se těm novým šablonám s {block} a {extends} říká „renderování v opačném pořadí“ :)

norbe
Backer | 405
+
0
-

LastHunter napsal(a):
Ne nadarmo se těm novým šablonám s {block} a {extends} říká „renderování v opačném pořadí“ :)

Ono to renderování v opačném pořadí ale funguje trochu jinak, než by člověk čekal a stačí pouze na jednodušší úkoly(rozšíření titulku stránky apod.).

Celé to funguje tak, že při parsování šablon je pro každý blok vytvoří funkce, které se pak volají podle toho, kde je který blok umístěný. Tím pádem to na funkčnost, kterou xuffo zamýšlel použít NELZE (viz. příklad) a musí se pracovat s {capture}.

Velmi zjednodušený příklad již přeložené šablony:

1. bez capture:

// vyrenduje hlavicku s komponentou webloader
echo header();
// vyrenderuje obsah s komponentou, která se snaží dynamicky nahrát
// potřebný js a css do komponenty webloader - ale ta již byla vykreslena
echo content();

2. s capture:

// vyrenderuje obsah s komponentou, která se snaží dynamicky nahrát
// potřebný js a css do komponenty webloader - ještě nebyla vykreslena => ok
$content = content();
// vyrenduje hlavicku s komponentou webloader
echo header();
// vypiseme zachyceny obsah
echo $content;
Ondřej Mirtes
Člen | 1536
+
0
-

Ale lze, jen ty CSS (JS) nebudou v jednom souboru, ale ve výsledku to budou dvě volání widget pod sebou, takže dva výsledné soubory – jeden s CSS pro celý web a další s CSS pro DataGrid.

Co se dělá v opačném pořadí je právě to renderování, ne něco uprostřed kódu komponent či Presenteru. Pomocí {block} a {extends} tak můžeš do hlavičky přidat víc odkazů na CSSka, aniž by o tom layout věděl.

Editoval LastHunter (5. 11. 2009 11:20)

norbe
Backer | 405
+
0
-

Cílem tohoto je, aby si každá komponenta mohla sama automaticky vložit potřebné soubory, aniž bych to musel nastavovat kdekoli jinde mimo továrničku(pak by jsme přišli o lazy loading) čímž by se velmi zjednodušilo jejich používání.

Toho podle mne docílit nejde bez použití capture. Samotné {block} a {extends} je na tohle krátké. Pokud ne, velmi rád bych se přiučil, jak toho docílit jelikož jsem s tím ztrávil nemálo času :-)

xificurk
Člen | 121
+
0
-

LastHunter napsal(a):

Co se dělá v opačném pořadí je právě to renderování, ne něco uprostřed kódu komponent či Presenteru. Pomocí {block} a {extends} tak můžeš do hlavičky přidat víc odkazů na CSSka, aniž by o tom layout věděl.

A tady se právě pleteš, stačí se podívat na kód vygenerovaných šablon. Abych to trochu usnadnil, připravil jsem minimální test-case pro demonstraci problému.

Jestli dokážeš bez použití {capture} ovlivnit z továrničky na widget test (nebo jeho šablony), co se vypíše v tom prvním renderování widget container, tak se ti pokloním a s tvým dovolením to půjdu pokorně implementovat do svého kódu.

<?php
class TestPresenter extends Presenter {
    public function createComponentContainer($name) {
        new ContainerControl($this, $name);
    }

    public function createComponentTest($name) {
        new TestControl($this, $name);
    }
}

class ContainerControl extends Control {

    public $log = array();

    public function render() {
        $this->template->setFile(dirname(__FILE__) . '/container.phtml');
        $this->template->log = $this->log;
        $this->template->render();
    }
}

class TestControl extends Control {

    public function render() {
        $this->template->setFile(dirname(__FILE__) . '/test.phtml');
        $this->template->render();
    }
}
?>
{*@layout.phtml*}
<html>
<head>
</head>
<body>

<?php $presenter['container']->log[] = '@layout.phtml' ?>

<h2>widget container</h2>
{control container}

<hr>
<h2>include #content</h2>
{include #content}

<hr>
<h2>widget container (vložený <em>po vyrenderování celého dokumentu</em>)</h2>
{control container}

</body>
</html>
{*Test.default.phtml*}
<?php $presenter['container']->log[] = 'Test.default.phtml' ?>

{block #content}
  <p>Bla bla</p>
  {control test}
{/block}
{*container.phtml*}
<pre><?php Debug::dump($log) ?></pre>
{*test.phtml*}
<?php $presenter['container']->log[] = 'test.phtml' ?>
mcmatak
Člen | 504
+
0
-

co třeba afterRender() ??

Ondřej Mirtes
Člen | 1536
+
0
-

Aha, takhle. To se omlouvám. Z komponenty to tedy nejspíš ovlivnit nejde, já to myslel tak, že v šabloně Presenteru, kde voláš třeba {control dataGrid} si zavoláš i potřebné {widget}y pro CSS a JS datagridu, které se ti automaticky přidají do hlavičky. Samy komponenty si na to sáhnout neumí.

Ale je to zajímavý feature request. Řešit DG.