AJAX & odrezki
V dobi sodobnih spletnih aplikacij, kjer je funkcionalnost pogosto razdeljena med strežnikom in brskalnikom, je AJAX nujen povezovalni element. Kakšne možnosti nam na tem področju ponuja Nette Framework?
- pošiljanje delov predloge, t.i. odrezkov
- posredovanje spremenljivk med PHP in JavaScriptom
- orodja za razhroščevanje AJAX zahtevkov
AJAX zahtevek
AJAX zahtevek se v bistvu ne razlikuje od klasičnega HTTP zahtevka. Pokliče se presenter z določenimi parametri. Od presenterja pa je odvisno, kako se bo na zahtevek odzval – lahko vrne podatke v formatu JSON, pošlje del HTML kode, XML dokument itd.
Na strani brskalnika inicializiramo AJAX zahtevek s funkcijo fetch()
:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// obdelava odgovora
});
Na strani strežnika prepoznamo AJAX zahtevek z metodo $httpRequest->isAjax()
storitve enkapsulirajoč HTTP zahtevek. Za zaznavanje uporablja HTTP glavo
X-Requested-With
, zato je pomembno, da jo pošiljamo. V okviru presenterja lahko uporabimo metodo
$this->isAjax()
.
Če želite poslati podatke v formatu JSON, uporabite metodo sendJson()
. Metoda prav tako
zaključi delovanje presenterja.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Če nameravate odgovoriti s posebno predlogo, namenjeno za AJAX, lahko to storite na naslednji način:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
// ...
}
Odrezki
Najmočnejše sredstvo, ki ga Nette ponuja za povezovanje strežnika s klientom, so odrezki. Zahvaljujoč njim lahko iz navadne aplikacije naredite AJAX aplikacijo z minimalnim naporom in nekaj vrsticami kode. Kako vse skupaj deluje, prikazuje primer Fifteen, katerega kodo najdete na GitHubu.
Odrezki omogočajo posodabljanje samo delov strani, namesto da bi se celotna stran ponovno nalagala. To ni samo hitrejše in učinkovitejše, ampak zagotavlja tudi udobnejšo uporabniško izkušnjo. Odrezki vas lahko spominjajo na Hotwire za Ruby on Rails ali Symfony UX Turbo. Zanimivo je, da je Nette predstavil odrezke že 14 let prej.
Kako odrezki delujejo? Ob prvem nalaganju strani (ne-AJAX zahtevek) se naloži celotna stran, vključno z vsemi odrezki. Ko uporabnik interagira s stranjo (npr. klikne na gumb, pošlje obrazec itd.), se namesto nalaganja celotne strani sproži AJAX zahtevek. Koda v presenterju izvede akcijo in odloči, katere odrezke je treba posodobiti. Nette te odrezke izriše in jih pošlje v obliki polja v formatu JSON. Obdelovalna koda v brskalniku prejete odrezke vstavi nazaj v stran. Prenaša se torej samo koda spremenjenih odrezkov, kar prihrani pasovno širino in pospeši nalaganje v primerjavi s prenosom vsebine celotne strani.
Naja
Za obdelavo odrezkov na strani brskalnika služi knjižnica Naja. To namestite kot node.js paket (za uporabo z aplikacijami Webpack, Rollup, Vite, Parcel in drugimi):
npm install naja
…ali pa jo neposredno vstavite v predlogo strani:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Najprej je treba knjižnico inicializirati:
naja.initialize();
Da bi se iz navadne povezave (signala) ali pošiljanja obrazca ustvaril AJAX zahtevek, je dovolj označiti 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>
ali
<form n:name="form">
<input n:name="submit" class="ajax">
</form>
Prekresljevanje odrezkov
Vsak objekt razreda Control (vključno s samim Presenterjem)
beleži, ali so se zgodile spremembe, ki zahtevajo njegovo prekreslitev. Za to služi metoda redrawControl()
:
public function handleLogin(string $user): void
{
// po prijavi je treba prekresliti relevantni del
$this->redrawControl();
// ...
}
Nette omogoča še natančnejši nadzor nad tem, kaj se mora prekresliti. Navedena metoda namreč lahko kot argument sprejme ime odrezka. Tako lahko razveljavimo (razumite: prisilimo prekreslitev) na ravni delov predloge. Če se razveljavi celotna komponenta, se prekresli tudi vsak njen odrezek:
// razveljavi odrezek 'header'
$this->redrawControl('header');
Odrezki v Latte
Uporaba odrezkov v Latte je izjemno enostavna. Če želite definirati del predloge kot odrezek, ga preprosto ovijte
z značkama {snippet}
in {/snippet}
:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
Odrezek ustvari v HTML strani element <div>
s posebnim generiranim id
. Pri prekreslitvi
odrezka se nato posodobi vsebina tega elementa. Zato je nujno, da se ob prvotnem izrisu strani izrišejo tudi vsi odrezki, čeprav
so lahko na začetku prazni.
Lahko ustvarite tudi odrezek z drugim elementom kot <div>
s pomočjo n:atributa:
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Območja odrezkov
Imena odrezkov so lahko tudi izrazi:
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
Tako dobimo več odrezkov item-0
, item-1
itd. Če bi neposredno razveljavili dinamični odrezek (na
primer item-1
), se ne bi prekreslilo nič. Razlog je ta, da odrezki res delujejo kot izrezki in se izrisujejo samo
neposredno oni sami. Vendar v predlogi dejansko ni nobenega odrezka z imenom item-1
. Ta nastane šele z izvajanjem
kode v okolici odrezka, torej zanke foreach. Zato označimo del predloge, ki se mora izvesti, s pomočjo značke
{snippetArea}
:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
In pustimo prekresliti tako sam odrezek kot tudi celotno nadrejeno območje:
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Hkrati je priporočljivo zagotoviti, da polje $items
vsebuje samo tiste elemente, ki se morajo prekresliti.
Če v predlogo s pomočjo značke {include}
vključujemo drugo predlogo, ki vsebuje odrezke, je treba
vključitev predloge ponovno vključiti v snippetArea
in jo razveljaviti skupaj z odrezkom:
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* included.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Odrezki v komponentah
Odrezke lahko ustvarjate tudi v komponentah in Nette jih bo
samodejno prekresljeval. Vendar obstaja določena omejitev: za prekreslitev odrezkov kliče metodo render()
brez
parametrov. Torej posredovanje parametrov v predlogi ne bo delovalo:
OK
{control productGrid}
ne bo delovalo:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Pošiljanje uporabniških podatkov
Skupaj z odrezki lahko klientu pošljete poljubne druge podatke. Dovolj je, da jih zapišete v objekt
payload
:
public function actionDelete(int $id): void
{
// ...
if ($this->isAjax()) {
$this->payload->message = 'Uspeh';
}
}
Posredovanje parametrov
Če komponenti s pomočjo AJAX zahtevka pošiljamo parametre, bodisi parametre signala ali persistentne parametre, moramo pri
zahtevku navesti njihovo globalno ime, ki vsebuje tudi ime komponente. Celotno 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'},
})
In handle
metoda z ustreznimi parametri v komponenti:
public function handleFoo(int $bar): void
{
}