Add a class to element with matching data attribute value - javascript

I want to add a class to an element that shares the same data attribute value using vanilla JS. The class is added on mouseenter.
My current setup only applies the class on hover to the first element and ignores the rest.
let section = document.querySelector('.section');
let links = document.querySelectorAll('.links a');
let triggerVal;
let linkedVal;
links.forEach(function(link, index) {
link.addEventListener('mouseenter', function() {
triggerVal = this.dataset.triggerValue;
linkedVal = section.dataset.linkedValue;
if (linkedVal === triggerVal) {
section.classList.add('is-active');
} else {
section.classList.remove('is-active');
}
});
});
<ul class="links">
<li>
<a data-trigger-value="red" href="#">Red</a>
</li>
<li>
<a data-trigger-value="yellow" href="#">Yellow</a>
</li>
<li>
<a data-trigger-value="blue" href="#">Blue</a>
</li>
</ul>
<div class="wrapper">
<div class="section" data-linked-value="red">
<h2>Red</h2>
</div>
<div class="section" data-linked-value="yellow">
<h2>Yellow</h2>
</div>
<div class="section" data-linked-value="blue">
<h2>Blue</h2>
</div>
</div>
Here's a Codepen: https://codepen.io/abbasarezoo/pen/7378e190ed6ad117faca968b634520b0
I've got a feeling it's to do with the .section element but I've tried a few things and nothing seems to give me what I need.
Any suggestions as to what I need to do to get the rest of the elements working?

You need to change two things:
First, get all sections:
const section = document.querySelectorAll('.section');
Then, inside your handler, you need to iterate over the NodeList returned by querySelectorAll():
for (const section of sections) {
linkedVal = section.dataset.linkedValue;
if (linkedVal === triggerVal) {
section.classList.add('is-active');
} else {
section.classList.remove('is-active');
}
}
This is your new JS:
const sections = document.querySelectorAll('.section');
const links = document.querySelectorAll('.links a');
let triggerVal;
let linkedVal;
links.forEach(function(link, index){
link.addEventListener('mouseenter', (e) => {
triggerVal = e.target.dataset.triggerValue;
for (const section of sections) {
linkedVal = section.dataset.linkedValue;
if (linkedVal === triggerVal) {
section.classList.add('is-active');
} else {
section.classList.remove('is-active');
}
}
});
});

You need to use document.querySelectorAll for sections and then forEach. And use toggle instead of add/remove for this case. https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
let sections = document.querySelectorAll('.section');
let links = document.querySelectorAll('.links a');
let triggerVal;
let linkedVal;
links.forEach(function(link, index) {
link.addEventListener('mouseenter', function() {
triggerVal = this.dataset.triggerValue;
sections.forEach(
section => section.classList.toggle(
'is-active',
section.dataset.linkedValue === triggerValue
)
);
});
});

Related

how to get index value of dynamic div

