Dynamické prvky stránky alias widgety

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

Neřešil někdo z vás jak dynamicky řešit doplňkový obsah stránky? V řeči wpressu to jsou widgety v řeči joomla to jsou moduly. Jsou to jednoduše drobné části webu co se můžou, ale nemusí opakovat napříč celým webem a jejich obsah a pozice není předem známa. Může to být např. na stránkách s obsahem seznam kategorií, tag cloud, ale zase na stránce eshopu žhavé zboží, tip měsíce atd.

Přemýšlel sem, že bych si udělal nějaký presenter pro admin, kde by se ty jednotlivé widgety vytvářely, ale zase jak řešit jejich „konfiguraci“ a seznam kdyby se měly postupem přidávat a dané cms tak o ně rozšiřovat? Udělat nějakou separátní složku do které by se ty widgety po složkách přidávaly a každému z nich udělat vlastní formulářovou továrničku aby se editovaly ty prvky co widget vyžaduje, a v okamžiku přidání nového widgetu by se jen načetly adresáře co tam jsou a na základě nich by se uživateli objevila nabídka co může přidat a co ne. A nebo si vytvořit XML soubor kde bude základní info o těch widgetech včetně popisu atd? A nebo ještě další možnost a to dát to info do neonu do vlastní sekce a tam si zadefinovat jake widgety jsou k dispozici?

Další věc je pak řešení front části. Jak je dynamicky získávat? Tady sem uvažoval udělat nějaký WidgetPresenter který by extendoval FrontPresenter a byl extendovany BasePresenterm, a v něm by byly zadefinovány všechny dostupné widgety přes createcomponent, ale jak je pak dostat do šablony? Mějme např. 3 sloupcový layout kde krajní sloupce slouží právě jen pro widgety a prostřední je pro obsah ale můžou zde být také widgety, třeba v záhlaví a zápatí. A cílem je aby si front načetl jaké widgety má zrovna na dané stránce načíst a do jaké pozice. Layout by měl být také dynamický, a tudíž v případě že by např v levém sloupci nebyl žáden widget tak by se přes příznak upravila šablona aby se střední sloupec natáhl.

V nonNette appce to řeším tak že widgety mám solo části kodu, a renderer stránky si je načítá podle potřeby. Má k dispozici metodu pro renderování widgetu, ale také pro zjištění počtu widgetů na dané pozici a dané stránce, takže pak může snadno rozhodnout zda tam ten sloupec bude či nebude, atd.

Řešil někdo z vás něco podobného?

Šaman
Člen | 2633
+
0
-

Připadá mi, že popisuješ komponenty. Nebo v čem by widgety měly být jiné?

akadlec
Člen | 1326
+
0
-

Šaman: ano popisuji komponenty, ale co řeším je to jak je dostat do stránky aniž bych je musel hardkodit do šablony.

mkoubik
Člen | 728
+
0
-
$presenter['nazevKomponenty']->render();
akadlec
Člen | 1326
+
0
-

a tím si pomůžu k čemu?

hAssassin
Člen | 293
+
+1
-

Ahoj, zatim jsem tohle v Nette neresil, jen kdysi v cistym PHP. Ale slozity to nebude. Potrebujes metodu napr createComponentWidget() kterou dej klidne do BasePresenteru nebo do nejakyho vlastniho WidgetPresenteru, ze kteryho pak ale nutne budou dedit vsechny presentery ve kterych se widgety muzou objevit (coz jsou ale vlastne vsechny minimalne na frontu).

Tahle metoda pak bude uvnitr rozlisovat typ kazdyho widget a podle toho ho vytvori. Dale tam muzes zakomponovat IDcko widgetu a Multiplier. No a z sablony to pak muzes vykreslovat pres: {control widget-typWidgetu-ID}. Fungovat by to melo jen si z hlavy netroufnu ani na nastrel kodu :-)

