AJAX & Snippetek
A modern webalkalmazások korában, ahol a funkcionalitás gyakran megoszlik a szerver és a böngésző között, az AJAX elengedhetetlen összekötő elem. Milyen lehetőségeket kínál nekünk a Nette Framework ezen a területen?
- sablonrészek, úgynevezett snippetek küldése
- változók átadása PHP és JavaScript között
- eszközök AJAX kérések debuggolásához
AJAX kérés
Az AJAX kérés alapvetően nem különbözik a klasszikus HTTP kéréstől. Meghív egy presentert bizonyos paraméterekkel. És a presenteren múlik, hogyan reagál a kérésre – visszaadhat adatokat JSON formátumban, küldhet HTML kód egy részét, XML dokumentumot stb.
A böngésző oldalán az AJAX kérést a fetch()
függvénnyel inicializáljuk:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// válasz feldolgozása
});
Szerveroldalon az AJAX kérést a HTTP kérést becsomagoló szolgáltatás
$httpRequest->isAjax()
metódusával ismerjük fel. Az észleléshez a X-Requested-With
HTTP
fejlécet használja, ezért fontos elküldeni. A presenterben a $this->isAjax()
metódus használható.
Ha adatokat szeretne küldeni JSON formátumban, használja a sendJson()
metódust. A metódus
szintén befejezi a presenter működését.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Ha egy speciális, AJAX-hoz szánt sablonnal tervez válaszolni, a következőképpen teheti meg:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
// ...
}
Snippetek
A Nette által kínált legerősebb eszköz a szerver és a kliens összekapcsolására a snippetek. Ezeknek köszönhetően egy átlagos alkalmazást minimális erőfeszítéssel és néhány sor kóddal AJAX-alapúvá alakíthat. Hogy mindez hogyan működik, azt a Fifteen példa demonstrálja, amelynek kódját a GitHubon találja meg.
A snippetek, vagyis kódrészletek, lehetővé teszik az oldal csak bizonyos részeinek frissítését, ahelyett, hogy az egész oldalt újra kellene tölteni. Ez nemcsak gyorsabb és hatékonyabb, hanem kényelmesebb felhasználói élményt is nyújt. A snippetek emlékeztethetnek a Hotwire for Ruby on Rails vagy a Symfony UX Turbo megoldásokra. Érdekesség, hogy a Nette már 14 évvel korábban bemutatta a snippeteket.
Hogyan működnek a snippetek? Az oldal első betöltésekor (nem AJAX kérés esetén) az egész oldal betöltődik, beleértve az összes snippetet is. Amikor a felhasználó interakcióba lép az oldallal (pl. gombra kattint, űrlapot küld stb.), az egész oldal betöltése helyett egy AJAX kérés indul. A presenterben lévő kód végrehajtja a műveletet, és eldönti, mely snippeteket kell frissíteni. A Nette ezeket a snippeteket rendereli és JSON formátumú tömbként küldi el. A böngészőben lévő kezelő kód a kapott snippeteket visszailleszti az oldalba. Így csak a megváltozott snippetek kódja kerül átvitelre, ami sávszélességet takarít meg és gyorsítja a betöltést az egész oldal tartalmának átvitelével szemben.
Naja
A snippetek böngészőoldali kezelésére a Naja könyvtár szolgál. Ezt telepítse node.js csomagként (Webpack, Rollup, Vite, Parcel és más alkalmazásokkal való használathoz):
npm install naja
…vagy közvetlenül illessze be az oldal sablonjába:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Először is inicializálni kell a könyvtárat:
naja.initialize();
Ahhoz, hogy egy egyszerű linkből (signal) vagy űrlapküldésből AJAX kérés legyen, elegendő a megfelelő linket,
űrlapot vagy gombot ajax
osztállyal megjelölni:
<a n:href="go!" class="ajax">Go</a>
<form n:name="form" class="ajax">
<input n:name="submit">
</form>
vagy
<form n:name="form">
<input n:name="submit" class="ajax">
</form>
Snippetek újrarajzolása
Minden Control osztályú objektum (beleértve magát a
Presentert is) nyilvántartja, hogy történt-e olyan változás, amely az újrarajzolását igényli. Erre szolgál a
redrawControl()
metódus:
public function handleLogin(string $user): void
{
// bejelentkezés után újra kell rajzolni a releváns részt
$this->redrawControl();
// ...
}
A Nette még finomabb vezérlést tesz lehetővé afölött, hogy mit kell újrarajzolni. Az említett metódus ugyanis argumentumként fogadhatja a snippet nevét. Így lehet invalidálni (értsd: újrarajzolást kényszeríteni) a sablon részei szintjén. Ha az egész komponenst invalidáljuk, akkor annak minden snippetje is újrarajzolódik:
// invalidálja a 'header' snippetet
$this->redrawControl('header');
Snippetek a Latte-ban
A snippetek használata a Latte-ban rendkívül egyszerű. Ha egy sablonrészt snippetként szeretne definiálni, egyszerűen
csomagolja be {snippet}
és {/snippet}
tag-ekkel:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
A snippet létrehoz egy <div>
elemet a HTML oldalon egy speciális, generált id
-val.
A snippet újrarajzolásakor ennek az elemnek a tartalma frissül. Ezért szükséges, hogy az oldal első renderelésekor az
összes snippet is renderelődjön, még akkor is, ha esetleg kezdetben üresek.
Létrehozhat snippetet <div>
-től eltérő elemmel is egy n:attribútum segítségével:
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Snippet területek
A snippetek nevei kifejezések is lehetnek:
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
Így több snippet jön létre: item-0
, item-1
stb. Ha közvetlenül invalidálnánk egy dinamikus
snippetet (például item-1
), semmi sem rajzolódna újra. Ennek oka az, hogy a snippetek valóban
kódrészletekként működnek, és csak közvetlenül önmaguk renderelődnek. Azonban a sablonban valójában nincs
item-1
nevű snippet. Az csak a snippet körüli kód, azaz a foreach ciklus végrehajtásakor jön létre. Ezért
megjelöljük a sablon azon részét, amelyet végre kell hajtani a {snippetArea}
tag segítségével:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
És újrarajzoltatjuk mind a snippetet magát, mind a teljes szülő területet:
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Ugyanakkor célszerű biztosítani, hogy az $items
tömb csak azokat az elemeket tartalmazza, amelyeket újra kell
rajzolni.
Ha a sablonba a {include}
tag segítségével egy másik sablont illesztünk be, amely snippeteket tartalmaz, a
sablon beillesztését ismét snippetArea
-ba kell foglalni, és azt a snippettel együtt kell invalidálni:
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* included.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Snippetek a komponensekben
Snippeteket komponensekben is létrehozhat, és a Nette
automatikusan újrarajzolja őket. De van egy korlátozás: a snippetek újrarajzolásához a render()
metódust
paraméterek nélkül hívja meg. Tehát a paraméterek átadása a sablonban nem fog működni:
OK
{control productGrid}
nem fog működni:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Felhasználói adatok küldése
A snippetekkel együtt tetszőleges további adatokat is küldhet a kliensnek. Egyszerűen írja be őket a
payload
objektumba:
public function actionDelete(int $id): void
{
// ...
if ($this->isAjax()) {
$this->payload->message = 'Sikeres';
}
}
Paraméterek átadása
Ha egy komponensnek AJAX kéréssel paramétereket küldünk, legyenek azok signal paraméterek vagy perzisztens paraméterek,
a kérésnél 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'},
})
És a handle metódus a megfelelő paraméterekkel a komponensben:
public function handleFoo(int $bar): void
{
}