Filter function in JavaScript - javascript

I'm currently working on an e-commerce site and I'm stuck on the search page, especially when filtering the products.
**
This is the HTML code for the product card:**
<div class="product-seller-item" data-brand="Louis Vuitton" data-category="Helmets">
<div class="product-page-seller-item-header">
<img src="/Images/user.png" alt="User profile image">
<p>citygirl1996</p>
</div>
<img class="product-page-seller-item-body-image" src="/Images/Products/product1.png" alt="">
<div class="product-page-seller-item-footer">
<div class="product-page-seller-item-footer-flex">
<p>£15.00</p>
</div>
<p class="product-page-seller-item-footer-description">Juicy Couture</p>
</div>
</div>
And this is the code for the checkbox:
<label class="container">Helmets
<input type="checkbox" value="Helmets">
<span class="checkmark"></span>
</label>
<label class="container">Louis Vuitton
<input type="checkbox" value="Louis Vuitton">
<span class="checkmark"></span>
</label>
And this is the code for JavaScript:
const checkboxes = document.querySelectorAll('input[type="checkbox"]');
const products = document.querySelectorAll('.product-seller-item');
for (let i = 0; i < checkboxes.length; i++) {
checkboxes[i].addEventListener('change', function () {
for (let j = 0; j < products.length; j++) {
let productVisible = false;
for (let k = 0; k < checkboxes.length; k++) {
if (checkboxes[k].checked && products[j].dataset.category === checkboxes[k].value) {
productVisible = true;
break;
} else if (checkboxes[k].checked && products[j].dataset.brand
&& products[j].dataset.brand === checkboxes[k].parentElement.textContent.trim()) {
productVisible = true;
break;
}
}
if (productVisible) {
products[j].style.display = 'block';
} else {
products[j].style.display = 'none';
}
}
});
}
The problem is as follows, when I select the Helmets category, it shows me all of them, everything works perfectly, but when I select the brand, it doesn't hide the product that doesn't match.

If the products have a category you can use filtermethod, that will return you a new array with the elements that have this conditions. For example:
const products = document.querySelectorAll('.products'):
const categories = document.querySelectorAll('input[type=checkbox]');
const filteredProducts
= products.filter(product => product.category == categories.checked);

Related

Shopping Cart Update Total Function doesnt work

