Nette Documentation Preview

syntax
AJAX & Snippets
***************

<div class=perex>

În era aplicațiilor web moderne, în care funcționalitatea se întinde adesea între server și browser, AJAX este un element de legătură esențial. Ce opțiuni oferă Nette Framework în acest domeniu?
- trimiterea unor părți din șablon, așa-numitele snippet-uri
- transmiterea de variabile între PHP și JavaScript
- instrumente de depanare a cererilor AJAX

</div>


Cerere AJAX .[#toc-ajax-request]
================================

O cerere AJAX nu diferă în mod fundamental de o cerere HTTP clasică. Un prezentator este apelat cu parametri specifici. Depinde de prezentator cum să răspundă la cerere - acesta poate returna date în format JSON, poate trimite o parte din codul HTML, un document XML etc.

Pe partea de browser, inițiem o cerere AJAX utilizând funcția `fetch()`:

```js
fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
	// prelucrarea răspunsului
});
```

Pe partea serverului, o cerere AJAX este recunoscută de metoda `$httpRequest->isAjax()` a serviciului [care încapsulează cererea HTTP |http:request]. Aceasta utilizează antetul HTTP `X-Requested-With`, deci este esențial să o trimitem. În cadrul prezentatorului, puteți utiliza metoda `$this->isAjax()`.

Dacă doriți să trimiteți date în format JSON, utilizați metoda [`sendJson()` |presenters#Sending a response] metoda . Metoda încheie, de asemenea, activitatea prezentatorului.

```php
public function actionExport(): void
{
	$this->sendJson($this->model->getData);
}
```

Dacă intenționați să răspundeți cu un șablon special conceput pentru AJAX, puteți face acest lucru după cum urmează:

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


Fragmente .[#toc-snippets]
==========================

Cel mai puternic instrument oferit de Nette pentru conectarea serverului cu clientul sunt snippets. Cu ajutorul acestora, puteți transforma o aplicație obișnuită într-una AJAX cu un efort minim și câteva linii de cod. Exemplul Fifteen demonstrează cum funcționează totul, iar codul său poate fi găsit pe [GitHub |https://github.com/nette-examples/fifteen].

Snippets, sau clippings, vă permit să actualizați doar părți ale paginii, în loc să reîncărcați întreaga pagină. Acest lucru este mai rapid și mai eficient și oferă, de asemenea, o experiență mai confortabilă pentru utilizator. Snippets s-ar putea să vă amintească de Hotwire pentru Ruby on Rails sau Symfony UX Turbo. Interesant este că Nette a introdus snippets cu 14 ani mai devreme.

Cum funcționează snippets? Atunci când pagina este încărcată pentru prima dată (o cerere non-AJAX), se încarcă întreaga pagină, inclusiv toate snippet-urile. Atunci când utilizatorul interacționează cu pagina (de exemplu, face clic pe un buton, trimite un formular etc.), în loc să se încarce întreaga pagină, se face o cerere AJAX. Codul din prezentator execută acțiunea și decide ce fragmente trebuie actualizate. Nette redă aceste fragmente și le trimite sub forma unei matrice JSON. Codul de manipulare din browser inserează apoi fragmentele primite înapoi în pagină. Prin urmare, este transferat doar codul fragmentelor modificate, ceea ce permite economisirea lățimii de bandă și accelerarea încărcării în comparație cu transferul întregului conținut al paginii.


Naja .[#toc-naja]
-----------------

Pentru a gestiona fragmente de text în browser, se utilizează [biblioteca Naja |https://naja.js.org]. [Instalați-o |https://naja.js.org/#/guide/01-install-setup-naja] ca un pachet node.js (pentru a fi utilizată cu aplicații precum Webpack, Rollup, Vite, Parcel și altele):

```shell
npm install naja
```

... sau inserați-o direct în șablonul de pagină:

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

Pentru a transforma un link obișnuit (semnal) sau un formular de trimitere într-o cerere AJAX, este suficient să marcați link-ul, formularul sau butonul respectiv cu clasa `ajax`:

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


Redesenarea fragmentelor .[#toc-redrawing-snippets]
---------------------------------------------------

Fiecare obiect al clasei [Control |components] (inclusiv prezentatorul însuși) păstrează o evidență a modificărilor care au avut loc și care necesită redesenarea sa. Metoda `redrawControl()` este utilizată în acest scop.

```php
public function handleLogin(string $user): void
{
	// după logare, este necesar să se redeseneze partea relevantă
	$this->redrawControl();
	//...
}
```

Nette permite, de asemenea, un control mai fin al elementelor care trebuie redesenate. Metoda menționată mai sus poate primi ca argument numele fragmentului. Astfel, este posibil să se invalideze (adică să se forțeze o redesenare) la nivelul părții de șablon. În cazul în care întreaga componentă este invalidată, fiecare fragment din aceasta este, de asemenea, redesenat:

```php
// invalidează fragmentul de "antet
$this->redrawControl('header');
```


Fragmente în Latte .[#toc-snippets-in-latte]
--------------------------------------------

Utilizarea snippet-urilor în Latte este extrem de ușoară. Pentru a defini o parte a șablonului ca fiind un snippet, este suficient să o înfășurați în etichetele `{snippet}` și `{/snippet}`:

```latte
{snippet header}
	<h1>Hello ... </h1>
{/snippet}
```

Snippet-ul creează un element `<div>` în pagina HTML cu o etichetă special generată `id`. Atunci când se redesenează un fragment, conținutul acestui element este actualizat. Prin urmare, atunci când pagina este redată inițial, toate fragmentele trebuie, de asemenea, redate, chiar dacă acestea pot fi inițial goale.

De asemenea, puteți crea un fragment cu un alt element decât `<div>` utilizând un atribut n::

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


Zone de fragmente .[#toc-snippet-areas]
---------------------------------------

Numele snippet-urilor pot fi, de asemenea, expresii:

```latte
{foreach $items as $id => $item}
	<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
```

În acest fel, vom obține mai multe fragmente precum `item-0`, `item-1`, etc. Dacă ar fi să invalidăm direct un fragment dinamic (de exemplu, `item-1`), nimic nu ar fi redesenat. Motivul este că snippet-urile funcționează ca adevărate extrase și doar ele însele sunt redate direct. Cu toate acestea, în șablon, nu există, din punct de vedere tehnic, un fragment numit `item-1`. Acesta apare doar atunci când se execută codul care înconjoară fragmentul, în acest caz, bucla foreach. Prin urmare, vom marca partea din șablon care trebuie executată cu eticheta `{snippetArea}`:

```latte
<ul n:snippetArea="itemsContainer">
	{foreach $items as $id => $item}
		<li n:snippet="item-{$id}">{$item}</li>
	{/foreach}
</ul>
```

Și vom redesena atât fragmentul individual, cât și întreaga zonă de ansamblu:

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

De asemenea, este esențial să ne asigurăm că matricea `$items` conține doar elementele care trebuie redesenate.

Atunci când se inserează un alt șablon în șablonul principal folosind eticheta `{include}`, care are fragmente, este necesar să se înfășoare din nou șablonul inclus într-un `snippetArea` și să se invalideze atât fragmentul, cât și zona împreună:

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

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

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


Snippets în componente .[#toc-snippets-in-components]
-----------------------------------------------------

Puteți crea fragmente în cadrul [componentelor |components], iar Nette le va redesena automat. Cu toate acestea, există o limitare specifică: pentru a redesena snippets, se apelează metoda `render()` fără niciun parametru. Astfel, trecerea parametrilor în șablon nu va funcționa:

```latte
OK
{control productGrid}

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


Trimiterea datelor de utilizator .[#toc-sending-user-data]
----------------------------------------------------------

Împreună cu fragmente, puteți trimite orice date suplimentare către client. Pur și simplu scrieți-le în obiectul `payload`:

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


Parametrii de trimitere .[#toc-sending-parameters]
==================================================

Atunci când trimitem parametrii către componentă prin intermediul unei cereri AJAX, fie că este vorba de parametri de semnal sau de parametri persistenți, trebuie să furnizăm numele lor global, care conține și numele componentei. Numele complet al parametrului returnează metoda `getParameterId()`.

```js
let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);

fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})
```

O metodă handle cu parametrii corespunzători din componentă:

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

AJAX & Snippets

În era aplicațiilor web moderne, în care funcționalitatea se întinde adesea între server și browser, AJAX este un element de legătură esențial. Ce opțiuni oferă Nette Framework în acest domeniu?

  • trimiterea unor părți din șablon, așa-numitele snippet-uri
  • transmiterea de variabile între PHP și JavaScript
  • instrumente de depanare a cererilor AJAX

Cerere AJAX

O cerere AJAX nu diferă în mod fundamental de o cerere HTTP clasică. Un prezentator este apelat cu parametri specifici. Depinde de prezentator cum să răspundă la cerere – acesta poate returna date în format JSON, poate trimite o parte din codul HTML, un document XML etc.

Pe partea de browser, inițiem o cerere AJAX utilizând funcția fetch():

fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
	// prelucrarea răspunsului
});

Pe partea serverului, o cerere AJAX este recunoscută de metoda $httpRequest->isAjax() a serviciului care încapsulează cererea HTTP. Aceasta utilizează antetul HTTP X-Requested-With, deci este esențial să o trimitem. În cadrul prezentatorului, puteți utiliza metoda $this->isAjax().

Dacă doriți să trimiteți date în format JSON, utilizați metoda sendJson() metoda . Metoda încheie, de asemenea, activitatea prezentatorului.

public function actionExport(): void
{
	$this->sendJson($this->model->getData);
}

Dacă intenționați să răspundeți cu un șablon special conceput pentru AJAX, puteți face acest lucru după cum urmează:

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

Fragmente

Cel mai puternic instrument oferit de Nette pentru conectarea serverului cu clientul sunt snippets. Cu ajutorul acestora, puteți transforma o aplicație obișnuită într-una AJAX cu un efort minim și câteva linii de cod. Exemplul Fifteen demonstrează cum funcționează totul, iar codul său poate fi găsit pe GitHub.

Snippets, sau clippings, vă permit să actualizați doar părți ale paginii, în loc să reîncărcați întreaga pagină. Acest lucru este mai rapid și mai eficient și oferă, de asemenea, o experiență mai confortabilă pentru utilizator. Snippets s-ar putea să vă amintească de Hotwire pentru Ruby on Rails sau Symfony UX Turbo. Interesant este că Nette a introdus snippets cu 14 ani mai devreme.

Cum funcționează snippets? Atunci când pagina este încărcată pentru prima dată (o cerere non-AJAX), se încarcă întreaga pagină, inclusiv toate snippet-urile. Atunci când utilizatorul interacționează cu pagina (de exemplu, face clic pe un buton, trimite un formular etc.), în loc să se încarce întreaga pagină, se face o cerere AJAX. Codul din prezentator execută acțiunea și decide ce fragmente trebuie actualizate. Nette redă aceste fragmente și le trimite sub forma unei matrice JSON. Codul de manipulare din browser inserează apoi fragmentele primite înapoi în pagină. Prin urmare, este transferat doar codul fragmentelor modificate, ceea ce permite economisirea lățimii de bandă și accelerarea încărcării în comparație cu transferul întregului conținut al paginii.

Naja

Pentru a gestiona fragmente de text în browser, se utilizează biblioteca Naja. Instalați-o ca un pachet node.js (pentru a fi utilizată cu aplicații precum Webpack, Rollup, Vite, Parcel și altele):

npm install naja

… sau inserați-o direct în șablonul de pagină:

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

Pentru a transforma un link obișnuit (semnal) sau un formular de trimitere într-o cerere AJAX, este suficient să marcați link-ul, formularul sau butonul respectiv cu clasa 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>

Redesenarea fragmentelor

Fiecare obiect al clasei Control (inclusiv prezentatorul însuși) păstrează o evidență a modificărilor care au avut loc și care necesită redesenarea sa. Metoda redrawControl() este utilizată în acest scop.

public function handleLogin(string $user): void
{
	// după logare, este necesar să se redeseneze partea relevantă
	$this->redrawControl();
	//...
}

Nette permite, de asemenea, un control mai fin al elementelor care trebuie redesenate. Metoda menționată mai sus poate primi ca argument numele fragmentului. Astfel, este posibil să se invalideze (adică să se forțeze o redesenare) la nivelul părții de șablon. În cazul în care întreaga componentă este invalidată, fiecare fragment din aceasta este, de asemenea, redesenat:

// invalidează fragmentul de "antet
$this->redrawControl('header');

Fragmente în Latte

Utilizarea snippet-urilor în Latte este extrem de ușoară. Pentru a defini o parte a șablonului ca fiind un snippet, este suficient să o înfășurați în etichetele {snippet} și {/snippet}:

{snippet header}
	<h1>Hello ... </h1>
{/snippet}

Snippet-ul creează un element <div> în pagina HTML cu o etichetă special generată id. Atunci când se redesenează un fragment, conținutul acestui element este actualizat. Prin urmare, atunci când pagina este redată inițial, toate fragmentele trebuie, de asemenea, redate, chiar dacă acestea pot fi inițial goale.

De asemenea, puteți crea un fragment cu un alt element decât <div> utilizând un atribut n::

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

Zone de fragmente

Numele snippet-urilor pot fi, de asemenea, expresii:

{foreach $items as $id => $item}
	<li n:snippet="item-{$id}">{$item}</li>
{/foreach}

În acest fel, vom obține mai multe fragmente precum item-0, item-1, etc. Dacă ar fi să invalidăm direct un fragment dinamic (de exemplu, item-1), nimic nu ar fi redesenat. Motivul este că snippet-urile funcționează ca adevărate extrase și doar ele însele sunt redate direct. Cu toate acestea, în șablon, nu există, din punct de vedere tehnic, un fragment numit item-1. Acesta apare doar atunci când se execută codul care înconjoară fragmentul, în acest caz, bucla foreach. Prin urmare, vom marca partea din șablon care trebuie executată cu eticheta {snippetArea}:

<ul n:snippetArea="itemsContainer">
	{foreach $items as $id => $item}
		<li n:snippet="item-{$id}">{$item}</li>
	{/foreach}
</ul>

Și vom redesena atât fragmentul individual, cât și întreaga zonă de ansamblu:

$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');

De asemenea, este esențial să ne asigurăm că matricea $items conține doar elementele care trebuie redesenate.

Atunci când se inserează un alt șablon în șablonul principal folosind eticheta {include}, care are fragmente, este necesar să se înfășoare din nou șablonul inclus într-un snippetArea și să se invalideze atât fragmentul, cât și zona împreună:

{snippetArea include}
	{include 'included.latte'}
{/snippetArea}
{* inclus.latte *}
{snippet item}
	...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');

Snippets în componente

Puteți crea fragmente în cadrul componentelor, iar Nette le va redesena automat. Cu toate acestea, există o limitare specifică: pentru a redesena snippets, se apelează metoda render() fără niciun parametru. Astfel, trecerea parametrilor în șablon nu va funcționa:

OK
{control productGrid}

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

Trimiterea datelor de utilizator

Împreună cu fragmente, puteți trimite orice date suplimentare către client. Pur și simplu scrieți-le în obiectul payload:

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

Parametrii de trimitere

Atunci când trimitem parametrii către componentă prin intermediul unei cereri AJAX, fie că este vorba de parametri de semnal sau de parametri persistenți, trebuie să furnizăm numele lor global, care conține și numele componentei. Numele complet al parametrului returnează metoda getParameterId().

let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);

fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})

O metodă handle cu parametrii corespunzători din componentă:

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