AJAX y fragmentos

En la era de las aplicaciones web modernas, donde la funcionalidad a menudo se extiende entre el servidor y el navegador, AJAX es un elemento de conexión esencial. ¿Qué opciones ofrece Nette Framework en este ámbito?

  • envío de partes de la plantilla, los llamados snippets
  • paso de variables entre PHP y JavaScript
  • herramientas para depurar peticiones AJAX

Solicitud AJAX

Una petición AJAX fundamentalmente no difiere de una petición HTTP clásica. Se llama a un presentador con parámetros específicos. Depende del presentador cómo responder a la petición – puede devolver datos en formato JSON, enviar una parte de código HTML, un documento XML, etc.

En el navegador, iniciamos una petición AJAX utilizando la función fetch():

fetch(url, {
	headers: {'X-Requested-With': 'XMLHttpRequest'},
})
.then(response => response.json())
.then(payload => {
	// tratamiento de la respuesta
});

En el lado del servidor, una petición AJAX es reconocida por el método $httpRequest->isAjax() del servicio que encapsula la petición HTTP. Utiliza la cabecera HTTP X-Requested-With, por lo que es imprescindible enviarla. Dentro del presentador, puede utilizar el método $this->isAjax().

Si desea enviar datos en formato JSON, utilice el método sendJson() método. El método también finaliza la actividad del presentador.

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');
	}
	//...
}

Recortes

La herramienta más potente que ofrece Nette para conectar el servidor con el cliente son los snippets. Con ellos, puedes convertir una aplicación ordinaria en una AJAX con el mínimo esfuerzo y unas pocas líneas de código. El ejemplo Fifteen demuestra cómo funciona todo, y su código puede encontrarse en GitHub.

Los snippets, o recortes, permiten actualizar sólo partes de la página, en lugar de recargarla entera. Esto es más rápido y eficiente, y también proporciona una experiencia de usuario más cómoda. Puede que los snippets te recuerden a Hotwire para Ruby on Rails o a Symfony UX Turbo. Curiosamente, Nette introdujo los snippets 14 años antes.

¿Cómo funcionan los fragmentos? Cuando se carga la página por primera vez (una 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 cargarse toda la página, se realiza una solicitud AJAX. El código del presentador ejecuta la acción y decide qué fragmentos deben actualizarse. Nette renderiza estos fragmentos y los envía en forma de matriz JSON. A continuación, el código de gestión del navegador vuelve a insertar en la página los fragmentos recibidos. Por lo tanto, sólo 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 de todo el contenido de la página.

Naja

Para manejar snippets en el lado del navegador, se utiliza la librería Naja. Instálala como un paquete node.js (para usar con aplicaciones como Webpack, Rollup, Vite, Parcel, y otras):

npm install naja

… o insértala directamente en la plantilla de la página:

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

Primero hay que inicializar la biblioteca:

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">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>

Redibujar fragmentos

Cada objeto de la clase Control (incluido el propio Presentador) mantiene un registro de si se han producido cambios que requieran su redibujado. Para ello se emplea el método redrawControl().

public function handleLogin(string $user): void
{
	// después de iniciar la sesión, es necesario volver a dibujar la parte pertinente
	$this->redrawControl();
	//...
}

Nette también permite un control más preciso de lo que hay que redibujar. El método mencionado puede tomar el nombre del fragmento como argumento. Así, es posible invalidar (es decir: forzar un redibujado) a nivel de la parte de la plantilla. Si se invalida todo el componente, también se redibujan todos sus fragmentos:

// invalida el fragmento de cabecera
$this->redrawControl('header');

Fragmentos en Latte

Utilizar snippets en Latte es extremadamente fácil. Para definir una parte de la plantilla como fragmento, basta con envolverla en las etiquetas {snippet} y {/snippet}:

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

El fragmento crea un elemento <div> en la página HTML con un id especialmente generado. Al redibujar un fragmento, se actualiza el contenido de este elemento. Por lo tanto, cuando la página se renderiza inicialmente, también deben renderizarse todos los fragmentos, aunque inicialmente puedan estar vacíos.

También puede crear un fragmento con un elemento distinto de <div> mediante un atributo n:attribute:

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

Áreas de recortes

Los nombres de los recortes también pueden ser expresiones:

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

De este modo, obtendremos varios fragmentos como item-0, item-1, etc. Si invalidáramos directamente un fragmento dinámico (por ejemplo, item-1), no se volvería a dibujar nada. La razón es que los fragmentos funcionan como verdaderos extractos y sólo ellos mismos se renderizan directamente. Sin embargo, en la plantilla no existe técnicamente un fragmento llamado item-1. Sólo aparece cuando se ejecuta el código que rodea al fragmento, en este caso, el bucle foreach. Por lo tanto, marcaremos la parte de la plantilla que necesita ser ejecutada con la etiqueta {snippetArea}:

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

Y redibujaremos tanto el fragmento individual como toda el área general:

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

También es esencial asegurarse de que la matriz $items contiene sólo los elementos que deben ser redibujados.

Cuando se inserta otra plantilla en la principal utilizando la etiqueta {include}, que tiene fragmentos, es necesario envolver de nuevo la plantilla incluida en un snippetArea e invalidar conjuntamente el fragmento y el área:

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

Fragmentos en componentes

Puede crear fragmentos dentro de los componentes y Nette los redibujará automáticamente. Sin embargo, hay una limitación específica: para redibujar fragmentos, llama al método render() sin ningún parámetro. Por lo tanto, pasar parámetros en la plantilla no funcionará:

OK
{control productGrid}

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

Envío de datos de usuario

Junto con los fragmentos, puede enviar cualquier dato adicional al cliente. Simplemente escríbalos en el objeto payload:

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

Envío de parámetros

Cuando enviamos parámetros al componente a través de una petición AJAX, ya sean parámetros de señal o parámetros persistentes, debemos proporcionar su nombre global, que también contiene el nombre del componente. El nombre completo del parámetro 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'},
})

Un método handle con los parámetros correspondientes en el componente:

public function handleFoo(int $bar): void
{
}
versión: 4.0