I am building an eCommerce store website, and I am facing an issue. The function updateCartTotal doesn't work at all. The script is also added to the bottom of the HTML body.
Thanks in advance.
HTML:
<span class="material-symbols-outlined" id="cart-icon">
shopping_cart
</span>
<div class="cart">
<h2 class="cart-title">Your Shopping Cart</h2>
<div class="cart-content">
<div class="cart-box">
<img src="/Monn-Homme/images/tie1.jpg" class="cart-image">
<div class="detail-box">
<div class="cart-product-title">
Tie
</div>
<div class="cart-price"> £10.99</div>
<input type="number" value="1" class="cart-qty">
</div>
<span class="material-symbols-outlined" id="cart-remove">
delete
</span>
</div>
</div>
<div class="total">
<div class="total-title">Total</div>
<div class="total-price">£10.99</div>
</div>
<button type="button" class="buy-btn">Buy Now</button>
<span class="material-symbols-outlined" id="close-cart">
close
</span>
</div>
</div>
Javascript:
let cartIcon = document.getElementById("cart-icon");
let cart = document.querySelector(".cart");
let CloseCart = document.querySelector("#close-cart");
cartIcon.onclick = () => {
cart.classList.add("active");
};
CloseCart.onclick = () => {
cart.classList.remove("active");
};
if (document.readyState == "loading") {
document.addEventListener("DOMContentLoaded", ready);
} else {
ready();
}
function ready() {
var removeCartButtons = document.getElementsByClassName("material-symbols-outlined");
for (var i = 0; i < removeCartButtons.length; i++) {
var button = removeCartButtons[i];
button.addEventListener("click", removeCartItem)
}
// Quantity Change //
var quantitInputs = document.getElementsByClassName("cart qty");
for (var i = 0; i < quantitInputs.length; i++) {
var input = quantitInputs[i];
input.addEventListener("change", quantityChanged);
}
}
function removeCartItem(event) {
var buttonClicked = event.target;
buttonClicked.parentElement.remove();
updateCartTotal();
}
quantityChanged = (event) => {
var input = event.target;
if (isNaN(input.value) || input.value <= 0) {
input.value = 1;
}
updateCartTotal();
}
function updateCartTotal() {
var cartContainer = document.getElementsByClassName("cart-content")[0];
var cartBox = cartContainer.getElementsByClassName("cart-box");
var total = 0
for (var i = 0; i < cartBox.length; i++) {
var cartBox = cartBox[i]
var priceElement = cartBox.getElementsByClassName("cart-price")[0]
var quantityElement = cartBox.getElementsByClassName("cart-qty")[0]
price = parseFloat(priceElement.innerText.replace("£", ""))
quantity = quantityElement.value
total = total + (price * quantity)
}
document.getElementsByClassName("total-price")[0].innerText = total
}
i am expecting the total to update as the quantity changes, and the function to work
You have the following mistakes-
There is no element with the class name cart qty.
var quantitInputs = document.getElementsByClassName("cart qty");
quantityChanged function should have a function keyword.
You are using the same name variable cartBox in updateCartTotal function which is creating confusion-
var cartBox = cartContainer.getElementsByClassName("cart-box");
for (var i = 0; i < cartBox.length; i++) {
var cartBox = cartBox[i]
}
Now, after fixing these mistakes, your code will look like the below which is working.
Note- I moved all the declarations to the top and I replaced those two methods-
getElementsByClassName() = querySelectorAll()
getElementsByClassName()[0] = querySelector()
let cartIcon = document.querySelector("#cart-icon");
let cart = document.querySelector(".cart");
let CloseCart = document.querySelector("#close-cart");
var quantitInputs = document.querySelectorAll(".cart-qty");
var removeCartButtons = document.querySelectorAll(".material-symbols-outlined");
var cartContainer = document.querySelector(".cart-content");
var cartBox = cartContainer.querySelectorAll(".cart-box");
var totalEl = document.querySelector(".total-price")
cartIcon.onclick = () => {
cart.classList.add("active");
};
CloseCart.onclick = () => {
cart.classList.remove("active");
};
if (document.readyState == "loading") {
document.addEventListener("DOMContentLoaded", ready);
} else {
ready();
}
function ready() {
for (var i = 0; i < removeCartButtons.length; i++) {
var button = removeCartButtons[i];
button.addEventListener("click", removeCartItem);
}
// Quantity Change //
for (var i = 0; i < quantitInputs.length; i++) {
var input = quantitInputs[i];
input.addEventListener("change", quantityChanged);
}
}
function removeCartItem(event) {
var buttonClicked = event.target;
buttonClicked.parentElement.remove();
updateCartTotal();
}
function quantityChanged(event) {
var input = event.target;
if (isNaN(input.value) || input.value <= 0) {
input.value = 1;
}
updateCartTotal();
};
function updateCartTotal() {
var total = 0;
for (var i = 0; i < cartBox.length; i++) {
var cartBoxEl = cartBox[i];
var priceElement = cartBoxEl.querySelector(".cart-price");
var quantityElement = cartBoxEl.querySelector(".cart-qty");
price = parseFloat(priceElement.innerText.replace("£", ""));
quantity = quantityElement.value;
total = total + price * quantity;
}
if (totalEl) {
totalEl.innerText = total;
}
}
<div>
<span class="material-symbols-outlined" id="cart-icon">
shopping_cart
</span>
<div class="cart">
<h2 class="cart-title">Your Shopping Cart</h2>
<div class="cart-content">
<div class="cart-box">
<img src="/Monn-Homme/images/tie1.jpg" class="cart-image">
<div class="detail-box">
<div class="cart-product-title">
Tie
</div>
<div class="cart-price"> £10.99</div>
<input type="number" value="1" class="cart-qty">
</div>
<span class="material-symbols-outlined" id="cart-remove">
delete
</span>
</div>
</div>
<div class="total">
<div class="total-title">Total</div>
<div class="total-price">£10.99</div>
</div>
<button type="button" class="buy-btn">Buy Now</button>
<span class="material-symbols-outlined" id="close-cart">
close
</span>
</div>
</div>

