AJAX & snippet
Nell'era delle moderne applicazioni web, dove la funzionalità è spesso suddivisa tra server e browser, AJAX è un elemento di collegamento essenziale. Quali opzioni ci offre Nette Framework in questo campo?
- invio di parti del template, i cosiddetti snippet
- passaggio di variabili tra PHP e JavaScript
- strumenti per il debug delle richieste AJAX
Richiesta AJAX
Una richiesta AJAX non è fondamentalmente diversa da una classica richiesta HTTP. Viene chiamato un presenter con determinati parametri. E spetta al presenter decidere come reagire alla richiesta: può restituire dati in formato JSON, inviare una parte di codice HTML, un documento XML, ecc.
Sul lato browser, inizializziamo la richiesta AJAX utilizzando la funzione fetch()
:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// elaborazione della risposta
});
Sul lato server, riconosciamo una richiesta AJAX con il metodo $httpRequest->isAjax()
del servizio incapsulando la richiesta HTTP. Per il rilevamento utilizza l'header HTTP
X-Requested-With
, quindi è importante inviarlo. All'interno del presenter è possibile utilizzare il metodo
$this->isAjax()
.
Se si desidera inviare dati in formato JSON, utilizzare il metodo sendJson()
. Il metodo termina anche
l'attività del presenter.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Se si prevede di rispondere con un template speciale progettato per AJAX, è possibile farlo come segue:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
// ...
}
Snippet
Lo strumento più potente offerto da Nette per collegare il server al client sono gli snippet. Grazie ad essi, è possibile trasformare un'applicazione ordinaria in una AJAX con uno sforzo minimo e poche righe di codice. L'esempio Fifteen, il cui codice si trova su GitHub, dimostra come funziona il tutto.
Gli snippet, o frammenti, consentono di aggiornare solo parti della pagina invece di ricaricare l'intera pagina. Questo non solo è più veloce ed efficiente, ma offre anche un'esperienza utente più confortevole. Gli snippet potrebbero ricordarvi Hotwire per Ruby on Rails o Symfony UX Turbo. È interessante notare che Nette ha introdotto gli snippet già 14 anni prima.
Come funzionano gli snippet? Al primo caricamento della pagina (richiesta non AJAX), viene caricata l'intera pagina, inclusi tutti gli snippet. Quando l'utente interagisce con la pagina (ad esempio, fa clic su un pulsante, invia un form, ecc.), viene attivata una richiesta AJAX invece di caricare l'intera pagina. Il codice nel presenter esegue l'azione e decide quali snippet devono essere aggiornati. Nette esegue il rendering di questi snippet e li invia come array in formato JSON. Il codice di gestione nel browser riceve gli snippet e li inserisce nuovamente nella pagina. Viene trasferito solo il codice degli snippet modificati, risparmiando larghezza di banda e accelerando il caricamento rispetto al trasferimento dell'intero contenuto della pagina.
Naja
Per gestire gli snippet sul lato browser, viene utilizzata la libreria Naja. Installala come pacchetto node.js (per l'uso con applicazioni Webpack, Rollup, Vite, Parcel e altre):
npm install naja
…o inseriscila direttamente nel template della pagina:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Innanzitutto, è necessario inizializzare la libreria:
naja.initialize();
Per trasformare un link ordinario (segnale) o l'invio di un form in una richiesta AJAX, è sufficiente contrassegnare il link,
il form o il pulsante pertinente con la classe ajax
:
<a n:href="go!" class="ajax">Vai</a>
<form n:name="form" class="ajax">
<input n:name="submit">
</form>
oppure
<form n:name="form">
<input n:name="submit" class="ajax">
</form>
Ridisegno degli snippet
Ogni oggetto della classe Control (incluso il Presenter stesso)
tiene traccia se ci sono state modifiche che richiedono il suo ridisegno. A tale scopo viene utilizzato il metodo
redrawControl()
:
public function handleLogin(string $user): void
{
// dopo il login, è necessario ridisegnare la parte pertinente
$this->redrawControl();
// ...
}
Nette consente un controllo ancora più preciso su cosa deve essere ridisegnato. Il metodo menzionato può infatti accettare il nome dello snippet come argomento. È quindi possibile invalidare (cioè: forzare il ridisegno) a livello di parti del template. Se l'intero componente viene invalidato, verrà ridisegnato anche ogni suo snippet:
// invalida lo snippet 'header'
$this->redrawControl('header');
Snippet in Latte
L'uso degli snippet in Latte è estremamente facile. Per definire una parte del template come snippet, è sufficiente
racchiuderla tra i tag {snippet}
e {/snippet}
:
{snippet header}
<h1>Ciao ... </h1>
{/snippet}
Lo snippet crea un elemento <div>
nella pagina HTML con un id
speciale generato. Quando lo
snippet viene ridisegnato, il contenuto di questo elemento viene aggiornato. Pertanto, è necessario che al rendering iniziale
della pagina vengano renderizzati anche tutti gli snippet, anche se potrebbero essere inizialmente vuoti.
È possibile creare uno snippet con un elemento diverso da <div>
utilizzando l'attributo n:
<article n:snippet="header" class="foo bar">
<h1>Ciao ... </h1>
</article>
Aree di snippet
I nomi degli snippet possono anche essere espressioni:
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
In questo modo vengono creati diversi snippet item-0
, item-1
, ecc. Se invalidassimo direttamente uno
snippet dinamico (ad esempio item-1
), non verrebbe ridisegnato nulla. Il motivo è che gli snippet funzionano davvero
come ritagli e vengono renderizzati solo direttamente. Tuttavia, nel template non esiste effettivamente uno snippet chiamato
item-1
. Questo viene creato solo eseguendo il codice circostante lo snippet, cioè il ciclo foreach. Pertanto,
contrassegniamo la parte del template che deve essere eseguita utilizzando il tag {snippetArea}
:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
E facciamo ridisegnare sia lo snippet stesso che l'intera area genitore:
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Allo stesso tempo, è consigliabile assicurarsi che l'array $items
contenga solo gli elementi che devono essere
ridisegnati.
Se inseriamo un altro template nel template utilizzando il tag {include}
, che contiene snippet, è necessario
includere nuovamente l'inserimento del template in snippetArea
e invalidarlo insieme allo snippet:
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* included.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Snippet nei componenti
È possibile creare snippet anche nei componenti e Nette li
ridisegnerà automaticamente. Ma c'è una limitazione: per ridisegnare gli snippet, chiama il metodo render()
senza
parametri. Quindi, il passaggio di parametri nel template non funzionerà:
OK
{control productGrid}
non funzionerà:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Invio di dati utente
Insieme agli snippet, è possibile inviare al client qualsiasi altro dato. È sufficiente scriverli nell'oggetto
payload
:
public function actionDelete(int $id): void
{
// ...
if ($this->isAjax()) {
$this->payload->message = 'Success';
}
}
Passaggio di parametri
Se inviamo parametri a un componente tramite una richiesta AJAX, siano essi parametri di segnale o parametri persistenti,
dobbiamo specificare il loro nome globale nella richiesta, che include anche il nome del componente. Il nome completo del
parametro viene restituito dal metodo getParameterId()
.
let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
E il metodo handle con i parametri corrispondenti nel componente:
public function handleFoo(int $bar): void
{
}