Simplificando el desarrollo web dinámico con Django y HTMX (Parte 4) : ¿Cómo se envía el payload?

Al realizar solicitudes Ajax utilizando fetch de JavaScript, es común enviar un payload construyéndolo directamente con JSON.stringify() para las solicitudes POST.

Algo así:

fetch("/api/todos/", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-CSRFToken": csrftoken
  },
  body: JSON.stringify({
    title: "장보기",
    done: false,
    priority: 3
  })
})

Entonces, ¿qué ocurre con HTMX? ¿Cómo exactamente hx-post envía datos al servidor? Muchos ejemplos solo muestran funcionalidades muy básicas, lo que hace sorprendentemente difícil encontrar ejemplos prácticos para enviar payloads complejos.

En esta publicación, vamos a desglosar completamente los métodos de envío de datos en HTMX y cómo procesarlos en el servidor.

Diagrama del flujo de datos de HTMX en Django


Métodos de envío de datos en HTMX

La forma de enviar payloads con fetch y los métodos de envío de datos de hx-post son bastante diferentes.

Con fetch, el desarrollador crea directamente un objeto y lo convierte a JSON para incluirlo en el cuerpo de la solicitud. Por otro lado, HTMX, por defecto, recopila los valores presentes en el DOM y los envía.

La filosofía de HTMX no es "construir directamente objetos JS", sino más bien "recopilar valores de elementos HTML para crear parámetros de solicitud". Para esto, generalmente se utilizan los siguientes tres métodos:

1) Recopilar y enviar valores basados en formularios

Este es el método más fiel a HTML y el que mejor se adapta a Django.

<form hx-post="/todos/create/" hx-target="#todo-list">
    <input type="text" name="title" placeholder="제목">
    <input type="number" name="priority" value="3">
    <input type="hidden" name="done" value="false">
    <button type="submit">등록</button>
</form>

En este caso, HTMX recopila los valores de los campos de entrada dentro del formulario y los incluye en la solicitud. La codificación predeterminada es URL-encoded form data, al igual que en una presentación de formulario normal.

En una vista de Django, puedes recibirlos como de costumbre:

def create_todo(request):
    title = request.POST.get("title")
    priority = request.POST.get("priority")
    done = request.POST.get("done")

2) Incluir valores de otros elementos sin formulario: hx-include

Se usa cuando deseas adjuntar hx-post a un solo botón y enviar solo ciertos valores de entrada que están separados en el DOM.

<input type="text" id="title" name="title" placeholder="제목">
<input type="number" id="priority" name="priority" value="3">

<button hx-post="/todos/create/"
        hx-include="#title, #priority"
        hx-target="#todo-list">
    등록
</button>

hx-include incluye los valores de los elementos especificados en la solicitud. Permite crear un resultado similar a un payload sin necesidad de envolver todo en un <form>, lo que es muy útil cuando hay pocos campos de entrada.

3) Añadir valores ocultos o calculados: hx-vals

Esta es la funcionalidad más cercana al concepto de construir directamente parte del objeto payload en fetch.

<button hx-post="/todos/create/"
        hx-vals='{"title": "장보기", "done": false, "priority": 3}'
        hx-target="#todo-list">
    빠른 등록
</button>

hx-vals añade parámetros adicionales a la solicitud. Por defecto, utiliza sintaxis JSON como se muestra arriba, pero puedes usar el prefijo js: para enviar valores calculados dinámicamente con JavaScript.

<input type="text" id="title" placeholder="제목">

<button hx-post="/todos/create/"
        hx-vals='js:{title: document.querySelector("#title").value, done: false, priority: 3}'
        hx-target="#todo-list">
    등록
</button>

En este caso, Django también lee los datos a través de request.POST. Esto se debe a que hx-vals define valores con una sintaxis similar a JSON, pero no convierte el cuerpo de la solicitud en JSON.


Advertencia: hx-vals no es un "JSON Payload"

Esta es la parte más confusa para quienes se inician en HTMX.

<button hx-post="/my-url/" hx-vals='{"a":1, "b":2}'>

Este código no significa "enviar un objeto JSON como cuerpo", sino más bien "añadir a=1&b=2 a los parámetros de la solicitud". Es decir, el servidor debe seguir procesándolo como datos de formulario.

¿Realmente quieres enviar un cuerpo JSON?

Si es absolutamente necesario utilizar el formato application/json, como en fetch(... JSON.stringify(payload)), entonces necesitarás la extensión json-enc de HTMX.

Configúralo como se indica en la documentación oficial:

<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/json-enc.js"></script>

<button hx-post="/api/todos/"
        hx-ext="json-enc"
        hx-vals='{"title": "장보기", "done": false, "priority": 3}'
        hx-target="#result">
    JSON으로 전송
</button>

Ahora sí, finalmente tenemos el método de payload JSON al que estamos acostumbrados. En este caso, la vista de Django encontrará request.POST vacío, por lo que deberá leer directamente request.body.

import json

def create_todo_api(request):
    data = json.loads(request.body)
    title = data.get("title")
    # ... procesar lógica ...
    return JsonResponse({"ok": True})

HTMX y DRF tienen filosofías distintas, pero no es necesario fusionarlos.

Aunque es posible enviar JSON usando una extensión, la pregunta sigue siendo si esta es la "manera HTMX". Esto se debe a que es un punto de colisión entre dos filosofías: "centrada en datos (DRF)" y "centrada en hipermedia (HTMX)".

Si has decidido usar HTMX correctamente, quizás sea necesario alejarse momentáneamente de la mentalidad centrada en datos. Enviar valores con <form>, hx-include, hx-vals y que el servidor los reciba con request.POST para luego devolver un fragmento de HTML en lugar de JSON: ahí es donde HTMX brilla más.

¿Pero qué pasa si los serializadores de DRF son demasiado valiosos para renunciar a ellos?

Los serializadores de DRF son realmente potentes. ¿Deberíamos abandonar esta conveniente herramienta de validación y volver a la "rutina" de request.POST.get() solo por usar HTMX?

Afortunadamente, los serializadores de DRF validan de manera excelente no solo JSON, sino también datos de formulario. Dado que esta sección podría ser extensa, en la próxima entrega abordaremos en profundidad la "coexistencia de HTMX y los serializadores de DRF".


Conclusión

Aquí un resumen de lo aprendido hoy:

  1. HTMX, por defecto, recopila valores del DOM. Su enfoque es diferente al de fetch, que construye directamente objetos payload.
  2. Tres métodos de envío: <form> para enviar todo, hx-include para seleccionar elementos, y hx-vals para añadir valores.
  3. El formato predeterminado son los datos de formulario. Ten en cuenta que, aunque hx-vals usa sintaxis JSON, no significa que el cuerpo de la solicitud sea JSON.
  4. Si necesitas JSON, usa la extensión json-enc. Sin embargo, recuerda que la esencia de HTMX reside en el intercambio de fragmentos HTML.

¡En la próxima ocasión, volveremos con métodos para integrar la conveniencia de DRF en HTMX!

Artículos relacionados