Kazdopadne stejne musis mit nejak usporadany ty widgety na strance do nejaky mrizky apod, takze stejne se tomu vykresleni v sablone nevyhnes. Ja to kdysi mel tak ze sem mel widget v obsahu stranky, obsah se delil na sekce (napr horni radek, pod nim dva sloupecky a pak zase radek) a v nich pak byly v urcitym poradi widgety. To bylo ulozeny v DB a cely se to vytahlo, usparadalo, predalo do sablony a pak pres par foreachu se vykreslily sekce i widgety (ale jak rikam nebylo to Nette).

akadlec
Člen | 1326
+
0
-

No to je směr nad kterým sem uvažoval, využít Multiplier přes IDčka těch widgetu, tvůj nápad tam ještě zahrnout ten typ je fajn. to by pak stačila jedna metoda pro vytvoření komponenty.

Co je ale zatím pro mě oříšek je jak to hodit do šablony aby to nebylo nahardkoděné přímo v ní. Jednoduše aby člověk skočil do admina a řekl že ten, ten a ten widget se zobrazí na homepage jinde ne atd. V nonNette to vyřešené mám, ale jak na to v nette?

David Matějka
Moderator | 6445
+
0
-

na multiplier bych se vykaslal. udelej si nejakou komponentu, neco jako WidgetManager (urcite te napadne lepsi nazev :) ), kterej bude mit pri inicializaci parametr treba left nebo right, aby vedel, jaky komponenty ma vykreslit.

potom z databaze nebo odjinud vytahne seznam komponent s jejich parametry, rucne je inicializuje a prida do stromu komponent pres addComponent

do templaty by potom melo stacit poslat pole s nazvy jednotlivych komponent a

{foreach $controls as $controlName}
	{control $controlName}
{/foreach}
akadlec
Člen | 1326
+
0
-

matej21: Pokud jsem tě správně pochopil tak do šablony bych měl do jednotlivých sloupcu atd hodit zavolání komponenty {control $widgetManagerLeft} a ten ten by pak sam načetl jaké komponenty vložit do levého sloupce a ty by pak předal do vlastní šablony jako pole a pak je vykreslil?

hAssassin
Člen | 293
+
0
-

jeste k tomu predtim, muzes rict jak to mas vyreseny ted? my jsme prave meli vzdy definou oblast, ktera ty widgety mohla obsahovat a v sablone pak bylo vykresleni takovy oblasti (podle ID) a ta si pak nacetla sekce a widgety a ty se vykreslily. Stejne vzdy nejak ty widgety musis v sablone pres foreach projit a vykreslit, takze vzdy to hardcoded bude, jen zalezi v jaky sablone to mas…

David Matějka
Moderator | 6445
+
0
-

akadlec: jj nejak tak, jen ne {control $widgetManagerLeft} ale {control widgetManagerLeft} :)

akadlec
Člen | 1326
+
0
-

hAssassin: právě že já to nahardkoděné neměl. Ve zkratce to fungovalo tak, že sem měl službu pro widgety. Tahle služba si při loadu načetla všechny widgety z DB co byly platné pro danou stránku a uložila si je k sobě. Jakmile došlo ke zpracování daného modulu a appka začala vykreslovat šablonu, tak v šabloně se volaly jednotlivé metody te služby pro widgety. Jedna z těch metod byla pro ověření zda v dané pozici opravdu něco je a nebo ji má přeskočit. Pokud tam něco bylo tak se v metodě té služby foreach prošly widgety co se mají zobrazit, odchytily výstupy a ty se plácly do šablony.

Takto jsem mohl měnit vzhled clého webu aniž bych šáhl na kod, či šablonu samotnou atd.

matej21: jasně, dolar mě tam vlezl omylem ;) No je fakt že to nezní špatně.

Editoval akadlec (4. 4. 2013 14:17)

David Matějka
Moderator | 6445
+
0
-

jeste me napadlo tam pridat jednu uroven komponent a to ten multipler, ale na uroven toho WidgetManager, ze bys to mel definovany zhruba takhle (ted se omlouvam, jestli to mam blbe, multiplier uz jsem dlouho nepouzival):