I have created some dynamic divs under 'parent' div and set fetched data from json in these dynamic divs. now I want to use a onclick function to get the index value of these dynamic divs which will be clicked. but don't know how to do.
fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(json => display(json));
function display(datas) {
let parent = document.getElementById("parent");
datas.forEach(data => {
let div = document.createElement('div');
div.classList.add('list');
div.innerHTML = `
<li>country Name: ${data.name.common}</li>
<li>country Continents: ${data.continents[0]}</li>
<li>Total arae: ${data.area}</li>
<img src="${data.flags.png}" alt="" style="display: none">`
parent.appendChild(div);
})
}
<body>
<div id="parent">
<img src="" alt="">
</div>
<script src="country.js"></script>
</body>
You can get the index of an html element relative to a parent by getting a collection of the elements, convert it to an array, and then get the index in the usual way with the native array indexOf method:
function getIndex(div) {
const parent = document.getElementById("parent");
const collection = Array.from(parent.querySelectorAll(".list"));
const index = collection.indexOf(div);
return index;
}
You can use event delegation to automatically assign a click handler to all .list elements as they are created
const parent = document.getElementById("parent");
parent.addEventListener('click', function (event) {
const item = event.target;
if (item.matches(".list")) {
console.log("index: ", getIndex(item));
}
});
fetch(//...
example:
const parent = document.getElementById("parent");
function getIndex(div) {
const collection = Array.from(parent.querySelectorAll(".list"));
const index = collection.indexOf(div);
return index;
}
parent.addEventListener('click', function (event) {
const item = event.target;
if (item.matches(".list")) {
console.log("index: ", getIndex(item));
}
});
parent.innerHTML = `
<div class="list">A</div>
<div class="list">B</div>
<div class="list">C</div>
<div class="list">D</div>
`;
<div id="parent"></div>
You can achieve index using second argument of forEach iteration function
arr.forEach(function callback(currentValue, index, array) {
//your iterator
}[, thisArg]);
So, part of your code will look like this:
datas.forEach((data, index) => {

How can I write these js codes with for loop?

I have 3 buttons and 3 content.
When I click on any button, I want to show the content of that button. When I click on the other button, I want to show the content of that button and delete the content of the other button.
I managed to do this with simple logic, but how can I do it with a for loop?
const content = document.querySelectorAll('.content');
const contentClose = document.querySelector('.close-content');
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent[0].addEventListener('click',
function() {
content[1].classList.remove('show-modal');
content[2].classList.remove('show-modal');
content[0].classList.add('show-modal');
}
)
btnsOpenContent[1].addEventListener('click',
function() {
content[0].classList.remove('show-modal');
content[2].classList.remove('show-modal');
content[1].classList.add('show-modal');
}
)
btnsOpenContent[2].addEventListener('click',
function() {
content[0].classList.remove('show-modal');
content[1].classList.remove('show-modal');
content[2].classList.add('show-modal');
}
)
Use simple forEach loops along with classList.toggle(className: string, force: boolean):
const content = document.querySelectorAll('.content');
const contentClose = document.querySelector('.close-content');
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent.forEach((btn, index) => {
btn.addEventListener('click', () => {
content.forEach((cnt, idx) => cnt.classList.toggle('show-modal', idx === index))
})
})
I strongly suggest to delegate, which means instead of adding a listener to each and every button, you instead capitalize on the bubbling mechanism of events, listening to clicks instead on just one common ancestor element of all buttons. Then inside the click handler you implement a guard that checks if the click actually came from a button.
Note I added hidden to the last two content to start with content 1
You can also add active to the button that was clicked and remove from the siblings
NOTE: This code does not change no matter how many buttons as long as there is a matching number of content divs.
Also note there is no need for a class to show and hide
Lastly note I implemented your close too
const nav = document.getElementById("nav");
const contents = document.getElementById("contents");
const buttons = nav.querySelectorAll("#nav .show-content");
const contentDivs = contents.querySelectorAll("div.content")
nav.addEventListener("click", e => {
const tgt = e.target; // what was clicked?
if (!tgt.matches(".show-content")) return; // not a button
buttons.forEach((but,i) => contentDivs[i].hidden = but !== tgt); // hide if not the content that belongs to button
})
contents.addEventListener("click", e => {
const tgt = e.target;
if (!tgt.matches(".close")) return; // not a button
tgt.closest("div").hidden = true; // hide the div the close button is in
})
<div id="nav">
<button class="show-content">Show 1</button>
<button class="show-content">Show 2</button>
<button class="show-content">Show 3</button>
</div>
<div id="contents">
<div class="content">Content 1 <button class="close">X</button></div>
<div class="content" hidden>Content 2 <button class="close">X</button></div>
<div class="content" hidden>Content 3 <button class="close">X</button></div>
</div>
The most important thing to note is that querySelectorAll does not return an array, and instead returns a NodeList. ES6 has introduced a handy method that is intended for this exact purpose NodeList.prototype.forEach().
The easiest approach in my experience to create tabbed content is to add some sort of identifier for each ".content" tab on the button triggering the event. the "data" attribute is often used for this, however, there several other options.
An example of your button html using the data attribute would look like the following:
<button class="show-content" data-content="content1">Show content 1</button>
<button class="show-content" data-content="content2">Show content 2</button>
<button class="show-content" data-content="content3">Show content 3</button>
For this technique your content HTML would need an id that matches the identifier used on your buttons:
<div class="content" id="content1">Some content for 1</div>
<div class="content" id="content2">Some content for 2</div>
<div class="content" id="content3">Some content for 3</div>
And the corresponding javascript would look similar to below:
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent.forEach((button) => {
button.addEventListener('click', (event) => {
let contentId = event.target.getAttribute('data-content');
let targetContent = document.getElementById(contentId);
// Hide all contents
document.querySelectorAll('content').forEach((element) => {
element.classList.remove('show-modal');
})
// Show selected content
targetContent.classList.add('show-modal');
});
});
This code can even be shortened:
const btnsOpenContent = document.querySelectorAll('.show-content');
btnsOpenContent.forEach((button) => {
button.addEventListener('click', (event) => {
let contentId = event.target.dataset.content;
let targetContent = document.getElementById(contentId);
document.querySelectorAll('content').forEach((element) => {
element.classList.toggle('show-modal', contentId === element.id);
})
});
});
Maybe something like this?
const content = document.querySelectorAll('.content');
const contentClose = document.querySelector('.close-content');
const btnsOpenContent = document.querySelectorAll('.show-content');
for (let i = 0; i <= 2; i++) {
btnsOpenContent[i].onclick = () => {
for (let j = 0; j <= 2; j++) {
i == j ? content[j].classList.add('show-modal') : content[j].classList.remove('show-modal');
}
};
}

Toggle switch display on selected

I would like to display creating product in admin section sub categories in tree only when selected else closed in woocommerce product categoreis. How can I achieve this ? Presently it appears like this. Tried css but didn't work.
<li id="tire_sizes-52"><label class="radiall"><input value="52" type="checkbox" name="tax_input[tire_sizes][]" id="in-tire_sizes-52"> 145</label><ul class="children">
<li id="tire_sizes-62"><label class="radiall"><input value="62" type="checkbox" name="tax_input[tire_sizes][]" id="in-tire_sizes-62"> 65</label> <ul class="children">
<li id="tire_sizes-87"><label class="radiall"><input value="87" type="checkbox" name="tax_input[tire_sizes][]" id="in-tire_sizes-87"> 15</label></li>
</ul>
</li>
</ul>
</li>
I want it closed and open only if selected
// if a checkbox has a child, add a css class="has-children"
// select them
const parents = document.querySelectorAll('.has-children')
const caches = parents.map(() => ([]))
function clear(parent) {
const id = parent.getAttribute('id');
const ul = parent.querySelector('.children');
ul.css.display = 'none'; // hide from the DOM
// you may also need to remove it from the DOM
/*
children = [];
while (parent.firstChild) {
const firstChild = parent.firstChild
parent.removeChild(firstChild)
caches[id].push(firstChild);
}*/
}
// inital setup
parents.forEach(function (parent) {
const id = parent.getAttribute('id');
clear(parent);
// setup listen for changes to parents
parent.addEventListener('change', function() {
let child;
if (child = parent.querySelector('children')) {
if (child.css.display === 'block') {
clear(parent)
} else {
//caches[this.getAttribute('id')].forEach(c =>
// this.appendChild(c)
delete caches[this.getAttribute('id')]
const ul = parent.querySelector('.children');
ul.css.display = 'block'; // show from bom
}
});
})
something like this. Checkout jquery it may be easier
If this is right, please give me points, i'm a nooob
I haven't executed this code , so it will need your attentions. but that is the essance of it. As I say. look into jquery. This can be accomplished in 3 lines of code.

Show ID onclick JS

Hi sorry in advance if this has already been asked but I can't find the answer.
I have a set of links that trigger certain ids to show onclick, it works but the one link is suppose to trigger 2 ids to show. My javascript knowledge is not great. Thanks for any help.
Here is my codepen https://codepen.io/louise-fourie/pen/abVdwyZ
<li>
Fitness & Wellness
</li>
<li>
Business
</li>
<li>
Arts & Entertainment
</li>
</ul>
<div class="articles">
<div id="el-57d5b6f71db32029">fitness</div>
<div id="el-e881a23a64890108">business</div>
<div id="el-65ebd7b2380005a1">art</div>
</div>
<script>
var divs = ["el-57d5b6f71db32029", "el-e881a23a64890108", "el-65ebd7b2380005a1"];
var visibleId = null;
function show(id) {
for(i = 0; i < divs.length; i++) {
if(visibleId !== id) {
visibleId = id;
}
}
hide();
}
function hide() {
var div, i, id;
for(i = 0; i < divs.length; i++) {
id = divs[i];
div = document.getElementById(id);
if(visibleId === id) {
div.style.display = "block";
} else {
div.style.display = "none";
}
}
}
</script>
There is something fishy with your show-hide logic.
Check out my suggestion below, where you can pass an array of IDs, which you want to show. All other elements will be hidden.
function onClick(elements) {
document.querySelectorAll('.articles div').forEach(articleDiv => articleDiv.style.display = 'none');
elements.forEach(element => {
const domEl = document.querySelector('#' + element)
domEl.style.display = 'block';
})
}
<ul>
<li>
Fitness & Wellness
</li>
<li>
Business
</li>
<li>
Arts & Entertainment
</li>
</ul>
<div class="articles">
<div id="fitness">fitness</div>
<div id="business">business</div>
<div id="art">art</div>
</div>
<script>
</script>
Your function seems to be running, but every time you receive an ID in your function, you hide everything else with the "hide" function, so in the end, the last ID sent, is the only one that will show, try this:
Call the function once, but pass the IDs as one string separated by commas
Business
Then change your "show" function like this:
function show(ids) {
let idArr = ids.split(",");
divs.forEach( x => {
div = document.getElementById(x);
div.style.display = (idArr.includes(x) ? "block" : "none");
});
}
What this does, is that it will create an array of IDs based on the parameter you send, and for each item it will check if the ID was sent and show/hide it.
Please let me know if this helps or if you need more details.
EDIT: Formatting in the JavaScript code and simplifying it. Please also note that here I am not validating if the an element with the ID exists, it is only to show you how it can work. It will need additional validations
You can try it
<ul>
<li>
Fitness & Wellness
</li>
<li>
Business
</li>
<li>
Arts & Entertainment
</li>
</ul>
<div class="articles">
<div id="el-57d5b6f71db32029" style="display:none;">fitness</div>
<div id="el-e881a23a64890108" style="display:none;">business</div>
<div id="el-65ebd7b2380005a1" style="display:none;">art</div>
</div>
<script>
document.querySelectorAll('.article-btn').forEach(item => {
item.addEventListener('click', event => {
show((item.getAttribute('data-id')).split(";"));
})
})
const show = (id) => {
document.querySelectorAll('.articles>div').forEach(item => {
if(id.includes(item.getAttribute('id'))){
item.style["display"] = "block";
}else{
item.style["display"] = "none";
}
});
}
</script>
Inline JavaScript is generally discouraged these days, so here's a solution that removes that dependency. It puts the ids of the list items into the dataset instead. You can then create an array from that dataset, iterate over the articles, and if the id is included in the array of ids either hide or show it.
// Cache the list and the articles
const ul = document.querySelector('ul');
const articles = document.querySelectorAll('.articles div');
// Add an event listener to the list
ul.addEventListener('click', handleClick, false);
function handleClick(e) {
// Get the node name and the dataset
// from the element that was clicked
const { nodeName, dataset } = e.target;
// Check that it was an anchor
if (nodeName === 'A') {
// Get an array of ids from the dataset
const ids = dataset
.ids.split(',')
.map(id => id.trim());
// Hide all the articles
articles.forEach(article => {
article.classList.add('hide');
});
// Show the articles where the id is in
// the list of ids
articles.forEach(div => {
if (ids.includes(div.id)) {
div.classList.remove('hide');
}
});
}
}
.hide { display: none; }
ul li:hover { cursor: pointer; color: red; }
<ul>
<li>
<a data-ids="el-57d5b6f71db32029">Fitness & Wellness</a>
</li>
<li>
<a data-ids="el-57d5b6f71db32029, el-e881a23a64890108">Business</a>
</li>
<li>
<a data-ids= "el-65ebd7b2380005a1">Arts & Entertainment</a>
</li>
</ul>
<div class="articles">
<div id="el-57d5b6f71db32029" class="hide">fitness</div>
<div id="el-e881a23a64890108" class="hide">business</div>
<div id="el-65ebd7b2380005a1" class="hide">art</div>
</div>

HTML CSS JS - OnClick working on just 1 div

I am trying to shrink right side div when left navigation menu is open. Here is my code example,
HTML :
<div id="home" class="full"><p>Home</p></div>
<div id="about" class="full"><p>About</p></div>
<div id="portfolio" class="full"><p>Portfolio</p></div>
<div id="skills" class="full"><p>Skills</p></div>
<div id="contact" class="full"><p>Contact</p></div>
Css :
.full{height: 100vh;color: #fff;}
.PageShrink{margin-left: 30vh;}
JS:
const fullPage = document.querySelector('.full');
let menuOpen = false;
menuBtn.addEventListener('click', () => {
if(!menuOpen) {
fullPage.classList.add('PageShrink');
menuOpen = true;
} else {
fullPage.classList.remove('PageShrink');
menuOpen = false;
}
});
The issue is, when I click on menuBtn to slide in from left, Home shrinks 30vh according to code, but after that if I click on About,portfolio etc its not shrinking even though they have same class. Not sure whats wrong but it just works on 1st div i.e, home.
Please assist,
Thank you in advance.
Your issue is within the JS. You use querySelector which only targets the first element with that class. So you have to use querySelectorAll. However this only gives you an array of all elements and the JS change is only applied to the first element still. So you need to cycle through all elements with that class by using: fullPage.forEach(el => el.classList.add('PageShrink'));
const fullPage = document.querySelectorAll('.full');
let menuOpen = false;
menuBtn.addEventListener('click', () => {
if(!menuOpen) {
fullPage.forEach(el => el.classList.add('PageShrink'));
menuOpen = true;
} else {
fullPage.forEach(el => el.classList.remove('PageShrink'));
menuOpen = false;
}
});
For demonstration and testing:
function caseA() {
document.querySelector(".test").style.color = "red";
}
function caseB() {
document.querySelectorAll(".test").style.color = "green";
}
function caseC() {
document.querySelectorAll(".test").forEach(el => el.style.color = "blue");
}
<ul>
<li class="test">Test</li>
<li class="test">Test</li>
<li class="test">Test</li>
<li class="test">Test</li>
<li class="test">Test</li>
</ul>
<button onclick="caseA()">querySelector</button>
<button onclick="caseB()">querySelectorAll</button>
<button onclick="caseC()">querySelectorAll + forEach</button>
You see that "Case A" is onyl targetign the first element. "Case B" will cause an error as querySelectorAll returns an array of elements not an element itself. If you use forEach you can apply your JS comamnd to all the elements as it cycles through the array and applies it to every element of the array.

Categories