No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Proyecto: Eliminar elementos

21/23
Recursos

Aportes 11

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

o inicia sesión.

Algo interesante que se ve en esta clase es cómo cada vez que se hace algún cambio a la lista es necesario llamar a la función que vacía la tabla y luego la que hace el render de los items. Esto sucede porque se está usando jQuery como librería de renderizado.

Otras librerías (o frameworks) como React, Angular o Vue no requieren el renderizado manual con cada cambio de estado porque utilizan un paradigma de programación llamado reactivo basado en el patrón de diseño de Observers. Entidades de ES que permiten detectar el cambio de “estado” y ejecutar el re-renderizado de los elementos del DOM cuyo valor/contenido dependan de éste. (bueno … más o menos) 😉

Algunas referencias sobre este tema:

La función .map de JavaScript puede recibir un segundo argumento que por convención se llama index y nos sirve para obtener la posición de un elemento dentro de nuestro array 👍.

Practica general del curso sin utilizar jQuery 🤢, para este caso solo se utilizo JS puro lo cual también se conoce como vanilla.

const compose = (...functions) => data => functions.reduceRight((value, func) => func(value), data);

// Se utiliza $ para identificar que una variable o constante hace referencia
// a elementos del DOM
const $DESCRIPTION = document.getElementById("description");
const $CALORIAS = document.getElementById("calorias");
const $CARBOIDRATOS = document.getElementById("carboidratos");
const $PROTEINAS = document.getElementById("proteinas");
const ERROR_CLASS = "is-invalid";
const SUCCESS_CLASS = "is-valid";

// Arreglo de elementos
let LIST = [];

// consiguiendo los atributes para las etiquetas HTML
const attributesToString = (obj = {}) => {
    const ENTRIES = Object.entries(obj);
    const ATTRS = [];
    for (let i = 0; i < ENTRIES.length; i += 1) {
        const AUX = ENTRIES[i];
        const ATTR = AUX[0];
        const VALUE = AUX[1];
        ATTRS.push(`${ATTR}="${VALUE}"`);
    }
    return ATTRS.join("");
};

// Creando etiquetas html con atributos
const createTagAttr = obj => (content = "") => {
    const { tag, attr } = obj;
    return `
      <${tag}${attr ? ` ${attributesToString(attr)}` : ""}>
        ${content}
      </${tag}>`;
};

// Generando tag de maner dinamica
const createTag = (tag) => {
    const TAG = (typeof tag === "string")
        ? createTagAttr({ tag })
        : createTagAttr(tag);
    return TAG;
};

const trashIcon = createTag({ tag: "i", attr: { class: "fas fa-trash-alt" } })("");
const tableCell = createTag("td");
const tableCells = items => items.map(tableCell).join("");

const tableRowTag = createTag("tr");
const tableRow = items => compose(tableRowTag, tableCells)(items);


// Asignacion de evntos siempre que se desea asignar un evento aun elemento del DOM
// es necesario utilizar addEventListener
$DESCRIPTION.addEventListener("keypress", () => $DESCRIPTION.classList.remove(ERROR_CLASS));
$CALORIAS.addEventListener("keypress", () => $CALORIAS.classList.remove(ERROR_CLASS));
$CARBOIDRATOS.addEventListener("keypress", () => $CARBOIDRATOS.classList.remove(ERROR_CLASS));
$PROTEINAS.addEventListener("keypress", () => $PROTEINAS.classList.remove(ERROR_CLASS));

// Limpiando inputs
const cleanInputs = () => {
    $DESCRIPTION.classList.remove(SUCCESS_CLASS);
    $DESCRIPTION.value = "";
    $CALORIAS.classList.remove(SUCCESS_CLASS);
    $CALORIAS.value = "";
    $CARBOIDRATOS.classList.remove(SUCCESS_CLASS);
    $CARBOIDRATOS.value = "";
    $PROTEINAS.classList.remove(SUCCESS_CLASS);
    $PROTEINAS.value = "";
};

