Problem with JS add and remove class for elements - javascript

I have a code that adds or removes a class from elements. But it does this only for the menu, but not for the content (a huge white square in the middle), although it should for both at the same time. I need that when clicking on any button in the menu, the 'is-active' class changes both in the menu and in the content.
without clicking on the menu.png
with a click on the menu.png
const list = Array.from(document.querySelectorAll('.list'))
const play = Array.from(document.querySelectorAll('.play'))
const clearActiveClass = (element, className = 'is-active') => {
element.find(item => item.classList.remove(`${ className }`))
}
const setActiveClass = (element, index, className = 'is-active') => {
element[index].classList.add(`${ className }`)
}
const checkoutTabs = (item, index) => {
item.addEventListener('click', () => {
if (item.classList.contains('is-active')) return
console.log(item)
clearActiveClass(list)
clearActiveClass(play)
setActiveClass(list, index)
setActiveClass(play, index)
})
}
list.forEach(checkoutTabs)

The inner function to find needs to be something that finds an element containing className. Once you have found the element, you can remove the className class.
const clearActiveClass = (element, className = 'is-active') => {
element.find(item => item.classList.contains(className)).classList.remove(className);
}

Related

Close button popup doesn't work (JAVASCRIPT)

i'm trying to create a custom pupop in javascript, this is my first time with this.
I have a problem with the close button, the "x" target correctly the div to close, but doesn't remove the "active" class at click.
https://demomadeingenesi.it/demo-cedolino/
HTML CODE
<div class="spot spot-2">
<div class="pin"></div>
<div class="contenuto-spot flex flex-col gap-3">
<img class="chiudi-popup" src="img/chiudi.svg" />
[---CONTENT---]
</div>
</div>
</div>
JAVASCRIPT CODE
const tooltips = function () {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
const chiudiPopup = document.querySelector(".chiudi-popup");
spot.classList.add("active");
contenuto.classList.add("show");
if (curActive && curActive !== spot) {
curActive.classList.toggle("active");
contActive.classList.toggle("show");
}
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
});
});
const chiudiPopup = document.querySelector(".chiudi-popup");
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
What the code above does is adding an click listener, but it's inside another click listener, so all it's doing is adding an click listener on the first .chiudi-popup that removes .active and .show from the last spot element.
It's hard to see if this is correct, because you haven't given us enough to reproduce the problem, but I moved the code above outside the spot.addEventListener("click", () => { and instead of searching the whole document with const chiudiPopup = document.querySelector(".chiudi-popup"); the code nows only targets the .chuidi-popup element within the spot: const chiudiPopup = spot.querySelector(".chiudi-popup");
const tooltips = function() {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
spot.classList.add("active");
contenuto.classList.add("show");
if (curActive && curActive !== spot) {
curActive.classList.toggle("active");
contActive.classList.toggle("show");
}
});
// MOVED FROM THE CLICK LISTENER
const chiudiPopup = spot.querySelector(".chiudi-popup");
chiudiPopup.addEventListener("click", () => {
spot.classList.remove("active");
contenuto.classList.remove("show");
});
});
EDIT: I missed that you have the img.chiudi-popup inside your container, which will trigger both event listeners. I would honestly just simplify the code and always hide the container when clicking on it again. You can still have the img.chiudi-popup (close image) to make it easier for the users to understand that they can click on it.
const tooltips = function() {
const spots = document.querySelectorAll(".spot");
spots.forEach((spot) => {
const contenuto = spot.querySelector(".contenuto-spot");
const pin = spot.querySelector(".pin");
spot.addEventListener("click", () => {
let curActive = document.querySelector(".spot.active");
let contActive = document.querySelector(".contenuto-spot.show");
if (curActive !== spot) {
spot.classList.add("active");
contenuto.classList.add("show");
}
if (curActive) {
curActive.classList.remove("active");
contActive.classList.remove("show");
}
});

event bubbling causes event to register with children of element

I am trying to make a drop down list that dynamically adds elements from an API. When the user selects an item in the dropdown, it should add a class called "current" to that item. Only one dropdown item in the list can have the class 'current' applied to it.
I have successfully create the HTML elements (listItem) and appended them to the list. However, when I try to add event listener, the event registers with the child elements img and div with text such that when user clicks those, the class 'current' is applied there and not the parent node.
I read up on "event bubbling" but not sure if this is my issue or not.
document.addEventListener('DOMContentLoaded', async () => {
const dropDownToggle = document.querySelector('.w-dropdown-toggle')
const dropDownList = document.querySelector('.w-dropdown-list')
const countries = await getCountries();
countries.forEach((country) => {
const countryName = country.name.common;
const cca2 = country.cca2;
const svgUrl = country.flags.svg;
let prefix = country.idd.root + country.idd.suffixes?.[0]
prefix = Number.isNaN(prefix) ? prefix="" : prefix
//console.log(countryName, cca2, prefix)
const listItem = createListItem(countryName, cca2, svgUrl, prefix);
// Bad code here: <a> tag gets event listener but so do its children
listItem.addEventListener("click", (event) => {
console.log(event);
//console.log(event);
//document.querySelector('current')?.classList.remove('current');
//document.querySelector('current').ariaSelected = false;
console.log('hello')
event.target.classList.add("current");
event.target.ariaSelected = true;
console.log('goodbye')
});
dropDownList.append(listItem);
})
});
const getCountries = async () => {
let url = 'https://restcountries.com/v3.1/all'
const response = await fetch(url)
const data = await response.json();
return data;
}
const createListItem = (countryName, cca2, svgUrl, prefix) => {
const template = `
<a data-element="item" aria-role="option" aria-selected="false" href="#" class="dropdown_item w-inline-block" tabindex="0"><img src="${svgUrl}" loading="lazy" data-element="flag" alt="" class="dropdown_flag"><div data-element="value" class="dropdown_text">${cca2}</div></a>
`
const listItem = document.createElement("template");
listItem.innerHTML = template.trim();
return listItem.content.firstElementChild
}
This is expected. event.target is the innermost element that was actually clicked, not the one that the event listener is installed on.
To use the latter, just refer to event.currentTarget or listItem, instead of event.target.

How to short this statements

How to short this code and which array method i can use here. I want to add class on every click on a single element and rest of elements remove class.
const activeLink = document.querySelectorAll(".nav-item");
activeLink[0].addEventListener("click", () => {
activeLink[0].classList.add("active");
activeLink[1].classList.remove("active");
activeLink[2].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[1].addEventListener("click", () => {
activeLink[1].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[2].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[2].addEventListener("click", () => {
activeLink[2].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[1].classList.remove("active");
activeLink[3].classList.remove("active");
});
activeLink[3].addEventListener("click", () => {
activeLink[3].classList.add("active");
activeLink[0].classList.remove("active");
activeLink[1].classList.remove("active");
activeLink[2].classList.remove("active");
});
Rather then looping through all items per click, you can just keep track of which item is currently active and reset this one.
https://codesandbox.io/s/compassionate-northcutt-50ucp?file=/src/index.js
let activeLink = null;
document.querySelectorAll(".nav-item").forEach(navItem => {
navItem.addEventListener("click", () => {
if (activeLink) {
activeLink.classList.remove("active");
}
navItem.classList.add("active");
activeLink = navItem;
});
});

How to access nth parent node

I try to get height of the parent div of children elements.
I have a Parent div with class="Parent" this have also n children element like <div data-elementid="el_ryz-E9a349" class="row">
Parent have a fix height: 220px and I need to know if children element (n) <div data-elementid="el_ryz-E9a349" class="row"> appear in parrent height if not execute scrollIntoView() to this children.
Important I can't delete this both elements, empty div and <div class="container" because affects my design.
...
const scrollToBottom = () => {
const elementNode = document.querySelector(`[data-elementid='${action.payload.id}']`);
const parentElementNode = elementNode.parentNode;
const elementsHeight = parentElementNode.offsetHeight;
const menuContainer = parentElementNode.parentNode.offsetHeight;
if (elementsHeight > menuContainer) {
elementNode.scrollIntoView({
behavior: 'smooth',
block: 'end',
});
}
};
setTimeout(scrollToBottom, 200);
...
It's obvious if I've n children elements it's redundant to make elementNode.parentNode.parentNode.parentNode to access Parent node to get height property.
Use this function to go up in your element parents and search for you parent classname:
const getParent = (element, cls) => {
if (element && element.parentElement) {
const parentClassName = element.parentElement.className;
if (element.parentElement && parentClassName && parentClassName.match(new RegExp(cls, 'g'))) {
return element.parentElement; // Found it
}
getParent(element.parentElement, cls);
} else {
return false; // No parent with such a className
}
}
const scrollToBottom = () => {
const elementNode = document.querySelector(`[data-elementid='${action.payload.id}']`);
const parentElementNode = getParent(elementNode, 'parent'); // second arg is the parent classname you looking for.
if (parentElementNode) {
const elementsHeight = parentElementNode.offsetHeight;
const menuContainer = parentElementNode.parentNode.offsetHeight;
if (elementsHeight > menuContainer) {
elementNode.scrollIntoView({
behavior: 'smooth',
block: 'end',
});
}
}
console.log('no parent found!')
};
setTimeout(scrollToBottom, 200);
Select with data-atttribute:
const getParentWithAttr = (element, attr) => {
if (element && element.parentElement) {
const parentElement = element.parentElement;
if (parentElement && parentElement.getAttribute('data-attr') === attr) {
return parentElement; // Found it
}
getParent(parentElement, attr);
} else {
return false; // No parent with such a className
}
}
Use case should be like this:
<div id="..." class="..." data-attr="parent">// parrent
... // chilren
</div>
getParentWithAttr(document.querySelector('.element'), 'parent');
Since the question's tag says React.js, I would instead refer to ReactJS how to scroll to an element. This uses React refs and makes your code much simpler. That being said, it looks like the question is actually using pure JavaScript on static HTML.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class SampleComponent extends Component {
scrollToDomRef = () => {
const myDomNode = ReactDOM.findDOMNode(this.myRef.current)
myDomNode.scrollIntoView()
};
render() {
return <> // '<></>' is a React 16.3.0+ feature, use <div> if previous
<button onClick={this.scrollToDomRef}>Click me</div>
<div ref={ re => { this.myRef = re } }></div>
</>
}
}

Event listener on multiple elements with the same class

On the site I have a navbar. When I click on the a in navbar I want a new div (with content) appears and everything else "hides". I did it withdisplay: none. In the corner there should be an "X" which closes the div and makes "first screen" appear again.
I did the first part, but have some problems with closing. Here is my code:
const about = document.getElementById('li-about');
const work = document.getElementById('li-work');
const contact = document.getElementById('li-contact');
^These are links in a navbar.
const openSlide = (id) => {
document.querySelector('main article#' + id).style.display = 'block';
document.querySelector('header').style.display = 'none';
};
about.addEventListener('click', () => openSlide('about'));
work.addEventListener('click', () => openSlide('work'));
contact.addEventListener('click', () => openSlide('contact'));
And here is the code. It works. But when I want to do similar thing to a closing div function, it only works with about, not work and contact.
const closeSlide = (id) => {
document.querySelector('main article#' + id).style.display = 'none';
document.querySelector('header').style.display = 'block';
};
const close = document.querySelector('.close');
close.addEventListener('click', () => closeSlide('about'));
close.addEventListener('click', () => closeSlide('work'));
close.addEventListener('click', () => closeSlide('contact'));
Can you tell me why? There is no error in console, I tried to replace closeSlide function with a simple console.log and it doesn't work as well. Is it possible that JavaScript detects only one (first in HTML code) .close div?
Is it possible that JavaScript detects only one (first in HTML code) .close div?
Yes, document.querySelector returns the first matching element it finds in the DOM. If you want to add your listeners to every .close on the page, either loop through the NodeList returned by document.querySelectorAll:
const closeList = document.querySelectorAll('.close');
closeList.forEach(element => {
element.addEventListener('click', () => closeSlide('about'));
element.addEventListener('click', () => closeSlide('work'));
element.addEventListener('click', () => closeSlide('contact'));
};
or add a listener to an element containing all of your .close elements that only takes action if a .close was clicked:
document.querySelector('#some-container').addEventListener('click', evt => {
if (!evt.target.closest('.close')) return;
closeSlide('about');
closeSlide('work');
closeSlide('contact');
});

Categories

Resources