AJAX y fragmentos
En la era de las aplicaciones web modernas, donde la funcionalidad a menudo se divide entre el servidor y el navegador, AJAX es un elemento de conexión esencial. ¿Qué posibilidades nos ofrece Nette Framework en esta área?
- envío de partes de la plantilla, los llamados fragmentos (snippets)
- paso de variables entre PHP y JavaScript
- herramientas para depurar peticiones AJAX
Petición AJAX
Una petición AJAX no difiere en principio de una petición HTTP clásica. Se llama a un presenter con ciertos parámetros. Y depende del presenter cómo reaccionará a la petición: puede devolver datos en formato JSON, enviar parte del código HTML, un documento XML, etc.
En el lado del navegador, inicializamos la petición AJAX usando la función fetch()
:
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
// procesar la respuesta
});
En el lado del servidor, reconocemos una petición AJAX con el método $httpRequest->isAjax()
del servicio que encapsula la petición HTTP. Para la detección, utiliza la cabecera HTTP
X-Requested-With
, por lo que es importante enviarla. Dentro del presenter, se puede usar el método
$this->isAjax()
.
Si desea enviar datos en formato JSON, use el método sendJson()
. El método también
finaliza la actividad del presenter.
public function actionExport(): void
{
$this->sendJson($this->model->getData);
}
Si planea responder con una plantilla especial diseñada para AJAX, puede hacerlo de la siguiente manera:
public function handleClick($param): void
{
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
// ...
}
Fragmentos (Snippets)
El recurso más potente que ofrece Nette para conectar el servidor con el cliente son los fragmentos (snippets). Gracias a ellos, puedes convertir una aplicación ordinaria en una AJAX con un esfuerzo mínimo y unas pocas líneas de código. El ejemplo Fifteen demuestra cómo funciona todo, cuyo código puedes encontrar en GitHub.
Los fragmentos, o recortes, permiten actualizar solo partes de la página, en lugar de recargar toda la página. Esto no solo es más rápido y eficiente, sino que también proporciona una experiencia de usuario más cómoda. Los fragmentos pueden recordarle a Hotwire para Ruby on Rails o Symfony UX Turbo. Curiosamente, Nette introdujo los fragmentos 14 años antes.
¿Cómo funcionan los fragmentos? En la primera carga de la página (petición no AJAX), se carga toda la página, incluidos todos los fragmentos. Cuando el usuario interactúa con la página (por ejemplo, hace clic en un botón, envía un formulario, etc.), en lugar de cargar toda la página, se realiza una petición AJAX. El código en el presenter ejecuta la acción y decide qué fragmentos deben actualizarse. Nette renderiza estos fragmentos y los envía en forma de array en formato JSON. El código de manejo en el navegador inserta los fragmentos recibidos de nuevo en la página. Por lo tanto, solo se transfiere el código de los fragmentos modificados, lo que ahorra ancho de banda y acelera la carga en comparación con la transferencia del contenido de toda la página.
Naja
Para manejar los fragmentos en el lado del navegador, se utiliza la librería Naja. Instálela como un paquete node.js (para usar con aplicaciones Webpack, Rollup, Vite, Parcel y otras):
npm install naja
…o insértela directamente en la plantilla de la página:
<script src="https://unpkg.com/naja@2/dist/Naja.min.js"></script>
Primero, es necesario inicializar la librería:
naja.initialize();
Para convertir un enlace ordinario (señal) o el envío de un formulario en una petición AJAX, basta con marcar el enlace,
formulario o botón correspondiente con la clase ajax
:
<a n:href="go!" class="ajax">Ir</a>
<form n:name="form" class="ajax">
<input n:name="submit">
</form>
o
<form n:name="form">
<input n:name="submit" class="ajax">
</form>
Redibujar fragmentos
Cada objeto de la clase Control (incluido el propio Presenter)
registra si se han producido cambios que requieran su redibujado. Para ello se utiliza el método
redrawControl()
:
public function handleLogin(string $user): void
{
// después de iniciar sesión, es necesario redibujar la parte relevante
$this->redrawControl();
// ...
}
Nette permite un control aún más fino de lo que se debe redibujar. De hecho, el método mencionado puede aceptar el nombre del fragmento como argumento. Por lo tanto, es posible invalidar (léase: forzar el redibujado) a nivel de partes de la plantilla. Si se invalida todo el componente, también se redibujará cada uno de sus fragmentos:
// invalida el fragmento 'header'
$this->redrawControl('header');
Fragmentos en Latte
Usar fragmentos en Latte es extremadamente fácil. Si desea definir una parte de la plantilla como un fragmento, simplemente
envuélvala con las etiquetas {snippet}
y {/snippet}
:
{snippet header}
<h1>Hola ... </h1>
{/snippet}
El fragmento crea un elemento <div>
en la página HTML con un id
especial generado. Al
redibujar el fragmento, se actualiza el contenido de este elemento. Por lo tanto, es necesario que en la renderización inicial de
la página se rendericen también todos los fragmentos, aunque puedan estar vacíos al principio.
También puede crear un fragmento con un elemento distinto de <div>
usando un n:attribute:
<article n:snippet="header" class="foo bar">
<h1>Hola ... </h1>
</article>
Áreas de fragmentos
Los nombres de los fragmentos también pueden ser expresiones:
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
De esta manera, creamos varios fragmentos item-0
, item-1
, etc. Si invalidáramos directamente un
fragmento dinámico (por ejemplo, item-1
), no se redibujaría nada. La razón es que los fragmentos realmente
funcionan como recortes y solo se renderizan ellos mismos directamente. Sin embargo, en la plantilla no hay realmente ningún
fragmento llamado item-1
. Este se crea solo al ejecutar el código alrededor del fragmento, es decir, el bucle
foreach. Por lo tanto, marcamos la parte de la plantilla que se debe ejecutar usando la etiqueta {snippetArea}
:
<ul n:snippetArea="itemsContainer">
{foreach $items as $id => $item}
<li n:snippet="item-{$id}">{$item}</li>
{/foreach}
</ul>
Y hacemos que se redibuje tanto el fragmento en sí como toda el área padre:
$this->redrawControl('itemsContainer');
$this->redrawControl('item-1');
Al mismo tiempo, es conveniente asegurarse de que el array $items
contenga solo los elementos que se deben
redibujar.
Si incluimos otra plantilla que contiene fragmentos en la plantilla usando la etiqueta {include}
, es necesario
incluir de nuevo la inclusión de la plantilla en snippetArea
e invalidarla junto con el fragmento:
{snippetArea include}
{include 'included.latte'}
{/snippetArea}
{* included.latte *}
{snippet item}
...
{/snippet}
$this->redrawControl('include');
$this->redrawControl('item');
Fragmentos en componentes
También puede crear fragmentos en componentes y Nette los
redibujará automáticamente. Pero hay una cierta limitación: para redibujar los fragmentos, llama al método
render()
sin parámetros. Por lo tanto, no funcionará pasar parámetros en la plantilla:
OK
{control productGrid}
no funcionará:
{control productGrid $arg, $arg}
{control productGrid:paginator}
Envío de datos de usuario
Junto con los fragmentos, puede enviar cualquier otro dato al cliente. Simplemente escríbalos en el objeto
payload
:
public function actionDelete(int $id): void
{
// ...
if ($this->isAjax()) {
$this->payload->message = 'Éxito';
}
}
Paso de parámetros
Si enviamos parámetros a un componente mediante una petición AJAX, ya sean parámetros de señal o parámetros persistentes,
debemos indicar en la petición su nombre global, que también incluye el nombre del componente. El nombre completo del parámetro
lo devuelve el método getParameterId()
.
let url = new URL({link //foo!});
url.searchParams.set({$control->getParameterId('bar')}, bar);
fetch(url, {
headers: {'X-Requested-With': 'XMLHttpRequest'},
})
Y el método handle con los parámetros correspondientes en el componente:
public function handleFoo(int $bar): void
{
}