[2008-12-22] Flash messaging – zasílání zpráv
- David Grudl
- Nette Core | 8228
Do Nette jsem přidal dlouho odkládanou podporu pro zasílání tzv. flash zpráv. Jde o zprávy, které např. informují o výsledku operace a uživateli se zobrazí až po přesměrování.
Problematiku jsem popisoval už dříve. Implementace by měla být dostatečně robustní a řešit všechny zádrhele, nicméně beru ji zatím jako testovací a očekávám vaše nápady a připomínky.
Jak to funguje? Zasílání obstarává nová metoda flashMessage() třídy
Nette\Application\Control (tj. je možno zasílat zprávy i na úrovni
komponent). Ukázky použití najdete v příkladech
example/akrabat.forms
a example/akrabat.old
v distribuci.
Kód presenteru nebo komponenty:
public function deleteFormSubmitted(AppForm $form)
{
Model::delete(); // neco smazeme
$this->flashMessage('Item has been deleted.');
$this->redirect('default');
}
Metoda flashMessage() zprávu vkládá přímo do šablony do parametru
flashes
(jako pole objektů stdClass). Šablona pak může vypadat
třeba takto:
{foreach $flashes as $flash}<div class="flash">{$flash->message}</div>{/foreach}
Jako druhý parametr metody lze uvést typ zprávy (výchozí hodnota je
"info"
) nebo její kód.
$this->flashMessage('Item has been deleted.', 'warning');
Typ zprávy pak lze použít například jako CSS třídu:
{foreach $flashes as $flash}<div class="flash {$flash->type}">{$flash->message}</div>{/foreach}
Také je možné do zprávy přidat extra informace:
$message = $this->flashMessage('Obrázek byl uložen');
$message->image = $image;
$message->width = 123;
Nejdůležitejší samozřejmě je, že pokud po uložení zprávy
flashMessage() následuje přesměrování, bude i v dalším požadavku
v šabloně existovat stejný parametr flashes
. Zprávy zůstanou
poté živé další 3 sekundy – například pro případ, že by z důvodu
chybného přenosu uživatel stránku dal obnovit.
Nezapomeňte po update smazat TEMP
- onge
- Člen | 53
Jeste by se mi libilo, kdyby se dala zprava rovnou vyplivnout jako nejaky defaultni html kod (treba jako je to v prikladu vypisu). Zkratka ze by bylo mozne zavolat treba metodu showFlashMessage() a hotovo.
Taky by nebylo spatne, kdyby se dalo tech zprav na stejne urovni nadefinovat vic a ruznych typu – to uz je sice slozitost navic, ale uz jsem zazil radu situaci, kdy je dobre vyhodit vic nez jednu zpravu.
- Ondrej
- Člen | 110
onge napsal(a):
Taky by nebylo spatne, kdyby se dalo tech zprav na stejne urovni nadefinovat vic a ruznych typu – to uz je sice slozitost navic, ale uz jsem zazil radu situaci, kdy je dobre vyhodit vic nez jednu zpravu.
Ano, taky bych uvital, aby se zpravy pridavaly do pole a pak se daly v sablone prochazet pres foreach. Situaci, kdy je potreba zobrazit uzivateli vice zprav ruznych typu je dost.
- Vitek Jezek
- hledá kolegy | 285
Super! Diky za pekny darek k Vanocum : )
(doufam, ze jsem timhle spis pomohl nez uskodil : D )
Jen se taky priklanim k poli zprav nez k zprave jedne (praxe ukazuje, ze v jen trosku robustnejsi aplikaci se velice vyplati, kdyz jednotlive casti kodu mohou nacpat sve informace nezavisle).
- David Grudl
- Nette Core | 8228
Pole zpráv? No proč ne… Ale máte nějaký reálný příklad, kdy je to potřeba?
Totiž obvykle flashMessage volám 1× před redirectem a nesetkal jsem se s tím, že bych potřeboval zaslat zpráv více.
- nAS
- Člen | 277
David Grudl napsal(a):
Pole zpráv? No proč ne… Ale máte nějaký reálný příklad, kdy je to potřeba?
Totiž obvykle flashMessage volám 1× před redirectem a nesetkal jsem se s tím, že bych potřeboval zaslat zpráv více.
Rozhodně. Všeobecně tam, kde se na nějakou událost reaguje na více místech aplikace.
Např: Je smazán uživatel, správce uživatelů zahlásí, že byl smazán uživatel. Správce souborů zahlásí, že se změnil vlastník několika souborů, správce komentářů také přidá nějaký výšplecht…
- David Grudl
- Nette Core | 8228
Tito správci předpokládám nemají přístup k presenteru, takže jejich zprávy se zpracovávají na jednom místě, ne?
Není pak vhodnější použít uživatelský tvar zprávy, jako např.:
$message = $this->flashMessage('Uživatel byl smazán');
$message->details[] = ...;
$message->details[] = ...;
$message->details[] = ...;
- Ondrej
- Člen | 110
David Grudl napsal(a):
Pole zpráv? No proč ne… Ale máte nějaký reálný příklad, kdy je to potřeba?
Totiž obvykle flashMessage volám 1× před redirectem a nesetkal jsem se s tím, že bych potřeboval zaslat zpráv více.
Ulozim formular → Info:„Objekt byl upraven“
Ve formulari byl zaroven aktivovan check box(select box), na ktery reaguji → Info:„Aktivoval jste objekt, takze se odeslalo oznameni vsem zucastnenym uzivatelum v objeku“
Mailovaci komponenta zaloguje Flash zpravu → Warning:„Pozor, nejdulezitejsi osoba v objektu nema vyplneny email, takze se o vasi zmene nedozvi“
Obecne jde o to, ze kazda komponenta si nezavisle na sobe zaloguje vlastni zpravu pro zobrazeni.
- nAS
- Člen | 277
Tak něco jiného ;)
Třeba nákupní košík mi připadá jako dobrý nápad na persistentní komponentu (teda Control). Přidávání a ubírání zboží bude realizováno signály. Ale když uživatel odešle objednávku, tak presenter zavolá nějakou kontrétní metodu košíku na vyprázdnění. A v tomhle případě bych nechal košík říct, že byl košík vyprázdněn. A presenter by měl říci, že byla objednávka úspěšně odeslána.
- David Grudl
- Nette Core | 8228
No, to asi není úplně šťastný příklad, po odeslání objednávky je zpráva „košík byl vyprázdněn“ vyloženě matoucí ;)
Každopádně pokud je košík vizuální komponenta (Control), má své vlastní úložiště flash zpráv nezávislé na presenteru (metoda flashMessage patří třídě Control).
- nAS
- Člen | 277
David Grudl napsal(a):
No, to asi není úplně šťastný příklad, po odeslání objednávky je zpráva „košík byl vyprázdněn“ vyloženě matoucí ;)
No tak dobře, šlo mi o ty nezávislé vizuální komponenty :)
Každopádně pokud je košík vizuální komponenta (Control), má své vlastní úložiště flash zpráv nezávislé na presenteru (metoda flashMessage patří třídě Control).
Aha, tak to jsem nějak nepostřehl, v tom případě si myslím, že by to takhle mělo stačit.
Na druhou stranu, udělat to jako pole je celkem triviální úprava a uškodit to nemůže ;)
- Vitek Jezek
- hledá kolegy | 285
ja jsem zatim dokoncil v nette jen jeden projekt (internetovy dotaznik) a potreboval jsem tam 2 flash msg najednou:
- potvrdit prihlaseni („byl jste prihlasen“)
- pokud to byl admin a nebyla vlozena prvni stranka, tak se automaticky vlozila a napsalo se to do druhe zpravy
Oboji to delal presenter na „jeden beh“. Jasne, dokazal bych se obejit bez toho a udelat si nejaky vlastni dodelavky, ale prijde me to lepsi zavest primo „do jadra“ : )
Rozumim tomu dobre, ze muzu v controlu sahnout si na flash msg presenteru pres $this->presenter->flash = „A“, ale jinak ma dana Controla vlastni flash? (tj ve vlastnich sablonach v Controle pri {$flash->message} vypise to, co si do toho ulozila sama Controla pres $this->flash = „B“?)
- David Grudl
- Nette Core | 8228
Ok, upravil jsem to na pole. Místo parametru $flash se do šablony nyní předává pole $flashes. Zaktualizoval jsem i propozice v prvním příspěvku.
- lucass
- Člen | 89
Zdravím,
měl bych následující dotaz: máme v šabloně layoutu BasePresenteru globální místo pro výpis zpráv. Poté máme různé vykreslovací komponenty, např. přihlášení, formuláře atp., které mají vlastní „holder“ na flash zprávy. Jde nám o to, jakým způsobem zprávy komponent vypisovat v konkrétním presenteru. Uvedu příklad:
Máme komponentu formuláře pro vkládání/editaci položky. Položka se uloží do dabatáze a do flash této komponenty se přidá informační hlášení ve smyslu „Položka XY byla úspěšně uložena.“. Následně se provede redirect na nějakou nadřazenou úroveň, kde tuto komponentu již nepoužíváme, ale je tam nějaký výpis všech položek například, což je view presenteru. V tuto chvíli tedy nemáme možnost z původní editované komponenty získat flash message, protože tuto komponentu již po redirectu neinstancializujeme.
Napadlo nás řešení takové, že v BasePrenteru bychom měli třídní proměnnou např. $globalFlash, kam by se tyto typy globálních zpráv, které budeme chtít zobrazit v globálním místě layoutu BasePresenteru, ukládaly a později i vypisovaly (v komponentě přes $this->parent->globalFlash). Lokální flash messages, tedy ty, co jsou součástí nějaké komponenty a my je nepotřebujeme vypisovat v presenteru, by nadála zůstavaly v ní samotné (např. kontaktní formulář, který bývá umístěn v dolní části stránky, by měl mít případná chybová hlášení uložen lokálně). Nebo existuje v Nette jiná možnost?
Dříve, než jsme začali používat Nette, jsme měli tzv. MsgBuffer, který fungoval na principu session, kdy se po uložení a následném po redirectu provedl dump session MsgBufferu a session se vyprázdnila. Tímto způsobem jsme měli jaksi globálně udržovány veškeré zprávy, které se vypsali vždy na konkrétní místo v šabloně layoutu. Ale samozřejmě měl i své nedostatky.
- stpnkcrk
- Generous Backer | 190
lucass napsal(a):
Zdravím,
měl bych následující dotaz: máme v šabloně layoutu BasePresenteru globální místo pro výpis zpráv. Poté máme různé vykreslovací komponenty, např. přihlášení, formuláře atp., které mají vlastní „holder“ na flash zprávy. Jde nám o to, jakým způsobem zprávy komponent vypisovat v konkrétním presenteru. Uvedu příklad:
Vzhledem k tomu, že prapředek všech presenterů je třída Presenter, potom se vždy flash message ukládají do „globálního úložiště“ a tudíž je možné je použít kdykoliv…
- Ondrej
- Člen | 110
skocourek napsal(a):
Vzhledem k tomu, že prapředek všech presenterů je třída Presenter, potom se vždy flash message ukládají do „globálního úložiště“ a tudíž je možné je použít kdykoliv…
neni to tak. Zkusil jsem to na prikladu z tutorialu a pokud komponenta Datagrid zaloguje hlaseni, tak je dostupne pouze ze sve sablony.
<?php
public function handlePage($page)
{
$this->flashMessage('Strana byla zmenena'); // dostupne v sablone grid.phtml
$this->getPresenter()->flashMessage('Strana byla zmenena'); // dostupne na glogalni urovni v @layout.phtml
}
?>
- David Grudl
- Nette Core | 8228
lucass napsal(a):
Zdravím,
měl bych následující dotaz: máme v šabloně layoutu BasePresenteru globální místo pro výpis zpráv. Poté máme různé vykreslovací komponenty, např. přihlášení, formuláře atp., které mají vlastní „holder“ na flash zprávy. Jde nám o to, jakým způsobem zprávy komponent vypisovat v konkrétním presenteru. Uvedu příklad:
V podstatě lze použít dva odlišné přístupy. První bych nazval „chytrá komponenta“, která bude výsledky ukládat přímo do úložiště flash zpráv presenteru:
<?php
class Komponenta extends Control
{
public function handleLogin()
{
...
$this->getPresenter()->flashMessage('Uživatel byl přihlášen');
}
?>
Nebo přístup „nevolejte nám, my zavoláme vám“:
<?php
class Komponenta extends Control
{
/** @var array handlery události Xyz */
public $onXyz;
public function handleLogin()
{
...
$this->onXyz(); // volej handlery
}
?>
a použití v presenteru (takto hezky to jde v PHP 5.3)
<?php
class HomepagePresenter extends Presenter
{
public function startup()
{
$komponenta = new Komponenta($this, 'k');
$komponenta->onXyz[] = function() { // registruje handler
$this->flashMessage('Uživatel byl přihlášen');
};
}
?>
nebo pomocí metody v PHP 5.2.x
<?php
class HomepagePresenter extends Presenter
{
public function startup()
{
$komponenta = new Komponenta($this, 'k');
$komponenta->onXyz[] = array($this, 'xyzHandler'); // registruje handler
}
public function xyzHandler()
{
$this->flashMessage('Uživatel byl přihlášen');
}
?>
Druhý přístup vede k lepší znovupoužitelnosti komponenty, na událost si její vlastník reaguje zcela posvém.
- brabo
- Člen | 19
Problém je v tom, že pokud níže zmíněná Komponenta je použita v Presenteru A, a po úspěšném přihlášení se přesměruji na Presenter B, tak se mi zpráva ztratí, protože je uložena v Presenteru A.
<?php
class Komponenta extends Control
{
public function handleLogin()
{
...
$this->getPresenter()->flashMessage('Uživatel byl přihlášen'); // hlasku ulozime do presenteru A
$this->redirect('Presenter B'); // ale presmerujeme na presenter B
}
?>
Ideální by bylo ukládat zprávy do nějakého „top presenteru“, od kterého všechny používané presentery dědí. Otázka je, jak získat univerzálně jeho instanci.
Možná je ale jenom problém v tom, že celý navržený princip zpráv špatně chápeme. Jak to chápu, tak v tuto chvíli má každý kontroler svoje zprávy a musí si i sám řešit jejich generování v šabloně? Nám se v minulosti osvědčil způsob, že se nám všechny zprávy nezávisle na zdroji zobrazují na jednom pevně daném místě základního layoutu, což je to, čeho se s Lucassem snažíme dosáhnout.
- David Grudl
- Nette Core | 8228
brabo napsal(a):
Problém je v tom, že pokud níže zmíněná Komponenta je použita v Presenteru A, a po úspěšném přihlášení se přesměruji na Presenter B, tak se mi zpráva ztratí, protože je uložena v Presenteru A.
Mezi presentery se nijak nerozlišuje, zpráva se vždy uložít do stejného místa session.
Možná je ale jenom problém v tom, že celý navržený princip zpráv špatně chápeme. Jak to chápu, tak v tuto chvíli má každý kontroler svoje zprávy a musí si i sám řešit jejich generování v šabloně? Nám se v minulosti osvědčil způsob, že se nám všechny zprávy nezávisle na zdroji zobrazují na jednom pevně daném místě základního layoutu, což je to, čeho se s Lucassem snažíme dosáhnout.
Každý Control má své úložiště, ale nikdo vás je nenutí používat.
Prostě používejte $this->presenter->flashMessage(...)
- phx
- Člen | 651
Slo by dodelat aby v sablone byly dostupne pole jednotlivych typu zprav? Bohate by stacilo neco jako:
$this->getFlashMessages($type);
Otazka: je reseno smazani ze session pri zobrazeni? Jde mi o to, co kdyz nekdo 2× za sebou rychle klikne tak zda i na druhe strance uvidi hlasku. Rekl bych, ze to je nezadouci.
Editoval phx (7. 1. 2009 20:21)
- David Grudl
- Nette Core | 8228
phx napsal(a):
Slo by dodelat aby v sablone byly dostupne pole jednotlivych typu zprav? Bohate by stacilo neco jako:
Tohle bych asi nechal na uživateli.
Otazka: je reseno smazani ze session pri zobrazeni? Jde mi o to, co kdyz nekdo 2× za sebou rychle klikne tak zda i na druhe strance uvidi hlasku. Rekl bych, ze to je nezadouci.
Pokud nekdo 2× za sebou zmáčkne F5, tak mu zpráva nezmizí, pokud klikne jinam, tak ji už neuvidí.