Nette Documentation Preview

syntax
AJAX & Snippetek
****************

<div class=perex>

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

</div>


AJAX kérés .[#toc-ajax-request]
===============================

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:

```js
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ó |http:request] 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()` |presenters#Sending a response] módszert. A metódus a prezenter tevékenységét is befejezi.

```php
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:

```php
public function handleClick($param): void
{
	if ($this->isAjax()) {
		$this->template->setFile('path/to/ajax.latte');
	}
	//...
}
```


Snippets .[#toc-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 |https://github.com/nette-examples/fifteen].

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 .[#toc-naja]
-----------------

A snippetek böngészőoldali kezeléséhez a [Naja könyvtárat |https://naja.js.org] használjuk. [Telepítse |https://naja.js.org/#/guide/01-install-setup-naja] node.js csomagként (olyan alkalmazásokkal használható, mint a Webpack, Rollup, Vite, Parcel és mások):

```shell
npm install naja
```

... vagy illessze be közvetlenül az oldal sablonjába:

```html
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
```

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:

```html
<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 .[#toc-redrawing-snippets]
--------------------------------------------------

A [Control |components] 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.

```php
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:

```php
// érvényteleníti a "fejléc"-részletet.
$this->redrawControl('header');
```


Szemelvények a Latte-ban .[#toc-snippets-in-latte]
--------------------------------------------------

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:

```latte
{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:

```latte
<article n:snippet="header" class="foo bar">
	<h1>Hello ... </h1>
</article>
```


Snippet területek .[#toc-snippet-areas]
---------------------------------------

A snippet nevek kifejezések is lehetnek:

```latte
{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:

```latte
<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:

```php
$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:

```latte
{snippetArea include}
	{include 'included.latte'}
{/snippetArea}
```

```latte
{* included.latte *}
{snippet item}
	...
{/snippet}
```

```php
$this->redrawControl('include');
$this->redrawControl('item');
```


Snippetek az összetevőkben .[#toc-snippets-in-components]
---------------------------------------------------------

 [Komponenseken |components] 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:

```latte
OK
{control productGrid}

will not work:
{control productGrid $arg, $arg}
{control productGrid:paginator}
```


Felhasználói adatok küldése .[#toc-sending-user-data]
-----------------------------------------------------

A snippetek mellett bármilyen további adatot is küldhet az ügyfélnek. Egyszerűen írja őket a `payload` objektumba:

```php
public function actionDelete(int $id): void
{
	//...
	if ($this->isAjax()) {
		$this->payload->message = 'Success';
	}
}
```


Küldési paraméterek .[#toc-sending-parameters]
==============================================

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.

```js
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:

```php
public function handleFoo(int $bar): void
{
}
```

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>

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

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
{
}