This question already has answers here:
Multiple show/hide divs with separate toggle
(5 answers)
Closed 8 months ago.
I am trying to show and hide different div's on click on different buttons. For example, when I click button "1", it shows the block with id="block-1". When I click on button "4", the block with id="block-4" shows and previous block #block-1 hides. I tried using different ID's because I don't know any other solution to show blocks with different content inside. Unfortunately, my current code doesn't work properly: it toggles the right class to show the div, but I can't hide the previous div or change the block once the button with number is clicked. On the default state, when the page is loaded, the first block (#block-1) should always be visible. Here's the link to codepen with the result: https://codepen.io/tomavl/pen/vYRLJVY
<div class="filter">
<button class="filter-btn active" id="1">1</button>
<button class="filter-btn" id="2">2</button>
<button class="filter-btn" id="3">3</button>
<button class="filter-btn" id="4">4</button>
</div>
<div class="block">
<div class="block-1 block-card active" id="block-1">Block 1</div>
<div class="block-2 block-card" id="block-2">Block 2</div>
<div class="block-3 block-card" id="block-3">Block 3</div>
<div class="block-4 block-card" id="block-4">Block 4</div>
</div>
background-color: red;
color: white;
}
.block-card {
display: none;
}
.block-card.active {
display: block;
}
var filterBtn = document.querySelectorAll(".filter-btn");
for (var i = 0; i < filterBtn.length; i++) {
filterBtn[i].onclick = function () {
if (this.classList) {
for (var j = 0; j < filterBtn.length; j++) {
filterBtn[j].classList.remove("active");
}
this.classList.add("active");
} else {
this.active += " " + active;
}
};
}
$("#2").on("click", function (e) {
e.stopImmediatePropagation();
if ($(this).hasClass("active")) {
$(".block-2").addClass("active");
} else {
$(".block-2").removeClass("active");
}
});
$("#3").on("click", function (e) {
e.stopImmediatePropagation();
if ($(this).hasClass("active")) {
$(".block-3").addClass("active");
} else {
$(".block-3").removeClass("active");
}
});
$("#4").on("click", function (e) {
e.stopImmediatePropagation();
if ($(this).hasClass("active")) {
$(".block-4").addClass("active");
} else {
$(".block-4").removeClass("active");
}
});
You can achieve what you need with much less code by using common classes to group content by behaviour. You can use data attributes where required to store custom metadata in an element.
In the following example all buttons use the same event handler. The differences come simply from the data attribute on the button used to change the selector. The code just removes the active class from all relevant elements before applying it to the target.
let $blocks = $('.block-card');
$('.filter-btn').on('click', e => {
let $btn = $(e.target).addClass('active');
$btn.siblings().removeClass('active');
let selector = $btn.data('target');
$blocks.removeClass('active').filter(selector).addClass('active');
});
body {
background-color: red;
color: white;
}
.block-card {
display: none;
}
.block-card.active {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div class="filter">
<button class="filter-btn active" data-target="#block-1">1</button>
<button class="filter-btn" data-target="#block-2">2</button>
<button class="filter-btn" data-target="#block-3">3</button>
<button class="filter-btn" data-target="#block-4">4</button>
</div>
<div class="block">
<div class="block-card active" id="block-1">Block 1</div>
<div class="block-card" id="block-2">Block 2</div>
<div class="block-card" id="block-3">Block 3</div>
<div class="block-card" id="block-4">Block 4</div>
</div>
Related
This question already has answers here:
What does the "~" (tilde/squiggle/twiddle) CSS selector mean?
(3 answers)
What does the "+" (plus sign) CSS selector mean?
(9 answers)
Closed last month.
I tried to create a nested collapsible <div>.
The problem I am facing: when I tried to click the second <div> the first time after loading the page, it is not working. Whereas, if we try to click the first <div>, it works perfectly.
document.addEventListener('DOMContentLoaded', function() {
const found = document.querySelectorAll('.collapse-content .collapse-head-content');
for (let i = 0, len = found.length; i < len; i++) {
found[i].addEventListener('click', handler, true);
}
function handler(e) {
const hidden = e.target.classList.contains('hide-content');
if (hidden) {
e.target.classList.remove('hide-content');
}
else {
e.target.classList.add('hide-content');
}
}
});
.collapse-content .collapse-head-content::before {
content: ' ^ ';
cursor: pointer;
}
.collapse-content .hide-content.collapse-head-content::before {
content: ' > ';
}
.collapse-content .hide-content.collapse-head-content~.collapse-body-content {
display: none;
}
<div class="bank-details collapse-content">
<div class="state-name collapse-head-content hide-content">
Click me to collapse / show
</div>
<div class="collapse-body-content">
<div class="collapse-content">
<div class="district-name collapse-head-content hide-content">
District
</div>
<div class="branch-name collapse-body-content">
name
name
name
</div>
</div>
</div>
<div class="state-name collapse-head-content hide-content">
Click me to collapse / show
</div>
<div class="collapse-body-content">
<div class="collapse-content">
<div class="district-name collapse-head-content hide-content">
District
</div>
<div class="branch-name collapse-body-content">
name
name
</div>
</div>
</div>
</div>
Use an adjacent sibling selector + to toggle visibility
In the updated snippet below, I modified the third CSS block to use + selector. That is, if .collapse-body-content block is directly following .hide-content, it's hiddent; otherwise, it'll remain visible. In doing so, each time you toggle the .hide-content class, its content will automatically toggle its visibility.
document.addEventListener('DOMContentLoaded', function() {
const found = document.querySelectorAll('.collapse-content .collapse-head-content');
for (let i = 0, len = found.length; i < len; i++) {
found[i].addEventListener('click', handler, true);
}
function handler(e) {
const hidden = e.target.classList.contains('hide-content');
if (hidden) {
e.target.classList.remove('hide-content');
}
else {
e.target.classList.add('hide-content');
}
}
});
.collapse-content .collapse-head-content::before {
content: ' ^ ';
cursor: pointer;
}
.collapse-content .hide-content.collapse-head-content::before {
content: ' > ';
}
.hide-content + .collapse-body-content {
display: none;
}
<div class="bank-details collapse-content">
<div class="state-name collapse-head-content hide-content">
Click me to collapse / show
</div>
<div class="collapse-body-content">
<div class="collapse-content">
<div class="district-name collapse-head-content hide-content">
District
</div>
<div class="branch-name collapse-body-content">
name
name
name
</div>
</div>
</div>
<div class="state-name collapse-head-content hide-content">
Click me to collapse / show
</div>
<div class="collapse-body-content">
<div class="collapse-content">
<div class="district-name collapse-head-content hide-content">
District
</div>
<div class="branch-name collapse-body-content">
name
name
</div>
</div>
</div>
</div>
I have multiple divs on the page with the class 'item' – I'd like to include a button within the div that when clicked will toggle append/remove the class 'zoom' on the 'item' div…
<div class="item">
<button class="zoomer"></button>
</div>
I've found plenty of code examples that target an id element, but struggling to find a solution that works with multiples of the same class element on one page.
Many thanks in advance!
You can use querySelectorAll to get all of the buttons and then you can use forEach so you can target the element's item parent.
// Get all the buttons
let zoomer_button = document.querySelectorAll('.zoomer');
// Loop through the buttons.
// Arrow function allows to pass the element
zoomer_button.forEach(button => {
// Add an event listener for a click on the button.
button.addEventListener('click', function(e) {
// the e is the event, and then you check what the target is, which is the button.
// then you can toggle a 'zoom' class on the parent 'item'
e.target.parentNode.classList.toggle('zoom');
});
});
.item.zoom {
background-color: blue;
}
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
<div class="item">
<button class="zoomer">button</button>
</div>
If it's nested a layer deeper, you can use parentNode twice.
// Get all the buttons
let zoomer_button = document.querySelectorAll('.zoomer');
// Loop through the buttons.
// Arrow function allows to pass the element
zoomer_button.forEach(button => {
// Add an event listener for a click on the button.
button.addEventListener('click', function(e) {
// the e is the event, and then you check what the target is, which is the button.
// then you can toggle a 'zoom' class on the parent 'item'
e.target.parentNode.parentNode.classList.toggle('zoom');
});
});
.item.zoom {
background-color: blue;
}
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
<div class="item">
<div class="media">
<button class="zoomer">button</button>
</div>
</div>
You can use querySelectorAll and access each element with e.target
document.querySelectorAll('.item > .zoomer')
.forEach(elem => elem.addEventListener('click', e => {
e.target.classList.toggle('someClass')
}))
.someClass{
background:limegreen;
}
<div class="item">
<button class="zoomer">1</button>
</div>
<div class="item">
<button class="zoomer">2</button>
</div>
<div class="item">
<button class="zoomer">3</button>
</div>
<div class="item">
<button class="zoomer">4</button>
</div>
In the example below, are 7 <button>s that do various stuff -- details are commented in example.
// Render 7 <menu>/<button> combos
[...new Array(7)].forEach((item, index) => {
document.querySelector('main').insertAdjacentHTML('beforeend', `
<menu class="item${index}">
<button class="btn${index}">${index}</button>
</menu>`);
});
/*~~~~~~~~~~~~~~~~~~~~~~~.btn0*/
// Click <button> remove it's parent (which also removes the <button>)
document.querySelector('.btn0').onclick = function(e) {
this.parentElement.remove();
}
/*~~~~~~~~~~~~~~~~~~~~~~~.btn1*/
// Click <button> -- <button> is removed but it's contents is left behind
document.querySelector('.btn1').onclick = unWrap;
function unWrap(e) {
const clicked = e.target;
const parent = clicked.parentElement;
while (clicked.firstChild) {
parent.insertBefore(clicked.firstChild, clicked);
}
clicked.remove();
}
/*~~~~~~~~~~~~~~~~~~~~~.btn4-6*/
// Collect all tags with a class that starts with "btn"
const btns = document.querySelectorAll("[class^='btn']");
// Adding .target class to the last 2 <button>s
btns.forEach((btn, idx) => {
if (idx > 4) btn.classList.add('target')
});
/*~~~~~~~~~~~~~~~~~~~~~~~.btn2*/
// Target third <button> by index
/*
When <button> clicked, it's parent gets .hide class which is:
visibility:hidden which would normally hide the <button> as well, but
.btn2 has visibility explicitly set to visible
*/
btns[2].onclick = e => e.target.closest('menu').classList.toggle('hide');
/*~~~~~~~~~~~~~~~~~~~~~~~.btn3*/
/*
Everytime the <button> is clicked, a copy of itself is made and the
clones also have this ability as well
*/
btns[3].addEventListener('click', copySelf);
function copySelf(e) {
let dupe = e.target.cloneNode(true);
e.target.parentElement.append(dupe);
dupe.onclick = copySelf;
}
/*~~~~~~~~~~~~~~~~~~~~~.btn4-6*/
/*
The click event is bound to the parent/ancestor tag <section>
Any click to any <button> will trigger the event handler.
.btn4, .btn5, and .btn6 all react in a specific manner because
the event handler, delegateClick(e) is using flow control statements and
specific criteria.
*/
document.querySelector('main').onclick = delegateClick;
let armed = false;
function delegateClick(e) {
const clicked = e.target;
if (clicked.matches('button') && !armed) {
clicked.classList.add('armed');
armed = true;
return;
}
if (clicked.matches('.armed.target') && armed) {
clicked.parentElement.style.cssText = `font-size: 5rem; margin: 0`
clicked.replaceWith(`💥`);
return;
}
if (clicked.matches('.target') && armed) {
clicked.classList.add('armed');
return;
}
if (clicked.matches('.armed') && armed) {
clicked.classList.remove('armed');
armed = false;
}
}
menu {
outline: dashed red 1px;
}
.hide {
visibility: hidden;
}
.btn2 {
visibility: visible
}
.armed {
animation: warning 1s linear infinite;
}
#keyframes warning {
50% {
opacity: 0;
}
}
.target.armed {
background: red;
color: white;
}
button {
font: inherit;
cursor: pointer;
}
<main></main>
To summarise the code, I have buttons that display different tabs when pressed. Within the tabs, there are more buttons that change the color of some div elements and only one tab can be opened at a time. All this works as it should for the most part.
All buttons had been using focus but I wanted to replace it with javascript so that the selection will be retained when clicking on different elements. No tabs should be visible if the current opened tab button is pressed like it does when the code first runs.
I have had a few issues trying to get this to work properly. At the moment, the color buttons remain clicked. When tab toggles, the tab button loses selection and the tab div doesn't close when I click on the current selected tab's button.
https://jsfiddle.net/gkde169x/4/
<button class="tabButton" onclick="toggle_tab('tabOne');">Tab One</button>
<button class="tabButton" onclick="toggle_tab('tabTwo');">Tab Two</button>
<div id="tabOne" class="clickedTab" style="display: none;">
<br><br>
<div id="paletteOne">
<button class="paletteButton" style="background-color: blue"></button>
<button class="paletteButton" style="background-color: red;"></button>
<button class="paletteButton" style="background-color: yellow;"></button>
<button class="paletteButton" style="background-color: Green;"></button>
<button class="paletteButton" style="background-color: Orange;"></button>
<button class="paletteButton" style="background-color: white;"></button>
</div>
</div>
<div id="tabTwo" class="clickedTab" style="display: none;">
<br><br>
<div id="paletteTwo">
<button class="paletteButton" style="background-color: blue"></button>
<button class="paletteButton" style="background-color: red;"></button>
<button class="paletteButton" style="background-color: yellow;"></button>
<button class="paletteButton" style="background-color: Green;"></button>
<button class="paletteButton" style="background-color: Orange;"></button>
<button class="paletteButton" style="background-color: white;"></button>
</div>
</div>
<div id="change1"></div>
<div id="change2"></div>
<script type="text/javascript">
const divOne = document.getElementById('change1');
const divTwo = document.getElementById('change2');
document.querySelectorAll('#paletteOne button').forEach(function (el) {
el.addEventListener('click', function () {
divOne.style.backgroundColor = el.style.backgroundColor;
el.className = "paletteSelect";
});
});
document.querySelectorAll('#paletteTwo button').forEach(function (el) {
el.addEventListener('click', function () {
divTwo.style.backgroundColor = el.style.backgroundColor;
el.className = "paletteSelect";
});
});
function toggle_tab(id) {
const target = document.getElementById(id);
if (!target) {
return;
}
// Hide unselected tabs
const tabs = document.querySelectorAll('.clickedTab');
for (const tab of tabs) {
tab.style.display = 'none';
}
// Show current tab
target.style.display = 'block';
}
What's the best way to accommodate this in my code?
to unclick the color button I would do something like this, (with each click check for clicked buttons and unclick)
const pal = document.getElementById('paletteOne')
pal.addEventListener('click', function(e) {
document.querySelectorAll('#paletteOne button').forEach(function(el) {
el.className = "paletteButton"});
if(e.target.className==="paletteButton"){
divOne.style.backgroundColor = e.target.style.backgroundColor;
e.target.className = "paletteSelect";
}
});
to hide selected tab when clicked on
const tabs = document.querySelectorAll('.clickedTab');
for (const tab of tabs) {
if(tab!== target || target.style.display === 'block'){
tab.style.display = 'none';
}else{
target.style.display = 'block';}
}
obviously these things can be done differently, I'm just working off your code...
In your javascript
function toggle_tab(id) {
const target = document.getElementById(id);
if (!target) {
return;
}
const tabShown = document.querySelectorAll('.show')
tabShown.forEach((tab) => {
if(target != tab) tab.classList.remove('show')
})
target.classList.toggle('show');
}
Also in your CSS use classes. (You can create one class and give it to both of them since they have so many styles in common and use tabTwo and tabOne classes only for differences.)
.tabContainer {/*here use this class, give this to both tabs*/
position: absolute;
margin-top: 38px;
height: 100px;
width: 100px;
padding-left: 50px;
padding-bottom: 50px;
border-style: solid;
border-color: black;
background: white;
display:none;/*here*/
}
.tabTwo {/*here use class*/
margin-left: 20px;
}
.show{
display:block;
}
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
i'am trying to do a "tab" menu with only pure JS CSS and HTML, where basically i have 7 divs with content and 7 "buttons" which should open the matching div. The strategy i want to use is to put all the divs with the "hidden" or the "Display: none" attribute stacked in the same spot and then, when i click on button it turns it´s matching div to visible. The problem i am facing is how to tell the button which div it should open (using arrays instead of doing it manually), and how to set it back to invisible when i click in other div (i was thinking about an if() that just turns on the visibility if the number of the button selected matches the number of the div, but supposedly every button has a div, so i am confused).
I think you want something like this?
document.addEventListener('click', ({ target: { dataset: { id = '' }}}) => {
if (id.length > 0) {
document.querySelectorAll('.tab').forEach(t => t.classList.add('hidden'));
document.querySelector(`#${id}`).classList.remove('hidden');
}
});
.hidden {
display:none;
}
<button data-id="tab1">tab 1</button>
<button data-id="tab2">tab 2</button>
<div id="tab1" class="tab">Tab 1</div>
<div id="tab2" class="tab hidden">Tab 2</div>
But the reality is we probably don't want to actually use button.
So lets change that up.
const tabClick = ({ target }) => {
const { dataset: { id = '' }} = target;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('selected'));
target.classList.add('selected');
document.querySelectorAll('.tab-panel').forEach(t => t.classList.add('hidden'));
document.querySelector(`#${id}`).classList.remove('hidden');
};
const bindTabs = () => {
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', tabClick);
})
};
// Belts and braces, lets ensure our DOM is loaded and only assign click to the `tabs`
document.addEventListener('DOMContentLoaded', () => {
bindTabs();
});
.tab {
display: inline-block;
background-color: grey;
padding: 0.75rem;
color: #fff;
}
.selected {
background-color: black;
}
.tab-panel {
border: 2px solid black;
min-height: 50px;
max-width: 250px;
padding: 1rem;
}
.hidden {
display:none;
}
<div data-id="tab1" class="tab selected">tab 1</div>
<div data-id="tab2" class="tab">tab 2</div>
<div data-id="tab3" class="tab">tab 3</div>
<div data-id="tab4" class="tab">tab 4</div>
<div id="tab1" class="tab-panel">Tab 1</div>
<div id="tab2" class="tab-panel hidden">Tab 2</div>
<div id="tab3" class="tab-panel hidden">Tab 3</div>
<div id="tab4" class="tab-panel hidden">Tab 4</div>
So how can we approach this with basic javascript.
const tabCount = 4; // If we add a new tab, increase.
const tabClick = (event) => {
const tabButtonClicked = event.target;
const id = event.target.dataset.id;
// First remove selected and hide all tabs
for(let i = 1; i <= tabCount; i++) {
let tabButtonID = "#tabButton" + i;
let tabButton = document.querySelector(tabButtonID);
let tabID = "#" + tabButton.dataset.id;
let tab = document.querySelector(tabID);
tabButton.classList.remove("selected");
tab.classList.add("hidden");
}
// Now we set selected and show the selected tab.
document.querySelector("#" + id).classList.remove("hidden");
tabButtonClicked.classList.add("selected");
};
const bindTabs = () => {
// Loop through number of tabs and add a click event.
for(let i = 1; i <= tabCount; i++) {
let tabButtonID = "#tabButton" + i;
let tabButton = document.querySelector(tabButtonID);
tabButton.addEventListener('click', tabClick);
}
};
// Belts and braces, lets ensure our DOM is loaded and only assign click to the `tabs`
document.addEventListener('DOMContentLoaded', () => {
bindTabs();
});
.tab {
display: inline-block;
background-color: grey;
padding: 0.75rem;
color: #fff;
}
.selected {
background-color: black;
}
.tab-panel {
border: 2px solid black;
min-height: 50px;
max-width: 250px;
padding: 1rem;
}
.hidden {
display:none;
}
<div id="tabButton1" data-id="tab1" class="tab selected">tab 1</div>
<div id="tabButton2" data-id="tab2" class="tab">tab 2</div>
<div id="tabButton3" data-id="tab3" class="tab">tab 3</div>
<div id="tabButton4" data-id="tab4" class="tab">tab 4</div>
<div id="tab1" class="tab-panel">Tab 1</div>
<div id="tab2" class="tab-panel hidden">Tab 2</div>
<div id="tab3" class="tab-panel hidden">Tab 3</div>
<div id="tab4" class="tab-panel hidden">Tab 4</div>
I have a list of items:
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
<div class="crew-item>
<div class="crew-grid"></div>
<div class="crew-detail></div>
</div>
When I click on a selected 'crew-grid' I'd like to add a class ('active') to its 'crew-item' parent, but I have no idea how to achieve that using vanilla js or jQuery.
The goal is to reveal the 'crew-detail' part, with active class added to its parent.
Like this?:
$('.crew-grid').on('click', function () {
$(this).closest('.crew-item').addClass('active');
});
Basically, starting from the clicked element, get the closest ancestor element which matches that selector. You don't need an id to target an element, just a way to identify it based on the information you have (in this case the clicked element).
If you want to de-activate other elements at the same time:
$('.crew-grid').on('click', function () {
$('.crew-item').removeClass('active');
$(this).closest('.crew-item').addClass('active');
});
Using jQuery :
$('.crew-grid').click(function() {
$(this).closest('.crew-item').addClass('active');
});
Use Document.querySelectorAll()
var crews = document.querySelectorAll('.crew-item');
if (crews) {
for (var i = 0; i < crews.length; i++) {
var grid = crews[i].querySelector('.crew-grid');
grid.addEventListener('click', toggleActive, false);
}
}
function toggleActive() {
var grids = document.querySelectorAll('.crew-item');
for (var i = 0; i < grids.length; i++) {
if (grids[i].classList.contains('active')) {
grids[i].classList.remove('active');
}
}
this.parentNode.classList.add('active');
}
.crew-item.active {
background: #DDD;
}
.crew-grid:hover {
cursor: pointer;
background: #eee;
}
<div class="crew-item active">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>
<br>
<div class="crew-item">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>
<br>
<div class="crew-item">
<div class="crew-grid">crew-grid</div>
<div class="crew-detail">crew-detail</div>
</div>