[2008-12-12] Podpora JavaScriptu a AJAX

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 7769
+
0
-

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

  1. změna: AjaxDriver se stal benevolentnějším a z interface zmizeli metody updateSnippet nebo fireEvent. 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.

  1. novinka: při AJAXovém požadavku se metodám handleXYZ(…) předávají i POST parametry
  2. 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().
  3. 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).
  4. 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');
  1. 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 | 113
+
0
-

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

Honza Marek
Člen | 1664
+
0
-

To bude asi kvůli cachování. Zkus smazat cache.

LuKo
Člen | 113
+
0
-

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

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

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

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

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().

A.
Člen | 87
+
0
-

David Grudl napsal(a):

  1. novinka: při AJAXovém požadavku se metodám handleXYZ(…) předávají i POST parametry

Ahoj,

nedival jsem se jeste na zpusob implementace. Jaky je duvod, aby to bylo jen pro AJAXove pozadavky? Nebo je to tak jen „zatim“?

David Grudl
Nette Core | 7769
+
0
-

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

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.

Jod
Člen | 701
+
0
-

To sa aj dá, nevyužívať Nette/Forms? :D

A celý post sa do premennej predať nejde? Potom by si to mal rovnako ako vo Forms v $params všetky hodoty. To máš rovnako ako u forms potom.

David Grudl
Nette Core | 7769
+
0
-

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

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

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)

A.
Člen | 87
+
0
-

Jod napsal(a):
Tak to nemusíš používať :D :D :D

To me nejak nenapadlo.

David Grudl
Nette Core | 7769
+
0
-

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.

Jod
Člen | 701
+
0
-

Aha, jasné dík