JavaScript click event keeps adding previously clicked items to list - javascript

Please excuse my spaghetti code. I'm trying to learn.
When I click on the add food button, it adds a food item to the nutrition list. However, when I choose another food and add that, the previous food item is added along with the current chosen food. Again, if I continue the process, all of the previously added items are added, including the current item. So the result is a list of previously added food and it's terrible. It seems that whenever I click on a food item, all the previously clicked items are being "held" or still referenced somehow. I don't understand why this is happening.
I need to make it so that only one food item is added at a time. I've researched a lot but can't find solutions similar to my problem.
Here is my code. I've left comments around the problem area.
JavaScript:
const callApi = (url) => {
fetch(url).then(function(response) {
return response.json();
})
.then(function (data) {
if (data.hits.length > 0) {
data.hits.forEach((foodItem) => {
makeFoodInstance(foodItem);
});
} else {
displayResults.innerHTML = "No results match this search."
}
});
}
const makeFoodInstance = (f) => {
let food = new Food(f.fields.item_id, f.fields.item_name);
food.carbohydrates(f.fields.nf_dietary_fiber, f.fields.nf_sugars,
f.fields.nf_total_carbohydrate);
food.fats(f.fields.nf_saturated_fat, f.fields.nf_polyunsaturated_fat,
f.fields.nf_monounsaturated_fat, f.fields.nf_trans_fatty_acid,
f.fields.nf_total_fat);
food.minerals(f.fields.nf_calcium_dv, f.fields.nf_sodium,
f.fields.nf_iron_dv);
food.vitamins(f.fields.nf_vitamin_a_dv, f.fields.nf_vitamin_c_dv);
food.protein(f.fields.nf_protein);
food.calories(f.fields.nf_calories, f.fields.nf_calories_from_fat);
food.water(f.fields.nf_water_grams);
food.servings(f.fields.nf_servings_per_container,
f.fields.nf_serving_size_qty, f.fields.nf_serving_size_unit);
displayResults.children[0].innerHTML = '';
resultsList.insertAdjacentHTML('afterbegin', `<li class="results-list-
item">${food.name}</li>`);
getNutrition(food);
}
const getNutrition = (nutrition) => {
let foodResultItem = document.querySelector('.results-list-item');
let nutritionListItem =
`<li class="nutrition-list-item">
<p>${nutrition.name}</p>
<p>Sugars ${nutrition.sugars}</p>
<p>Total Carbohydrates ${nutrition.total_carbs}</p>
<p>Saturated Fat ${nutrition.satured}</p>
<p>Polyunsaturated Fat ${nutrition.polyunsaturated}</p>
<p>Monounsaturated Fat ${nutrition.monounsaturated}</p>
<p>Trans Fat ${nutrition.trans}</p>
<p>Total Fat ${nutrition.total_fat}</p>
</li>
`;
/*
this is where the problem is starting (i think)
passing the nutrition variable to storeFood()
*/
foodResultItem.addEventListener('click', function(e) {
nutritionList.innerHTML = '';
nutritionList.insertAdjacentHTML('afterbegin', nutritionListItem);
storeFood(nutrition);
}, false);
}
const storeFood = (f) => {
addFood.addEventListener('click', (a) => {
if (localStorage) {
localStorage.setItem(f.id, f.name.toString());
userAddedFood.insertAdjacentHTML('afterbegin', `<li class="selected-
food-item">${f.name}</li>`);
} else {
userAddedFood.append('Sorry, storage is full');
}
}, false);
}
HTML:
<div class="container">
<section class="search">
<h1>Health Tracker</h1>
<input type="text" id="search-bar">
<input type="submit" value="search" id="search-btn">
</section>
<br>
<br>
<br>
<section class="results">
<h3>Search results will display here</h3>
<ul id="results-list"></ul>
</section>
<section class="nutrition-results">
<h3>Nutrition Facts</h3>
<ul id="nutrition-list"></ul>
<input type="button" value="add-food" id="add-food-button">
</section>
<section class="selected-food">
<h3>Foods you've eaten today</h3>
<ul class="users-food-result"></ul>
</section>
<section class="user-nutrition-facts">
<h3>Your daily nutrition totals</h3>
<div class="users-nutrition-totals"></div>
</section>
</div>

