[2008-12-12] Podpora JavaScriptu a AJAX
- David Grudl
- Nette Core | 8227
V několika posledních revizích došlo ke změnám, které (řekl bych že velmi) zpříjemňují podporu AJAXu, ale mohou způsobit zpětnou nekompatibilitu. Pokusím se rozdíly popsat.
Poznámka: ajaxDriver ve verzi 0.9 už vůbec neexistuje
- změna: AjaxDriver se stal benevolentnějším a
z interface zmizeli metody
updateSnippet
nebofireEvent
. Je možné nastavovat zcela libovolné parametry a to přes overloading. Zmíněné metody se dají nahradit třeba takto:
// misto $this->getAjaxDriver()->updateSnippet($id, $content);
$this->getAjaxDriver()->snippet[$id] = $content;
// misto $this->getAjaxDriver()->fireEvent($event);
$this->getAjaxDriver()->events[] = array($event);
(staré metody fungují, ale vygenerují varování)
Nyní je tedy možné nastavit jakékoliv parametry a je věcí obslužného skriptu, jak s nimi naloží. Příklad použití najdete níže.
- novinka: při AJAXovém požadavku se metodám handleXYZ(…) předávají i POST parametry
- změna: o renderování snippetů se už nestará třída
Control, ale nový helper pro šablony – SnippetHelper. Místo dosavadního
$control->beginSnippet($name)
&$control->endSnippet($name)
použijte$snippet = SnippetHelper::create($control, $name)
&$snippet->finish()
. - DŮLEŽITÉ: v HTML měly snippety v ID znak dvojtečky, kterou bylo nutno escapovat při použití s jQuery nebo CSS. Dvojtečku jsem proto nahradil za dvě podtržítka (chování můžete navíc změnit přepsáním metody getSnippetId ve svém potomkovi).
- změna: filtr curlyBrackets trošku narostl, tak jsem jej přesunul do samostatné třídy CurlyBracketsFilter.
// misto $template->registerFilter(/*Nette\Templates\*/'TemplateFilters::curlyBrackets');
$template->registerFilter(/*Nette\Templates\*/'CurlyBracketsFilter::invoke');
- DŮLEŽITÉ: velmi důležitou novinkou curlyBrackets je přizpůsobení escapování podle lokálního typu obsahu. Uvnitř JavaScriptu nebo uvnitř CSS se escapuje jinak, než v HTML kódu. Díky tomu je možné zcela nativně používat PHP proměnné uvnitř JavaScriptového kódu.
// $pole = array(1, 2, 3);
// $name = "Jim Beam";
<script>
var pole = {$pole};
var name = {$name}; // ALE POZOR - NESMÍ se už používat uvozovky, jako třeba var name = "{$name}";
</script>
Pokud všechny tyto věci dáme dohromady, můžeme třeba takto implementovat auto complete.
Na straně serveru:
class DefaultPresenter extends /*Nette\Application\*/Presenter
{
public function handleAutoComplete($text)
{
$list = array(...); // hodnoty
$matched = array();
foreach ($list as $item) {
if (strncasecmp($item, $text, strlen($text)) === 0) {
$matched[] = $item;
}
}
$this->ajaxDriver->autocomplete = $matched; // výstup bude v data.autocomplete
$this->terminate();
}
}
Na straně klienta:
<input type="text" id="name">
<div id="output">
<script type="text/javascript">
$('#name').keyup(function() {
$.post({link autoComplete!}, // bez uvozovek!
{text: this.value}, // hodnota se předá do handleAutoComplete
function(data){ $('#output').html(data.autocomplete.join("<br>")); },
'json' // jako komunikační formát použijeme JSON
);
});
</script>
Nebo jiná ukázka, jak lze pomocí jQuery nastavit hodnotu snippetu:
var data = ... // data získaná ze serveru
var snippetId = {$presenter->getSnippetId('flash')}; // místo dřívějšího :flash bude hodnota __flash
$('#' + snippetId).html(data.snippets[snippetId]);
- LuKo
- Člen | 116
Nevím, jestli sem mohu psát postřehy. Upgradoval jsem na poslední verzi, v basePresenteru vyměnil
<?php
$this->template->registerFilter(/*Nette\Templates\*/'CurlyBracketsFilter::invoke');
//$this->template->registerFilter(/*Nette\Templates\*/'TemplateFilters::curlyBrackets');
?>
ale když cokoli změním v kterémkoli presenteru dědícím z basePresenteru, dostanu při prvním zavolání stránky hlášku:
<b>Warning</b>: Deprecated: use $template->registerFilter('CurlyBracketsFilter::invoke') instead. in <b>C:\Home\razeni\admin\libs\Nette\Templates\Filters\TemplateFilters.php</b> on line <b>78</b><br />
Při opětovném načtení stránky hláška opět zmizí (do další změny kódu presenteru).
- LuKo
- Člen | 116
Honza M. napsal(a):
To bude asi kvůli cachování. Zkus smazat cache.
To bylo první, co jsem udělal. „Problém“ se stále opakuje. Není to nic zásadního, první vygenerování provedu já a při dalších zobrazeních stránky se již chyba nezopakuje, takže uživatelé zbytečně neplaší. Jen to hlásím, kdyby to byl signál třeba nějakého většího problému.
- LuKo
- Člen | 116
Ještě jeden zajímavý postřeh. Mám tabulku, kde jednotlivé záznamy edituji formulářem v „bublině“ (aby se zbytečně nenačítala nová stránka). V presenteru mám tedy něco takového:
<?php
public function actionEdit($id = 0)
{
$form = new AppForm(...);
/* ...definice formulare... */
if ($this->isAjax()) {
echo $form;
$this->terminate(); // do vystupu vlozi navic []
}
}
?>
Toto mi vyhodí správně pouze formulář, ale na konec ještě přidá znaky: []
<?php
public function actionEdit($id = 0)
{
$form = new AppForm(...);
/* ...definice formulare... */
if ($this->isAjax()) {
echo $form;
exit; // do vystupu nic nevklada
}
}
?>
Exit se chová korektně a vrátí pouze formulář bez znaků navíc. Všiml jsem si toho až po upgradu na (před)poslední verzi.
- David Grudl
- Nette Core | 8227
LuKo napsal(a):
ale když cokoli změním v kterémkoli presenteru dědícím z basePresenteru, dostanu při prvním zavolání stránky hlášku:
<b>Warning</b>: Deprecated: use $template->registerFilter('CurlyBracketsFilter::invoke') instead. in <b>C:\Home\razeni\admin\libs\Nette\Templates\Filters\TemplateFilters.php</b> on line <b>78</b><br />
Místo trigger_error dej do TemplateFilters.php vyhození výjimky a uvidíš, kde to vzniká.
LuKo napsal(a):
Toto mi vyhodí správně pouze formulář, ale na konec ještě přidá znaky: []
Pravda, je tam bug. Posílám opravu.
- Jod
- Člen | 701
LuKo napsal(a):
Honza M. napsal(a):
To bude asi kvůli cachování. Zkus smazat cache.
To bylo první, co jsem udělal. „Problém“ se stále opakuje. Není to nic zásadního, první vygenerování provedu já a při dalších zobrazeních stránky se již chyba nezopakuje, takže uživatelé zbytečně neplaší. Jen to hlásím, kdyby to byl signál třeba nějakého většího problému.
Nezabudol si to prepísať v nejakom controle?
V metóde render jedného controlu som vytváral snippet, no v novej revízii mi to nefunguje a nejak sa nevyznám jak to tam teraz narvať. Neviete niekto?
- David Grudl
- Nette Core | 8227
Jod napsal(a):
V metóde render jedného controlu som vytváral snippet, no v novej revízii mi to nefunguje a nejak sa nevyznám jak to tam teraz narvať. Neviete niekto?
Místo $control->beginSnippet($name)
&
$control->endSnippet($name)
použij
$snippet = SnippetHelper::create($control, $name)
&
$snippet->finish()
.
- David Grudl
- Nette Core | 8227
Co by to způsobilo? Jako nevýhoda je zásadní BC break, všechny aplikace by se museli zkontrolovat, jestli ve formuláři nepoužívají prvek se stejným názvem, jako má parametr metod render—, action— nebo persistentní parametr. A výhoda? V podstatě mě žádná nenapadá – jaké by to mělo výhody?
- A.
- Člen | 87
Duvod byl popsan jiz v jinem threadu. Nemusi to byt az na urovni parametru presenteru a
perzistentnich parametru, bohate staci pridat parametry pouze do volani
metody handlXYZ
. Tim by se nic nepremazalo, ne? Pouze na
lokalni urovni metody handle
a to v pripade, ze ocekava vychozi
hodnotu.
Prakticky se odprostis od konstrukci (pokud nevyuzivam Nette\Forms) typu:
handleFormSubmit()
{
$param1 = $this->presenter->request->post->param1;
$param2 = $this->presenter->request->post->param2;
}
Prijde mi to jako velka vyhoda a usnadneni prace. Jak jiz jsem psal, tuto vlastnost jsem si velice oblibil u pythoniho frameworku cherrypy.
Nejde vubec o to, aby se tyto parametry chovaly stejne jako parametry presenteru.
- David Grudl
- Nette Core | 8227
Hned na začátku životního cyklu presenteru proběhne přerozdělení všech parametrů do „balíčků“ pro jednotlivé komponenty, takže předávat něco jiného handle— a něco jiného třeba render— tak snadno nelze – nehledě na to, že by to bylo celkem matoucí.
Ale k věci – smysl to má asi jen při použití „ručních“ formulářů, tj. bez Nette\Forms. Pokud bych použil Nette\Forms, k jednotlivým parametrům se dostanu:
function handleFormSubmit()
{
$values = $form->getValues();
...
}
když použiji vlastní formuláře, bude to takto:
function handleFormSubmit()
{
$values = $this->presenter->request->post;
...
}
Což je velmi podobné. Každopádně mi to připadá mnohem jednodušší,
než vypisovat všechny prvky jako parametry metody
handleFormSubmit()
, navíc s odchylkou pro
<input type="image">
. Není vhodnější třeba tohle?
function handleFormSubmit()
{
extract($this->presenter->request->post);
...
}
- A.
- Člen | 87
Ok, ok. Jsou to rozumne argumenty. Ono to samozrejme neni vhodne pro nejake vetsi zpracovani formularu, ale spis pro takove „formularky“.
Jeste dotaz, kdyz se ted budu snazit delat aplikaci, ktera funguje jak ajaxove, tak neajaxove, parametry se ted v pripade ajaxoveho volani napriklad odeslani nejakeho formulare z POST vytahnou, v normalnim pripade ne. Tzn je nutne delat neco typu:
function handleAction($foo, $bar)
{
if ($this->presenter->isAjax()) {
doAction($foo, $bar);
...
} else {
$foo = $this->presenter->request->post['foo'];
$bar = $this->presenter->request->post['bar'];
doAction($foo, $bar);
..
}
}
Nejak se mi nelibi to, $foo a $bar jsou definovany v jednom pripade a v druhem ne-e. Prijde mi to take matouci.
- Jod
- Člen | 701
Tak to nemusíš používať :D :D :D
Inak mám menší problém. Mám control Filter a spravil som tam
autocomplete, lenže sa mi ten post text parameter nepredáva cez
public function handleAutoComplete($text)
. Zato
$this->presenter->request->post['text']
ide v pohode.
(rev.158)
Editoval Jod (17. 12. 2008 13:03)
- David Grudl
- Nette Core | 8227
Jod napsal(a):
Inak mám menší problém. Mám control Filter a spravil som tam autocomplete, lenže sa mi ten post text parameter nepredáva cez
public function handleAutoComplete($text)
. Zato$this->presenter->request->post['text']
ide v pohode. (rev.158)
Ten parameter v POST musí být plně kvalifikován, tedy například
filter-text
. Je to vlastně
$control->getUniqueId() . self::NAME_SEPARATOR . 'text'
. Asi
přidám metodu getParamId(), která bude tyto řetězce vracet.