AJAX & Snippetek
A modern webes alkalmazások korában, ahol a funkciók gyakran a szerver és a böngésző között helyezkednek el, az AJAX elengedhetetlen összekötő elem. Milyen lehetőségeket kínál a Nette Framework ezen a területen?
- A sablon részeinek, az ún. snippeteknek a küldése.
- változók átadása a PHP és a JavaScript között
- AJAX-kérések hibakeresésére szolgáló eszközök
AJAX kérés
Az AJAX-kérés alapvetően nem különbözik a klasszikus HTTP-kéréstől. Egy bemutatót hívunk meg bizonyos paraméterekkel. A bemutatótól függ, hogyan válaszol a kérésre – adhat vissza adatokat JSON formátumban, küldhet egy HTML kódrészt, egy XML-dokumentumot stb.
A böngésző oldalán AJAX-kérést kezdeményezünk a fetch()
funkcióval:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// a válasz feldolgozása
});
A szerveroldalon az AJAX-kérést a HTTP-kérést kapszulázó
szolgáltatás $httpRequest->isAjax()
módszere ismeri fel. Ez a X-Requested-With
HTTP fejlécet
használja, ezért elengedhetetlen a küldése. A prezenteren belül a $this->isAjax()
metódust
használhatja.
Ha JSON formátumban szeretne adatokat küldeni, használja a sendJson()
módszert. A metódus a
prezenter tevékenységét is befejezi.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Ha azt tervezi, hogy egy speciális, AJAX-hoz tervezett sablonnal válaszol, akkor azt a következőképpen teheti meg:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
//...
}
Snippets
A Nette által kínált leghatékonyabb eszköz a szerver és az ügyfél összekapcsolására a snippetek. Segítségükkel egy közönséges alkalmazásból minimális erőfeszítéssel és néhány sornyi kóddal AJAX-alkalmazássá alakítható. A Fifteen példája bemutatja, hogyan működik mindez, és a kódja megtalálható a GitHubon.
A snippetek, vagyis a kivágások lehetővé teszik, hogy az oldalnak csak egyes részeit frissítsük, ahelyett, hogy az egész oldalt újratöltenénk. Ez gyorsabb és hatékonyabb, és kényelmesebb felhasználói élményt is nyújt. A snippetek a Ruby on Rails Hotwire vagy a Symfony UX Turbo programjára emlékeztethetnek. Érdekes módon a Nette 14 évvel korábban vezette be a snippeteket.
Hogyan működnek a snippetek? Az oldal első betöltésekor (egy nem-AJAX kérés) a teljes oldal betöltődik, beleértve az összes snippetet is. Amikor a felhasználó interakcióba lép az oldallal (pl. rákattint egy gombra, elküld egy űrlapot stb.), a teljes oldal betöltése helyett AJAX-kérés történik. A prezenterben lévő kód végzi el a műveletet, és dönti el, hogy mely snippetek igényelnek frissítést. A Nette rendereli ezeket a részleteket, és egy JSON tömb formájában elküldi őket. A böngészőben lévő kezelő kód ezután visszahelyezi a kapott részleteket az oldalba. Ezért csak a módosított snippetek kódja kerül átvitelre, ami a teljes oldaltartalom átviteléhez képest sávszélességet takarít meg és gyorsítja a betöltést.
Naja
A snippetek böngészőoldali kezeléséhez a Naja könyvtárat használjuk. Telepítse node.js csomagként (olyan alkalmazásokkal használható, mint a Webpack, Rollup, Vite, Parcel és mások):
npm install naja
… vagy illessze be közvetlenül az oldal sablonjába:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Először inicializálni kell a könyvtárat:
naja.initialize();
Ahhoz, hogy egy közönséges linket (jelet) vagy űrlapküldést AJAX-kérelemmé tegyen, egyszerűen jelölje meg az adott
linket, űrlapot vagy gombot a ajax
osztállyal:
<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>
Snippetek újrarajzolása
A Control osztály minden objektuma (beleértve magát a
Presentert is) nyilvántartást vezet arról, hogy történtek-e olyan változások, amelyek miatt újra kell rajzolni. Erre a
célra a redrawControl()
metódus szolgál.
public function handleLogin(string $user): void
{
// a bejelentkezés után újra kell rajzolni a vonatkozó részt
$this->redrawControl();
//...
}
A Nette lehetővé teszi annak finomabb ellenőrzését is, hogy mit kell átrajzolni. A fent említett módszer argumentumként elfogadhatja a snippet nevét. Így lehetséges a sablonrész szintjén érvényteleníteni (azaz: újrarajzolást kikényszeríteni). Ha a teljes komponens érvénytelenítése megtörténik, akkor annak minden egyes részlete is újrarajzolásra kerül:
// érvényteleníti a "fejléc"-részletet.
$this->redrawControl('header');
Szemelvények a Latte-ban
A snippetek használata a Latte-ban rendkívül egyszerű. Ha a sablon egy részét snippetként szeretné definiálni,
egyszerűen csomagolja be a {snippet}
és {/snippet}
címkékbe:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
A snippet létrehoz egy elemet <div>
a HTML-oldalon egy speciálisan generált id
címmel.
A snippet újrarajzolásakor ennek az elemnek a tartalma frissül. Ezért az oldal kezdeti megjelenítésekor az összes
snippetet is meg kell jeleníteni, még akkor is, ha azok kezdetben üresek lehetnek.
A snippet létrehozható egy másik elemmel is, mint a <div>
n:attribútummal:
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Snippet területek
A snippet nevek kifejezések is lehetnek:
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
item-0
, item-1
, stb. Ha közvetlenül érvénytelenítenénk egy dinamikus snippetet (pl.
item-1
), akkor semmi sem rajzolódna újra. A snippetek ugyanis valódi részletként működnek, és csak maguk a
snippetek kerülnek közvetlenül megjelenítésre. A sablonban azonban technikailag nincs item-1
nevű snippet. Ez
csak a snippet környező kódjának, jelen esetben a foreach ciklusnak a végrehajtásakor jelenik meg. Ezért a sablon azon
részét, amelyet végre kell hajtani, a {snippetArea}
címkével jelöljük:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
És újrarajzoljuk mind az egyes snippetet, mind a teljes átfogó területet:
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Lényeges annak biztosítása is, hogy a $items
tömb csak azokat az elemeket tartalmazza, amelyeket újra kell
rajzolni.
Ha a {include}
tag segítségével egy másik sablont illesztünk be a fő sablonba, amely szeleteket tartalmaz,
akkor a bevont sablont ismét be kell csomagolni a snippetArea
címkébe, és a szeletet és a területet együttesen
érvényteleníteni kell:
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* included.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Snippetek az összetevőkben
Komponenseken belül is létrehozhat snippeteket, amelyeket a
Nette automatikusan átrajzol. Van azonban egy sajátos korlátozás: a snippetek újrarajzolásához paraméterek nélkül hívja
meg a render()
metódust. Így a paraméterek átadása a sablonban nem fog működni:
OK
{control productGrid}
will not work:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Felhasználói adatok küldése
A snippetek mellett bármilyen további adatot is küldhet az ügyfélnek. Egyszerűen írja őket a payload
objektumba:
public function actionDelete(int $id): void
{
//...
if ($this->isAjax()) {
$this->payload->message = 'Success';
}
}
Küldési paraméterek
Amikor AJAX-kérésen keresztül paramétereket küldünk a komponensnek, legyenek azok jelparaméterek vagy állandó
paraméterek, meg kell adnunk a globális nevüket, amely tartalmazza a komponens nevét is. A paraméter teljes nevét a
getParameterId()
metódus adja vissza.
let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
A komponensben lévő megfelelő paraméterekkel rendelkező handle metódust:
public function handleFoo(int $bar): void
{
}