How to iterate through all classes with a javascript toggle?

This is a toggle switch that when unchecked, will display 'min'. If checked, then 'min' will switch to 'max' and will toggle back and forth.
My expected result is that however many times this checkbox is on the page, the function will work across the entire page.
var x = document.getElementsByClassName("text");
function queryToggle() {
for (let i = 0; i < x.length; i++) {
if (x.innerHTML === "min") {
x.innerHTML = "max";
} else {
x.innerHTML = "min";
}
}
}
<div class="options mb-2">
<label for="toggleQuery">Use 'max-width'?</label>
<input type="checkbox" name="toggleQuery" id="toggleQuery" onclick="queryToggle()" />
</div>
<span class="text">min</span>
Using you current code, try this:
function queryToggle() {
let x = document.getElementsByClassName("text");
for (let i = 0; i < x.length; i++) {
console.log(x[i])
if (x[i].innerHTML === "min") {
x[i].innerHTML = "max";
} else {
x[i].innerHTML = "min";
}
}
}
Since x is an 'array-like' object you have to iterate over each item in it by its index, you were operating on the entire array, if that makes sense.
Shorter version
function queryToggle() {
let x = document.getElementsByClassName("text");
for (let i = 0; i < x.length; i++) {
x[i].innerHTML = x[i].innerHTML === "min" ? "max" :"min";
}
}
Another shorter version with spread operator no need to create the x array unless you need it somewhere else, downside is that you will be accessing the DOM on every call
function queryToggle() {
[...document.getElementsByClassName("t_text")].forEach((i)=>
i.innerHTML = i.innerHTML === "min" ? "max" :"min")
}
I have adjusted your code and included comments.
If you want to re-use the checkbox then simply change the id of each new checkbox you create and it will work across the page.
Run the snippet below:
//your span
var x = document.getElementsByClassName("text");
//here we are passing "input" as a function parameter
function queryToggle(input) {
//checkbox will take the id of any new input we create on the page
//the id must change for each new checkbox
var checkbox = document.getElementById(input.id);
for (let i = 0; i < x.length; i++) {
//check if checkbox is checked, if checked then change innerHTML to "max"
if (checkbox.checked == true) {
x[i].innerHTML = "max";
// else change innerHTML to "min"
} else {
x[i].innerHTML = "min";
}
}
}
<div class="options mb-2">
<label for="toggleQuery">Use 'max-width'?</label>
<input type="checkbox" name="toggleQuery" id="toggleQuery1" onChange="queryToggle(this)" />
<input type="checkbox" name="toggleQuery" id="toggleQuery2" onChange="queryToggle(this)" />
<input type="checkbox" name="toggleQuery" id="toggleQuery3" onChange="queryToggle(this)" />
<input type="checkbox" name="toggleQuery" id="toggleQuery4" onChange="queryToggle(this)" />
</div>
<span class="text">min</span>
<span class="text">min</span>
<span class="text">min</span>
<span class="text">min</span>
<span class="text">min</span>

How to only trigger event on element being clicked

