En la publicación pasada habíamos visto cómo renderizar un array de objetos en un documento HTML, mediante el siguiente array:
const tareas = [
{titulo: "Tarea 1", completa: false},
{titulo: "Tarea 2", completa: false},
{titulo: "Tarea 3", completa: false},
{titulo: "Tarea 4", completa: true},
{titulo: "Tarea 5", completa: false},
{titulo: "Tarea 6", completa: true},
{titulo: "Tarea 7", completa: false},
{titulo: "Tarea 8", completa: true},
{titulo: "Tarea 9", completa: true},
{titulo: "Tarea 10", completa: false}
];
Luego generábamos la lista mediante una interfaz de usuario:
En esta ocasión veremos cómo realizar un proceso similar, pero mediante el consumo de Ajax.
Ajax
Asynchronous JavaScript And XML (JavaScript asíncrono y XML) Es una tecnología que permite enviar peticiones desde el cliente (navegador web) al servidor sin recargar la página.
Método fetch
Es una alternativa a XMLHttpRequest mucho más fácil de usar:
fetch('url')
.then(response => response.json())
.then(data => console.log(data));
Para realizar este proceso debemos consumir un API, pero ¿qué es una API?
Una application programming interface (o interfaz de programación de aplicaciones) nos permite consumir e intercambiar información.
¿Qué necesito para hacer un API?
Muchos más adelante, cuando veamos Node.js, vamos a aprender a crear un API ya que esto implica el uso de alguna tecnología de backend como, Java, PHP, Python o .NET, o por supuesto Javascript, pero en el lado del servidor.
Independientemente de esto como desarrolladores del fronted, sin embargo, debemos aprender a utilizar un API.
jsonplaceholder
El uso de un API muchas veces requiere de ciertos temas de seguridad que no vamos a ver en esta ocasión, aunque sí lo haremos más adelante.
Json place holder nos provee de un API pública que nos servirá para poder probar el funcionamiento de este tipo de tecnologías:
Podemos probar esto ejecutando como script:
fetch('https://jsonplaceholder.typicode.com/todos')
.then(response => response.json())
.then(json => {
console.log(json);
});
Si abrimos la consola de Javascript veremos que esto nos devuelve un array de objetos, en donde cada elemento tendrá una esctructura similar a:
Podemos reutilizar el ejemplo que habíamos hecho en la publicación pasada, pero tendremos que reemplazar las propiedades de «titulo» y «completa», por «title» y «completed» respectivamente.
El código HTML quedará igual:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ejemplo de DOM</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1>Ejemplo de listas</h1>
<select id="select_filtro" class="form-select mb-3">
<option value=""> Mostrar todo </option>
<option value="completas"> Mostrar completas </option>
<option value="incompletas"> Mostrar incompletas </option>
</select>
<button type="button" id="btn_mostrar_tareas" class="btn btn-primary mb-3"> Mostrar lista de tareas </button>
<ul id="ul_lista" class="list-group">
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<script src="js/scripts.js"></script>
</body>
</html>
Luego vamos a reemplazar partes del código, en primer lugar, el array tareas lo vamos a eliminar, porque quien nos proveerá esa información será el API.
Empecemos por referenciar los elementos que vamos a usar:
const ul_lista = document.querySelector("#ul_lista");
const select_filtro = document.querySelector("#select_filtro");
const btn_mostrar_tareas = document.querySelector("#btn_mostrar_tareas");
Luego vamos a cambiar la función que renderizaba la lista, reemplazando las propiedades que mencionamos anteriormente:
const renderizarTareas = (p_lista) => {
//Limpiamos el <ul> por si ya había elementos previamente.
ul_lista.innerHTML = "";
//Recorremos un array que se va a pasar como parámetro en la función.
p_lista.forEach(item => {
//Creamos un elemento <li>
let li = document.createElement("li");
//Le agregamos una clase con el nombre "list-group-item"
li.classList.add("list-group-item");
//Verificamos si la tarea está completa.
if(item.completed){
//Si lo está, le agregamos la clase list-group-item-success.
li.classList.add("list-group-item-success");
}else{
//Si NO lo está, le agregamos la clase list-group-item-danger.
li.classList.add("list-group-item-danger");
}
//Insertamos el título de la tarea en la etiqueta <li>
li.innerText = item.title;
//Agregamos la etiqueta <li> dentro del <ul>
ul_lista.appendChild(li);
});
}
Y por último vamos a cambiar la función mostrarTareas() para que llame al API:
const mostrarTareas = () => {
//Llama al API de Json place holder.
fetch("https://jsonplaceholder.typicode.com/todos")
//Una vez obtenida la respuesta la convierte en JSON.
.then(response => response.json())
//Una vez convertida en JSON renderiza a código HTML.
.then(json => {
renderizarTareas(json);
});
}
btn_mostrar_tareas.addEventListener("click", mostrarTareas);
Método then()
Es aquí en donde entramos en un terreno que al principio puede resultar difícil de entender, y tal vez después también un poco… Pero con paciencia lo lograremos ????
Más adelante veremos algo llamado programación asíncrona, por lo cual no profundizaremos demasiado en esto ahora, pero antes de continuar es importante entender una cosa:
Al llamar a un API, y dependiendo de lo bien que funcione ésta, lo complejo que sea el proceso de respuesta y la velocidad de la red; puede tardar una x cantidad de tiempo.
Pensá cuando vas a un supermercado y pagás con una billetera virtual ¿cuánto tiempo se tarda desde el momento en que el usuario presioná el botón comprar a la respuesta de pago? Tal vez una milésima de segundos, medio minuto o lo suficiente para que la gente que está detrás tuyo en la fila se acuerde de tu familia. Pero lo que está claro es que no se hace de inmediato.
Ese tipo de bloques de código que no están sincronizados y que se completarán en etapas, son procesos asíncronos.
El método then() es un método que va a dispararse cuando el API de Json place holder, o de cualquier otro servicio devuelva una respuesta.
En este caso nosotros tendremos que pedirle mediante un callback que una vez completo se resuelva:
Llama al API de Json place holder.
fetch("https://jsonplaceholder.typicode.com/todos")
//Una vez obtenida la respuesta la convierte en JSON.
.then(response => response.json())
//Una vez convertida en JSON renderiza a código HTML.
.then(json => {
renderizarTareas(json);
});
Método catch
Pero en la programación asíncrona nunca existen caminos felices.
Volviendo al tema que mencionamos anteriormente, si realizamos una compra desde una billetera virtual pueden pasar dos cosas:
- Que la compra se complete
- Que la compra NO se complete
La petición al API puede fallar: el usuario pierde la conexión a internet, el servidor del API se puede caer, la URL puede estar mal escrita, etc.
Por ejemplo podemos probar esto escribiendo una URL falsa, que obligará a disparar el método catch, indicando que hubo un error:
mostrarTareas = () => {
fetch("pepito")
.then(response => response.json())
.then(json => {
renderizarTareas(json);
})
//Hubo un problema.
.catch(error => {
console.log(error);
alert("Surgió un error");
})
}
Filtros
En la publicación pasada habíamos visto cómo filtrar un array mediante el método filter()
Por suerte el API de Json place holder nos da la posibilidad de filtrar la información desde la URL, mediante variables en la misma:
URL | Resultado |
---|---|
https://jsonplaceholder.typicode.com/todos | Nos devuelve todos los resultados |
https://jsonplaceholder.typicode.com/todos?completed=true | Nos devuelve los resultados filtrados por la propiedad completed true |
https://jsonplaceholder.typicode.com/todos?completed=false | Nos devuelve los resultados filtrados por la propiedad completed false |
El código finalmente quedará:
const ul_lista = document.querySelector("#ul_lista");
const select_filtro = document.querySelector("#select_filtro");
const btn_mostrar_tareas = document.querySelector("#btn_mostrar_tareas");
const renderizarTareas = (p_lista) => {
//Limpiamos el <ul> por si ya había elementos previamente.
ul_lista.innerHTML = "";
//Recorremos un array que se va a pasar como parámetro en la función.
p_lista.forEach(item => {
//Creamos un elemento <li>
let li = document.createElement("li");
//Le agregamos una clase con el nombre "list-group-item"
li.classList.add("list-group-item");
//Verificamos si la tarea está completa.
if(item.completed){
//Si lo está, le agregamos la clase list-group-item-success.
li.classList.add("list-group-item-success");
}else{
//Si NO lo está, le agregamos la clase list-group-item-danger.
li.classList.add("list-group-item-danger");
}
//Insertamos el título de la tarea en la etiqueta <li>
li.innerText = item.title;
//Agregamos la etiqueta <li> dentro del <ul>
ul_lista.appendChild(li);
});
}
mostrarTareas = () => {
let url = 'https://jsonplaceholder.typicode.com/todos';
if(select_filtro.value == "completas"){
url += '?completed=true';
}else if(select_filtro.value == "incompletas"){
url += '?completed=false';
}
//Llama al API de Json place holder.
fetch(url)
//Una vez obtenida la respuesta la convierte en JSON.
.then(response => response.json())
//Una vez convertida en JSON renderiza a código HTML.
.then(json => {
renderizarTareas(json);
})
//Hubo un problema.
.catch(error => {
console.log(error);
alert("Surgió un error");
})
}
btn_mostrar_tareas.addEventListener("click", mostrarTareas);