Alessandro
Alessandro

Reputation: 77

How to fix a nesting forEach

I practicing on an exercise of DOM manipulation (pure vanilla JS) where I need to check all the prices and if any of them is more than £3,00 the whole product card should change its background color into red. For some unknown reasons the code is working perfectly if I'm changing only the price div but, when I'm trying to target the whole card it will change all the cards into red and not only the one with a price of more than £3,00

I don't know what I'm doing wrong. Any help will be appreciated

Here also the link to test the code from the console: : https://www.monoprix.fr/courses/legumes-frais-mpx_c0000400?page=3

Here my snippet:

function exercise_3() {
  var price = document.querySelectorAll(".grocery-item__normal-price");
  var block = document.querySelectorAll(".grocery-item-item");
  let changePriceColor = 300;

  block.forEach((card) => {
    price.forEach((p) => {
      var a = p.innerText.slice(0, 4).replace(/,/g, "");
      if (a > changePriceColor) {
        card.style.background = "red";
      } else {
        return "";
      }
    });
  });
}

exercise_3();
<div class="grocery-item-item">
  <div class="grocery-item__description-offre">
    <a href="/courses/artichaut-blanc--2414383-p">
      <div class="grocery-item__description-wrapper">
        <div class="grocery-item__dimmer"></div>
        <div class="grocery-item__product-card-content discount">
          <div class="grocery-item__product-img">
            <img 
              data-tc='["","tc_event_20",""]' 
              data-tcdata='{"tc_event_11":0,"tc_event_20":{"ecommerceGA":"product_click","productId":2414383,"quantity":0},"tc_event_39":{"action":"cross sell | undefined","position":0}}' 
              src="https://cdn.monoprix.fr/cdn-cgi/image/width=180,quality=60,format=auto,metadata=none/assets/images/grocery/2414383/580x580.jpg"
              width="100" 
              height="100" 
              centered="true"
            >
          </div>
          <div class="grocery-item__description">
            <div class="grocery-item-brand">
              <p></p>
            </div>
            <div class="grocery-item-range">ARTICHAUT BLANC</div>
            <div class="conditioning-description">L'unité</div>
          </div>
        </div>
      </div>
      <div class="withPromotionBand-sc-1jbwr08-0 cfLNRM grocery-item__promotions_band">
        <div class="promo_map">
          <div class="promo_band _red">
            <div class="promo_red">-50%</div>
            sur le 2ème
          </div>
          {" "}
        </div>
      </div>
    </a>
  </div>
  <div class="grocery-item__price-cart">
    <div class="grocery-item__price-wrapper">
      <div class="grocery-item__price-line-through"></div>
      <div class="grocery-item__normal-price">2,00 €</div>
      <div class="grocery-item__price-unit">
        <p>2,00 € / p</p>
      </div>
    </div>
    <div class="styledAddToCartButton-sc-15efgme-0 fNiyOV">
      <div 
        class="add-to-cart-button__add-button" 
        id="addToCartButton-2414383" 
        value="AddToCart" 
        data-tc='["tc_event_20",""]' 
        data-tcdata='{"tc_event_20":{"ecommerceGA":"product_add","productId":2414383,"quantity":0},"tc_event_38":{"position":0,"productId":2414383}}'
      >
        <img 
          src="/contents/images/cart_icon.svg" 
          width="40" 
          height="40" 
          alt="panier" 
          class="pannel"
        >
      </div>
    </div>
  </div>
</div>
</>
</>;

Upvotes: 0

Views: 74

Answers (3)

zer00ne
zer00ne

Reputation: 43880

Note: .forEach() does not return anything

You can run flow control statements in one .forEach() method because typical DOM trees are simple parent/child hierarchies (<table>s are the exception).

The following function passes 3 parameters:

  1. limit Number a real float or integer that is compared to each value
  2. priceTag String a CSS selector of the elements that contains text or has a value property (form controls) of a price
  3. itemCard String a CSS selector of the element that contains priceTag