I am trying to create custom increment / subtract number input arrows
The issue I'm having is how to find a way to only trigger the event on the element being clicked - Currently it's triggering the function on all input fields
EDIT**
Code has been updated as I should mention I am looping through items and each input has a wrapper div with the class ".cart__qty-input"
var quantityInput = document.querySelectorAll('.cart__qty-input');
var plusBtn = document.querySelectorAll('.inc_plus');
var minusBtn = document.querySelectorAll('.inc_minus');
for (i = 0; i < plusBtn.length; i++) {
plusBtn[i].addEventListener('click', addQuantity)
}
function addQuantity(event) {
const currentTarget = event.currentTarget;
let activeIndex = null;
// Find Index
for (index = 0; index < plusBtn.length; index++) {
if(plusBtn[index] === currentTarget) {
activeIndex = index;
}
}
var quantityValue = Number(quantityInput[activeIndex].value)
var newQuantity = quantityValue + 1;
quantityInput[activeIndex].value = newQuantity;
}
for (i = 0; i < minusBtn.length; i++) {
minusBtn[i].addEventListener('click', minusQuantity)
}
function minusQuantity(event) {
const currentTarget = event.currentTarget;
let activeIndex = null;
// Find Index
for (index = 0; index < minusBtn.length; index++) {
if(minusBtn[index] === currentTarget) {
activeIndex = index;
}
}
var quantityValue = Number(quantityInput[activeIndex].value)
var newQuantity = quantityValue - 1
quantityInput[activeIndex].value = newQuantity
}
<div class="cart__qty-input">
<span class="inc_minus">-</span>
<input id="test" class="cart__qty-input" type="number" name="updates[]" value="1" min="0" pattern="[0-9]*">
<span class="inc_plus">+</span>
</div>
<br>
<div class="cart__qty-input">
<span class="inc_minus">-</span>
<input id="test" class="cart__qty-input" type="number" name="updates[]" value="1" min="0" pattern="[0-9]*">
<span class="inc_plus">+</span>
</div>
Your current issue is that you have not implemented a logc to pick which is the row against which the +/- button has been clicked.
I have done that in the following way.
Select the target node against which the +/- button has been clicked.
Parse through the array of the +/- buttons to find the index.
Once the index has been found, update the input corresponding to that index only.
Hope this is helpful
<div class="cart__qty-input">
<span class="inc_minus">-</span>
<input id="test" class="cart__qty-input" type="number" name="updates[]" value="1" min="0" pattern="[0-9]*">
<span class="inc_plus">+</span>
</div>
<br>
<div class="cart__qty-input">
<span class="inc_minus">-</span>
<input id="test" class="cart__qty-input" type="number" name="updates[]" value="1" min="0" pattern="[0-9]*">
<span class="inc_plus">+</span>
</div>
<script>
var quantityInput = document.querySelectorAll('.cart__qty-input input.cart__qty-input');
var plusBtn = document.querySelectorAll('.inc_plus');
var minusBtn = document.querySelectorAll('.inc_minus');
for (i = 0; i < plusBtn.length; i++) {
plusBtn[i].addEventListener('click', addQuantity)
}
function addQuantity(event) {
const currentTarget = event.currentTarget;
let activeIndex = null;
// Find Index
for (index = 0; index < plusBtn.length; index++) {
if (plusBtn[index] === currentTarget) {
activeIndex = index;
}
}
var quantityValue = Number(quantityInput[activeIndex].value)
var newQuantity = quantityValue + 1;
quantityInput[activeIndex].value = newQuantity;
}
for (i = 0; i < minusBtn.length; i++) {
minusBtn[i].addEventListener('click', minusQuantity)
}
function minusQuantity(event) {
const currentTarget = event.currentTarget;
let activeIndex = null;
// Find Index
for (index = 0; index < minusBtn.length; index++) {
if (minusBtn[index] === currentTarget) {
activeIndex = index;
}
}
var quantityValue = Number(quantityInput[activeIndex].value)
var newQuantity = quantityValue - 1
quantityInput[activeIndex].value = newQuantity
}
</script>

How to write javascript to combine multiple filters