it looks like every time a foodResultItem gets clicked, you call storeFood() passing in the current nutrition item. storeFood itself creates a click handler on addFood that adds that specific food item to the list. These click handlers for each specific food stick around on the addFood element, even when you create new ones by clicking on a different foodResultItem. So when you click the addFood button, ALL of the event handlers you have added to it run, giving you the unwanted results.

Related

Delete product with filter in Localstorage

I'm working on a shopping cart project and I'm stuck at the product removal stage.
I tried to work it with the filter method and it works without really working in the sense that what I defined works only in the case where the product having a dataset id and equivalent to the id of the localstorage but that the color is different then there the deletion of the selected product works and the localStorage is updated.
On the other hand when I want to delete a product having a different id but which has the same color as the product selected previously it deletes both products.
An example :
If I have a sofa that has an id 001 and a blue color and I have another sofa that has an id 002 and a blue color and I press the delete button then both products disappear...
How can I avoid this?
I think the definition of the condition I wrote here is a little shaky but I don't know yet how to fix it:
const deleteProduct = function ()
{
let deleteButton = document.querySelectorAll('.deleteItem');
let localStorageProducts2 = localStorage.getItem('Produits')
// Loop to get all buttons
for (let i = 0; i < deleteButton.length; i++)
{
// More clear for the syntax
let buttons = deleteButton[i];
// Link each button to his article
let myActualProduct = deleteButton[i].closest('article');
let getStorageProducts2 = JSON.parse(localStorageProducts2);
buttons.addEventListener("click",() =>
{
getStorageProducts2 = getStorageProducts2.filter(productsInLocalStorage => productsInLocalStorage.id === myActualProduct.dataset.id && productsInLocalStorage.colors !== myActualProduct.dataset.color);
// Update the localStorage
localStorage.setItem('Produits',JSON.stringify(getStorageProducts2));
myActualProduct.remove()
alert('Le produit a bien été supprimé')
// Reload the page
window.location.href ="cart.html";
// Update the productsInLocalStorage
})
}
}
deleteProduct()
<section id="cart__items">
<article class="cart__item" data-id="{product-ID}" data-color="{product-color}">
<div class="cart__item__img">
<img src="../images/product01.jpg" alt="Photographie d'un canapé">
</div>
<div class="cart__item__content">
<div class="cart__item__content__description">
<h2>Nom du produit</h2>
<p>Vert</p>
<p>42,00 €</p>
</div>
<div class="cart__item__content__settings">
<div class="cart__item__content__settings__quantity">
<p>Qté : </p>
<input type="number" class="itemQuantity" name="itemQuantity" min="1" max="100" value="42">
</div>
<div class="cart__item__content__settings__delete">
<p class="deleteItem">Supprimer</p>
</div>
</div>
</div>
</article>
</section>
The HTML part doesn't work here in JSFiddle , I'm working with an API on local.
I find the solution by myself thanks to an article teaching how to use findIndex on array of object and the splice method.

Cypress: lenght of an invisble array

