I have this function:
const sliderTextChange = document.getElementsByClassName('slider') // text change
const changeSliderText = change => {
const sliderLeft = document.getElementsByClassName('switch-left')
const sliderRight = document.getElementsByClassName('switch-right')
for (let i = 0; i < change.length; i++) {
change[i].addEventListener('click', () => {
sliderRight[i].style.display = 'flex';
sliderLeft[i].style.display = 'none';
});
}
}
changeSliderText(sliderTextChange);
This is one of the many sliders on the website:
<div class="flex-column">
<h3>Text Colour</h3>
<div class="slider">
<div class="slider-back"></div>
<div class="slider-circle"></div>
</div>
<h3 class="switch-left">White</h3>
<h3 class="switch-right">Black</h3>
</div>
This function is quite a lot like many other functions in my code but they're only firing once. AKA I fire the event listener and but then I can't fire it again.
What's the issue here?
I have tried to simplify your code and keep the scope to be modular and reusable view.
function bindEvent() {
const sliderList = document.querySelectorAll('.slider');
[...sliderList].forEach((slider) => slider.addEventListener('click', () => {
const left = slider.parentElement.querySelector('.switch-left');
const right = slider.parentElement.querySelector('.switch-right');
const leftDisplay = left.style.display || 'flex';
const rightDisplay = right.style.display || 'none';
left.style.display = rightDisplay;
right.style.display = leftDisplay;
}, false));
}
window.onload = bindEvent;
<div>
<button class="slider"> - SLIDER 1 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
<div>
<button class="slider"> - SLIDER 2 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
<div>
<button class="slider"> - SLIDER 3 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
<div>
<button class="slider"> - SLIDER 4 - </button>
<div class="switch-left">L</div><div class="switch-right">R</div>
</div>
Parameters you have chosen for your function are not really intuitive and make your example more complex.
We use querySelector, it's nicer to read but if you prefer speed, just go for getElementsByClassName, it also works on any DOM element.
Related
This question already has answers here:
What do querySelectorAll and getElementsBy* methods return?
(12 answers)
Closed 3 months ago.
I have tried the answers given on the similar questions but I really can't do this one and I actually have no idea what I am supposed to do which is why I made this new question. The respective counter is supposed increase by one each time the respective countUp button is clicked. But now, I got NaN in both counter when I click on the first countUp button. Could you please help? Thank you.
const countUp = document.querySelectorAll('.countUp')
const countDown = document.querySelector('.countDown')
const counter = document.querySelectorAll('.num')
let count = counter.textContent
countUp.forEach((countUp) => {
countUp.addEventListener('click', () => {
counter.forEach((countUp) => {
count++
countUp.innerHTML = count
})
})
});
<div class="rating">
<button class="countUp">+</button>
<span class="num">0</span>
<button class="countDown">-</button>
</div>
<div class="rating">
<button class="countUp">+</button>
<span class="num">0</span>
<button class="countDown">-</button>
</div>
You cannot access the relative span using querySelectorAll. It returns a collection
If you delegate and navigate relatively among siblings, you can save a lot of code and headaches
const container = document.getElementById("container");
container.addEventListener('click', (e) => {
const tgt = e.target.closest("button");
if (!tgt) return; // or use e.target and test tgt.matches("button")
const numSpan = tgt.closest(".rating").querySelector(".num");
if (numSpan.matches(".clicked")) return; // already clicked
numSpan.classList.add("clicked");
let num = +numSpan.textContent;
num += tgt.classList.contains("countUp") ? 1 : -1;
numSpan.textContent = num;
});
<div id="container">
<div class="rating">
<button class="countUp">+</button>
<span class="num">1</span>
<button class="countDown">-</button>
</div>
<div class="rating">
<button class="countUp">+</button>
<span class="num">0</span>
<button class="countDown">-</button>
</div>
</div>
Hope someone can help me out.
I am trying to dynamically create some cards on my webpage out of a dictionary.
I tried to create the function but the code inside the first <div>
cards.map((character)=>(
is not recognizing the array of dictionaries.
Any ideas on how to fix it?
function MemoryCards() {
const images = [
"./img/Abadango.jpeg",
"./img/abradolf.jpeg",
"./img/Adjudicator.jpeg",
"./img/AgencyDirector.jpeg",
"./img/Alan.jpeg",
"./img/Albert.jpeg",
"./img/Alexander.jpeg",
"./img/AlienMorty.jpeg",
"./img/AlienRick.jpeg",
"./img/Annie.jpeg",
"./img/AntsJonson.jpeg",
"./img/Beth.jpeg",
"./img/Jerry.jpeg",
"./img/morty.jpeg",
"./img/ricky.jpeg",
"./img/summer.jpeg"
]
const cards = [];
let len = images.length;
for (let i = 0; i < len; i++) {
let end = images[i].indexOf('.', 3);
let name = images[i].substring(6, end);
let card = { 'name': name, 'img': images[i], 'id': i };
cards.push(card);
}
return (
<div>
cards.map((character)=>(
<div class="card">
<div className="card_header">
<img src={cards.img}></img>
</div>
<div className="card_body">
<h3>{cards.name}</h3>
</div>
</div>
))
</div>
)
}
export default MemoryCards;
Inside your loop you have {cards.img} and {cards.name} but what you want is {character.img} and {character.name}
Also you are missing curly brackets {} before initializing cards loop
Note, you have a typo, instead of className you have just class here: <div class="card">
function MemoryCards() {
const images = [
"./img/Abadango.jpeg",
"./img/abradolf.jpeg",
"./img/Adjudicator.jpeg",
"./img/AgencyDirector.jpeg",
"./img/Alan.jpeg",
"./img/Albert.jpeg",
"./img/Alexander.jpeg",
"./img/AlienMorty.jpeg",
"./img/AlienRick.jpeg",
"./img/Annie.jpeg",
"./img/AntsJonson.jpeg",
"./img/Beth.jpeg",
"./img/Jerry.jpeg",
"./img/morty.jpeg",
"./img/ricky.jpeg",
"./img/summer.jpeg"
];
const cards = [];
let len = images.length;
for (let i = 0; i < len; i++) {
let end = images[i].indexOf(".", 3);
let name = images[i].substring(6, end);
let card = { name: name, img: images[i], id: i };
cards.push(card);
}
return (
<div>
{cards.map((character, idx) => (
<div key={idx} className="card">
<div className="card_header">
<img src={character.img} alt="" />
</div>
<div className="card_body">
<h3>{character.name}</h3>
</div>
</div>
))}
</div>
);
}
export default MemoryCards;
You need to wrap your variables in curly braces {} for it to work inside JSX:
return (
<div>
{cards.map((card)=>(
<div key={card.name} class="card">
<div className="card_header">
<img src={card.img}></img>
</div>
<div className="card_body">
<h3>{card.name}</h3>
</div>
</div>
))}
</div>
)
In the example above, you can see that the entire map block should be inside the curly braces, also don't forget to add an ID to the element inside the map and to use the actual variable defined inside the map function
I'm trying to make a Ping Pong scoreKeeper. Everything is done except the part where the scores are compared and a winner is declared. I'm trying to use the if statement to compare the innerText of two variables and whether their scores match or not. But it's not working.
Here's the Javascript and HTML code I've written.
const p1Score = document.querySelector("#p1Score")
const p2Score = document.querySelector("#p2Score")
const increaseP1Score = document.querySelector("#increaseP1Score")
const increaseP2Score = document.querySelector("#increaseP2Score")
const resetScore = document.querySelector("#resetScore")
const scoreKeeper = document.querySelector("#scoreKeeper")
increaseP1Score.addEventListener('click', function(event) {
p1Score.innerText++
// if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
// console.log("Here it works!")
})
increaseP2Score.addEventListener('click', function() {
p2Score.innerText++
})
resetScore.addEventListener('click', function() {
p1Score.innerText = 0;
p2Score.innerText = 0;
})
if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
console.log("Working!")
}
<div id="container">
<header id="header">
<h1 id="scoreKeeper">Current Score: <span id="p1Score">0</span> to <span id="p2Score">1</span></h1>
</header>
<footer id="footer">
<button id="increaseP1Score">+1 Player One</button>
<button id="increaseP2Score">+1 Player Two</button>
<button id="resetScore">Reset</button>
</footer>
</div>
You'll see a comment in my JS code. When I try to compare the values there, it somehow works. But I don't know why it doesn't work outside the event listener.
const p1Score = document.querySelector("#p1Score")
const p2Score = document.querySelector("#p2Score")
const increaseP1Score = document.querySelector("#increaseP1Score")
const increaseP2Score = document.querySelector("#increaseP2Score")
const resetScore = document.querySelector("#resetScore")
const scoreKeeper = document.querySelector("#scoreKeeper")
increaseP1Score.addEventListener('click', function(event) {
p1Score.innerText++
checkScore();
// if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
// console.log("Here it works!")
})
increaseP2Score.addEventListener('click', function() {
p2Score.innerText++
checkScore();
})
resetScore.addEventListener('click', function() {
p1Score.innerText = 0;
p2Score.innerText = 0;
})
function checkScore(){
if (p1Score.innerText == 5 && p1Score.innerText > p2Score.innerText) {
//console.log("Working!")
alert("working!");
}
}
<div id="container">
<header id="header">
<h1 id="scoreKeeper">Current Score: <span id="p1Score">0</span> to <span id="p2Score">1</span></h1>
</header>
<footer id="footer">
<button id="increaseP1Score">+1 Player One</button>
<button id="increaseP2Score">+1 Player Two</button>
<button id="resetScore">Reset</button>
</footer>
</div>
Your if statement is just running once when the page loads. You could put the functionality... in a function like checkScore() above and call it when you increment the scores. This is more re-usable and a better solution to hard-coding it in each incrementer.
I understand that the wording of the question is incorrect (if someone can write it correctly, please). The task is this, I have 30 elements on the page and I need to sort them with the resulting array. That is, I get an array - let order = [2, 5, 3, 6, 12 ...] and sorting should take place in accordance with this order, that is, the first element is the 2nd element from HTML, the second element is the 5th element from HTML (according to the given array). The initial order is equal to the number in data-custom-sort.
There will be many such an array. And I don't understand how to do it universally. Can someone have any ideas?
I have not formulated very well, so if you have questions - ask.
The HTML is something like this:
<a id="sort-best" class="choose-cat">best</a>
<div>
<article data-custom-sort="1">
...
</article>
<article data-custom-sort="2">
...
</article>
<article data-custom-sort="3">
...
</article>
//and etc
</div>
These are product cards in the catalog. I need to sort them
document.querySelector('#sort-best').onclick = sortBest;
function sortBest() {
let nav = document.querySelector('#game-items-cart');
for (let i = 0; i < nav.children.length; i++) {
for (let j = i; j < nav.children.length; j++) {
if (+nav.children[i].getAttribute('data-sort') > +nav.children[j].getAttribute('data-sort')) {
replaceNode = nav.replaceChild(nav.children[j], nav.children[i]);
insertAfter(replaceNode, nav.children[i]);
}
}
}
}
function insertAfter(elem, refElem) {
return refElem.parentNode.insertBefore(elem, refElem.nextSibling);
}
I used this code to sort through the data attributes. That is, the number in the data attribute = the ordinal after sorting.
Like this?
let order = [2, 1, 3];
const container = document.getElementById("container");
document.getElementById("sort-best").addEventListener("click", e => {
e.preventDefault()
order.forEach(idx => container.appendChild(container.querySelector("[data-custom-sort='" + idx + "']")))
})
<a id="sort-best" class="choose-cat">best</a>
<div id="container">
<article data-custom-sort="1">
One
</article>
<article data-custom-sort="2">
Two
</article>
<article data-custom-sort="3">
Three
</article>
</div>
More generic:
const sortArticles = (cont, order) => {
const container = document.getElementById(cont);
order.forEach(idx => container.appendChild(container.querySelector("[data-custom-sort='" + idx + "']")))
};
document.getElementById("sort").addEventListener("click", e => {
const tgt = e.target;
if (tgt.classList.contains("choose-cat")) {
e.preventDefault()
sortArticles("container", tgt.dataset.order.split(","))
}
})
<div id="sort">
<a id="sort-best" class="choose-cat" data-order="3,1,2">best</a> |
<a id="sort-default" class="choose-cat" data-order="1,2,3">default</a>
</div>
<div id="container">
<article data-custom-sort="1">
One
</article>
<article data-custom-sort="2">
Two
</article>
<article data-custom-sort="3">
Three
</article>
</div>
Here is another way of doing this
// Sort reference array
const sortRef = [2, 5, 3, 1, 4];
// Get, sort, update function
const sortFn = () => {
// Apply new order by sorting and replacing sortContainer content
const newArray = [];
for(let i = 0; i < sortRef.length; i++) newArray.push(document.querySelector("[data-custom-sort='" + sortRef[i] + "']").outerHTML);
// Update html
document.getElementById("sortContainer").innerHTML = newArray.join('');
}
// Add click event
document.getElementById("clickMe").addEventListener('click', event => {
sortFn();
});
article {
border: 1px solid #ff0000;
padding: 3px;
width: 100px;
}
<div id="sortContainer">
<article data-custom-sort="1">
1
</article>
<article data-custom-sort="2">
2
</article>
<article data-custom-sort="3">
3
</article>
<article data-custom-sort="4">
4
</article>
<article data-custom-sort="5">
5
</article>
</div>
<p></p>
<button id="clickMe">Sort html data</button>
I need to change navbar when scrolling a page. How to catch the moment when to change it? How to do it right, in accordance with the concepts of React? As far as I know, use getElementById is that bad tone?
const useState = React.useState
const useEffect = React.useEffect
const Component = () => {
const [topNavbarHide, setTopNavbarHide] = useState(true);
useEffect(() => {
window.addEventListener('scroll', function () {
let navbarSize = document.getElementById('navbar').offsetHeight;
console.log("navbarSize " + navbarSize + "px");
let scrollTop = document.documentElement.scrollTop;
console.log("scrollTop " + scrollTop);
if (scrollTop > navbarSize) {
setTopNavbarHide(false)
} else {
setTopNavbarHide(true)
}
console.log(topNavbarHide);
});
});
return (
<div>
<div id={"navbar"} className="navbar">
<div
className={(topNavbarHide) ? "topNavbar" : "topNavbar hide"}>topNavbar
</div>
<div className="bottomNavbar">bottomNavbar</div>
</div>
<div className="box"></div>
<div className="box1"></div>
<div className="box2"></div>
<div className="box"></div>
<div className="box1"></div>
</div>
)
};
ReactDOM.render(
<Component/>,
document.getElementById('root')
);
https://codepen.io/slava4ka/pen/wvvGoBX
It is perfectly fine to add an event listener, in the way that you are currently doing it, to a React hook. The way that you are doing it is the correct way.
There is another, more simpler way with react-waypoint. You can place invisible waypoints on your screen and it can trigger events when waypoint enter or leave screen.
For example:
const Component = () => {
const [topNavbarHide, setTopNavbarHide] = useState(true);
return (
<div>
<div id={"navbar"} className="navbar">
<div className={topNavbarHide ? "topNavbar" : "topNavbar hide"}>
topNavbar
</div>
<div className="bottomNavbar">bottomNavbar</div>
</div>
<Waypoint
onEnter={() => setTopNavbarHide(true)}
onLeave={() => setTopNavbarHide(false)}
/>
<div className="box" />
<div className="box1" />
<div className="box2" />
<div className="box" />
<div className="box1" />
</div>
);
};
It is basically working like your example.
https://codesandbox.io/s/hungry-hodgkin-5jucl