Items in an array disappear mysteriously - javascript

So I'm currently doing a Calorie Counter project that consists on giving the user the option to firstly, add items with the respective name and number of calories, remove items or update them when clicking on an edit icon next to the item, and finally removing all items at once.
The UI will basically display all the items that the user has added (including the name and the number of calories), where each item will have an edit icon next to it, and if the icon is clicked, it will give the user the option to edit them and delete them.
I still haven't gotten to the edit part because I'm currently stuck in the delete part.
Let's say I have 3 items in the list, when I click on the edit button and then delete, everything works out fine, the html element is deleted and it looks good. If I repeat the process one more time it still works, but when I repeat the process one last time, the problem happens.
For some reason, when I hit the edit button nothing happens, I've checked and apparently the item array is completely empty, even though I only deleted 2 out of the 3 items.
I've tried everything and I've been completely stuck for 3 days straight.
// Item Controller
const ItemController = function() {
// Hard coded items
data = [{
name: "Hamburguer",
id: 0,
calories: 1000
},
{
name: "Pasta",
id: 1,
calories: 700
},
{
name: "Apple",
id: 2,
calories: 70
}
]
return {
getItems: function() {
return data;
},
deleteAllItems: function() {
data.items = [];
UIController().clearItems();
},
getTotalCalories: function() {
totalCalories = 0;
this.getItems().forEach(item => {
totalCalories += parseInt(item.calories)
});
UIController().changeToTotalCalories(totalCalories);
},
removeSingleItem: function(item, li) {
// Getting the index of the item
indexItem = items.getItems().indexOf(item);
// Deleting item from array
items.getItems().splice(indexItem, 1);
// Deleting li item from UI
li.remove();
console.log(items.getItems());
}
}
};
const items = ItemController();
// UI controller
const UIController = function() {
return {
displayItems: function(itemsPresented) {
itemsPresented.forEach(function(item) {
itemList = document.getElementById("item-list");
itemList.innerHTML += `
<li class="collection-item" id="${item.id}">
<strong>${item.name}: </strong><em>${item.calories} calories</em>
<a href="#" class="secondary-content">
<i class="edit-item fa fa-pencil">
</i>
</a>
</li>
`;
})
},
clearItems: function() {
itemList = document.getElementById("item-list");
itemList.innerHTML = "";
items.getTotalCalories();
},
changeToTotalCalories: function(totalCalories) {
document.querySelector(".total-calories").textContent = totalCalories;
},
}
}
const uiCtrl = UIController();
// So when the page loads, the hard coded items can be represented
uiCtrl.displayItems(items.getItems());
// To delete all the items at once
clearAllBtn = document.querySelector(".clear-btn");
clearAllBtn.addEventListener("click", (e) => {
items.deleteItems();
e.preventDefault();
})
// Getting the li element (The one that has all the hard-coded items)
itemList = document.getElementById("item-list");
itemList.addEventListener("click", e => {
// Checking if the user is clicking the Edit Icon
if (e.target.classList.contains("edit-item")) {
items.getItems().forEach(item => {
li = e.target.parentElement.parentElement;
// Getting the item that has the edit icon that the user clicked
if (item.id === parseInt(e.target.parentElement.parentElement.id)) {
// Putting the name and the calories of the item that is being edited in the input fields
document.getElementById("item-name").value = item.name;
document.getElementById("item-calories").value = item.calories;
// Changing the buttons so when the user edits an item, they have the options Update and Delete
document.querySelector(".add-btn").style.display = "none";
document.querySelector(".update-btn").style.display = "block";
document.querySelector(".delete-btn").style.display = "block";
document.querySelector(".back-btn").style.display = "none";
// If the user clicks the delete button
document.querySelector(".delete-btn").addEventListener("click", e => {
// Changing all the buttons back to normal
document.querySelector(".add-btn").style.display = "block";
document.querySelector(".update-btn").style.display = "none";
document.querySelector(".delete-btn").style.display = "none";
document.querySelector(".back-btn").style.display = "block";
// Clearing out the input fields
document.getElementById("item-name").value = "";
document.getElementById("item-calories").value = "";
// Deleting item
items.removeSingleItem(item, li);
// Updating the calories
items.getTotalCalories();
e.preventDefault();
});
}
});
}
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<nav>
<div class="nav-wrapper blue">
<div class="container">
<a href="#" class="brand-logo center">
Tracalorie
</a>
<ul class="right">
<li>
<a href="#" class="clear-btn btn blue lighten-3">
Clear All
</a>
</li>
</ul>
</div>
</div>
</nav>
<br>
<div class="container">
<!-- Form Card -->
<div class="card">
<div class="card-content">
<span class="card-title">
Add Meal / Food Item
</span>
<form class="col">
<div class="row">
<div class="input-field col s6">
<input type="text" id="item-name" placeholder="Add item">
<label for="item-name">Meal</label>
</div>
<div class="input-field col s6">
<input type="text" id="item-calories" placeholder="Add calories">
<label for="item-calories">Calories</label>
</div>
<button class="add-btn btn blue darken-3"><i class="fa fa-plus"></i>
Add Meal</button>
<button style="display: none;" class="update-btn btn orange" display=><i class="fa fa-pencil-square-o"></i>
Update Meal</button>
<button style="display: none;" class="delete-btn btn red"><i class="fa fa-remove"></i>
Delete Meal</button>
<button class="back-btn btn grey pull-right"><i class="fa fa-chevron-circle-left"></i>
Back</button>
</div>
</form>
</div>
</div>
<!-- Calorie Count -->
<h3 class="center-align">Total Calories: <span class="total-calories">
0
</span></h3>
<!-- Item list -->
<ul id="item-list" class="collection">
</ul>
</div>

It seems like you add an eventListener to the delete button every single time a user clicks on the edit pencil. You never remove these eventListeners. So when the first edit is done, there is one delete event and one items gets deleted. The next time a user clicks on the edit button, a second event gets added to the same html element, thus two items gets deleted (both events will trigger one after the other). This becomes apparent when your hardcoded list would contain 10 items, you would see 1,2,3 and lastly 4 items disappear. I suggest you look into resetting/removing eventlisteners.

Related

adding quantity through buttons to a shopping cart item - Javascript

I am working on a personal project, just practicing on my own and getting to know and familiarized with the language.I am trying to learn by doing so I am creating a shopping cart and I came across a situation that I can not find a proper solution.
I have a list on my shopping cart already rendered of n items. When I 'click' on a plus button to add quantity to an item, it only adds to the quantity to the item that is first on the list and the 'price' of the second item.
Thank you in advance!
function renderCarrito(){
// carritoEl.innerHTML = "";
while(carritoEl.firstChild){
carritoEl.removeChild(carritoEl.firstChild)
};
carrito.forEach((item) =>{
carritoEl.innerHTML += `
<li class="buyItem">
<img src=${item.image}>
<div class="productCartInfo">
<h5 class="prdTitle">${item.title}</h5>
<h6>${item.price}</h6>
<div class="qnty">
<div>
<button class="mbtn">-</button>
<span class="countOfProduct">${item.cantidad}</span>
<button class="pbtn">+</button>
</div>
<div><i class="fas fa-times-circle dltProduct" data-id="${item.id}"></i></div>
</div>
</div>
</li>
`
const plusBtn = document.querySelectorAll('.pbtn');
const minusBtn = document.querySelectorAll('.mbtn');
const count = document.querySelector('.countOfProduct');
plusBtn.forEach(pbtn =>{
pbtn.addEventListener('click', () => {
item.cantidad++;
count.innerHTML = item.cantidad;
carritoSumaTotal();
})});
})
carritoSumaTotal();
}
``

card rendering function fails only after clicking on pagination button

I'm trying to figure out why my function that renders bootstrap 5 cards fails but only after first clicking a pagination button then selecting a drop down menu option. The pagination buttons work fine it seems and the dropdown menu also works fine but only if a pagination button is never clicked.
The problem seems to be in my displayList() function. After clicking a pagination button and clicking a dropdown option the correct array is passed into displayList() but let paginatedItems = items.slice(start, end); returns an empty array. Any help at all would be greatly appreciated.
I've my stripped down code so that hopefully it's easier to read and understand:
const setPosters = [{id:'1',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Sam Smith',category:'treatments'},{id:'2',img:'libs/images/pdfFile.jpg',title:'Treatments',author:'Dave Smith',category:'illness'},{id:'3',img:'libs/images/pdfFile.jpg',title:'Pain',author:'Sam Smith',category:'illness'},{id:'4',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Bob Burke',category:'illness'},{id:'5',img:'libs/images/pdfFile.jpg',title:'Pain Treatments',author:'James Frank',category:'cures'},{id:'6',img:'libs/images/pdfFile.jpg',title:'Sinus Treatments',author:'Ted Reed',category:'illness'},{id:'7',img:'libs/images/pdfFile.jpg',title:'Migrain Treatments',author:'Ted Reed',category:'remedy'},{id:'8',img:'libs/images/pdfFile.jpg',title:'Flu Treatments',author:'Ted Reed',category:'remedy'},{id:'9',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'James Frank',category:'remedy'},{id:'10',img:'libs/images/pdfFile.jpg',title:'Flu Treatments',author:'Ralph Barnes ',category:'remedy'},{id:'11',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Thomas Smith',category:'cures'},{id:'12',img:'libs/images/pdfFile.jpg',title:'Pain Treatments',author:'Ralph Barnes',category:'remedy'},{id:'13',img:'libs/images/pdfFile.jpg',title:'Virus Treatments',author:'Sam Smith',category:'treatments'},{id:'14',img:'libs/images/pdfFile.jpg',title:'Treatments',author:'Dave Smith',category:'illness'},{id:'15',img:'libs/images/pdfFile.jpg',title:'Pain',author:'Sam Smith',category:'illness'},]
// this continues for a total of about 80 cards in my local version
let posters;
const cardElement = $('#cards');
const paginationElement = $('#pagination');
let currentPage = 1;
let rows = 10;
let page_count;
///////////\\\\\\\\\\\
// **** Functions ***\\
//////////\\\\\\\\\\\\\\
function displayList(items, wrapper, rows_per_page, page) {
wrapper.html("");
page--;
console.log('displayList', items);
let start = rows_per_page * page;
let end = start + rows_per_page;
let paginatedItems = items.slice(start, end);
console.log('paginatedItems:', paginatedItems);
for (let i = 0; i < paginatedItems.length; i++) {
wrapper.append(`
<div class="col gx-2">
<div class="card">
<img
src=${paginatedItems[i].img}
class="card-img-top"
alt="..."
/>
<div class="card-body p-1">
</div>
<div class="card-footer">
Title: ${paginatedItems[i].title}
Category: <strong>${paginatedItems[i].category}</strong><br>
Author: ${paginatedItems[i].author}<br>
id: ${paginatedItems[i].id}<br>
</div>
</div>
</div>
`)
};
};
function setupPagination(items, wrapper, rows_per_page) {
wrapper.html("");
page_count = Math.ceil(items.length / rows_per_page) + 1;
for (let i = 1; i < page_count; i++) {
wrapper.append(`<li class="page-item"><a class="page-link" href="#">${i}</a></li>`);
};
};
/////////////////////\\\\\\\\\\\\\\\\\\\\
// **** Posters By Category Dropdown ***\\
////////////////////\\\\\\\\\\\\\\\\\\\\\\
//Adds Categories to Dropdown Select Options
uniqCatergoriesArray = uniqCatergories(setPosters, it => it.category);
function uniqCatergories(data, key) {
return [...new Map(data.map(x => [key(x), x])).values()]
}
for (var i = 0; i < uniqCatergoriesArray.length; i++) {
$('#selPosterCat').append($('<option>', {
value: uniqCatergoriesArray[i].category,
text: uniqCatergoriesArray[i].category,
}));
}
//Displays Posters By Category
$('#selPosterCat').on('change', function() {
console.log($('#selPosterCat').val());
$('#textSearchResult').html('&nbsp');
$('#inlineFormInput').val('');
if ($('#selPosterCat').val() === 'all') {
posters = setPosters.filter(item => item);
displayList(posters, cardElement, rows, currentPage);
setupPagination(posters, paginationElement, rows);
} else {
posters = setPosters.filter(item => item.category === $('#selPosterCat').val());
console.log('dropdown', posters);
displayList(posters, cardElement, rows, currentPage);
setupPagination(posters, paginationElement, rows);
}
});
/////////////////\\\\\\\\\\\\\
// **** Pagination Click ***\\
/////////////////\\\\\\\\\\\\\\
$(document).on("click", "ul.pagination li a:not(.static)", function(e) {
currentPage = parseInt($(this).text());
posters = setPosters.filter(item => item);
displayList(posters, cardElement, rows, currentPage);
});
posters = setPosters.filter(item => item)
displayList(posters, cardElement, rows, currentPage);
setupPagination(posters, paginationElement, rows);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<!-- *** Main Content Container *** -->
<div class="container-fluid" id="mainContainer">
<div id="mainCardContainer">
<!-- *** Search Bar *** -->
<div class="container-fluid">
<div class="row">
<div class="col">
<form id="form-id">
<div class="mb-3 d-flex justify-content-center" style="margin-top: 5vh;">
<select id="selPosterCat" class="form-select-sm w-25" aria-label="Default select example" style="border-right: none; border-width: 1px; height: 2rem;">
<option value="all" selected>All Categories</option>
<!-- other options added here by javaScript -->
</select>
</div>
</form>
</div>
</div>
<div class="row row-cols-1 row-cols-lg-5 row-cols-sm-2 g-4" id="cards">
<!-- individual cards added here by javaScript -->
</div>
<!-- *** Pagination Bar *** -->
<div class="container-fluid mt-3 pe-0 me-0">
<nav aria-label="Page navigation example">
<ul class="pagination justify-content-end">
<li class="page-item" id="previousBtn">
<a class="page-link static" href="#" tabindex="-1" aria-disabled="true" id="">Previous</a>
</li>
<div class="d-flex" id="pagination">
<!-- page numbers added here by javaScript -->
</div>
<li class="page-item" id="nextBtn">
<a class="page-link static" id="" href="#">Next</a>
</li>
</ul>
</nav>
</div>
Just change this code:
item => item.category === $('#selPosterCat').val()
With this:
item => item.category = $('#selPosterCat').val()
and be sure from adding these links respectively:
https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/css/bootstrap.min.css
https://cdn.jsdelivr.net/npm/bootstrap#5.0.1/dist/js/bootstrap.bundle.min.js
http://jsfiddle.net/rewan_95/107zxr3g is the fiddle I tested on.
Note: don't forget to add the above links for testing on jsfiddle

Javascript event-listener not working when trying to link to another page

I'm currently trying to display an individual.html page based off what the user clicks on a products.html page. For example, clicking on product1 would show the individual page for product1 and clicking on product2 would show the individual page for product2 etc...
I currently have a UI class in my app.js file which holds the logic for displaying information.
There is a function in there called displayIndividualProject() which has an event listener that says, "If a card is clicked (if (event.target.classList.contains('img-container'))), then inject this HTML structure into the individual.html" page. However, after I click on it, it's not injecting the sequence.
Also, displayIndividualProduct is meant for the individual.html page while displayProducts is meant for the product.html page.
Here is what part of my UI class looks like:
const individualProductsDOM = document.querySelector('.single-product');
class UI{
displayIndividualProduct(){
document.addEventListener("click", event => {
if (event.target.classList.contains('img-container'))
individualProductsDOM.innerHTML =
`
<div class='section-title'>
<h2>${product.title}</h2>
</div>
<div class='indi-img-container'>
<img src=${product.image} data-id='${product.id} alt="">
</div>
<div class="product-footer">
<h3>Estimated Cost: $ <span class='item-total'>0</span></h3>
<button class='bag-btn-2' data-id='${product.id}'>
<i class='fas fa-shopping-cart'></i>
add to cart
</button>
</div>
`
})
individualProductsDOM.innerHTML += `injected`;
}
displayProducts(products){
let result = '';
products.forEach(product => {
result += `
<article class="product" data-id='${product.id}'>
<div class='img-container'>
<a href='/individual.html'>
<img src=${product.image} alt="product" class='product-img' data-id='${product.id}'>
<button class='bag-btn' data-id='${product.id}'>
<i class='fas fa-shopping-cart'></i>
add to cart
</button>
</a>
</div>
<h3>${product.title}</h3>
<h4>$${product.price}/roll</h4>
</article>
`
});
//insert the products into the productsDOM
productsDOM.innerHTML = result;
}
}
and this is being called at the bottom of my JS through ui.displayIndividualProduct();
document.addEventListener("DOMContentLoaded", () => {
const ui = new UI();
const products = new Products();
// setup app
ui.setupAPP();
//get all products
products.getProducts().then(products => {
//first display, then save, and then connect the add cart buttons
ui.displayProducts(products);
Storage.saveProducts(products);
}).then( () => {
ui.getBagButtons();
});
ui.displayIndividualProduct();
});
any help would be appreciated!

Toggle between icons depending on number of child elements

I figured out how to change the icon when more elements are added, but I can't figure out to how return it to the original icon while the cart is empty.
Javascript
let cartItems = document.getElementsByClassName('cart-container')[0];
if (cartItems.childElementCount <= 1) {
let cartBtn = document.getElementsByClassName('cart-btn')[0]
cartBtn.innerHTML = `
<i class="fa fa-cart-plus cart-btn text-danger"></i>`
}
HTML
<h1 class="cart-btn">
<i class="fa fa-shopping-cart"></i>
</h1>
<div class="container cart-container d-flex flex-column pb-5">
<div class="row mt-5 mb-4">
<div class="col">
</div>
</div>
</div>
I have a shopping cart button on a navbar that I need to switch between different states depending on if the cart is empty or not. I figured out how to change it to one state when an item was added to the cart, but couldn't figure out how to change it back to the original state when I emptied the cart. However, I used this code to accomplish that task.
Javascript
function checkNavBtn() {
let cartItems = document.getElementsByClassName('cart-container')[0];
let cartBtn = document.getElementsByClassName('fa-shopping-cart')[0];
if (cartItems.childElementCount >= 0) {
cartBtn.classList.add('fa-cart-plus', 'text-danger');
} if (cartItems.childElementCount <= 0) {
cartBtn.classList.remove('fa-cart-plus', 'text-danger');
}
}

Why is my <i> tag being appended multiple times for each click as I click the add button?

I am making a Todo list using HTML, CSS and jQuery. So basically when the person types an activity and presses the '+' button it gets added to the list along with a 'Delete'(Font Awesome Recycle Bin) icon so that the user can delete the activity. I have implemented this using the .append() function. However, when a user adds the first item there is one delete button. However for every other list the number of delete buttons multiply. (Eg. when the user adds the second item there are two delete buttons for that item, and when they add the third item there are three delete buttons for that item). I can't understand why this is happening and what is the best way to fix this?
I have used .append() on the .listInput and appended the .newItem.
I have then used .append() on the .newItem and appended the can icon.
$(".enter").click(function() {
var $item = $('input[name=add]').val();
if ($item.length > 0) {
$(".listInput").append('<li class="newItem animated fadeIn">' + $item + '</li>');
$('.newItem').append('<i class="animated fadeIn far fa-trash-alt fa-1x"></i>');
} else {
alert("Enter an acitvity to add");
}
})
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="takeInput">
<input type="text" name="add" value="Add Your Item Here">
<i class="enter fas fa-plus fa-3x"></i>
</div>
<ul class="listInput">
</ul>
I want only one trash icon for each list item.
$('.newItem').append() will append something to all elements with the class newItem, not just the one you just added. To fix that just use a single append:
$(".enter").click(function() {
var $item = $('input[name=add]').val();
if ($item.length > 0) {
$(".listInput").append('<li class="newItem animated fadeIn">' + $item + '<i class="animated fadeIn far fa-trash-alt fa-1x"></i></li>');
} else {
alert("Enter an acitvity to add");
}
})
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="takeInput">
<input type="text" name="add" value="Add Your Item Here">
<i class="enter fas fa-plus fa-3x"></i>
</div>
<ul class="listInput">
</ul>
Please try this
var $item = $('input[name="add"]').val();
The problem is that $('.newItem') refers to any element that has that class. For example, if you have five items on the list they will all have the class new item. If you were to add another item (that makes six items), then you are adding a bin icon to each element with class 'newItem'
$(".enter").click(function() {
var $item = $('input[name=add]').val();
if ($item.length > 0) {
$(".listInput").append('<li class="newItem animated fadeIn">' + $item + '</li>' + '<i class="animated fadeIn far fa-trash-alt fa-1x"></i>');
} else {
alert("Enter an acitvity to add");
}
})
$(".enter").click(function() {
let item = $('input[name=add]').val();
let li = $('<li class="newItem animated fadeIn"/>');
let iconTrash = $('<i style="margin-left:1em;" class="animated fadeIn far fa-trash-alt fa-1x"/>')
if (item.length >= 0) {
li.append(item,iconTrash)
$(".listInput").append(li);
} else {
alert("Enter an acitvity to add");
}
})
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css" integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="takeInput">
<input type="text" name="add" value="Add Your Item Here">
<i class="enter fas fa-plus fa-3x"></i>
</div>
<ul class="listInput">
</ul>
The problem is the trash icon is added to all the elements that have the class 'newItem'. So you have to create a li(list item) element and append it to the ul(unordered list) element. Then create the icon and append it to the created list item element. You can create element using the document.createElement(tagName) function. and append it using javascript or jquery

Categories

Resources