public function createComponentWidgetManager()
{
	return new Multiplier(function($position)) {
		return new WidgetManager($position);
	});

}

(lepsi by bylo pres factory, aby se hezky injectnuly zavislosti do konstruktoru)

v template (jako pro presenter, nebo v @layout) bys pouzil jen

{control widgetManager-left}
{control widgetManager-right}
{control widgetManager-foo}

a vytvorily by se odpovidajici „spravci komponent“ pro danou pozici. takhle by stacilo tedy pridavat nove „pozice“ jen upravou sablony a databaze bez dalsiho zasahu do nejakyho basepresenteru.

WidgetManager by potom, jak jsem psal vys, akorat vytahnul jednotlivy podkomponenty pro danou pozici, pridal by je do stromu pod nejakym jmenem (treba jen cisla) a tyhle jmena by predal do templaty, kde by je foreachem vykreslil.

EDIT: jedinej „problem“ je tech mnoho urovni. asi by to nette nemelo v nicem vadit, jen muzou blbe vypadat odkazy z tech pod-pod-komponent :) (jestli je tam teda vubec budes pouzivat, coz vubec neni nutnost.. a kdyz to bude v nejakem formulari, tak si toho uzivatel nevsimne)

Editoval matej21 (4. 4. 2013 14:29)

akadlec
Člen | 1326
+
0
-

matej21: Díky za tipy, myslím že to poměrně dostatečně řeší ten problém co s těmi widgety/komponentami mám ;) Linky by měly být bez problému, protože ty widgety jsou více méně informační boxy a pokud mají nějakou akci, jakože např. odkazy na kategorie blogu tak se odkazují na konkrétní presneter takže to by mělo být ok.

stefi023
Člen | 71
+
0
-

Resil jsem podobny problem, ale sel jsem na to trosku jinak. Chtel jsem, aby si do obsahu stranky mohli davat libovolne nejruznejsi komponenty (banner, anketa, galerie,…) ktere si predem zadefinuji.

Predpokladal jsem kompetentni uzivatele a tem dal pravo pouzivat v obsahu „stranky“ krome texy i latte kod, vsechny „pouzitelne“ komponenty zaregistroval pres config, prepsal metodu createComponent a pak uz do textu stranek (ktere jsou ulozeny v DB) stacilo napsat napr neco jako:

Lorem Ipsum
############
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam turpis dui,
vestibulum vel tempor et, consequat et odio. Sed dictum dolor ut tellus mollis
semper. Etiam pretium blandit elit, in mattis ipsum volutpat nec.

{control randomBannerRenderer wide}

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam turpis dui,
vestibulum vel tempor et, consequat et odio. Sed dictum dolor ut tellus mollis
semper. Etiam pretium blandit elit, in mattis ipsum volutpat nec.

{block #rightColumn}
{include #parent}
{control cathegoryRenderer-5 thumb}
{/block}

{block #topColumn}
{control menuRenderer-8 inline}
{/block}

Tzn jsem vlastne mohl ovlivnovat i sablony ze kterych jsem extendoval.

Cele je to samozrejme trochu problematictejsi a urcite by to chtelo lepe „zabezpecit“, omezit je jen na urcitou latte syntax, zaregistrovat vlastni makra, udelat vice user-friendly syntax, atd… Me to ale pro svuj ucel stacilo

akadlec
Člen | 1326
+
0
-

stefi023: zajímavý nápad, ale ten předpokládá že lidi co s tím budou pracovat ví co je to nette nebo ví jak použít danou syntaxi, čímž to poměrně dost filtruje.

stefi023
Člen | 71
+
0
-

To ano :) je to ve fazi kdy jsem to chtel zkusit, zda by to vubec fungovalo. Kdyz mohu „nutit“ lidi pouzivat texy syntax (s cimz mam casto problem), tak tech par mohu naucit i jeden dva prikazy navic :) Ale asi by stalu si na to napsat i nejaky texy plugin, ktery by prevedl texy do latte a az pak do html.

Muj cil je totiz ze uzivatele budou psat jen texty, vse ostatni (od tucnosti, pres nadpisy, obrazky az po vkladane widgety) si naklikaji a nekde v pozadi se jim bude tvorit takovyto kod (pak uz asi i bez texy, kdyz si to budou klikat) o kterym vedet nebudou… ale to je bohuzel dlouha cesta, tak postupuji poporade :)

