the porpuse is that when you click one button it toggle his popup
and also hide the other popups if they are shown.
if possible i would rather a solution in Vanilla JS
here is my code:
const buttons = document.querySelectorAll("button");
const popup = document.querySelectorAll(".borderBottom");
popup.forEach(function(value, index, array) {
for(let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', e => {
array[i].classList.toggle("show");
})
}
});
There are many, many ways of achieving this. Your beginning is a good start, but it has two main issues;
You are looping through the popups with popup.forEach() and in each iteration, you also loop through the buttons; you have nested loops. This means if you have 3 buttons, each "click" event handler is added 3 times. Just one loop is enough :)
Your .toggle() targets only the popup whose button was clicked. If you click Button1 and then Button2 the popups of Button1 were never closed.
I made one example based on your code that could still be greatly improved but hopefully points to the right direction.
const buttons = document.querySelectorAll("button");
const popup = document.querySelectorAll(".borderBottom");
// Go through all popups
popup.forEach(function(value, index, array) {
// "index" is which popup are we looking at?
// "buttons[index]" is the button for this popup
buttons[index].addEventListener('click', e => {
// If this one is open, close it and return
if (array[index].classList.contains("show")) {
array[index].classList.remove("show")
return
}
// If not, hide the old popup if there is one
const oldPopup = document.querySelector(".show")
if (oldPopup) {
oldPopup.classList.remove("show")
}
// Finally show the one whose button was clicked
array[index].classList.add("show")
})
})
.borderBottom {
display: none;
z-index: 2;
background: lightpink;
position: absolute;
top:2em;
left:0.2em;
height: 2em;
width: 10em;
}
.borderBottom.show {
display: grid;
place-items: center;
}
<button>B1</button>
<div class="borderBottom"><b>Popup B1</b></div>
<button>B2</button>
<div class="borderBottom"><b>Popup B2</b></div>
<button>B3</button>
<div class="borderBottom"><b>Popup B3</b></div>
Related
^I would like to be able for the style to be enabled for only one at a time.
^I'm able to do this, which I don't want the user to be able to do.
So it's weirdly hard framing a question for what is possibly an easy solution. I basically have a list of build versions where I want the user to select one. When one of the versions are selected, it adds a border to the item to display that its clicked. However, with my code right now the user is able to select all 3 items and enable their CSS elements. I would like for the user to be able to only "activate" one item from the list.
HTML and CSS:
<ul class="listContents">
<li><p>Stable</p></li>
<li><p>Preview</p></li>
<li><p>LTS</p></li>
</ul>
<style>
.colorText {
background-color: #58a7ed;
color: white;
}
</style>
and the JS stuff:
const btn = document.querySelectorAll('.links');
for (let i = 0; i < btn.length; i++ ) {
btn[i].addEventListener("click", function() {
btn[i].classList.add('colorText')
})
}
I really hope I made myself clear, I feel like I'm failing my English trying to word this right lol.
You can also use a forEach loop, accessing the clicked link using event.target
const btns = document.querySelectorAll('.links');
btns.forEach(btn => {
btn.addEventListener('click', e => {
// remove any existing active links
btns.forEach(b => b.classList.remove('colorText'));
// activate the clicked link
e.target.classList.add('colorText');
})
});
.colorText {
background-color: #58a7ed;
color: white;
}
<ul class="listContents">
<li>
<p>Stable</p>
</li>
<li>
<p>Preview</p>
</li>
<li>
<p>LTS</p>
</li>
</ul>
Just before you add the colorText class to the desired item, we can remove colorText from ALL of them, ensuring that only 1 at a time gets the class.
// the rest is the same...
btn[i].addEventListener("click", function() {
// remove it from all:
btn.forEach(function(item) {
item.classList.remove('colorText');
});
// add it back to the desired one
btn[i].classList.add('colorText')
})
you can also use simple for of
const btn = document.querySelectorAll(".links");
for (let bt of btn) {
bt.addEventListener("click", (e) => {
btn.forEach((b) => b.classList.remove("colorText"));
e.target.classList.add("colorText");
});
}
I'm implementing slide div for creating theme color. It works fine but when I click the outside it's not closing that div and I tried a lot but not working my code so help me out for this..and I research many resources from the internet I got, but don't know how to implement this. here is my code
function clickedThemebtn() {
var ele = document.getElementsByClassName("theme-colors")[0];
if (ele.classList.contains("shown")) {
ele.classList.remove("shown");
} else {
ele.classList.add("shown");
}
}
Here is my fiddle you can Check Here
Please check this fiddle out.
My approach here was to add an fixed positioned div which will occupy the entire screen and it will handle clicking outside.
HTML Outline
<div class='toggleclickoutside' onClick="handleOutsideClick()"></div>
<div id="toggleshown" class="theme-colors">...</div>
JS handler for outside click
function handleOutsideClick() {
var ele = document.getElementsByClassName("theme-colors")[0];
if (ele.classList.contains("shown")) {
ele.classList.remove("shown");
}
}
Css for new div
.toggleclickoutside{
position: fixed;
width: 100vw;
height: 100vh;
top: 0px;
left: 0px;
}
You can attach an event listener to the document, that it triggers the close event. Keep in mind that the following will hide the menu when you click anywhere inside the menu. You need further checks to prevent it.
function clickedThemebtn(e) {
var ele = document.getElementsByClassName("theme-colors")[0];
// If the click is outside of the button, remove the class
if (e.target.id === "slideBtn") {
if (ele.classList.contains("shown")) {
ele.classList.remove("shown");
} else {
ele.classList.add("shown");
}
return;
}
// all other cases: outside the button
ele.classList.remove("shown");
}
document.addEventListener('click', clickedThemebtn);
Updated fiddle
Here's a jQuery solution since the fiddle had jQuery fiddle
You'd have to listen for the click event on the document object, then check if the click was within the theme selector before closing it.
document.addEventListener('click', handleDocumentClick);
function handleDocumentClick(event) {
const themeSelector = getThemeSelector();
if (!themeSelector.contains(event.target)) {
hideThemeSelector();
}
}
function getThemeSelector() {
return document.getElementsByClassName("theme-colors")[0];
}
function hideThemeSelector() {
const themeSelector = getThemeSelector();
themeSelector.classList.remove("shown");
}
function showThemeSelector() {
const themeSelector = getThemeSelector();
themeSelector.classList.add("shown");
}
Note that I added a few handy functions for convenience sake.
I updated your fiddle and now it looks like this: https://jsfiddle.net/0nqzpyko/
I have a sidebar and I want to close it when someone clicks on a link. In my code, the sidebar just closes for a millisecond when I click on an anchor element. How can I fix this without using jQuery?
The a tags are linking to a html page
JS:
var elem = document.getElementById('slidebar').getElementsByClassName('button')[0]
element.addEventListener("click", slide);
function slide() {
document.getElementById('slidebar').classList.toggle('active');
}
var slidebar = document.getElementById('slidebar');
slidebar.addEventListener('click', handleMenuClick);
function handleMenuClick(event) {
if (event.target instanceof HTMLAnchorElement) {
document.getElementById('slidebar').classList.add('close');
}
}
CSS:
#slidebar.active {
left: 0px;
}
#slidebar.close {
display: none;
}
First, make sure you prevent the default event when clicking the anchor tag. Otherwise, it might be re-rendering the page.
But based on your code, it looks like you're adding two functions onto the slidebar. One that closes and one that opens. Since the anchor tag that closes the slidebar is inside the slidebar - when you click it you first fire off the handleMenuClick function and then it bubbles up and fires off the slide function. So it closes and opens quickly.
Instead, add a third element that is used to open the slidebar and attach the slide function there.
Also, you don't need two classes for managing the state of hidden/not hidden. You can just provide a class that sets the display to none and toggle that class list. If you want transition effects you can do that in CSS
Maybe something like this:
window.addEventListener('DOMContentLoaded', e => {
let slidebar = document.getElementById('slidebar')
let collapseButton = slidebar.getElementById('close-button')
let openButton = slidebar.getElementById('open-button')
collapseButton.on('click', toggleClassList)
openButton.on('click', toggleClassList)
const toggleClassList = e => {
e.preventDefault()
slidebar.classList.toggle('hidden')
}
})
#slide-bar.hidden {
display: none;
}
#slide-bar.hidden #close-button {
display: none;
}
#slide-bar #open-button {
display: none;
}
Obviously, it depends a bit on the code you have already written. But this is a basic example that would work. Just need to add the transitions for the sliding effect in CSS
I am trying to recreate the WoW talent calculator as seen here - https://classicdb.ch/?talent#h
Project files - https://codepen.io/jjchrisdiehl/pen/gNQLgR
This is a project to help better understand Javascript, so please avoid any jQuery workarounds for this - thanks.
If you look at the HTML code, you'll see I have the HTML set up as div's with the class 'item' and then I have another div nested inside of the 'item' div with the class 'points'.
<div class="item two nature_wispsplode button" data-max="5">
<div class="points"></div>
</div>
<div class="item three fire_fireball button" data-max="3">
<div class="points"></div>
</div>
The idea is to append a Javascript event listener called logMouseButton to every div with the class 'item'. This will listen for a click and log whether it was a left or right mouse click.
/* Get item div element for addEventListener*/
let itemButton = document.getElementsByClassName("item");
/* Apply logMouseButton to every itemButton */
for (var i = 0; i < itemButton.length; i++) {
itemButton[i].addEventListener("mouseup", logMouseButton, false);
}
Now the logMouseButton code was hacked from the Moz page on MouseEvents .button. My thoughts are to use a switch to manage adding or subtracting points to each item's individual counters.
https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
/* Set Counter */
var counter = 0;
/* Add or subtract points based on button clicked */
function logMouseButton(e) {
/* Set the max number of clicks in points counter based off of data-max attribute */
var maxPoints = this.getAttribute("data-max");
if (typeof e === "object") {
switch (e.button) {
case 0:
if (counter == 0 || counter < maxPoints) {
counter = counter + 1;
}
document.querySelector(".item .points").innerHTML = counter;
// alert(counter);
break;
case 1:
log.textContent = "Middle button clicked.";
break;
case 2:
if (counter > 0) {
counter = counter - 1;
}
document.querySelector(".item .points").innerHTML = counter;
break;
default:
log.textContent = `Unknown button code: ${btnCode}`;
}
}
}
Left click increments, right click decrements. As you can see in my codepen project, the right/left clicks work, but only for one item.
My question is - how would I apply this to each item individually so that they manage their own counter independently of the other items?
Thanks!
Note - I managed to get the counters working with some direction from klugjo. I ended up logging the HTML value of 'points', incrementing the value, then adding the new value back to the innerHTML: https://codepen.io/jjchrisdiehl/pen/JgXzKe
If you have any insights as to why this isnt the best way to do it, or why another way is better, let me know!
You need to access the div that corresponds to the element that was clicked.
Using document.querySelector(".item .points") will always select the first element in the DOM.
You can use e.target to access the element that was clicked, and since what you need is the only child of that element, you can replace
document.querySelector(".item .points").innerHTML = counter;
with
e.target.children[0].innerHTML = counter;
Then you will run into another issue, which is that your counter is global and common to all the buttons.
So you will have to use a hashmap (JS Object) instead of a single integer for counter
var counter = {};
An approach is to select the .item elements and create an array of .item length (number of .item elements in the page) to store the counter for each one individually.
Here's a demo, it contains some helpful comments :
/** selecting the elements with ".item" class and declaring an array to store each element counter separately **/
const items = document.querySelectorAll('.item'),
countArr = new Array(items.length);
/** loop through the elements and add "mouseup" listener (to ensure catching both left and right clicks) for each element **/
/**
* el: the current element from the ".item" elements collection
* i: the index of that elemnt in the collection
**/
items.forEach((el, i) => {
countArr[i] = 0; /** initialize each array element with 0 so we can count later (new Array puts "undefined" as the array elements values) **/
/** add the "mouseup" listener **/
el.addEventListener('mouseup', e => {
let txtCount = el.querySelector('.count'); /** selecting the "span" that contains the counter from the current elemnt (don't forget that we're looping in ".item" elements collection) **/
if(e.which === 1) countArr[i]++; /** left click **/
else if(e.which === 3) countArr[i]--; /** right click **/
txtCount.textContent = countArr[i]; /** update the "span" wih the calculated counter as left click adds 1 and right click removes 1 from the counter of each element **/
});
});
/** basic styling for the demo **/
.item {
display: inline-block;
margin: 15px 0;
padding: 8px;
border: 2px solid teal;
user-select: none;
}
.item .count {
display: inline-block;
background-color: #181818;
color: #fff;
font-size: 1.2rem;
font-weight: bold;
padding: 8px;
border-radius: 4px;
}
<div class="item">counter = <span class="count">0</span></div>
<div class="item">counter = <span class="count">0</span></div>
<div class="item">counter = <span class="count">0</span></div>
This question was quite hard to summarize in the title, but what I have is a group of elements with the class panel. When I click a panel, I add a class of open to it. What I also want to do is remove the open class if another panel already has the open class.
Here is the code:
const panels = document.querySelectorAll('.panel');
function toggleOpen() {
this.classList.toggle('open');
}
panels.forEach(panel => panel.addEventListener('click', toggleOpen));
Right now I can add the open class to however many panels I want, but I only want one panel to have the open class at a time.
Any help no how to achieve this?
The most efficient way is cache the DOM node is currently selected:
const panels = document.querySelectorAll('.panel');
let openedPanel = null;
function toggleOpen() {
if (openedPanel)
openedPanel.classList.remove('open');
this.classList.add('open');
openedPanel = this;
}
panels.forEach(panel => panel.addEventListener('click', toggleOpen));
As was mentioned, it would be more efficient also delegate the event, so if all the panels share some ancestor, you should add the event listener to that ancestor, and then from the event listener doing something like:
toggleOpen({target}) {
const panel = target.closest('.panel')
if (openedPanel)
openedPanel.classList.remove('open');
panel.classList.add('open');
openedPanel = panel;
}
But as said they need to share a common ancestor.
Because you only want one opened at a time. You can directly target that element by getting the elements with class open, targeting the first element and removing class open before you add it to the selected one.
let opened = document.getElementsByClassName('open')[0];
if(opened!=undefined)
opened.classList.toggle('open');
This way you dont have to loop or save an extra global variable.
const panels = document.querySelectorAll('.panel');
function toggleOpen() {
let opened = document.getElementsByClassName('open')[0];
if(opened!=undefined)
opened.classList.toggle('open');
this.classList.toggle('open');
}
panels.forEach(panel => panel.addEventListener('click', toggleOpen));
.panel {
width: 50px;
height: 50px;
margin: 1px;
background-color: aquamarine;
}
.open {
background-color: tomato;
}
<div class="panel"></div>
<div class="panel"></div>
<div class="panel"></div>
var doc = document;
var panelButtons = doc.querySelectorAll(".panel");
for (var i = 0; i < panelButtons.length; i++) {
panelButtons[i].addEventListener("click", function (evt) {
clearBlueFromButtons();
evt.target.classList.add("blue");
});
}
function clearBlueFromButtons(){
for (var i = 0; i < panelButtons.length; i++) {
panelButtons[i].classList.remove("blue");
}
}
.blue{
background: blue;
}
<button class="panel">click me</button>
<button class="panel">click me</button>
<button class="panel">click me</button>
<button class="panel">click me</button>
<button class="panel">click me</button>
<button class="panel">click me</button>
<button class="panel">click me</button>
<button class="panel">click me</button>
You can set the reference of the last opened panel in a variable and then remove the class name "open" when opening another panel, below an exemple:
// select all panels
const panels = document.querySelectorAll('.panel');
// define variable for the last clicked panel
let lastOpenedPanel;
/*
* Add the open class name for the current panel and remove it from the previous one
*/
function toggleOpen(
{
this.classList.toggle('open');
setLastOpenedTab(this);
}
/*
* Set the last opened tab and remove the open class from the previous one
*/
function setLastOpenedTab(context) {
if(lastOpenedPanel){
lastOpenedPanel.classList.remove('open');
}
lastOpenedPanel = context;
}
panels.forEach(panel => panel.addEventListener('click', toggleOpen))
I recommend the use of javascript module pattern to better organize and share your functions
I recommend also the use of Jsdoc to better add documentation to your javascript code
Note that the property "classList" is not supported by IE9:
https://www.w3schools.com/howto/howto_js_toggle_class.asp
Try adding these lines BEFORE “this.classList.toggle” in your toggleOpen function:
for (var i = 0; i < panels.length; i++){
panels[i].classList.remove(“active”);
}
Use an if statement to check if the element has "open" and "panel" then remove the open class. Below is the pseudo code:
if ((element.classList.contains(open)) == True && (element.classList.contains(panel))){
element.classList.remove("open");
}