// Actualizando totales
const updateTotals = () => {
    let carboidratos = 0;
    let proteinas = 0;
    let calorias = 0;
    // Por buenas praxticas se utiliza un forEach y cumple la mism funcion que se busca
    // practicamente una funcion de alto orden, no utiizo el map por que  map siempre regresa
    // algo y de momento no importa capturar lo que regresa la iteracion que realizamos.
    LIST.forEach((item) => {
        carboidratos += item.carboidratos;
        proteinas += item.proteinas;
        calorias += item.calorias;
    });
    document.querySelector("#totalCalorias").textContent = calorias;
    document.querySelector("#totalCarboidratos").textContent = carboidratos;
    document.querySelector("#totalProteinas").textContent = proteinas;
};

// Permite realizar el rendereo de los items
const renderItems = () => {
    // otra manera de poder obtener un elemento por el name TAG
    // solo que este metodo consigue un arreglo de elementos
    const $CONTAINER = document.getElementsByTagName("tbody")[0];
    $CONTAINER.innerHTML = "";
    const ROWS = LIST.map((item, index) => {
        const {
            calorias, description,
            carboidratos, proteinas,
        } = item;
        const removeButton = createTag({
            tag: "button",
            attr: {
                class: "btn btn-outline-danger",
                onclick: `removeItem(${index})`,
            },
        })(trashIcon);

        return tableRow([description, calorias, carboidratos, proteinas, removeButton]);
    });
    $CONTAINER.innerHTML = ROWS.join("");
};


// Agregar elementos en la lista
const addElement = () => {
    const newItem = {
        description: $DESCRIPTION.value,
        calorias: parseInt($CALORIAS.value, 10),
        carboidratos: parseInt($CARBOIDRATOS.value, 10),
        proteinas: parseInt($PROTEINAS.value, 10),
    };
    LIST.push(newItem);
    cleanInputs();
    updateTotals();
    renderItems();
};

// funcion para validar los inputs
const validateInputs = () => {
    // Por cuestion de buenas practicas el resultado de una condición
    // Ternaria tiene que ser asignada a una variable o constante
    const DESCRIPTION_CLASS = (($DESCRIPTION.value) ? SUCCESS_CLASS : ERROR_CLASS);
    $DESCRIPTION.classList.add(DESCRIPTION_CLASS);

    const CALORIAS_CLASS = (($CALORIAS.value) ? SUCCESS_CLASS : ERROR_CLASS);
    $CALORIAS.classList.add(CALORIAS_CLASS);

    const CARBOIDRATOS_CLASS = (($CARBOIDRATOS.value) ? SUCCESS_CLASS : ERROR_CLASS);
    $CARBOIDRATOS.classList.add(CARBOIDRATOS_CLASS);

    const PROTEINAS_CLASS = (($PROTEINAS.value) ? SUCCESS_CLASS : ERROR_CLASS);
    $PROTEINAS.classList.add(PROTEINAS_CLASS);

    if ($DESCRIPTION.value && $CALORIAS.value && $CARBOIDRATOS.value && $PROTEINAS.value) {
        addElement();
    }
};

// Permite remover un item seleccionado
const removeItem = (position) => {
    LIST = LIST.filter((item, index) => position !== index);
    updateTotals();
    renderItems();
};

Otra forma de eliminar el item de la lista es utilizando el metodo .filter() quedaria asi:

const removeItem = (index) => {
//list.splice(index,1) esto sera sustituido por el código debajo 
  list = list.filter((item,i) => i !== index)

  updateTotals()
  renderItems()
}

Para saber mas sobre este método : Array.prototype.filter() - MDN 👍🏻

Viendo este proyecto con JS Vanilla y con JQuery veo que los frameworks como react o vue o angular disminuyen demasiado el trabajo, lo hacen ver sencillo a comparacion de JS Vanilla

La función tag() que creamos me recuerda mucho a la función h() de Vue cuando usamos renderFunctions, solo que digamos que ahí la función es un poco más poderosa por así decirlo.

Y el usar esas funciones con JavaScript para crear elementos se resuelve con JSX xD

Muy interesante.

const compose = (...functions) => data =>
  functions.reduceRight((value, func) => func(value), data)

const attrsToString = (obj = {}) => Object.keys(obj).map(attr => `${attr}="${obj[attr]}"`).join(' ')

const tagAtrrs = obj => (content = "") => `<${obj.tag}${obj.attrs ? ' ' : ''}${attrsToString(obj.attrs)}>${content}</${obj.tag}>`

