Usar fetch con Servlets para enviar parámetros POST y devolver HTML
En el artículo anterior vimos que es posible construir aplicaciones web dinámicas con Java, Servlets, Thymeleaf y JavaScript sin necesidad de incorporar obligatoriamente un framework frontend como React o Angular.
En esta entrada vamos a llevar esa idea a un ejemplo práctico.
Si estamos trabajando con Servlets, Thymeleaf y JavaScript, podemos utilizar fetch() para enviar datos al servidor y hacer que el Servlet responda directamente con HTML.
En este ejemplo vamos a implementar una búsqueda dinámica de productos.
La idea es sencilla:
Usuario escribe el nombre
↓
JavaScript ejecuta fetch()
↓
Servlet recibe parámetros POST
↓
ProductoService realiza la búsqueda
↓
Servlet genera HTML
↓
fetch recibe el HTML
↓
Se reemplaza una sección de la página
La página inicial
La página de administración de productos se genera inicialmente utilizando Thymeleaf.
Tenemos un campo de búsqueda:
<input
oninput="cargarSeccion('BUSCAR_NOMBRE', this.value);"
type="text"
class="form-control"
name="nombre"
placeholder="Buscar por nombre">
Aquí estamos utilizando el evento:
oninput
Esto significa que cada vez que cambia el contenido del campo se ejecuta:
cargarSeccion('BUSCAR_NOMBRE', this.value);
El primer parámetro indica la operación que queremos realizar:
BUSCAR_NOMBRE
El segundo corresponde al texto que está escribiendo el usuario:
this.value
Por ejemplo, si el usuario escribe:
monitor
la llamada será equivalente a:
cargarSeccion("BUSCAR_NOMBRE", "monitor");
La función JavaScript
La función encargada de enviar los datos al Servlet es la siguiente:
function cargarSeccion(parametro1, parametro2) {
const datos = new URLSearchParams();
datos.append("ACC", parametro1);
datos.append("productoNom", parametro2);
fetch("adminProducto", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: datos
})
.then(response => {
if (!response.ok) {
throw new Error(
"Error HTTP: " + response.status
);
}
return response.text();
})
.then(html => {
document
.getElementById("contenido")
.innerHTML = html;
})
.catch(error => {
console.error(
"Error:",
error
);
});
}
Vamos a revisar qué está haciendo.
Crear los parámetros POST
Primero creamos un objeto:
const datos = new URLSearchParams();
URLSearchParams nos permite construir los parámetros que enviaremos al servidor.
Agregamos dos parámetros:
datos.append("ACC", parametro1);
datos.append("productoNom", parametro2);
La solicitud resultante conceptualmente contiene algo similar a:
ACC=BUSCAR_NOMBRE&productoNom=monitor
Es importante que estos nombres coincidan con los utilizados posteriormente por el Servlet.
Enviar la petición con fetch
La petición se realiza contra:
fetch("adminProducto", {
y especificamos que será mediante POST:
method: "POST"
También indicamos el tipo de contenido:
headers: {
"Content-Type":
"application/x-www-form-urlencoded"
}
Finalmente enviamos los parámetros:
body: datos
Por lo tanto, JavaScript está enviando una petición similar a un formulario tradicional, pero sin recargar completamente la página.
Recibir los parámetros en el Servlet
En el método doPost() del Servlet recuperamos primero la empresa almacenada en sesión:
int empresaid =
(int) request
.getSession()
.getAttribute("empresaid");
Luego creamos el servicio correspondiente:
ProductoService productoService =
new ProductoService(empresaid);
Y obtenemos la operación enviada desde JavaScript:
String acc =
request.getParameter("ACC");
El nombre:
ACC
es exactamente el mismo que utilizamos aquí:
datos.append("ACC", parametro1);
Utilizar un switch para determinar la operación
Ahora podemos decidir qué acción ejecutar:
ArrayList<Producto> listaProducto =
new ArrayList<>();
switch (acc) {
case "BUSCAR_NOMBRE" ->
listaProducto =
productoService.searchNom(
request.getParameter(
"productoNom"));
}
También podemos observar la correspondencia del segundo parámetro.
JavaScript envía:
datos.append(
"productoNom",
parametro2
);
y Java recupera:
request.getParameter(
"productoNom"
)
Por lo tanto tenemos:
JavaScript Servlet
ACC -----------> getParameter("ACC")
productoNom -----------> getParameter("productoNom")
Esta correspondencia es fundamental.
Buscar los productos
La búsqueda se delega a:
productoService.searchNom(...)
De esta manera el Servlet no necesita conocer cómo se realiza directamente la consulta SQL.
El flujo queda:
AdminProducto
↓
ProductoService
↓
DAO
↓
Base de datos
El resultado se almacena en:
ArrayList<Producto> listaProducto
Construir HTML desde Java
Una vez obtenidos los productos, el Servlet construye la tabla.
Primero creamos:
StringBuilder html =
new StringBuilder();
Luego agregamos la estructura:
html.append(
"<table class=\"table table-striped table-bordered\">"
);
html.append(
"<thead class=\"table-dark\">"
);
html.append("""
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Precio</th>
<th>Acciones</th>
</tr>
""");
html.append(
"</thead><tbody>"
);
Después recorremos los productos:
for (Producto producto : listaProducto) {
html.append("<tr>");
html.append("<td>")
.append(
String.valueOf(
producto.getProductoid()
)
)
.append("</td>");
html.append("<td>")
.append(
String.valueOf(
producto.getProductonom()
)
)
.append("</td>");
html.append("<td>")
.append(
String.valueOf(
producto.getPrecioventa()
)
)
.append("</td>");
html.append("<td>");
html.append(
"<button type=\"button\" " +
"class=\"btn btn-warning\">" +
"Editar</button>"
);
html.append(
"<button type=\"button\" " +
"class=\"btn btn-danger\">" +
"Eliminar</button>"
);
html.append("</td>");
html.append("</tr>");
}
Finalmente cerramos la tabla:
html.append(
"</tbody></table>"
);
El Servlet devuelve HTML
Aquí está uno de los puntos centrales del ejemplo.
La respuesta no será JSON.
Indicamos:
response.setContentType(
"text/html;charset=UTF-8"
);
y enviamos:
response
.getWriter()
.write(
html.toString()
);
Por lo tanto el navegador recibe directamente algo como:
<table class="table table-striped table-bordered">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Precio</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
<tr>
<td>12</td>
<td>Monitor Samsung</td>
<td>129990</td>
<td>
<button
type="button"
class="btn btn-warning">
Editar
</button>
<button
type="button"
class="btn btn-danger">
Eliminar
</button>
</td>
</tr>
</tbody>
</table>
¿Por qué usamos response.text()?
Como el Servlet está devolviendo:
text/html
JavaScript debe interpretar la respuesta como texto.
Por eso utilizamos:
return response.text();
No utilizamos:
response.json();
porque el servidor no está enviando JSON.
El resultado pasa entonces al siguiente bloque:
.then(html => {
La variable:
html
contiene todo el HTML construido por Java.
Reemplazar solamente una parte de la página
Nuestra página tiene este contenedor:
<div id="contenido">
<table class="table table-striped table-bordered">
<!-- tabla inicial generada por Thymeleaf -->
</table>
</div>
Cuando fetch() recibe el nuevo contenido, ejecutamos:
document
.getElementById("contenido")
.innerHTML = html;
Esto reemplaza únicamente el contenido dentro del div.
Por lo tanto:
antes
<div id="contenido">
tabla inicial
</div>
se transforma en:
después
<div id="contenido">
tabla devuelta por el Servlet
</div>
El resto de la página permanece intacto.
No hay una recarga completa.
La página inicial sigue utilizando Thymeleaf
Una característica interesante de esta implementación es que estamos utilizando dos formas de renderizado.
Al entrar inicialmente a la página, doGet() obtiene los productos:
ArrayList<Producto> arrayproducto =
objProductoService
.obtenerProductos();
Luego los agrega al contexto:
context.setVariable(
"productos",
arrayproducto
);
y Thymeleaf genera la vista:
String contenido =
templateEngine.process(
"/productoview/adminproducto",
context
);
En el HTML tenemos:
<tr th:each="producto : ${productos}">
Por lo tanto la primera carga funciona de forma tradicional:
GET
↓
Servlet
↓
Thymeleaf
↓
HTML completo
↓
Navegador
Pero la búsqueda funciona de forma asíncrona:
oninput
↓
fetch POST
↓
Servlet
↓
HTML parcial
↓
innerHTML
Estamos utilizando ambos modelos dentro de la misma aplicación.
No todo tiene que ser JSON
Cuando se habla de fetch(), muchas veces los ejemplos muestran inmediatamente una API que devuelve:
{
"id": 10,
"nombre": "Monitor",
"precio": 129990
}
Pero fetch() no exige JSON.
Puede recibir perfectamente:
JSON
XML
HTML
texto
En este caso resulta bastante práctico retornar directamente HTML porque nuestro objetivo final precisamente es actualizar una parte del documento.
Tampoco necesitamos un framework frontend
Para este comportamiento utilizamos solamente:
HTML
Bootstrap
JavaScript
fetch
Servlets
Thymeleaf
JDBC
El navegador ya tiene las herramientas necesarias para realizar una petición asíncrona.
No necesitamos obligatoriamente introducir:
React
Angular
Vue
Node.js
npm
para implementar una búsqueda dinámica.
Esto puede ser especialmente útil en sistemas administrativos, CRUD, POS y aplicaciones internas donde queremos mantener una arquitectura sencilla.
Un detalle: oninput genera varias peticiones
Existe un aspecto que debemos considerar.
Estamos utilizando:
oninput="cargarSeccion(
'BUSCAR_NOMBRE',
this.value
);"
Esto ejecuta una petición cada vez que el usuario modifica el campo.
Si escribimos:
MONITOR
podrían enviarse consultas para:
M
MO
MON
MONI
MONIT
MONITO
MONITOR
Para una aplicación pequeña esto puede ser perfectamente aceptable.
Si el número de usuarios o consultas aumenta, podemos implementar posteriormente un mecanismo llamado:
debounce
para esperar algunos milisegundos después de que el usuario deje de escribir antes de realizar la consulta.
Separación de responsabilidades
El ejemplo además mantiene una estructura bastante clara.
El HTML contiene:
estructura visual
JavaScript maneja:
interacción del navegador
El Servlet maneja:
la solicitud HTTP
ProductoService maneja:
la lógica relacionada con productos
y la capa de datos maneja:
SQL y persistencia
El flujo completo queda:
HTML
↓
JavaScript
↓
fetch
↓
AdminProducto Servlet
↓
ProductoService
↓
DAO
↓
MySQL
y la respuesta vuelve:
MySQL
↓
Producto
↓
Servlet
↓
HTML
↓
fetch
↓
innerHTML
Conclusión
Con unas pocas líneas de JavaScript podemos agregar comportamiento dinámico a una aplicación Java tradicional.
Nuestra función central fue simplemente:
function cargarSeccion(parametro1, parametro2) {
const datos = new URLSearchParams();
datos.append(
"ACC",
parametro1
);
datos.append(
"productoNom",
parametro2
);
fetch("adminProducto", {
method: "POST",
headers: {
"Content-Type":
"application/x-www-form-urlencoded"
},
body: datos
})
.then(response => {
if (!response.ok) {
throw new Error(
"Error HTTP: " +
response.status
);
}
return response.text();
})
.then(html => {
document
.getElementById(
"contenido"
)
.innerHTML = html;
})
.catch(error => {
console.error(
"Error:",
error
);
});
}
Dos parámetros entran al Servlet:
ACC
productoNom
el Servlet ejecuta la búsqueda y devuelve:
HTML
y JavaScript reemplaza:
<div id="contenido">
sin tener que recargar toda la página.
Es una forma bastante directa de agregar interactividad a una aplicación basada en Servlets sin transformar toda la arquitectura en una aplicación frontend separada.
Comentarios
Publicar un comentario