AJAX
Většina moderních aplikací již dnes používá AJAX. Díky Nette je „zajaxování“ celé aplikace velmi snadné a téměř nebudete muset měnit původní kód. Nyní si ukážeme, jak na to.
Největší síla AJAXu v Nette spočívá v použití tzv. snippetů. Tyto speciálně označené bloky kódu se pak při AJAXových požadavcích přenášejí ke klientovi, který si podle nich aktualizuje stránku. Takto je možné posílat jen upravené části stránky. Největší výhodou je, že vše je prakticky bez práce.
Příprava
Ještě než začneme, musíme si připravit některé pomocné skripty. Nette bohužel zatím nemá žádný oficiální JavaScript na podporu AJAXu na straně klienta, proto musíme buď sáhnout po již existujících skriptech do doplňků, nebo si napsat vlastní. S využitím jQuery je to však snadné:
Uvedený skript v sobě nemá ošetření chyb a neumožňuje rozlišit, jakým tlačítkem byl formulář odeslán. Pro naše účely však plně postačuje. Ve skutečné aplikaci raději sáhněte po řešení z doplňků.
jQuery.ajaxSetup({
cache: false,
dataType: 'json',
success: function (payload) {
if (payload.snippets) {
for (var i in payload.snippets) {
$('#' + i).html(payload.snippets[i]);
}
}
}
});
// odesílání odkazů
$('a.ajax').live('click', function (event) {
event.preventDefault();
$.get(this.href);
});
// odesílání formulářů
$('form.ajax').live('submit', function (event) {
event.preventDefault();
$.post(this.action, $(this).serialize());
});
Tento skript jednoduše nechá všechny odkazy a formuláře s třídou
ajax odeslat pomocí AJAXu. Díky metodě live se
událost vykoná i v případě odkazů a formulářů, které budou
vytvořeny později AJAXem.
Skript uložíme například do www/js/ajax.js. Poté jej
nalinkujeme do hlavičky stránky v @layout.latte. Musíme jej
však vložit až za načtení jQuery, takže v hlavičce budou následující
skripty:
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6/jquery.min.js"></script>
<script type="text/javascript" src="{$basePath}/js/netteForms.js"></script>
<script type="text/javascript" src="{$basePath}/js/ajax.js"></script>
Nyní už můžeme AJAX začít používat.
Komponenta TaskList
Začneme s komponentou s výpisem úkolů. Celý obsah šablony obalíme do
makra {snippet} a odkazu na splnění úkolu přidáme třídu
ajax:
{snippet}
<table class="tasks">
<thead>
<tr>
<th class="created"> </th>
<th class="tasklist" n:if="$displayTaskList">Seznam</th>
<th class="text">Úkol</th>
<th class="user" n:if="$displayUser">Přiřazeno</th>
<th class="action"> </th>
</tr>
</thead>
<tbody>
{foreach $tasks as $task}
<tr n:class="$iterator->isOdd() ? odd : even, $task->done ? done">
<td class="created">{$task->created|date:'j. n. Y'}</td>
<td class="tasklist" n:if="$displayTaskList">{$task->tasklist->title}</td>
<td class="text">{$task->text}</td>
<td class="user" n:if="$displayUser">{$task->user->name}</td>
<td class="action"><a n:if="!$task->done" n:href="markDone! $task->id" class="icon tick ajax">hotovo</a></td>
</tr>
{/foreach}
</tbody>
</table>
{/snippet}
Poté jen upravíme signál markDone tak, aby při AJAXových
požadavcích neprováděl přesměrování, ale nechal zaslat opravenou
tabulku:
public function handleMarkDone($taskId)
{
$this->model->getTasks()->where(array('id' => $taskId))->update(array('done' => 1));
if (!$this->presenter->isAjax()) {
$this->presenter->redirect('this');
} else {
$this->invalidateControl();
}
}
Metoda isAjax() třídy Presenter zjišťuje, zda
se jedná o AJAXový požadavek. Metodou invalidateControl() pak
necháme celou komponentnu (a tedy všechny snippety v ní) zneplatnit a pak se
pošlou klientovi. Pokud metodě uvedeme jako parametr název snippetu, bude
zneplatněn pouze uvedený snippet.
A to je vše. Pokud máme správně zavedený dříve uvedený script a správně upravenou šablonu, bude nyní označování hotových úkolů probíhat s využitím AJAXu.
Přidávání úkolů
Nyní by bylo vhodné upravit TaskPresenter tak, aby
i přidávání úkolů probíhalo s využitím AJAXu. Stačí jen formuláři
přidat třídu ajax a pokud chceme formulář nechat jednoduše po
odeslání vymazat, obalíme jej také do snippetu:
{var $title = $taskList->title}
{block content}
<h1>{$taskList->title}</h1>
{snippet form}
<fieldset>
<legend>Přidat úkol</legend>
{form taskForm class => ajax}
<div class="task-form">
{control $form errors}
{label text /} {input text size => 30, autofocus => true} {label userId /} {input userId} {input create}
</div>
{/form}
</fieldset>
{/snippet}
{control taskList}
{/block}
V metodě, která zpracovává odeslaný formulář, provedeme podobnou
úpravu, jako u signálu v komponentě TaskList:
public function taskFormSubmitted(Form $form)
{
$this->model->getTasks()->insert(array(
'text' => $form->values->text,
'user_id' => $form->values->userId,
'created' => new DateTime(),
'tasklist_id' => $this->taskList->id
));
$this->flashMessage('Úkol přidán.', 'success');
if (!$this->isAjax()) {
$this->redirect('this');
} else {
$form->setValues(array(), TRUE);
$this->invalidateControl('form');
$this['taskList']->invalidateControl();
}
}
Tentokrát však invalidujeme konkrétní snippet (form) a celou
komponentu taskList. Také vymazáváme odeslané hodnoty ve
formuláři pomocí volání setValues. Prvním parametrem této
metody je seznam hodnot, zde prázdné pole, druhý říká, že chceme
u neuvedených prvků hodnoty vymazat. Pokud bychom tento parametr neuvedli,
Nette by jen přepisovalo hodnoty prvků, které jsme uvedli v poli.
To je opět vše. Nyní by i přidivání úkolů mělo fungovat
s využitím AJAXu. Pokud to zkusíme, zjistíme, že by možná bylo
vhodnější nemazat hodnoty v celém formuláři, ale ponechat v selectu
hodnotu vybraného uživatele. To zajistíme jednoduchým upravením volání
$form->setValues():
$form->setValues(array('userId' => $form->values->userId), TRUE);
Flash zprávičky
Všechny komponenty se sice aktualizují, ale nezobrazují se nám flash zprávičky. Řešení je opět velmi jednoduché: stačí flash zprávičky obalit do snippetu a ten ve vhodný okamžik invalidovat.
@layout.latte:
{snippet flashMessages}
<div n:foreach="$flashes as $flash" class="flash {$flash->type}">{$flash->message}</div>
{/snippet}
Invalidaci můžeme provést v metodě beforeRender() v
BasePresenteru:
public function beforeRender()
{
$this->template->taskLists = $this->model->getTaskLists()->order('title ASC');
if ($this->isAjax()) {
$this->invalidateControl('flashMessages');
}
}
Opět hotovo.
Závěr
Jak vidíte, je práce s AJAXem v Nette opravdu velmi snadná. Jedním chytrým skriptem a několika málo změnami v kódu jsme převedli klíčové prvky naší aplikace do AJAXu.