tooExpensive()

  1. collects all priceTag into a NodeList
    const prices = document.querySelectorAll(priceTag);
    
  2. then converts NodeList into an array to run it through .forEach()
    [...prices].forEach(node => {...
    
  3. next it determines what type of string to extract and if either one does exist:
    // <div>textContent</div> <!--or--> <input value="value">
    
    let text = node.textContent || node.value;
    if (text.length > 0) {...
    
  4. when there is a value, then all commas "," are replaced by dots ".", then every character except numbers and dots are removed.
    let price = text.replaceAll(',', '.').split(/[^0-9.]/).join(''); 
    
  5. next, it determines if the priceTag has a itemCard as a ancestor
    let parent = node.closest(itemCard);
    
  6. Finally, if the price greater than the limit and itemCard (or parent) does exist, change the parent style by adding class .red
    if (parseFloat(price) > limit && parent) {
      parent.classList.add('red');
    }
    

const tooExpensive = (limit, priceTag, itemCard) => {
  const prices = document.querySelectorAll(priceTag);
  [...prices].forEach(node => {
    let text = node.textContent || node.value;
    if (text.length > 0) {
      let price = text.replaceAll(',', '.').split(/[^0-9.]/).join('');
      //console.log(price);
      let parent = node.closest(itemCard);
      if (parseFloat(price) > limit && parent) {
        parent.classList.add('red');
      }
    }
  });
};

tooExpensive(3, '.price', '.item');
.item {
  width: min-content;
  padding: 5px 10px;
  margin: 5px 0;
  border: 1px black solid;
}

.red {
  background: red;
  color: white;
}
<fieldset class='item'>
<input class='price' value='$5.00'>
</fieldset>
<div class='item'>
<input class='price' value='2,00'>
</div>
<section class='item'>
<output class='price'>0.6</output>
</section>
<article class='item'>
<p class='price'>8,00</p>
</article>
<div class='item'>
<p class='price'>£3,00</p>
</div>
<aside class='item'>
<div>2999</div>
</aside>
<nav>
<fieldset class='price'>99</fieldset>
</nav>

Upvotes: 0

JustDebuggin
JustDebuggin

Reputation: 368

The way you have structured your nested for loops is the causing you to loop over all the prices for each card. Meaning for the 1st card you look at all the prices, inevitably one is higher than 300 so it turns red, then it goes to the next card, looks at all the prices again finds one that is higher than 300 and turns that card red also, it goes on like this until every card has been turned red

function exercise_3() {
  var price = document.querySelectorAll(".grocery-item__normal-price");
  let changePriceColor = 300;

  // So you don't need to run a forloop over the "block", 
  // instead you could just find the closest '.grocery-item-item' 
  // to the price that meets your condition, and change its background
  price.forEach((p) => {
    var a = p.innerText.slice(0, 4).replace(/,/g, "");
    if (a > changePriceColor) {
      const card = p.closest('.grocery-item-item')
      card.style.background = "red";
    } else {
      return "";
    }
  });

}

exercise_3();

Upvotes: 1

A_A
A_A

Reputation: 1932

The problem is, that for all cards you always look at all prices. Instead you should only look at the prices inside the current card. This can be done with card.querySelectorAll, where card is the current block.

function exercise_3() {
  const blocks = document.querySelectorAll(".grocery-item-item");
  const changePriceColor = 300;

  blocks.forEach((card) => {
    const prices = card.querySelectorAll(".grocery-item__normal-price");
    prices.forEach((p) => {
      const a = p.innerText.slice(0, 4).replace(/,/g, "");
      
      if (a > changePriceColor) {
        card.style.background = "red";
      } else {
        return "";
      }
    });
  });
}

exercise_3();
.grocery-item-item {
  border: 1px solid;
}
<div class="grocery-item-item">
     <div class="grocery-item__normal-price">2,00 €</div>
     <div class="grocery-item__normal-price">2,00 €</div>
</div>

<div class="grocery-item-item">
     <div class="grocery-item__normal-price">4,00 €</div>
</div>

<div class="grocery-item-item">
     <div class="grocery-item__normal-price">1,00 €</div>
</div>

Upvotes: 2

Related Questions