const tag = t => typeof t === 'string' ? tagAtrrs({tag: t}) : tagAtrrs(t)

const tableRowTag = tag('tr')
//const tableRow = items => tableRowTag(tableCells(items))
const tableRow = items => compose(tableRowTag, tableCells)(items)

const tableCell = tag('td')
const tableCells = items => items.map(tableCell).join('')

const trashIcon = tag({tag: 'i', attrs: {class: 'fas fa-trash-alt'}})('')

let description = $('#description')
let calories = $('#calories')
let carbs = $('#carbs')
let protein = $('#protein')

let list = []

description.keypress(() => {
  description.removeClass('is-invalid')
})

calories.keypress(() => {
  calories.removeClass('is-invalid')
})

carbs.keypress(() => {
  carbs.removeClass('is-invalid')
})

protein.keypress(() => {
  protein.removeClass('is-invalid')
})

const validateInputs = () => {

  description.val() ? '' : description.addClass('is-invalid')
  calories.val() ? '' : calories.addClass('is-invalid')
  carbs.val() ? '' : carbs.addClass('is-invalid')
  protein.val() ? '' : protein.addClass('is-invalid')

  if(
    description.val() &&
    calories.val() &&
    carbs.val() &&
    protein.val()
  ) add()
}

const add = () => {
  const newItem = {
    description: description.val(),
    calories: parseInt(calories.val()),
    carbs: parseInt(carbs.val()),
    protein: parseInt(protein.val())
  }
  list.push(newItem);
  cleanInputs()
  updateTotals()
  renderItems()
}

const updateTotals = () => {
  let calories = 0, carbs = 0, protein = 0

  list.map( item => {
    calories += item.calories;
    carbs += item.carbs;
    protein += item.protein;
  })

  $('#totalCalories').text(calories)
  $('#totalCarbs').text(carbs)
  $('#totalProtein').text(protein)
}

const cleanInputs = () => {
  description.val('')
  calories.val('')
  carbs.val('')
  protein.val('')
}

const renderItems = () => {
  $('tbody').empty()

  list.map((item, index ) => {

    const removeButton = tag({
      tag: 'button',
      attrs: {
        class: 'btn btn-outline-danger',
        onclick: `removeItem(${index})`
      }
    })(trashIcon)

    $('tbody').append(tableRow([item.description, item.calories, item.carbs, item.protein, removeButton]))
  })
}

const removeItem = (index) => {
  list.splice(index, 1)

  updateTotals()
  renderItems()
}

En el min 05:02 se observa cómo se cambia la sintaxis de la arrow function de item => { a (item, index) => {. Esto es porque al recibir solo un parámetro, no es necesario encerrarlo entre paréntesis, pero cuando es más de uno sí.

Simplifiqué el código de tableRow() y la función que genera el botón de borrado (buttonGenerator(idx)) para mayor claridad (par mí):

function tableRow(array) {
  return `<tr>
  <td>${array[0]}</td>
  <td>${array[1]}</td>
  <td>${array[2]}</td>
  <td>${array[3]}</td>
`
}
function buttonGenerator(idx){
  return `<td><button class="btn btn-outline-danger" onclick="removeItem(${idx})">
  <i class="fas fa-trash-alt"></i>
</button></td></tr>`;
}

Si estás guardando la lista en el localStorage, recuerda actualizarla al momento de eliminar el elemento de la lista

const removeItem = index => {
  list.splice(index, 1);
  updateTotals();
  renderItems();
  localStorage.setItem('list', JSON.stringify(list))
}

Cuando elimino el item me dice que deleteItem no esta definida pero si lo esta
ademas la funcion me aparece sombreada como si nunca la hubiese llamado

const deleteItem= (index)=>{
list.splice(index,1)

  updateTotals()  
  renderItems()
}



const renderItems = () => {

  const listWrapper = document.querySelector('tbody')

  listWrapper.innerHTML = ""  

  list.map((item,index) => {    
    const boton = tag({
      tag:"button",
      attrs:{
        class:"btn btn-outline-danger",
        onclick: `deleteItem(${index}) ` 
      },
    })(trashIcon)
    
    listWrapper.innerHTML += tableRow([
      item.description, 
      item.calories, 
      item.carbs, 
      item.protein,
      boton   
    ])
  })
}