AJAX e Snippet

Nell'era delle moderne applicazioni web, in cui le funzionalità si estendono spesso tra il server e il browser, AJAX è un elemento di connessione essenziale. Quali opzioni offre Nette Framework in questo ambito?

  • invio di parti del modello, i cosiddetti snippet
  • passaggio di variabili tra PHP e JavaScript
  • strumenti per il debug delle richieste AJAX

Richiesta AJAX

Una richiesta AJAX fondamentalmente non differisce da una richiesta HTTP classica. Un presentatore viene chiamato con parametri specifici. Spetta al presentatore rispondere alla richiesta: può restituire dati in formato JSON, inviare una parte di codice HTML, un documento XML, ecc.

Dal lato del browser, si avvia una 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, una richiesta AJAX viene riconosciuta dal metodo $httpRequest->isAjax() del servizio che incapsula la richiesta HTTP. Utilizza l'intestazione HTTP X-Requested-With, quindi è essenziale inviarla. 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 presentatore.

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

Se si intende rispondere con un modello speciale progettato per AJAX, si può procedere come segue:

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

Frammenti

Lo strumento più potente offerto da Nette per collegare il server al client sono gli snippet. Con essi, è possibile trasformare un'applicazione ordinaria in una AJAX con il minimo sforzo e poche righe di codice. L'esempio di Fifteen ne dimostra il funzionamento e il suo codice è disponibile su GitHub.

Gli snippet, o ritagli, consentono di aggiornare solo alcune parti della pagina, invece di ricaricare l'intera pagina. Questo è più veloce ed efficiente e offre anche un'esperienza d'uso più confortevole. Gli snippet potrebbero ricordare Hotwire per Ruby on Rails o Symfony UX Turbo. È interessante notare che Nette ha introdotto gli snippet 14 anni prima.

Come funzionano gli snippet? Quando la pagina viene caricata per la prima volta (una richiesta non-AJAX), viene caricata l'intera pagina, compresi tutti gli snippet. Quando l'utente interagisce con la pagina (ad esempio, fa clic su un pulsante, invia un modulo, ecc.), invece di caricare l'intera pagina, viene effettuata una richiesta AJAX. Il codice del presentatore esegue l'azione e decide quali snippet devono essere aggiornati. Nette esegue il rendering di questi frammenti e li invia sotto forma di array JSON. Il codice di gestione del browser inserisce quindi gli snippet ricevuti nella pagina. Pertanto, viene trasferito solo il codice degli snippet modificati, risparmiando larghezza di banda e velocizzando il caricamento rispetto al trasferimento dell'intero contenuto della pagina.

Naja

Per gestire gli snippet sul lato browser, si usa la libreria Naja. Installarla come pacchetto node.js (da usare con applicazioni come Webpack, Rollup, Vite, Parcel e altre):

npm install naja

… o inserirla direttamente nel modello di pagina:

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

Per prima cosa è necessario inizializzare la libreria:

naja.initialize();

Per rendere un normale link (segnale) o l'invio di un modulo una richiesta AJAX, è sufficiente contrassegnare il rispettivo link, modulo o pulsante con la classe 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>

Ridisegno degli snippet

Ogni oggetto della classe Control (compreso il Presentatore stesso) registra se sono avvenuti cambiamenti che richiedono il suo ridisegno. A tale scopo viene utilizzato il metodo redrawControl().

public function handleLogin(string $user): void
{
	// dopo l'accesso, è necessario ridisegnare la parte pertinente
	$this->redrawControl();
	//...
}

Nette consente anche un controllo più preciso di ciò che deve essere ridisegnato. Il metodo summenzionato può accettare il nome dello snippet come argomento. In questo modo, è possibile invalidare (cioè forzare un ridisegno) a livello di parte del modello. Se l'intero componente viene invalidato, anche ogni frammento viene ridisegnato:

// invalida lo snippet "header
$this->redrawControl('header');

Frammenti in Latte

L'uso degli snippet in Latte è estremamente semplice. Per definire una parte del modello come snippet, è sufficiente avvolgerla nei tag {snippet} e {/snippet}:

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

Lo snippet crea un elemento <div> nella pagina HTML con un elemento id appositamente generato. Quando si ridisegna uno snippet, il contenuto di questo elemento viene aggiornato. Pertanto, quando la pagina viene inizialmente resa, devono essere resi anche tutti gli snippet, anche se inizialmente possono essere vuoti.

È anche possibile creare uno snippet con un elemento diverso da <div> utilizzando l'attributo n:n:

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

Aree Snippet

I nomi dei frammenti possono anche essere espressioni:

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

In questo modo si otterranno diversi snippet come item-0, item-1, ecc. Se si invalidasse direttamente uno snippet dinamico (ad esempio, item-1), non verrebbe ridisegnato nulla. Il motivo è che gli snippet funzionano come veri e propri estratti e solo essi vengono resi direttamente. Tuttavia, nel template non esiste tecnicamente uno snippet chiamato item-1. Emerge solo quando si esegue il codice circostante lo snippet, in questo caso il ciclo foreach. Pertanto, contrassegneremo la parte del template che deve essere eseguita con il tag {snippetArea}:

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

E ridisegneremo sia il singolo snippet che l'intera area circostante:

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

È inoltre essenziale assicurarsi che l'array $items contenga solo gli elementi che devono essere ridisegnati.

Quando si inserisce un altro modello in quello principale usando il tag {include}, che ha degli snippet, è necessario avvolgere nuovamente il modello incluso in un snippetArea e invalidare sia lo snippet che l'area insieme:

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

Snippet nei componenti

È possibile creare snippet all'interno dei componenti e Nette li ridisegna automaticamente. Tuttavia, c'è una limitazione specifica: per ridisegnare gli snippet, viene chiamato il metodo render() senza alcun parametro. Pertanto, il passaggio di parametri nel modello non funziona:

OK
{control productGrid}

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

Invio di dati utente

Oltre agli snippet, è possibile inviare qualsiasi altro dato al client. È sufficiente scriverli nell'oggetto payload:

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

Parametri di invio

Quando si inviano parametri al componente tramite una richiesta AJAX, sia che si tratti di parametri di segnale che di parametri persistenti, occorre fornire il loro nome globale, che contiene anche il nome del componente. Il nome completo del parametro restituisce il metodo getParameterId().

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

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

Un metodo handle con i parametri corrispondenti nel componente:

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