Nette Documentation Preview

syntax
AJAX in sličice
***************

<div class=perex>

V dobi sodobnih spletnih aplikacij, kjer je funkcionalnost pogosto razpeta med strežnikom in brskalnikom, je AJAX bistven povezovalni element. Katere možnosti ponuja ogrodje Nette na tem področju?
- pošiljanje delov predloge, tako imenovanih snippets
- posredovanje spremenljivk med PHP in JavaScriptom
- orodja za razhroščevanje zahtevkov AJAX

</div>


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

Zahteva AJAX se v osnovi ne razlikuje od klasične zahteve HTTP. Predlagatelj se pokliče z določenimi parametri. Od predstavnika je odvisno, kako se bo odzval na zahtevo - lahko vrne podatke v obliki JSON, pošlje del kode HTML, dokument XML itd.

Na strani brskalnika sprožimo zahtevo AJAX s funkcijo `fetch()`:

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

Na strani strežnika zahtevo AJAX prepozna metoda `$httpRequest->isAjax()` storitve, ki [enkapsulira zahtevo HTTP |http:request]. Uporablja glavo HTTP `X-Requested-With`, zato jo je nujno poslati. V predstavitvenem programu lahko uporabite metodo `$this->isAjax()`.

Če želite poslati podatke v obliki JSON, uporabite metodo [`sendJson()` |presenters#Sending a response] metodo. Metoda tudi zaključi dejavnost predstavitvenega programa.

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

Če se nameravate odzvati s posebno predlogo, zasnovano za AJAX, lahko to storite na naslednji način:

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


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

Najzmogljivejše orodje, ki ga ponuja Nette za povezovanje strežnika z odjemalcem, so snippets. Z njimi lahko navadno aplikacijo spremenite v aplikacijo AJAX z minimalnim naporom in nekaj vrsticami kode. Primer Fifteen prikazuje, kako vse to deluje, njegovo kodo pa lahko najdete na [GitHubu |https://github.com/nette-examples/fifteen].

Snippets ali clippings omogočajo posodabljanje samo delov strani, namesto da bi ponovno naložili celotno stran. To je hitrejše in učinkovitejše, poleg tega pa zagotavlja udobnejšo uporabniško izkušnjo. Snippets vas morda spominjajo na Hotwire za Ruby on Rails ali Symfony UX Turbo. Zanimivo pa je, da je Nette snippets predstavil že 14 let prej.

Kako delujejo odlomki? Ob prvem nalaganju strani (zahteva, ki ni zahteva AJAX) se naloži celotna stran, vključno z vsemi snippets. Ko uporabnik komunicira s stranjo (npr. klikne gumb, odda obrazec itd.), se namesto nalaganja celotne strani izvede zahteva AJAX. Koda v predstavniku izvede dejanje in odloči, katere utrinke je treba posodobiti. Nette te utrinke prikaže in jih pošlje v obliki polja JSON. Obdelovalna koda v brskalniku nato vstavi prejete utrinke nazaj na stran. Zato se prenese samo koda spremenjenih izsekov, kar prihrani pasovno širino in pospeši nalaganje v primerjavi s prenosom celotne vsebine strani.


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

Za obdelavo izsekov na strani brskalnika se uporablja [knjižnica Naja |https://naja.js.org]. [Namestite jo |https://naja.js.org/#/guide/01-install-setup-naja] kot paket node.js (za uporabo z aplikacijami, kot so Webpack, Rollup, Vite, Parcel in druge):

```shell
npm install naja
```

... ali jo vstavite neposredno v predlogo strani:

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

Če želite, da običajna povezava (signal) ali oddaja obrazca postane zahteva AJAX, preprosto označite ustrezno povezavo, obrazec ali gumb z razredom `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>
```


Ponovno risanje utrinkov .[#toc-redrawing-snippets]
---------------------------------------------------

Vsak objekt razreda [Control |components] (vključno s samim Presenterjem) beleži, ali so se zgodile spremembe, ki zahtevajo njegovo ponovno izrisovanje. V ta namen se uporablja metoda `redrawControl()`.

```php
public function handleLogin(string $user): void
{
	// po prijavi je treba ponovno narisati ustrezni del
	$this->redrawControl();
	//...
}
```

Nette omogoča tudi natančnejši nadzor nad tem, kaj je treba ponovno narisati. Zgoraj omenjena metoda lahko kot argument sprejme ime izseka. Tako je mogoče razveljaviti (kar pomeni: prisiliti k ponovnemu izrisu) na ravni dela predloge. Če je celotna komponenta razveljavljena, se na novo nariše tudi vsak njen delček:

```php
// razveljavi odlomek 'header'
$this->redrawControl('header');
```


Utrinki v Latte .[#toc-snippets-in-latte]
-----------------------------------------

Uporaba snippetov v Latte je zelo preprosta. Če želite del predloge opredeliti kot izsek, ga preprosto ovijte v oznake `{snippet}` in `{/snippet}`:

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

Snippet ustvari element `<div>` na strani HTML s posebej ustvarjeno oznako `id`. Pri ponovnem izrisu sličice se vsebina tega elementa posodobi. Zato je treba ob začetnem izrisu strani izrisati tudi vse sličice, čeprav so lahko na začetku prazne.

Fragment lahko ustvarite tudi z elementom, ki ni `<div>` z uporabo atributa n::

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


Območja sličic .[#toc-snippet-areas]
------------------------------------

Imena snippetov so lahko tudi izrazi:

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

Tako bomo dobili več izsekov, kot so `item-0`, `item-1`, itd. Če bi neposredno razveljavili dinamični snippet (npr. `item-1`), se ne bi nič na novo izrisalo. Razlog za to je, da utrinki delujejo kot pravi izvlečki in se neposredno izrisujejo le oni sami. Vendar v predlogi tehnično ni nobenega odlomka z imenom `item-1`. Pojavi se šele ob izvajanju okoliške kode snippeta, v tem primeru zanke foreach. Zato bomo del predloge, ki ga je treba izvesti, označili z oznako `{snippetArea}`:

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

In na novo bomo narisali tako posamezni izsek kot celotno krovno območje:

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

Prav tako je treba zagotoviti, da polje `$items` vsebuje samo elemente, ki jih je treba ponovno narisati.

Pri vstavljanju druge predloge v glavno s pomočjo oznake `{include}`, ki ima izseke, je treba vključeno predlogo ponovno zaviti v `snippetArea` in razveljaviti tako izsek kot območje skupaj:

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

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

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


Utrinki v sestavinah .[#toc-snippets-in-components]
---------------------------------------------------

V [komponentah |components] lahko ustvarite sličice in Nette jih bo samodejno ponovno narisal. Vendar pa obstaja posebna omejitev: za ponovno izrisovanje snippetov je treba poklicati metodo `render()` brez parametrov. Tako posredovanje parametrov v predlogi ne bo delovalo:

```latte
OK
{control productGrid}

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


Pošiljanje uporabniških podatkov .[#toc-sending-user-data]
----------------------------------------------------------

Skupaj z odlomki lahko odjemalcu pošljete vse dodatne podatke. Preprosto jih zapišite v objekt `payload`:

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


Pošiljanje parametrov .[#toc-sending-parameters]
================================================

Ko komponenti z zahtevo AJAX pošiljamo parametre, bodisi signalne bodisi trajne, moramo navesti njihovo globalno ime, ki vsebuje tudi ime komponente. Polno ime parametra vrne metoda `getParameterId()`.

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

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

Metoda handle z ustreznimi parametri v komponenti:

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

AJAX in sličice

V dobi sodobnih spletnih aplikacij, kjer je funkcionalnost pogosto razpeta med strežnikom in brskalnikom, je AJAX bistven povezovalni element. Katere možnosti ponuja ogrodje Nette na tem področju?

  • pošiljanje delov predloge, tako imenovanih snippets
  • posredovanje spremenljivk med PHP in JavaScriptom
  • orodja za razhroščevanje zahtevkov AJAX

Zahteva AJAX

Zahteva AJAX se v osnovi ne razlikuje od klasične zahteve HTTP. Predlagatelj se pokliče z določenimi parametri. Od predstavnika je odvisno, kako se bo odzval na zahtevo – lahko vrne podatke v obliki JSON, pošlje del kode HTML, dokument XML itd.

Na strani brskalnika sprožimo zahtevo AJAX s funkcijo fetch():

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

Na strani strežnika zahtevo AJAX prepozna metoda $httpRequest->isAjax() storitve, ki enkapsulira zahtevo HTTP. Uporablja glavo HTTP X-Requested-With, zato jo je nujno poslati. V predstavitvenem programu lahko uporabite metodo $this->isAjax().

Če želite poslati podatke v obliki JSON, uporabite metodo sendJson() metodo. Metoda tudi zaključi dejavnost predstavitvenega programa.

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

Če se nameravate odzvati s posebno predlogo, zasnovano za AJAX, lahko to storite na naslednji način:

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

Utrinki

Najzmogljivejše orodje, ki ga ponuja Nette za povezovanje strežnika z odjemalcem, so snippets. Z njimi lahko navadno aplikacijo spremenite v aplikacijo AJAX z minimalnim naporom in nekaj vrsticami kode. Primer Fifteen prikazuje, kako vse to deluje, njegovo kodo pa lahko najdete na GitHubu.

Snippets ali clippings omogočajo posodabljanje samo delov strani, namesto da bi ponovno naložili celotno stran. To je hitrejše in učinkovitejše, poleg tega pa zagotavlja udobnejšo uporabniško izkušnjo. Snippets vas morda spominjajo na Hotwire za Ruby on Rails ali Symfony UX Turbo. Zanimivo pa je, da je Nette snippets predstavil že 14 let prej.

Kako delujejo odlomki? Ob prvem nalaganju strani (zahteva, ki ni zahteva AJAX) se naloži celotna stran, vključno z vsemi snippets. Ko uporabnik komunicira s stranjo (npr. klikne gumb, odda obrazec itd.), se namesto nalaganja celotne strani izvede zahteva AJAX. Koda v predstavniku izvede dejanje in odloči, katere utrinke je treba posodobiti. Nette te utrinke prikaže in jih pošlje v obliki polja JSON. Obdelovalna koda v brskalniku nato vstavi prejete utrinke nazaj na stran. Zato se prenese samo koda spremenjenih izsekov, kar prihrani pasovno širino in pospeši nalaganje v primerjavi s prenosom celotne vsebine strani.

Naja

Za obdelavo izsekov na strani brskalnika se uporablja knjižnica Naja. Namestite jo kot paket node.js (za uporabo z aplikacijami, kot so Webpack, Rollup, Vite, Parcel in druge):

npm install naja

… ali jo vstavite neposredno v predlogo strani:

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

Če želite, da običajna povezava (signal) ali oddaja obrazca postane zahteva AJAX, 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>

Ponovno risanje utrinkov

Vsak objekt razreda Control (vključno s samim Presenterjem) beleži, ali so se zgodile spremembe, ki zahtevajo njegovo ponovno izrisovanje. V ta namen se uporablja metoda redrawControl().

public function handleLogin(string $user): void
{
	// po prijavi je treba ponovno narisati ustrezni del
	$this->redrawControl();
	//...
}

Nette omogoča tudi natančnejši nadzor nad tem, kaj je treba ponovno narisati. Zgoraj omenjena metoda lahko kot argument sprejme ime izseka. Tako je mogoče razveljaviti (kar pomeni: prisiliti k ponovnemu izrisu) na ravni dela predloge. Če je celotna komponenta razveljavljena, se na novo nariše tudi vsak njen delček:

// razveljavi odlomek 'header'
$this->redrawControl('header');

Utrinki v Latte

Uporaba snippetov v Latte je zelo preprosta. Če želite del predloge opredeliti kot izsek, ga preprosto ovijte v oznake {snippet} in {/snippet}:

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

Snippet ustvari element <div> na strani HTML s posebej ustvarjeno oznako id. Pri ponovnem izrisu sličice se vsebina tega elementa posodobi. Zato je treba ob začetnem izrisu strani izrisati tudi vse sličice, čeprav so lahko na začetku prazne.

Fragment lahko ustvarite tudi z elementom, ki ni <div> z uporabo atributa n::

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

Območja sličic

Imena snippetov so lahko tudi izrazi:

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

Tako bomo dobili več izsekov, kot so item-0, item-1, itd. Če bi neposredno razveljavili dinamični snippet (npr. item-1), se ne bi nič na novo izrisalo. Razlog za to je, da utrinki delujejo kot pravi izvlečki in se neposredno izrisujejo le oni sami. Vendar v predlogi tehnično ni nobenega odlomka z imenom item-1. Pojavi se šele ob izvajanju okoliške kode snippeta, v tem primeru zanke foreach. Zato bomo del predloge, ki ga je treba izvesti, označili z oznako {snippetArea}:

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

In na novo bomo narisali tako posamezni izsek kot celotno krovno območje:

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

Prav tako je treba zagotoviti, da polje $items vsebuje samo elemente, ki jih je treba ponovno narisati.

Pri vstavljanju druge predloge v glavno s pomočjo oznake {include}, ki ima izseke, je treba vključeno predlogo ponovno zaviti v snippetArea in razveljaviti tako izsek kot območje skupaj:

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

Utrinki v sestavinah

komponentah lahko ustvarite sličice in Nette jih bo samodejno ponovno narisal. Vendar pa obstaja posebna omejitev: za ponovno izrisovanje snippetov je treba poklicati metodo render() brez parametrov. Tako posredovanje parametrov v predlogi ne bo delovalo:

OK
{control productGrid}

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

Pošiljanje uporabniških podatkov

Skupaj z odlomki lahko odjemalcu pošljete vse dodatne podatke. Preprosto jih zapišite v objekt payload:

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

Pošiljanje parametrov

Ko komponenti z zahtevo AJAX pošiljamo parametre, bodisi signalne bodisi trajne, moramo navesti njihovo globalno ime, ki vsebuje tudi ime komponente. Polno ime parametra vrne metoda getParameterId().

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

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

Metoda handle z ustreznimi parametri v komponenti:

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