Více presenterů tvořící jedinou stránku

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

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

  1. 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ě…

  1. Generování menu – V presenteru MenuPresenter se získají z databáze položky menu, které vygenerují odkazy na Menu presenter
  2. 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.
  3. 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)

Marax
Člen | 28
+
0
-

Přidávám se k dotazu. S nette teprv začínám a tohle mi není docela jasné.

na1k
Člen | 288
+
0
-

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

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

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

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

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

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

Peetee
Člen | 75
+
0
-

gmvasek: mohl bych poprosit o silnější nakopnutí. Zkoušel sem hledat WidgetContainer na foru nebo v API, ale nic co by mi pomohlo. Co to vlastně je? (omlouvám se za neznalost).

Jak můžu přidat „blok“ (?widget s parametry)do WidgetContaineru?

Aurielle
Člen | 1281
+
0
-

Jaj, já zapomněl dodat že je to má vlastní třída… najdeš ho tady

Peetee
Člen | 75
+
0
-

Wow. Pěkné, velice pěkné. Takto nějak jsem uvažoval (ve snu).

Chtěl jsem se zeptat, jak je to s licencí této třídy? Můžeme to použít? jaká jsou omezení?

Aurielle
Člen | 1281
+
0
-

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í? :)

nAS
Člen | 277
+
0
-

\PHPin\Tools::getHooker(); je super název metody! Fakt to funguje? :)

Aurielle
Člen | 1281
+
0
-

Přes monitor opravdu ne, taky pochybuju, že by měla metodu onWidgetContainerCreated :)) (už jsi druhý co na to upozornil, Hooker jsem tu už taky někde dával :D)

Peetee
Člen | 75
+
0
-

Super, děkuju.

Mohl bych ještě poprosit o příklad použití této třídy? Nerozumím tomu, jak ji naplním Widgety. Můžeš hodit nějaký příklad? děkuji.

Filip Procházka
Moderator | 4668
+
0
-

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

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

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)

Aurielle
Člen | 1281
+
0
-

Co máš proti statickým proměnným? :D WidgetContainer je stejně vytvořen během requestu jen jednou (pokud registruji komponenty přes hook), ale ten static odstraním, je tam na nic… :D Jak bys řešil renderování komponenty v kontejneru s určitým módem a parametry?

Filip Procházka
Moderator | 4668
+
0
-

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}