I have a webpage with a grid of figures, when I contruct the page I add css classes to represent certain things such as has image/doesnt have image to an enclosing div. Then I have a select element that the user can use to filter the tiles (nofilter, no image, has image)
<div class="mb-2">
<label for="selectArtistArtworkFilter" id="selectArtistArtworkFilterLabel">
Artist Artwork Filter
</label>
<select id="selectArtistArtworkFilter" class="custom-select col-3" aria-labelledby="selectArtistArtworkFilterLabel" onchange="artworkFilter('selectArtistArtworkFilter');">
<option value="1" selected="selected">
No Filter
</option>
<option value="2">
Without Artist Artwork
</option>
<option value="3">
With Artist Artwork
</option>
</select>
<span class="mx-2">
</span>
</div>
<div style="display:grid;grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));grid-gap: 5px;">
<div class="tile noimage albumimage">
<figure class="figure" style="position:relative">
<a href="StatusReport00108_byartistalbum00001.html">
<img src="../style/images/folder.jpg" class="figure-img" width="200" height="200">
</a>
</figure>
</div>
<div class="tile image albumimage">
<figure class="figure" style="position:relative">
<a href="StatusReport00108_byartistalbum00002.html">
<img src="../images/gb7iqGOT4duS062Nh2a3Xg==.jpg" class="figure-img" width="200" height="200">
</a>
</figure>
</div>
by calling a javascript function
function artworkFilter(selectbox)
{
var select = document.getElementById(selectbox);
var selected = select.options[select.selectedIndex].value;
if(selected=="1")
{
var figures = document.getElementsByClassName("tile");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "block";
}
}
else if(selected=="2")
{
var figures = document.getElementsByClassName("image");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "none";
}
figures = document.getElementsByClassName("noimage");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "block";
}
}
else if(selected=="3")
{
var figures = document.getElementsByClassName("image");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "block";
}
figures = document.getElementsByClassName("noimage");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "none";
}
}
}
This works, but I then needed to add another filter (on albumartwork) so i added another select
<label for="selectCoverArtFilter" id="selectCoverArtFilterLabel">
Cover Art Filter
</label>
<select id="selectCoverArtFilter" class="custom-select col-3" aria-labelledby="selectCoverArtFilterLabel" onchange="albumArtworkFilter('selectCoverArtFilter');">
<option value="1" selected="selected">
No Filter
</option>
<option value="2">
Without Cover Art
</option>
<option value="3">
With Cover Art
</option>
and another javascript function
function albumArtworkFilter(selectbox)
{
var select = document.getElementById(selectbox);
var selected = select.options[select.selectedIndex].value;
if(selected=="1")
{
var figures = document.getElementsByClassName("tile");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "block";
}
}
else if(selected=="2")
{
var figures = document.getElementsByClassName("albumimage");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "none";
}
figures = document.getElementsByClassName("noalbumimage");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "block";
}
}
else if(selected=="3")
{
var figures = document.getElementsByClassName("albumimage");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "block";
}
figures = document.getElementsByClassName("noalbumimage");
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = "none";
}
}
}
Now this ones work in isolation but doesnt take into account what the first filter is set to. So my next stage was to replace the two functions with a single function that took both selects into account in one go.
But then I remembered I needed a 3rd filter and it seems that this is very rapidly going to get very messy, and I think there must be a more elegant way to do this ?
Create a function that toggles the styles to save yourself the repetition..
function toggleDisplay(className, displayValue){
var figures = document.getElementsByClassName(className);
for (i = 0; i < figures.length; i++)
{
figures[i].style.display = displayValue;
}
}
Now you can turn your jungle of if statements into:
function albumArtworkFilter(selectbox)
{
var select = document.getElementById(selectbox);
var selected = select.options[select.selectedIndex].value;
if(selected=="1")
{
toggleDisplay("tile", "block");
}
else if(selected=="2")
{
toggleDisplay("albumimage", "none");
toggleDisplay("noalbumimage", "block");
}
else if(selected=="3")
{
toggleDisplay("albumimage", "block");
toggleDisplay("noalbumimage", "none");
}
}
Switch alternative:
switch(selected) {
case "1":
toggleDisplay("tile", "block");
break;
case "2":
toggleDisplay("albumimage", "none");
toggleDisplay("noalbumimage", "block");
break;
// .... etc
}
If you want to be able to pass multiple style properties for each elements.. modify the toggleDisplay function to the following:
function toggleDisplay(className, displayValue){
var figures = document.getElementsByClassName(className);
var keys = Object.keys(displayValue);
for (i = 0; i < figures.length; i++)
{
for (var j = 0; j < keys.length; j++){
figures[i].style[keys[j]] = displayValue[keys[j]];
}
}
}
Then you'd call that using
toggleDisplay("tile", {display: "block", color: "black" });

