AJAX in sličice
Sodobne spletne aplikacije danes tečejo pol na strežniku in pol v brskalniku. AJAX je ključni povezovalni dejavnik. Kakšno podporo ponuja ogrodje Nette?
- pošiljanje fragmentov predlog (tako imenovanih snippets)
- posredovanje spremenljivk med PHP in JavaScriptom
- razhroščevanje aplikacij AJAX
Zahteva AJAX
Zahteva AJAX se ne razlikuje od klasične zahteve – predvajalnik se pokliče z določenim pogledom in parametri. Prav tako je od predstavnika odvisno, kako se bo nanjo odzval: uporabi lahko svojo rutino, ki vrne fragment kode HTML (fragment HTML), dokument XML, objekt JSON ali kodo JavaScript.
Na strani strežnika je mogoče zahtevo AJAX zaznati s storitveno metodo, ki enkapsulira zahtevo HTTP $httpRequest->isAjax()
(zazna na podlagi
glave HTTP X-Requested-With
). Znotraj predstavnika je na voljo bližnjica v obliki metode
$this->isAjax()
.
Na voljo je vnaprej obdelan objekt, imenovan payload
, namenjen pošiljanju podatkov brskalniku
v obliki JSON.
public function actionDelete(int $id): void
{
if ($this->isAjax()) {
$this->payload->message = 'Success';
}
// ...
}
Za popoln nadzor nad izpisom JSON uporabite metodo sendJson
v svojem predstavitvenem programu. S tem se takoj
zaključi predstavitveni program, vi pa boste opravili brez predloge:
$this->sendJson(['key' => 'value', /* ... */]);
Če želimo poslati HTML, lahko nastavimo posebno predlogo za zahteve AJAX:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
// ...
}
Naja
Knjižnica Naja se uporablja za obdelavo zahtevkov AJAX na strani brskalnika. Namestite jo kot paket node.js (za uporabo s programi Webpack, Rollup, Vite, Parcel in drugimi):
npm install naja
…ali pa jo vstavite neposredno v predlogo strani:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Če želite ustvariti zahtevo AJAX iz običajne povezave (signala) ali oddaje obrazca, preprosto označite ustrezno povezavo,
obrazec ali gumb z razredom ajax
:
<a n:href="go!" class="ajax">Go</a>
<form n:name="form" class="ajax">
<input n:name="submit">
</form>
or
<form n:name="form">
<input n:name="submit" class="ajax">
</form>
.
Obstaja veliko močnejše orodje vgrajene podpore AJAX – snippets. Z njihovo uporabo je mogoče navadno aplikacijo spremeniti v aplikacijo AJAX z uporabo le nekaj vrstic kode. Kako vse to deluje, je prikazano v primeru Fifteen, katerega koda je na voljo tudi v sestavi ali na GitHubu.
Snippets delujejo tako, da se med začetno zahtevo (tj. zahtevo brez AJAX-a) prenese celotna stran, nato pa se pri vsaki podpovprašitvi AJAX-a (zahteva istega pogleda istega
predstavnika) v prej omenjeno shrambo payload
prenese le koda spremenjenih delov.
Snippets vas morda spominjajo na Hotwire za Ruby on Rails ali Symfony UX Turbo, vendar jih je Nette zasnoval že štirinajst let prej.
Neveljavnost nizov (Snippets)
Vsak potomec razreda Control (kar je tudi Presenter) si lahko
zapomni, ali je med zahtevo prišlo do kakšnih sprememb, zaradi katerih je treba ponovno prikazati. Za to obstaja par metod:
redrawControl()
in isControlInvalid()
. Primer:
public function handleLogin(string $user): void
{
// Objekt se mora ponovno prikazati, ko se uporabnik prijavi
$this->redrawControl();
// ...
}
Nette pa ponuja še natančnejšo ločljivost kot celotne komponente. Navedeni metodi kot neobvezni parameter sprejmeta ime tako imenovanega “snippeta”. “Snippet” je v bistvu element v vaši predlogi, ki je v ta namen označen z oznako Latte, več o tem pozneje. Tako je mogoče od komponente zahtevati, da na novo nariše samo delčke svoje predloge. Če je celotna komponenta razveljavljena, se ponovno izrišejo vse njene sličice. Komponenta je “neveljavna” tudi, če je neveljavna katera koli njena podkomponenta.
$this->isControlInvalid(); // -> false
$this->redrawControl('header'); // razveljavi odlomek z imenom 'header'
$this->isControlInvalid('header'); // -> true
$this->isControlInvalid('footer'); // -> false
$this->isControlInvalid(); // -> true, vsaj en delček je neveljaven
$this->redrawControl(); // razveljavi celotno komponento, vsak odlomek
$this->isControlInvalid('footer'); // -> true
Komponenta, ki prejme signal, je samodejno označena za ponovno izrisovanje.
Zahvaljujoč ponovnemu izrisu snippetov natančno vemo, katere dele katerih elementov je treba ponovno izrisati.
Oznaka {snippet} … {/snippet}
Prikazovanje strani poteka zelo podobno kot pri običajni zahtevi: naložijo se iste predloge itd. Bistveno pa je, da se izpustijo deli, ki naj ne bi dosegli izpisa; drugi deli se povežejo z identifikatorjem in pošljejo uporabniku v obliki, razumljivi za obdelovalnik JavaScript.
Sintaksa
Če je v predlogi kontrolni element ali izsek, ga moramo oviti z uporabo oznake {snippet} ... {/snippet}
pair – ta bo poskrbela, da bo izrisani izsek “izrezan” in poslan brskalniku. Prav tako ga bo zaprla v pomožno vrstico
<div>
(možno je uporabiti tudi drugačno oznako). V naslednjem primeru je opredeljen izsek z imenom
header
. Prav tako lahko predstavlja predlogo komponente:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
Snippet tipa, ki ni <div>
ali izsek z dodatnimi atributi HTML se doseže z uporabo različice
atributa:
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Dinamični utrinki
V programu Nette lahko na podlagi parametra v času izvajanja določite tudi dinamično ime snippetov. To je najbolj primerno za različne sezname, kjer moramo spremeniti samo eno vrstico, vendar ne želimo skupaj z njo prenesti celotnega seznama. Primer tega je:
<ul n:snippet="itemsContainer">
{foreach $list as $id => $item}
<li n:snippet="item-$id">{$item} <a class="ajax" n:href="update! $id">update</a></li>
{/foreach}
</ul>
Obstaja en statični niz z imenom itemsContainer
, ki vsebuje več dinamičnih nizov: item-0
,
item-1
in tako naprej.
Dinamičnega utrinka ne morete narisati neposredno (ponovno narisanje item-1
nima učinka), temveč morate
narisati njegov nadrejeni utrinek (v tem primeru itemsContainer
). To povzroči, da se izvede koda nadrejenega
odlomka, vendar se nato brskalniku pošljejo samo njegovi pododlomki. Če želite poslati samo enega od podnapisov, morate
spremeniti vnos za nadrejeni odlomek, da ne bo ustvaril drugih podnapisov.
V zgornjem primeru morate poskrbeti, da bo za zahtevo AJAX v polje $list
dodan samo en element, zato bo zanka
foreach
izpisala samo en dinamični odlomek.
class HomePresenter extends Nette\Application\UI\Presenter
{
/**
* This method returns data for the list.
* Usually this would just request the data from a model.
* For the purpose of this example, the data is hard-coded.
*/
private function getTheWholeList(): array
{
return [
'First',
'Second',
'Third',
];
}
public function renderDefault(): void
{
if (!isset($this->template->list)) {
$this->template->list = $this->getTheWholeList();
}
}
public function handleUpdate(int $id): void
{
$this->template->list = $this->isAjax()
? []
: $this->getTheWholeList();
$this->template->list[$id] = 'Updated item';
$this->redrawControl('itemsContainer');
}
}
Utrinki v vključeni predlogi
Lahko se zgodi, da je snippet v predlogi, ki je vključena iz druge predloge. V tem primeru moramo vključitveno kodo
v drugi predlogi oviti z oznako snippetArea
, nato pa ponovno narišemo območje snippetArea in dejanski
snippet.
Oznaka snippetArea
zagotavlja, da se koda v njej izvrši, vendar se brskalniku pošlje le dejanski odlomek iz
vključene predloge.
{* parent.latte *}
{snippetArea wrapper}
{include 'child.latte'}
{/snippetArea}
{* child.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('wrapper');
$this->redrawControl('item');
Kombinirate jo lahko tudi z dinamičnimi utrinki.
Dodajanje in brisanje
Če na seznam dodate nov element in razveljavite itemsContainer
, zahteva AJAX vrne odlomke, vključno z novim
elementom, vendar ga obdelovalnik javascript ne bo mogel prikazati. Razlog za to je, da ni elementa HTML z novo
ustvarjenim ID.
V tem primeru je najpreprostejši način, da celoten seznam zavijete v še en izsek in ga razveljavite:
{snippet wholeList}
<ul n:snippet="itemsContainer">
{foreach $list as $id => $item}
<li n:snippet="item-$id">{$item} <a class="ajax" n:href="update! $id">update</a></li>
{/foreach}
</ul>
{/snippet}
<a class="ajax" n:href="add!">Add</a>
public function handleAdd(): void
{
$this->template->list = $this->getTheWholeList();
$this->template->list[] = 'New one';
$this->redrawControl('wholeList');
}
Enako velja za brisanje elementa. Možno bi bilo poslati prazen snippet, vendar so običajno seznami lahko oštevilčeni in bi bilo zapleteno izvesti brisanje enega elementa in nalaganje drugega (ki je bil prej na drugi strani oštevilčenega seznama).
Pošiljanje parametrov sestavini
Ko komponenti prek zahteve AJAX pošiljamo parametre, bodisi signalne bodisi trajne, moramo navesti njihovo globalno ime, ki
vsebuje tudi ime komponente. Polno ime parametra vrne metoda getParameterId()
.
$.getJSON(
{link changeCountBasket!},
{
{$control->getParameterId('id')}: id,
{$control->getParameterId('count')}: count
}
});
In obdela metodo z ustreznimi parametri v komponenti.
public function handleChangeCountBasket(int $id, int $count): void
{
}