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');
}
};
}
Related
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) => {
I have two buttons defined with their IDs : but-toggle, but-close for show hide a panel :
<button id="but-toggle">Panel</button>
<div id="main-panel" style="display:none">
<button id="but-close">X</button>
<!-- some panel data -->
</div>
Now i have a JS function which toggle the panel (show/hide) assigned to but-toggle as following :
(function(){document.getElementById("but-toggle").addEventListener("click",function(){
var pan = document.getElementById("main-panel");
if(pan.style.display==="inline-block")
{pan.style.display="none"}
else
{pan.style.display="inline-block"}
});})();
My question is how to use the same function for both buttons without writing two func, one for each.
I want a solution for elements based on their ids, since the classes are differents and can't use getElementByClass for this
Thanks
You can use document.querySelector to get a list of elements from a list of IDs:
function toggleFn() {
const pan = document.getElementById("main-panel");
if(pan.style.display === "inline-block") {
pan.style.display = "none";
} else {
pan.style.display = "inline-block";
}
}
document.querySelector("#Id1, #Id2")
.forEach(elem => elem.addEventListener(toggleFn);
You can use the below to get all buttons
var btns=document.getElementsByTagName('button');
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener("click", function() {
//code
});
}
(OR)
if you use input tag with type button
document.querySelectorAll("input[type=button]");
(OR)
Based on multiple IDs
document.querySelectorAll('#id1, #id2, #id3');
then you can add eventListener
I want to toggle a list item to show. when it is clicked I want a tick icon to toggle with the click. It works with 1 <li> item, but when I add another and click that one, the first tick disappears.
How can I get the first tick to remain after the second one is clicked? This is a DOM specific exercise.
I am first setting up an event listener for submit for my form. On submit I am creating a div and an li. Then on click of the li I am adding the icon.
html:
<div class='body-container'>
<p class="subtitle">Please add an item</p>
<form id="myForm" class='form'>
<input type="text">
<button type="submit">submit</button>
</form>
<h2>Items</h2>
<div>
<ol class="item">
</ol>
</div>
<div class="footer">
</div>
</div>
js:
//grab the target from the form submission
const myForm = document.querySelector('#myForm');
const item = document.querySelector('.item');
const ol = document.querySelector('ol');
const i = document.createElement('i')
myForm.addEventListener('submit', (event) => {
event.preventDefault();
const value = event.target[0].value;
const li = document.createElement('li');
const div = document.createElement('div');
li.classList.add('test');
li.addEventListener('click', () => {
div.classList.toggle('strike');
div.appendChild(i);
i.classList.toggle('fas');
i.classList.toggle('fa-check');
})
li.appendChild(document.createTextNode(value));
div.appendChild(li)
ol.appendChild(div)
})
At the moment when I tick the second item the ticks from both li's disappear
This is the way to achieve it, your code was almost right
const myForm = document.querySelector('#myForm');
const item = document.querySelector('.item');
const ol = document.querySelector('ol');
myForm.addEventListener('submit', (event) => {
event.preventDefault();
const value = event.target[0].value;
const li = document.createElement('li');
const div = document.createElement('div');
const i = document.createElement('i');
li.id = `item-${new Date().getTime()}`
li.classList.add('test');
li.addEventListener('click', () => {
div.classList.toggle('strike');
div.appendChild(i);
i.classList.toggle('fas');
i.classList.toggle('fa-check');
})
li.appendChild(document.createTextNode(value));
div.appendChild(li)
ol.appendChild(div)
})
Hi Evandro Cavalcate Santos, I was thinking to just move the line
const i = document.createElement('i')
to the myForm eventListener as you did, because it was created with a global scope and each div would refer to that same global 'i'.
I don't think the 'id' is necessary on each li. Let me know.
Thanks
//grab the target from the form submission
const myForm = document.querySelector('#myForm');
const item = document.querySelector('.item');
const ol = document.querySelector('ol');
myForm.addEventListener('submit', (event) => {
event.preventDefault();
const value = event.target[0].value;
const li = document.createElement('li');
const div = document.createElement('div');
const i = document.createElement('i')
li.classList.add('test');
li.addEventListener('click', () => {
div.classList.toggle('strike');
div.appendChild(i);
i.classList.toggle('fas');
i.classList.toggle('fa-check');
})
li.appendChild(document.createTextNode(value));
div.appendChild(li)
ol.appendChild(div)
})
i.fas:after{
content: "X"
}
.strike{
border:1px solid red;
}
<div class='body-container'>
<p class="subtitle">Please add an item</p>
<form id="myForm" class='form'>
<input type="text">
<button type="submit">submit</button>
</form>
<h2>Items</h2>
<div>
<ol class="item">
</ol>
</div>
<div class="footer">
</div>
</div>
I am new to unit tests and want to create a test that will track the inner html after a click event which will indicate the switching between two users. I have done several searches and have not been able to figure how to properly attach the unit test to the event.
Most of the resources that I have found that are similar seem to be related to React and not Vanilla Javascript. This is my front-end code:
const xPlayer = 'X';
const oPlayer = 'O';
const boardSquares = document.querySelectorAll('.square');
let whosTurn = 0;
let board;
gameSetup();
function gameSetup() {
board = Array.from(Array(9).keys());
for (var i = 0; i < boardSquares.length; i++) {
boardSquares[i].innerText = '';
boardSquares[i].addEventListener('click', turnClick, false);
}
}
function turnClick(currentDiv) {
if(document.getElementById(currentDiv.target.id).innerHTML === '') {
writeInSquare(currentDiv);
}
}
function writeInSquare(currentDiv) {
if (whosTurn % 2 === 0) {
document.getElementById(currentDiv.target.id).innerHTML = xPlayer;
whosTurn++;
}
else {
document.getElementById(currentDiv.target.id).innerHTML = oPlayer;
whosTurn++;
}
}
export default{
boardSquares, whosTurn, gameSetup, turnClick, writeInSquare, checkIfGameComplete
};
This is my test file:
import sum from './index.js';
describe('sum', function() {
it('should change from xPlayer to oPlayer based on if whoseTurn is even or odd', function ({
}))
})
Html:
<div class="wrapper">
<div class="square" id="0">
</div>
<div class="square" id="1">
</div>
<div class="square" id="2">
</div>
<div class="square" id="3">
</div>
<div class="square" id="4">
</div>
<div class="square" id="5">
</div>
<div id="mocha"></div>
So basically there would be a switch between xplayer and yplayer that I would want to test to see if is happening from the click event
There is a way you could simulate an event on a DOM element manually. That's mentioned here. It involves creating a new Event (MouseEvent in your case) and then calling dispatchEvent with it, on the DOM element you wish to simulate the click. It's pretty tedious and has issues cross-browsers.
There is a popular library called simulant, that makes this really easy. A click event would be as simple as
simulant.fire( target, 'click' );
where target is the DOM element you want to simulate the click.
A simple demo where I simulate a click on a button.
const target = document.getElementById("target");
const simulate = document.getElementById("simulate");
target.addEventListener("click", () => alert("Target clicked!"))
simulate.addEventListener("click", () => simulant.fire(target, "click"))
<script src="https://unpkg.com/simulant/dist/simulant.umd.js"></script>
<button id="target">Click me</button>
<button id="simulate">Simulate</button>
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
)
);
});
});