Adding generated values from inputs

I have a site where I can enter the amount of an item, it will then take that input value and return the result on the page. I am then trying to get the results of all the items and return a grand total.
The issue is when I a loop to do this it will only add the first one.
I created a fiddle: https://jsfiddle.net/rc1mgLj5/4/
I am using querySelectorAll and using the length of all the classNames for the result of the first return.
Then looping them after parsing them to a number from text.
But at the moment it is only doing the first calculation. If I delete the for loop the first part works correctly again.
So since its only doing the first calculation for the first item, I get NaN for the second because it does not have a number to parse.
const total = document.querySelectorAll(".tot");
const price = document.querySelectorAll(".cost");
let textval = document.querySelectorAll(".qty-item");
const cal = document.getElementById("calc");
const errorMessage = document.querySelectorAll(".error");
cal.addEventListener("mouseover", function(e) {
console.log(total);
for (var i = 0; i < price.length; i++) {
let xPrice = price[i].innerHTML.split("$");
let parsePrice = parseFloat(xPrice[1]);
if (textval[i].value === "" || isNaN(textval[i].value)) {
setMessage("Please enter a number", "red");
} else {
let x = parseFloat(textval[i].value);
let y = parsePrice;
let z = x * y;
total[i].innerText = z.toFixed(2);
total[i].innerText = z;
for (i = 0; i < total.length; i++) {
let j = parseFloat(total[i].innerHTML);
console.log(j);
}
}
}
});
HTML:
<body>
<div class="main">
<span class="title">A Title</span>
</div>
<div class="content">
<div class="item">
<span>Item 1</span>
</div>
<div>
<span class="cost">$100.00</span>
</div>
<div id="qty">
<label>QTY:</label><input placeholder="0" class="qty-item">
<p class="error"></p>
</div>
<div class="tot">
<span><label>TOTAL</label> $0.0</span>
</div>
</div>
<br><br>
<div class="main">
<span class="title">A Title</span>
</div>
<div class="content">
<div class="item">
<span>Item 2</span>
</div>
<div>
<span class="cost">$50.00</span>
</div>
<div class="qty">
<label>QTY:</label><input placeholder="0" class="qty-item">
<p class="error"></p>
</div>
<div class="tot">
<span><label>TOTAL</label> $0.0</span>
</div>
</div>
<div class="calc-button">
<button id="calc">Calculate Prices</button>
</div>
</body>
You are nesting two fors using the same i variable as index:
cal.addEventListener('mouseover', function(e) {
console.log('total', total);
for (var i = 0; i < price.length; i++) {
//...
for (i = 0; i < total.length; i++) { // <== uses "i" again
let j = parseFloat(total[ii].innerHTML);
console.log(j);
}
}
}
});
Just replace that second for's variable with another name. Example:
for (let k = 0; k < total.length; k++) {
let j = parseFloat(total[k].innerHTML);
console.log(j);
}
Demo: https://jsfiddle.net/acdcjunior/gpLvszx3/
It seems you are using the same variable "i" for both loops and i is being reset in the second loop to 3 and hence the main loop runs only once. So i removed the following code and calculated the total outside main loop. seems to be working fine now. https://jsfiddle.net/uxr7ac9k/7/
total[i].innerText = z;
for (i=0;i < total.length; i++){
let j = parseFloat(total[i].innerHTML);
console.log(j);
}

Categories

Resources