I know this will be a long questing and I do apologize for it.
TL:TR I'm starting to learn Cypress and I stumbled upon a problem. I got a list which is higly dinamic (meaning that it can and it will have zero elements from time to time) and I want know its length to make some assertions. The thing is that when it has zero elements, Cypress is falling to get this DOM element.
I have no idea how to assert if the array is empty before trying to .get() it. Any clue on how to do it? Thank you in advance!
The post
I want to follow this logic To check if an item was added to the list:
Get array length, save it into a variable. (Need to learn how to)
Add an item (this hasn't been of any problem)
Get new array length, compare it. If new == old + 1, then it was added.
HTML5 (This code HAS an item into the list)
<section id="main" style="display: block;">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li>
<div class="view">
<input class="toggle" type="checkbox">
<label>a</label>
<button class="destroy"></button>
</div>
<input class="edit" value="a">
</li>
</ul>
</section>
HTML5 (This code HAS NOT an item into the list)
<section id="main" style="display: none;">
<input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
</section>
Cypress
cy.get('#todo-list').find('.view').each(($el, i) =>
{
cont = i + 1;
cy.log(cont);
})
This approach is clearly not working for many reasons. First of all if the list has zero elements, Cypress does not find it and I cannot proceed. And if it does, later on into the '>' statement my var cont is 0.
I'm sure I'm messing something up.
This is the app, so you can see the html and I can keep this post as short as I can:
Todo List
I've been also trying another approach with the footer > todo-count element and it's working while the list has an element into it. My problem is again when I doesn't:
cy.get('#todo-count').then(($el1) =>{
const prev = parseFloat($el1.text())
cy.log(prev)
{Here I add the item}
cy.get('#todo-count').invoke('text').then(parseFloat).should('be.gt', prev)
})
Again, if the element is not visible Cypress will not find it. Tried if/else with $el.css('display') == 'block' and .is(":visible") but I'm not getting it.
I don't know what's happened to the footer and the #id-count, but don't use it. Count the elements yourself
it('adds an item to the list', () => {
cy.get('ul').then($list => {
const initialCount = $list.find('li').length;
expect(initialCount).to.eq(0)
{Here add the item}
// const afterAddingCount = $list.find('li').length;
// expect(afterAddingCount).to.eq(1);
// This is better
cy.get('ul li')
.should('have.length', 1); // retry on this if animation, etc
})
})
it('updates the todo-count display', () => {
// show the HTML for the footer
// before and after adding an item
// then can add a test here
})
This is how I think you should test the list counter
it('updates the todo-count display', () => {
cy.get('#footer')
.should('have.css', 'display', 'none')
cy.get('#todo-count')
.should('not.exist') // verify counter is not present
// {Here add the item}
cy.get('#footer')
.should('have.css', 'display', 'block')
cy.get('#todo-count')
.should('exist') // now the counter exists
.invoke('text')
.should('include', '1') // and its value is '1'
})

I wanna attach a tooltip to ngx-newsticker events array "for each item"

I have a news bar and I used ngx-newsticker, I need to attach a tooltip for each item in the news bar. However, the plugin takes an array of string as shown below.
I tried to loop throw the array but it doesn't work:
<div class="M_announcementDiv" *ngIf="announcementsArrInTicker && announcementsArrInTicker.length>0">
<div class="container">
<div placement="bottom" [ngbTooltip]="announcementsArrInTicker">
<ngx-newsticker title="{{'ANNOUNCEMENTS'|translate}}" [events]="announcementsArrInTicker" [interval]="interval"></ngx-newsticker>
</div>
</div>
</div>
I need to push the current item to the tooltip in this line instead of pushing
the whole array.
<div placement="bottom" [ngbTooltip]="announcementsArrInTicker">
and this is the ts function that retrieves the data:
getGeneralAnnouncements() {
// this.isAuthenticated = this.authService.isAuthenticated();
this.isAuthenticated =false;
this.newsService.searchGeneralAnnouncements(this.searchModel, this.isAuthenticated).subscribe(res => {
this.announcementArr = res;
res.announcements.map((announcement) => {
if(this.translate.currentLang=="ar"){
this.announcementsArrInTicker.push(announcement.bodyAr);
}else{
this.announcementsArrInTicker.push(announcement.bodyEn);
}
});
}, error => {
// this.businessException = errorsUtility.getBusinessException(error); //this.notificationService.showNotification(this.businessException.message,
NotificationType.Error);
})
}

Content Editable = true, but only takes one value at a time

