Více presenterů tvořící jedinou stránku
- Edward
- Člen | 4
Zdravím všechny,
Mám takovou ideu, kterou nedokážu nějak logicky napasovat na Nette a
chtěl bych se zeptat co si o tom myslíte.
Celá aplikace by měla být plně modulární systém, který funguje
následovně.
- Web bude mít hlavní modul, který se stará o vytváření a editaci menu položek. Menu položky budou stromově větvené a datově by to vypadalo následovně..
Create table Menu (
IdMenu Int NOT NULL AUTO_INCREMENT,
MenuTitle Varchar(255),
MenuCoolUri Varchar(255),
MenuOrder Int,
IdParentMenu Int,
Primary Key (IdMenu)) ENGINE = MyISAM;
Menu samo o sobě nebude obsahovat nic dalšího, idea přichází až
s následující tabulkou, která je relačně spojená pomocí IdMenu a jde
o to, že každá položka menu, bude obsahově seskládaná z tzv.bloků.
Jednotlivé bloky budou obsluhovat a renderovat příslušné moduly. Výsledkem
by měla být stránka jejíž obsah je seskládán z několika různých
prezenterů (modulů), kterým jsou předány příslušné parametry. Na
stránce úvod tak budou pod sebou např následující bloky (např obalené
divem s id)…
- 4 aktuality
- 4 fotografie
- html text
- anketa
- html text.
datově by bloky vypadaly asi takto…
Create table Blocks (
IdBlock Int NOT NULL AUTO_INCREMENT, //id
IdMenu Int NOT NULL, //id menu (relace s Menu)
BlockName Varchar(255), //pracovní název
BlockOrder Int, //pozice bloku v rámci stránky
BlockDataReceiver Varchar(255), //presenter (modul), který blok obsluhuje a renderuje
BlockDataParams Text, //parametry se kterými se presenter bude volat
BlockSettings Text, //další vlastnosti bloku
Primary Key (IdBlock)) ENGINE = MyISAM;
Principielně by to fungovalo následovně…
- Generování menu – V presenteru MenuPresenter se získají z databáze položky menu, které vygenerují odkazy na Menu presenter
- Generování obsahu stránky – Menu presenter musí získat z databáze všechny bloky, které tvoří stránku a vyrenderovat je ve správném pořadí za sebou.
- Jak chápat bloky na stránce – Každý blok je nutné chápat jako samostatný modul s pohledem, případně s parametry. Každý modul by mohl mít více bloků. Každá stránka může mít více bloků jednoho modulu, které se ovšem budou volat například s jinými parametry.
Příklad získaných bloků pro stránku „Úvod“ z databáze:
$blocks=array(
//zobrazení posledních 5ti aktualit
0 => array(
'IdBlock'=>45,
'IdMenu'=>1,
'BlockName'=>'News',
'BlockOrder'=>1,
'BlockDataReceiver'=>'Aktuality:showLast',
'BlockSettings'=>array('limit'=>5),
),
//zobrazení statického Html
1 => array(
'IdBlock'=>46,
'IdMenu'=>1,
'BlockName'=>'StaticHtml',
'BlockOrder'=>2,
'BlockDataReceiver'=>'Html:showStatic',
'BlockSettings'=>array('id'=>24),
),
Problém je v tom, že nevím jak získat v nějakém cyklu z více presenterů jejich vyrenderované šablony, abych z nich mohl poskládat příslušnou stránku. Musím tedy získat vyrenderované bloky, které do šablony poskládám v příslušném pořadí za sebe.
..šablona v @layout.phtm bude menu např. následovně…
<ul n:if="$menu">
<li n:foreach="$menu as $mitem"><a href="{plink Menu:default,'id' => $mitem->IdMenu}">$mitem->MenuTitle</a></li>
</ul>
.. v šabloně pak vykreslení jednotlivých bloků …
<div id="PageContent">
{foreach $blocks as $block}
<div id="block{$block->IdBlock}">
... a zde nevím jak vložit obsah presenteru který block renderuje ..
</div>
{/foreach}
</div>
nevíte někdo jak dál ? Napadlo mě to řešit přes widget
,
ale nevím jestli je to správné řešení. Nedá se nějak zavolat z šablony
ne-aktuální presenter, který vrátí požadované vyrenderované data? Taky
je samozřejmě nutné, aby všechny presentery ze kterých je stránka
seskládána prošli svým životním cyklem (od action až po render).
Díky za každou reakci..
Přikládám ještě příklad jak se obsah stránky seskládává..
Editoval Edward (9. 9. 2010 14:24)
- na1k
- Člen | 288
Hoj!
Jednoduše bych to řekl asi tak, že jeden request = jeden presenter a jedna jeho action. Tudíž není možné do stránky vykreslit v jednom požadavku pohledy více presenterů.
K tomu slouží komponenty (přesněji vykreslitelné komponenty, dědící
od Control
), které se presenterům v mnohém podobají – mají
vlastní šablony, něco jako pohledy (renderXXX, renderYYY), mohou obsahovat
další vnořené komponenty,… Důležité ale je, že jsou na presenteru
relativně nezávislé. Tomu rozuměj tak, že je můžeš vykreslit ve
kterémkoliv presenteru. (Relativně proto, že je nutné komponentu
k presenteru připojit.)
Nevím sice jestli jde o best practice (ale to je spíš filozofická otázka, zda je AbsolutníModulárnost™ opravdu to, co zákazník chce), ale sám bych to asi řešil tak, že v šabloně nadefinuju bloky a v presenteru vždy podle příslušné action (obecně podle požadavku) by nějaká logika rozhodla, která komponenta (widget) se do kterého bloku vykreslí.
Pokud se do toho ponoříš hlouběji, zjistíš že by ti mohla stačit
jediná šablona definující bloky a akce v presenterech by byly prázdné.
Vše by řídila metoda createComponent
(volaná jako
createComponent('block1')
…) a vracela by instance
odpovídajících komponent. Pokud netušíš o čem teď mluvím, tak hledej
v dokumentaci „továrničky“ a taky se určitě podívej do API v jakém
vztahu je Control
k Presenter
;-)
- srigi
- Nette Blogger | 558
Tak ako pise na1k – Presenter chap ako handler, ktory obsluhuje (vyřizuje) request. Tvoj navstevnik pride na homepage – bolo by vhodne aby request odbavil nejaky HomepagePresenter a akcia/view default. Co sa zobrazuje na homepage je vecou sablony teplates/Homepage/default.phtml
HomepagePresenter
class HomepagePresenter extends Presenter
{
public function actionDefault()
{
// nic, alebo nejake operacie, vetvenie
// mozes dokonca zmenit View
$this->view = 'homelogedin';
}
public function renderDefault()
{
// kludne predaj do sablony nejake params
$this->template->newsHighlighted = 5;
}
}
A teraz povedzme ze vykreslujes tu zakladnu homepage obrazovku s blokmi menu, news, gallery, text. Pripravis teda sablonu, kde tie bloky definujes, prip predas parametre.
templates/Homepage/default.phtml
{block #content}
<div id="sidebar">
{widget manimenu}
</div>
<div id="main">
{widget news $newsHighlighted}
{widget gallery}
{widget text}
</div>
V momente ked sa bude vykreslovat sablona, engine Nette zisti, ze bude potrebovat vykreslit napr. komponentu news. Vypyta si teda komponentu od aktualneho Presentera. Ten musi byt na tuto situaciu pripraveny – musi mat tovarnicku na jej vytvorenie. Tak ju do Presentera dopis.
protected function createComponentNews() {
$news = new News();
// tu mozes volat metody komponenty atd.
return $news;
}
Ostava uz len definovat kod komponenty. Priprav si zlozku na components, tak aby ju nasiel RobotLoader. No a komponenta moze vypadat napr. takto:
components/NewsControl.php
class NewsControl extends Control
{
public function render($highlight)
{
// nejaka logika, co pracuje s tym parametrom - prisposobi render komponenty
$this->template->setFile(__DIR__ . '/news.phtml');
$this->template->foo = 'bar';
$this->template->render();
}
}
A posledna vec – sablona komponenty
components/news.phtml
<div id="news">
blablabla
{$foo}
atd. atd.
</div>
A to je cele, je to krasne organizovane. Komponenty mas definovane na jednom
mieste, lahko sa upravuju a ziskal si krasnu
znovuvyuzitelnost – rovnaku komponentu mozes vyuzivat
v roznych Presenteroch – staci do nich dopisat tovarnicku.
Enjoy.
- Filip Procházka
- Moderator | 4668
Heh, pěkně jsi popsal základní myšlenku na které stojí Kdyby :)
neboli Absolutní Modulárnost, jak to pěkně pojmenoval srigi
Dyštak koukni na moje řešení (mám ho v podpisu), třeba tě trochu inspiruje :)
- Peetee
- Člen | 75
Rád bych znovu otevřel myšlenku Absolutní Modulárnost™ ;-)
Chtěl bych poděkovat srigimu za vyčerpávající odpověď. Ale konkrétně tato část šablony mi stále není jasná:
<div id="main">
{widget news $newsHighlighted}
{widget gallery}
{widget text}
</div>
Tento kód předpokládá, že bloky budou muset být v tomto pořadí, ale jakým způsobem zpracovat, když nevíme v jakém pořadí bloky budou? nebo nevíme ani kolik bloků na stránce bude? (případně ani nemusíme vědět jaké bloky dokáže program zpracovat?). Domnívám se, že tímto směrem mířil i původní dotaz – cílem je pořadí a typy bloků načítat z databáze.
- Šaman
- Člen | 2666
Nette většinou předpokládá, že to co se zobrazí popisují šablony. Pokud budeš chtít pevnou strukturu stránky viz. první příspěvek, tak si vytvoříš šablonu s tímto rozvržením. A měnit se bude jen obsah komponent (bloků).
Pokud bys to chtěl řešit dynamicky, tak továrničky měj připravené v BasePresenteru (nebo v jiném předkovi všech presenterů které budou tyto komponenty používat) a do databáze si buď ukládej připravené šablony (jednodušší varianta), nebo si celou stránku sestav někde ve zvláštní komponentě která z dat z databáze postupně zavolá továrničky jednotlivých komponent a sestaví z nich .html výstup. A ten pak vlepíš do layoutu standardním způsobem.
Ale nechápu k čemu by to bylo. Pokud do databáze uložíš i nějaký nový nepovinný údaj který chceš zobrazit (např. nějaké recenze produktu) tak si je (komponentu na ně) přidáš i do šablony a ona už si sama ošeří co zobrazit pokud žádné dostupné recenze nejsou.
Ale generováním stránky z databáze tak, že nevím ani jaké bloky a v jakém pořadí na stránce budou, to je velká magie (takže i semeniště problémů) a navíc si pak nemůžeš moc hrát s formátováním (těžko to rozhážeš nějak logicky co patří do kterého sloupce apod.)
Editoval Šaman (12. 1. 2011 16:34)
- Aurielle
- Člen | 1281
Já tuhle myšlenku řeším WidgetContainerem, do kterého připojuji
komponenty ve startup fázi presenteru (přes hook) se jménem
kategoriewidgetu:nazevwidgetu
. Komponenty se vykreslují dle
předané priority, v případě že je priorita stejná, vykreslí se ta,
která byla připojena nejdříve. Komponentě jdou předat i parametry, popř.
metoda, kterou má komponenta renderovat (tohle sice řeším asi ne moc
čistě, ale funguje to :)). V šabloně si pak akorát zavolám
{control widget:kategoriewidgetu}
.
- Aurielle
- Člen | 1281
Ještě sám nemám jasno, jak budu licencovat kódy, které jsem vypustil na GitHub (PHP-in' CMS framework licence zatím ani neexistuje), každopádně tahle třída je celkem jednoduchá a asi bych umožnil volné šíření a upravování… (New BSD licence) Jakej jinej smysl by pak mělo její zveřejnění? :)
- Filip Procházka
- Moderator | 4668
příjde mi trošku divné tam hackovat nějaké závorky, moc jsem to teda nestudoval, ..
Class WidgetContainer extends Nette\Application\Control
{
private $widgets = array();
public function addWidget(Nette\Component $widget)
{
$name = $this->lookupPath(get_class($widget));
$this->widgets[$name] = $widget;
}
public function render()
{
$this->template->widgets = $widget;
$this->template->file = __DIR__ . '/widgets.latte';
$this->template->render();
}
}
s vlastní šablonou si tam můžeš jednoduše udělat třeba automatické kontejnery
{foreach $widgets as $name => $widget}
<div id="box-{$name|webalize}" class="box">
{control $widget}
</div>
{/foreach}
jak to naplnit a vykreslit je myslím velice jasné :)
PS: je to blbina, potřebuju víc kofeinu :)
btw gmvasek, ten statický pole teda nevypadá moc pěkně :P ale jestli jsem to dobře pochopil, tak si můžeš z jakéhokoliv kontejneru z jakékoliv části stránky kde si vytvoříš ten widgetContainer tak můžeš připojovat do „fronty“ jakéhokoliv kontejneru? to je zajímavé :)
Editoval HosipLan (12. 1. 2011 20:11)
- Aurielle
- Člen | 1281
Jde o to, že v BasePresenteru mám v továrničce vytvoření toho
WidgetContaineru, ve startup fázi přes Hooker navážu vytvoření všech
komponent pro konkrétní presenter (hook se zavolá po vytvoření
WidgetContaineru) – při předání jména komponenty předám i kategorii
(př. dashboard:logs
), a pak vykreslím určitou kategorii widgetů
v šabloně. Výhodou je, že můžu mít třeba 2 sidebary a dynamicky do
každého přidávat jiný obsah, a to z libovolné části aplikace.
- Filip Procházka
- Moderator | 4668
Vidím to, že až budu dělat svoji implementaci, tak udělám obecnou továrnu.
$presenter->getContainer('leftSidebar');
která bude vykreslitelná
{container leftSidebar} {* $presenter->getContainer('leftSidebar')->render(); *}
a getContainer bude vytvářet jednotlivé kontejnery (pro ukázku, přidám pak nějaký create)
class BasePresenter extends Presenter
{
private $containers = array();
public function getContainer($name)
{
if (!isset($containers[$name])) {
// tohle je moc jednoduché, asi to bude chtít vlastní container
$containers[$name] = new Nette\ComponentContainer;
$this->addComponent($containers[$name], 'container'.ucfirst($name));
}
return $containers[$name];
}
}
a taky to vidím na nějaký obecný registr komponent, ale to bude řešit
AddonLoader
z Kdyby :)
$component = $this->getContainer('leftSidebar')->load('Kdyby\Navigation');
$component->add('Homepage', ':Front:Homepage');
A pak budou i vlastní kontejnery, třeba s vlastním render
$container = $presenter->addContainer(new MyFancyContainer, 'MyFancy');
$container->load('Kdyby\Navigation');
{container myFancy:horizontaly} {* $presenter->getContainer('myFancy')->renderHorizontally(); *}
atd.. ještě mi nenastartovala hlava, tak to není domyšlené, ale základní myšlenka je myslím vidět ne? :)
Mělo by to umět to stejné a nejsou tam statické proměnné :P
Editoval HosipLan (14. 1. 2011 8:41)
- Filip Procházka
- Moderator | 4668
Snažím se vtlouct si do hlavy lepší OOP návyky a jedním takovým je, že pokud to nemusí být statické, tak to udělám jinak.
nevím co myslíš módem
parametry jednoduše
{container footer:horizontal $param1, $param2}