AJAX & Snippets
À l'ère des applications web modernes, où les fonctionnalités s'étendent souvent entre le serveur et le navigateur, AJAX est un élément de connexion essentiel. Quelles sont les possibilités offertes par le Nette Framework dans ce domaine ?
- l'envoi de parties du modèle, appelées "snippets
- le passage de variables entre PHP et JavaScript
- outils de débogage des requêtes AJAX
Demande AJAX
Une requête AJAX ne diffère pas fondamentalement d'une requête HTTP classique. Un présentateur est appelé avec des paramètres spécifiques. C'est au présentateur de décider comment répondre à la requête – il peut renvoyer des données au format JSON, envoyer une partie du code HTML, un document XML, etc.
Du côté du navigateur, nous lançons une requête AJAX à l'aide de la fonction fetch()
:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// traitement de la réponse
});
Du côté du serveur, une requête AJAX est reconnue par la méthode $httpRequest->isAjax()
du service encapsulant la requête HTTP. Elle utilise l'en-tête HTTP
X-Requested-With
, qu'il est donc essentiel d'envoyer. Dans le présentateur, vous pouvez utiliser la méthode
$this->isAjax()
.
Si vous souhaitez envoyer des données au format JSON, utilisez la méthode sendJson()
méthode. La méthode
met également fin à l'activité du présentateur.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Si vous envisagez de répondre avec un modèle spécial conçu pour AJAX, vous pouvez procéder comme suit :
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
//...
}
Bribes
Les snippets sont l'outil le plus puissant offert par Nette pour connecter le serveur au client. Grâce à eux, vous pouvez transformer une application ordinaire en une application AJAX avec un minimum d'effort et quelques lignes de code. L'exemple Fifteen montre comment tout cela fonctionne, et son code peut être trouvé sur GitHub.
Les snippets, ou clippings, vous permettent de ne mettre à jour que certaines parties de la page, au lieu de recharger toute la page. C'est plus rapide et plus efficace, et l'expérience utilisateur est plus confortable. Les snippets peuvent vous rappeler Hotwire pour Ruby on Rails ou Symfony UX Turbo. Il est intéressant de noter que Nette a introduit les snippets 14 ans plus tôt.
Comment fonctionnent les snippets ? Lorsque la page est chargée pour la première fois (requête non-AJAX), la page entière, y compris tous les snippets, est chargée. Lorsque l'utilisateur interagit avec la page (par exemple, lorsqu'il clique sur un bouton, soumet un formulaire, etc.), au lieu de charger la page entière, une requête AJAX est effectuée. Le code du présentateur exécute l'action et décide quels extraits doivent être mis à jour. Nette rend ces extraits et les envoie sous la forme d'un tableau JSON. Le code de traitement du navigateur réintègre alors les extraits reçus dans la page. Par conséquent, seul le code des extraits modifiés est transféré, ce qui permet d'économiser de la bande passante et d'accélérer le chargement par rapport au transfert de l'ensemble du contenu de la page.
Naja
La bibliothèque Naja est utilisée pour gérer les snippets du côté du navigateur. Installez-la en tant que paquetage node.js (pour une utilisation avec des applications telles que Webpack, Rollup, Vite, Parcel, et d'autres) :
npm install naja
… ou insérez-la directement dans le modèle de page :
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Vous devez d'abord initialiser la bibliothèque :
naja.initialize();
Pour faire d'un lien ordinaire (signal) ou d'une soumission de formulaire une requête AJAX, il suffit de marquer le lien, le
formulaire ou le bouton correspondant avec 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>
Redessiner des extraits
Chaque objet de la classe Control (y compris le Presenter
lui-même) garde une trace des changements survenus qui nécessitent son redécoupage. La méthode redrawControl()
est utilisée à cette fin.
public function handleLogin(string $user): void
{
// après la connexion, il est nécessaire de redessiner la partie concernée
$this->redrawControl();
//...
}
Nette permet également un contrôle plus fin de ce qui doit être redessiné. La méthode susmentionnée peut prendre le nom de l'extrait comme argument. Ainsi, il est possible d'invalider (c'est-à-dire de forcer un nouveau dessin) au niveau de la partie du modèle. Si l'ensemble du composant est invalidé, chaque extrait est également redessiné :
// invalide l'extrait "header" (en-tête)
$this->redrawControl('header');
Bribes dans Latte
L'utilisation des snippets dans Latte est extrêmement simple. Pour définir une partie du modèle comme un extrait, il suffit
de l'entourer des balises {snippet}
et {/snippet}
:
{snippet header}
<h1>Hello ... </h1>
{/snippet}
Le snippet crée un élément <div>
dans la page HTML avec un id
spécialement généré.
Lorsqu'un extrait est redessiné, le contenu de cet élément est mis à jour. Par conséquent, lors du rendu initial de la page,
tous les snippets doivent également être rendus, même s'ils sont initialement vides.
Vous pouvez également créer un extrait avec un élément autre que <div>
à l'aide d'un attribut n: :
<article n:snippet="header" class="foo bar">
<h1>Hello ... </h1>
</article>
Zones d'échantillonnage
Les noms des snippets peuvent également être des expressions :
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
De cette manière, nous obtiendrons plusieurs snippets comme item-0
, item-1
, etc. Si nous devions
invalider directement un extrait dynamique (par exemple, item-1
), rien ne serait redessiné. La raison en est que les
snippets fonctionnent comme de véritables extraits et qu'ils sont les seuls à être rendus directement. Cependant, dans le
modèle, il n'y a pas techniquement d'extrait nommé item-1
. Il n'apparaît que lors de l'exécution du code
environnant de l'extrait, dans ce cas, la boucle foreach. Par conséquent, nous marquerons la partie du modèle qui doit être
exécutée avec la balise {snippetArea}
:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
Et nous redessinerons à la fois l'extrait individuel et l'ensemble de la zone globale :
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Il est également essentiel de s'assurer que le tableau $items
ne contient que les éléments qui doivent être
redessinés.
Lors de l'insertion d'un autre modèle dans le modèle principal à l'aide de la balise {include}
, qui contient
des extraits, il est nécessaire d'envelopper à nouveau le modèle inclus dans une balise snippetArea
et d'invalider
à la fois l'extrait et la zone :
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* inclus.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Les snippets dans les composants
Vous pouvez créer des snippets dans les composants, et Nette les
redessinera automatiquement. Cependant, il y a une limitation spécifique : pour redessiner les snippets, il faut appeler la
méthode render()
sans aucun paramètre. Par conséquent, passer des paramètres dans le modèle ne fonctionnera
pas :
OK
{control productGrid}
will not work:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Envoi de données utilisateur
En plus des snippets, vous pouvez envoyer des données supplémentaires au client. Il suffit de les écrire dans l'objet
payload
:
public function actionDelete(int $id): void
{
//...
if ($this->isAjax()) {
$this->payload->message = 'Success';
}
}
Paramètres d'envoi
Lorsque nous envoyons des paramètres au composant via une requête AJAX, qu'il s'agisse de paramètres de signal ou de
paramètres persistants, nous devons fournir leur nom global, qui contient également le nom du composant. Le nom complet du
paramètre renvoie la méthode getParameterId()
.
let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
Une méthode handle avec les paramètres correspondants dans le composant :
public function handleFoo(int $bar): void
{
}