I'm working on a simple project where I need to make an item in a list editable and then update a JS item to store this.
I'm using Content Editable = True, and it works when I comment out my handleItemEdit function, but when I turn it on, I can only insert one character at a time, forcing me to keep clicking to edit.
Clearly this problem stems from my function, but I can't seem to figure out why.
//Responsible for listening for an edit and updating my object with the new text.
function handleEditItem() {
$('.js-shopping-item').on('input', function(event) {
const itemIndex = getItemIndexFromElement(event.currentTarget); //assigning the index of the the editted item to itemIndex
const updatedItem = STORE.items[itemIndex];
updatedItem.name = event.currentTarget.innerHTML;
renderShoppingList();
});
}
//Returns the index of an Item in the Store
function getItemIndexFromElement(item) {
const itemIndexString = $(item)
.closest('.js-item-index-element')
.attr('data-item-index');
return parseInt(itemIndexString, 10);
}
//Function responsible for returning the template HTHML to insert into the html.
function generateItemElement(item) {
let itemIndex = STORE.items.indexOf(item);
return `
<li class="js-item-index-element" data-item-index="${itemIndex}">
<span contentEditable='true' class="shopping-item js-shopping-item ${item.checked ? 'shopping-item__checked' : ''}">${item.name}</span>
<div class="shopping-item-controls">
<button class="shopping-item-toggle js-item-toggle">
<span class="button-label">check</span>
</button>
<button class="shopping-item-delete js-item-delete">
<span class="button-label">delete</span>
</button>
</div>
</li>`;
}

Angular/Javascript - Hide button with id onclick

I have multiple buttons on one page, "Add to cart" buttons where each button has a unique id attribute.
I want to hide a particular button when the user clicks on it.
The issue:
What's happening currently is that when a user clicks on a button 1 it hides, then clicks on button 2 it hides but on the same time it shows button 1
The expected behavior:
When the user clicks on button 1 it should hide and keep hiding even after clicking on button 2
P.S. the information of the buttons (products) gets added to an array.
Current code:
Html:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hideButton != i" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
JS
addToCart(itemDetails, index) {
this.hideButton = index;
}
You need an array of hidden buttons and you need to add the index to that array:
JS:
// at the top
hiddenButtons = [];
addToCart(itemDetails, index) {
this.hiddenButtons.push(index);
}
HTML:
<div *ngFor="let product of products; let i = index">
<div *ngIf="hiddenButton.indexOf(i) === -1" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
If you have a cart to which products are being added, you can look in the cart to check whether the product already exists in it, and use that to decide whether to display the ADD button.
If your product objects can have more properties to them, you can do away with indexes completely.
HTML
<div *ngFor="let product of products">
<div *ngIf="productInCart(product)" [attr.id]="product.id" class="addButton" (click)="addToCart(product)">ADD</div>
</div>
JS
productInCart(product) {
return this.products.findIndex(p => p.id==product.id)!=-1;
}
addToCart(product) {
this.products.push(product);
}
<div *ngFor="let product of products; let i = index">
<div *ngIf="!product.isHidden" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
In component
addToCart(itemDetails, index) {
itemDetails.isHidden = true;
this.products[index] = itemDetails;
}
Logic behind this is to create a new property in product when it clicked for add to cart. Initially there will be no property with name isHidden. SO, it will return undefined and undefined will treat as false.
I would suggest the following:
<div *ngFor="let product of products; let i = index">
<div *ngIf="!isInCart(product)" [attr.id]="i" class="addButton" (click)="addToCart(product, i)">ADD</div>
</div>
private hiddenProducts = new Set<FooProduct>();
products: FooProduct[] = [];
loadProducts(){
this.products = // some value
hiddenProducts = new Set<FooProduct>();
}
isInCart(product: FooProduct): boolean {
return this.hiddenProducts.has(product);
}
addToCart(product: FooProduct, index: number){
// optional: check if the element is already added?
this.hiddenProducts.add(product);
// rest of your addToCart logic
}
Why using a set instead of a simple array?
Performance: access time is constant.
Why not use the index as identifier?
Weak against list mutations (filter, reorder, etc)

Categories

Resources