akadlec
Člen | 1326
+
0
-

stefi023: tak to máme podobnou vizi ;) Já na starém systému měl k dispozici pluginy které doplňovaly texty např o galerii, či hot produkty atd, na formátování textu sem měl TinyMCE a ty pluginy se do textu hodily extra tlačítky co vložily spešl string ve tvaru {insertGallery 15 5 5} což ale je problém že ten člověk si to občas nedokázal představi, či omylem umazal část toho řetězce atd. Ideálni by bylo kdyby se ten obsah v editoru jakoby renderoval i včetně těch speciálních funkcí a uživateli uplně zakázat čumět na html kod.

akadlec
Člen | 1326
+
0
-

Takže jsem se po delší odmlce vrátil k realizaci dynamických komponent ale tak nějak myslím že bych to měl nějak předělat ;)

V presenteru aktivuju multiplier:

public function createComponentWidgetManager()
{
	return new Multiplier(function($position) {
		return new WidgetManager($position);
	});
}

A ten pro zadanou pozici, např.:

{control widgetManager-menu}

Spustí widget manager:

class WidgetManager extends \Nette\Application\UI\Control
{
	/** @var string */
	protected $position;

	/** @var array */
	protected $widgets = array(
		'menu'	=> array(
			1	=> array(
				'id'	=> 1,
				'name'	=> 'Main menu',
				'type'	=> 'mainMenu',
				'class'	=> 'MainMenuWidget'
			),
			2	=> array(
				'id'	=> 2,
				'name'	=> 'User menu',
				'type'	=> 'userMenu'
				'class'	=> 'UserMenuWidget',
			),
		)
	);

	protected function attached($presenter)
	{
		parent::attached($presenter);

		if ( !$presenter instanceof Presenter ) return;

		$this->addComponent(New \Nette\ComponentModel\Container(), "menu");
	}

	public function __construct($position)
	{
		// Set position
		$this->position = $position;
	}

	public function render()
	{
		$this->template->widgets = array();

		foreach($this->widgets[$this->position] as $widgetEntity) {
			// Create new widget
			$widget = new $widgetEntity['class']($this[$this->position], $widgetEntity['type']);


			// Activate inject methods
			$this->presenter->getContext()->callInjects($widget);

			// Render widget
			$widgetEntity['content'] = $widget->render();

			// Add widget to template
			$this->template->widgets[] = $widgetEntity;
		}

		return $this->renderWithTemplate('widgetManager');
	}
}

do šablony widgetManager.latte se pak pošlou jednotlivé widgety i s renderovaným obsahem:

{foreach $widgets as $widget}
	{$widget['content']}
{/foreach}

Editoval akadlec (24. 7. 2013 23:21)

David Matějka
Moderator | 6445
+
0
-

udelal bych to ciste pres komponenty, treba takto:

class WidgetManager extends \Nette\Application\UI\Control
{
    /** @var string */
    protected $position;

    /** @var array */
    protected $widgets = array(
        'menu'  => array(
            'mainMenu'   => array(
                'id'    => 1,
                'name'  => 'Main menu',
                'class' => 'MainMenuWidget'
            ),
            'userMenu'   => array(
                'id'    => 2,
                'name'  => 'User menu',
                'class' => 'UserMenuWidget',
            ),
        )
    );
    public function __construct($position)
    {
        // Set position
        $this->position = $position;
    }

    protected function createComponent($name)
    {
        if(!isset($this->widgets[$this->position][$name])) {
            return parent::createComponent($name);
        }
        $widgetEntity = $this->widgets[$name];
        $widget = new $widgetEntity['class'](); //o nastaveni parentu a jmena by se melo ted bez problemu postarat nette
        $this->presenter->getContext()->callInjects($widget); //tohle neni moc hezky, lepsi by byly nejaky tovarnicky :)
        return $widget;
    }

