Javascript, parte 12: Renderizar listas

Al genera HTML dinámico, lo mejor sería guardar los datos en un arreglo y de esta manera será más fácil poder trabajar esta información.

Más adelante vamos a ver cómo consumir un API, y cómo poder traer información de forma externa.

De momento vamos a realizar un ejemplo sencillo con una lista de tareas con dos estados (completa true y completa false) Y a través de un filtro vamos a mostrar toda la lista de tareas o bien filtradas por tareas completas y tareas incompletas:

El código HTML será el siguiente:

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

En cuanto al código Javascript, primero vamos a tener un array de objetos:

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}
];

Cómo se mencionó en una publicación pasada, un array puede guardar cualquier información, y es muy común utilizar el formato JSON, porque de esta manera, como se puede ver en el ejemplo, podemos guardar el nombre de la tarea y también su estado.

También vamos a crear las referencias al botón el <select> para seleccionar el tipo, el botón para filtrar y el <ul> donde se va a mostrar el resultado:

const ul_lista = document.querySelector("#ul_lista");

const select_filtro = document.querySelector("#select_filtro");
const btn_mostrar_tareas = document.querySelector("#btn_mostrar_tareas");

Siguiendo el ejemplo de listas de bootstrap:

https://getbootstrap.com/docs/5.0/components/list-group/

Vamos a crear una función que agrega elementos <li> dentro del <ul>:

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.completa){
            //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.titulo;
        //Agregamos la etiqueta <li> dentro del <ul>
        ul_lista.appendChild(li);
    });
}

Propiedad classList

Notar en el código que a los elementos HTML podemos acceder a las clases mediante la propiedad classList:

//Le agregamos una clase con el nombre "list-group-item"
li.classList.add("list-group-item");
//Verificamos si la tarea está completa.
if(item.completa){
    //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");
}

El método add() agrega clases, también podemos usar remove() para eliminar clases de un elemento o contains() para saber si un elemento tiene una clase.

Filtrar información

En esta primera etapa, no vamos a filtrar la lista, por lo cual que cualquier cosa que hagamos siempre nos devolverá la lista completa.

También vamos a agregar el evento que va a llamar a esta función:

const mostrarTareas = () => {
    //Sin ningún filtro, guardamos en un array el array de tareas completo.
    let tareas_filtradas = tareas;
    //Llamamos a la función que dibuja todo el código HTML.
    renderizarTareas(tareas_filtradas);
}

btn_mostrar_tareas.addEventListener("click", mostrarTareas);

Método .filter()

El método .filter() es un método que poseen los arrays que nos devuelve un nuevo array, pero con aquellos elementos que cumplan determinados filtros.

Teniendo en cuenta el <select>:

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

Vamos a cambiar la función mostrarTareas() de modo que dependiendo de la opción seleccionada por el usuario se le pase a la función renderizarTareas() tres arrays distintos:

  • Las tareas completas
  • Las tareas incompletas
  • Sin filtros, tal cual como es el array tareas
const mostrarTareas = () => {
    let tareas_filtradas = [];
    if(select_filtro.value == "completas"){
        tareas_filtradas = tareas.filter(item => item.completa == true);
    }else if(select_filtro.value == "incompletas"){
        tareas_filtradas = tareas.filter(item => item.completa == false);
    }else{
        tareas_filtradas = tareas;
    }
    renderizarTareas(tareas_filtradas);
}

El código finalmente quedará:

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}
];

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.completa){
            //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.titulo;
        //Agregamos la etiqueta <li> dentro del <ul>
        ul_lista.appendChild(li);
    });
}

const mostrarTareas = () => {
    let tareas_filtradas = [];
    if(select_filtro.value == "completas"){
        tareas_filtradas = tareas.filter(item => item.completa == true);
    }else if(select_filtro.value == "incompletas"){
        tareas_filtradas = tareas.filter(item => item.completa == false);
    }else{
        tareas_filtradas = tareas;
    }
    renderizarTareas(tareas_filtradas);
}

btn_mostrar_tareas.addEventListener("click", mostrarTareas);