    public function render()
    {
        $this->template->widgets = array_keys($this->widgets[$this->position]);

        return $this->renderWithTemplate('widgetManager');
    }
}

v sablone pak

{foreach $widgets as $widget}
    {control $widget}
{/foreach}

Editoval matej21 (24. 7. 2013 23:56)

akadlec
Člen | 1326
+
0
-

No já to ještě před chvíli trochu poladil jinak:

	/**
	 * Render all widgets
	 */
	public function render()
	{
		$this->template->widgets = array();

		foreach($this->widgets[$this->position] as $widgetEntity) {
			// Create new widget
			$widget = new $widgetEntity['class']($this[$this->position], $widgetEntity['type']);

			// Activate inject methods
			$this->presenter->getContext()->callInjects($widget);
		}

		$this->template->widgets = $this[$this->position]->components;

		return $this->renderWithTemplate('widgetManager');
	}

a pak v šabloně:

{foreach $widgets as $widget}
	{control $widget}
{/foreach}

a je fakt že mě ta metoda callInjects tam moc nevoní :( Nad továrničkama jsem také přemýšlel že bych injecty udělal v nich kde by to šlo bez problému a ty potřebné závislosti pak předal finalnimu widgetu.

akadlec
Člen | 1326
+
+2
-

Takže jsem trošku přepracoval původní řešení zde probírané a vytvořil jsem extension pro spravování takovýchto prvků stránek. Extension je zatím ve fázi tvoření, ale základní funkce již umí. Pokud by o tuto ext byl zájem tak ji hodím klidně do addons a můžeme ji vyvíjet společně ;)

Jinak je k nalezení klasicky na githubu;)

Funguje tak že:

  • zaregistrujete ji do appky
  • vytvoříte jednotlivé požadované widgety které budou dědit vždy od základího widgetu (viz ukázka widgetu)
  • takto vytvořený widget registrujete klasicky jako komponentu, ale ještě je potřeba ji otagovat ipub.widgets.widget
  • extesnion si pak sama tyto widgety natáhne, resp jejich továrničky

Extension poskytuje widgets manager pomocí kterého je možné jednotlivé widgety vytáhnout a rendereovat. Aby se widget dal renderovat tak je potřeba mu předat data a to je možné buď pomocí seteru a nebo přímo v render metodě.

Zatím to jen umí vytáhnout konkrétní widget a ten vyrenderovat. Do budoudna chci aby to dokázalo vytáhnout celou sadu widgetů pro nějakou pozici (uživatelé joomla a wpress znají) určit zda se mají zobrazit a podle toho je zobrazit. Samozřejmě chci i latte makra co mě toto budou nahrazovat a taky informovat o tom zda tam nějaké widgety jsou aby se dal podle toho ladit layout.

regiss
Člen | 61
+
0
-

akadlec napsal(a):

Takže jsem trošku přepracoval původní řešení zde probírané a vytvořil jsem extension pro spravování takovýchto prvků stránek. Extension je zatím ve fázi tvoření, ale základní funkce již umí. Pokud by o tuto ext byl zájem tak ji hodím klidně do addons a můžeme ji vyvíjet společně ;)

Jinak je k nalezení klasicky na githubu;)

Funguje tak že:

  • zaregistrujete ji do appky
  • vytvoříte jednotlivé požadované widgety které budou dědit vždy od základího widgetu (viz ukázka widgetu)
  • takto vytvořený widget registrujete klasicky jako komponentu, ale ještě je potřeba ji otagovat ipub.widgets.widget
  • extesnion si pak sama tyto widgety natáhne, resp jejich továrničky

Extension poskytuje widgets manager pomocí kterého je možné jednotlivé widgety vytáhnout a rendereovat. Aby se widget dal renderovat tak je potřeba mu předat data a to je možné buď pomocí seteru a nebo přímo v render metodě.

Zatím to jen umí vytáhnout konkrétní widget a ten vyrenderovat. Do budoudna chci aby to dokázalo vytáhnout celou sadu widgetů pro nějakou pozici (uživatelé joomla a wpress znají) určit zda se mají zobrazit a podle toho je zobrazit. Samozřejmě chci i latte makra co mě toto budou nahrazovat a taky informovat o tom zda tam nějaké widgety jsou aby se dal podle toho ladit layout.

Ahoj podarilo se mi zprovoznit WidgetsManager and WeatherWidget pres composer. Muzes mi prosim ukazat jak predat data WeatherWidget v presenteru a nasledne v sablone

akadlec
Člen | 1326
+
0
-

@regiss Hele aktuálně to funguje tak že si ty widgety vyrenderuješ v presenteru či komponentě a pak je jednoduše předáš šabloně. Zhruba takto:

Presenter:

class DashboardPresenter extends BasePresenter
{
	/**
	 * @autowire
	 * @var IPub\WidgetsManager\WidgetsProvider
	 */
	protected $widgetsProvider;

	public function renderDefault()
	{
		$widgets = [];

		// Get widgets metoda je na tobě, mě to konkrétně vrací pole s názvem a konfigurací:
		// 123 => array[
		//		params => array[
		//			location => "Brno, Czech Republic"
		//			id => "3078610"
		//			units => "metric"
		//		]
		//		type => "widget.weather"
		//	]
		foreach ($this->getWidgets() as $id => $data) {
			// Z provideru si vytáhneš onen widget
			if ($widget = $this->widgetsProvider->get($data['type'])) {
				// A jednoduše jej renderuješ, nic se nevykreslí, celé se ti to uloží do proměnné
				$widgets[$id] = $widget->render($data);
			}
		}

		$this->template->widgets = $widgets;
	}
}

No a v šabloně pak jedoduše „echuješ“

<div n:foreach="$widgets as $widget">
	{!$widget}
</div>

Ještě přemýšlím že render metodu poupravím tak aby se to v šabloně dalo použít jako klasická komponenta třeba takto:

<div n:foreach="$widgetsData as $data">
	{control $widget $data}
</div>
regiss
Člen | 61
+
0
-

Ahoj děkuji za návod. Nahral jsem widgetManager s weatherManagerem na github, bohužel mi to zkončí na hlášce v šabloně. Widget not found. Vytvořil jsem dashboardPresenter podle tvého návodu.
Mohl by jsi se na to mrknout, v composer.lock mam aktualni knihovny.
https://github.com/…idgetManager

akadlec
Člen | 1326
+
0
-

hele zkusil sem to u sebe a našel sem drobné chybky, resp není zadefinována jedna konstanta, kterou běžně používám a v té tvé verzi nefunguje translator, což nevím jak je globálně řešeno, to zkusím zkouknout a pak to commitnu. Nicméně tebou popsána chyba se mě neobjevila.

Filip Procházka
Moderator | 4668
+
0
-

Pěkné, tohle jsem přehlédl :)

Je vidět že to děláš hodně custom, jsou tam věci, které by obecného řešení asi nepatřily ani když hodně přimhouřím oči. Kdybys to rozdělil ještě na widgetManager a widgetManagerFeaturesWhichAreNeededByiPublikuj tak by se takové rozšíření mohlo pěkně uchytit :)

akadlec
Člen | 1326
+
0
-

No hele ty samotné extension se snažím vyčlenit maximálně aby nebyly závísle na cmsku atd. Toto je taková první pracovní verze, takže jakýkoliv tip na vylepšení se hodí ;)

akadlec
Člen | 1326
+
0
-

No už je to nějaký ten pátek co to řeším, padly tady nějaké nápady co a jak a tak jsem se je postupem času snažil dát dohromady a dokončit svou extension která by toto zastřešovala a tak nějak se mě povedlo a jsem se současnou verzí docela spokojen. Pokud o tom někdo chce diskutovat tak sem oddělil diskuzi zde jinak zde můžeme pokračovat v principech jak a proč to realizovat.