How to target specific div and then toggle class? - javascript

For example, I have this menu:
function toggleHiddenContent(tabClass) {
let t = document.querySelectorAll(tabClass);
for(var i = 0; i<t.length; i++) {
t[i].classList.toggle="visible-class";
}
}
.hidden-content {
display: none;
}
.visible-class {
display: block
}
<div>
<a class="main-holder" onClick="toggleHiddenContent('.main-holder')">Main one</a>
<div class="hidden-content">Hidden content One</div>
<a class="main-holder" onClick="toggleHiddenContent('.main-holder')">Main two</a>
<div class="hidden-content">Hidden content two</div>
</div>
However, it toggles for all classes. I do understand what is the issue, but how would I match only the one that is clicked and not the ones that are not active (clicked)?
I need it in vanilla js
Thanks guys

classList.toggle is a function, not an assignable property
https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
try this
function toggleHiddenContent(tabClass) {
let t = document.querySelectorAll(tabClass);
for (var i = 0; i < t.length; i++) {
t[i].classList.toggle("visible-class");
}
}
Based on your example I would suggest you a few little changes, which will improve readability, like setting a parent <div> (makes it easy to find parent), and removing onClick from HTML. check it out
fiddle
from the answer below, I didn't know about nextElementSibling, which won't need you to change your HTML like I've suggested

You may try this:
function toggleHiddenContent(e) {
if (e.target.nextElementSibling.classList.contains("visible-class") ) {
e.target.nextElementSibling.className = "hidden-content";
} else {
e.target.nextElementSibling.className = "visible-class";
}
}
.hidden-content {
display: none;
}
.visible-class {
display: block
}
<div>
<a class="main-holder" onClick="toggleHiddenContent(event)">Main one</a>
<div class="hidden-content">Hidden content One</div>
<a class="main-holder" onClick="toggleHiddenContent(event)">Main two</a>
<div class="hidden-content">Hidden content two</div>
</div>

You could try something like this (and also prevent obtrusive javascript):
//Get the element(s) you want to interact with and store these in an array:
let myElements = document.getElementsByClassName("main-holder");
//Loop through the array to add an event listener to every interactable element:
for (let i = 0; i < 10; i++) {
if (myElements[i]) {
myElements[i].addEventListener("click", function() {
//Your function:
toggleHiddenContent("visible-class", i);
});
}
}
function toggleHiddenContent(tabClass, target) {
//Get the element(s) you want to manipulate (or adjust) and store these in an array:
let targetElements = document.getElementsByClassName("hidden-content");
//Use the index you got from clicking on the previous element to determine which targeted element to manipulate (or adjust):
targetElements[target].classList.toggle(tabClass);
}
.hidden-content {
display: none;
}
.visible-class {
display: block;
}
<div>
<a class="main-holder">Main one</a>
<div class="hidden-content">Hidden content One</div>
<a class="main-holder">Main two</a>
<div class="hidden-content">Hidden content two</div>
</div>
JSFiddle
If you need any more information, please let me know in a comment.

Related

How do I target all index numbers in an array instead of just one?

I have a line of Javascript that is meant find all the HTML elements with the class "OldClassName" and replace their class with "NewClassName" however this line of code only replaces the first instance in the resulting array because of index [0]. How would I replace all the instances of "OldClassName" at once?
document.getElementsByClassName("OldClassName")[0].className = "NewClassName";
You can use querySelectorAll and loop over each selected element and change the class like below.
If you want to add use ele.classList.add(<classname>) and similarly ele.classList.remove(<classname>) to remove one.
(This one replaces all the class names with the new)
const elements = document.querySelectorAll(".old")
elements.forEach(ele => {
ele.classList = "new"
})
.old {
background: red;
}
.new {
background: blue;
}
<div class="old">1</div>
<div class="old">2</div>
<div class="old">3</div>
<div class="old">4</div>
<div class="old">5</div>
Hope this helps !
One way could be with a simple loop:
let elements = document.getElementsByClassName("OldClassName");
for (let element of elements) {
element.className = "NewClassName";
}
If you want to avoid removing any other classes on the element:
let elements = document.getElementsByClassName("OldClassName");
for (let element of elements) {
element.classList.remove("OldClassName");
element.classList.add("NewClassName");
}
You can use a for loop to achieve this.
let classToChange = document.querySelectorAll(".oldClass");
for(let i = 0; i < classToChange.length; i++)
{
classToChange[i].className = "newClass";
}
Note this will delete all other classes in the element. Several other answers address that as I type this edit out. :)
You can run for loop on the elements list length and change the class using classList and add, remove to change the class.
With the for loop you define an iterator i and then use that as your key to target each element in the list.
let className = document.querySelectorAll(".OldClassName");
for(let i = 0; i < className.length; i++){
className[i].classList.add('NewClassName');
className[i].classList.remove('OldClassName');
}
console.log(className);
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
<div class="OldClassName"></div>
Another way is to define your class in CSS and use toggle with a function. This would keep the old class and only add it on event handler and remove it on event handler.
function changeColor() {
var element = document.querySelectorAll(".OldClassElement");
for (let i = 0; i < element.length; i++) {
element[i].classList.toggle("NewClassElement");
}
}
.OldClassElement {
color: red;
}
.NewClassElement {
background-color: black;
color: white;
}
<div class="OldClassElement">
This is a DIV element.
</div>
<div class="OldClassElement">
This is a DIV element.
</div>
<div class="OldClassElement">
This is a DIV element.
</div>
<div class="OldClassElement">
This is a DIV element.
</div>
<button onclick='changeColor()'> click me</button>
From MDN
The getElementsByClassName method of Document interface returns an
array-like object of all child elements which have all of the given
class name (s)
In your case the relevant part is returns an array-like object of all child elements this means that in order to manipulate all these elements you must iterate on each one of them, as you have used the getElementsByClassName method you must iterate using for, but you can get the same result of elements using the document.querySelectorAll method but you have the advantage that you can use some methods of array like forEach in the example that makes your code more compact.
Please take a look at the following example
document.querySelector("button").addEventListener("click", toggle);
function toggle() {
document
.querySelectorAll(".old-class-name")
.forEach((element) => element.classList.toggle("new-class-name"));
}
.old-class-name {
font-weight: bold;
}
.new-class-name {
font-weight: normal;
}
<ul>
<li class="old-class-name">Item 1</li>
<li class="old-class-name">Item 2</li>
<li class="old-class-name">Item 3</li>
<li class="old-class-name">Item 4</li>
<li class="old-class-name">Item 5</li>
</ul>
<button>Toggle</button>
See
https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementsByClassName
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

Javascript set CSS Class for Multiple Elements (one element set, remaining elements unset)

I am developing a website which has a few filter buttons which are grouped into several groups. I am trying to find a way to set the class of one of these buttons to "filter-set" while all other buttons in the group are set to "not-set".
Each button is a DIV with a unique ID.
i have some bloated code where each button has its own function and sets the associated buttons to "not-set" but this seems inefficient and im sure there's a better way!
Bloated code example:
function setClassR(){
document.getElementById('filter_rare').className= 'filter-set';
document.getElementById('filter_common').className= 'not-set';
document.getElementById("filter_occasional").className = 'not-set';
}
function setClassC(){
document.getElementById('filter_rare').className= 'not-set';
document.getElementById('filter_common').className= 'filter-set';
document.getElementById("filter_occasional").className = 'not-set';
}
function setClassO(){
document.getElementById('filter_rare').className= 'not-set';
document.getElementById('filter_common').className= 'not-set';
document.getElementById("filter_occasional").className = 'filter-set';
}
I would like to be able to have a function for each group of filters which when run using an onClick=function() sets the clicked button to "filter-set" and all others to "not-set"
I have tried the following code but it doesnt appear to run:
function setClassSeas(rareClass, commonClass, occClass) {
setClass("filter_rare", rareClass);
setClass("filter_common", commonClass);
setClass("filter_occ", occClass);
}
function setClass(IDName, displayValue) {
var items = document.getElementById(IDName);
for (var i=0; i < items.length; i++) {
items[i].className = (displayValue? "filter-set" : "not-set");
}
}
UPDATE///
HTML Code for the Divs acting as buttons:
<div id="filter_rare" title="Rare"
class="not-set"
onclick="chosenFrequency('frequency=Rare'); setClassR();"></div>
<div id="filter_common" title="Common"
class="not-set"
onclick="chosenFrequency('frequency=Common'); setClassC();"></div>
<div id="filter_occasional" title="Occasional"
class="not-set"
onclick="chosenFrequency('frequency=Occasional'); setClassO();"></div>
If every button has a class, say filter-button, then you can address all buttons at once.
In modern development you should attach an event handler instead of using inline onclick handlers.
With all buttons having a common class you can find them all at once. I'm changing your buttons to look like this, adding the "filter-button" class and removing the onclick handler:
<div id="filter_rare" title="Rare"
class="filter-button not-set">Rare</div>
(I've put text in the div just to simplify this demonstration)
Now collect all the filter buttons:
let filters = document.querySelectorAll('div.filter-button');
This gets you a NodeList of elements (kind of like an Array but not one) You'll want to attach an onclick event handler to each of the buttons. To do this you can use the NodeList.forEach() call.
filters.forEach(node => node.addEventListener('click', someFunction));
In the function that gets called when you click a button, you want to clear any filter-set class that's currently set, put back the original not-set class, then set the filter-set class only on the button that was clicked. This will look something like this:
function someFunction(event) {
// again, use forEach to do the same thing to each filter button
filters.forEach( function(node) {
node.classList.remove('filter-set');
node.classList.add('not-set');
} );
// now add the 'filter-set' class on the button that was clicked
event.target.classList.add('filter-set');
}
The good thing about using classList instead of just doing className="something" is that classList can add/remove classes while leaving other classes alone; doing className="something" wipes out all the classes that are present and replaces them with "something".
Putting that all together, and using an anonymous function instead of named function gives this snippet:
let filters = document.querySelectorAll('div.filter-button');
filters.forEach(node => node.addEventListener('click',
function(event) {
console.log(event.target);
filters.forEach(function(node) {
node.classList.remove('filter-set');
node.classList.add('not-set');
});
event.target.classList.add('filter-set');
}));
/* Make these look like buttons; put a green border on them */
.filter-button {
min-height: 2ex;
max-width: 12em;
padding: .25em;
margin: .7em .3em;
background-color: lightgreen;
border: 2px solid green;
border-radius: 4px;
}
/* use a Red border on any button that has "filter-set" */
.filter-button.filter-set {
border: 2px solid red;
}
/* limit the height of the stack-snippet console */
div.as-console-wrapper {
max-height: 2.5em;
}
<div id="filter_rare" title="Rare"
class="filter-button not-set">Rare</div>
<div id="filter_common" title="Common"
class="filter-button not-set">Common</div>
<div id="filter_occasional" title="Occasional"
class="filter-button not-set">Occasional</div>
Using the class not-set is really redundant — you could just have no extra class on buttons by default and it would simplify things a little. Buttons would have the class(es) filter-button or filter-button filter-set.
Change your setClass function according to this. Hope it will work. document.getElementById() function will always return a single element (not a list of elements). Even if you have multiple elements having the same ID this function will always return the first element having the given ID. Do not forget to call your setClassSeas() function from html.
function setClassSeas(rareClass, commonClass, occClass) {
setClass("filter_rare", rareClass);
setClass("filter_common", commonClass);
setClass("filter_occ", occClass);
}
function setClass(IDName, displayValue) {
var item = document.getElementById(IDName);
item.className = displayValue ? "filter-set" : "not-set";
}
<div id="filter_rare" title="Rare" class="not-set"
onclick="chosenFrequency('frequency=Rare'); setClassSeas(true, false, false);"></div>
<div id="filter_common" title="Common" class="not-set"
onclick="chosenFrequency('frequency=Common'); setClassSeas(false, true, false);"></div>
<div id="filter_occasional" title="Occasional" class="not-set"
onclick="chosenFrequency('frequency=Occasional'); setClassSeas(false, false, true);"></div>
Here is another (as in "alternative") way to do it (but with jQuery, oh no!)
$('body').click(function(e) {
let $clicked = $(e.target);
console.log("Clicked " + $clicked.attr('id'))
if ($clicked.hasClass('filter')) {
let $filters = $clicked.closest('.filter-group').find('.filter');
let unset = $clicked.hasClass('set');
$filters.toggleClass('not-set', true);
$filters.toggleClass('set', false);
if (!unset) {
$clicked.toggleClass('not-set', false);
$clicked.toggleClass('set', true);
}
}
})
button.filter.not-set {
background: white;
}
button.filter.set {
color: white;
background: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="filter-group">
<button id="filter_rare" class="filter not-set">
filter_rare
</button>
<button id="filter_common" class="filter not-set">
filter_common
</button>
<button id="filter_occasional" class="filter not-set">
filter_occasional
</button>
</div>
<div class="filter-group">
<button id="filter_one" class="filter not-set">
filter_one
</button>
<button id="filter_two" class="filter not-set">
filter_two
</button>
<button id="filter_three" class="filter not-set">
filter_three
</button>
</div>

How do I hide the closest element to the element that was clicked without jQuery?

I have a function that changes the src attribute of an icon when this one is clicked.
I also want it to hide the closest icon of the class fave_icon. I tried the following but it's not working:
function trash(event, trashcan){
event.stopPropagation();
if (trashcan.getAttribute('src') == "Iconos/tacho.png")
{
trashcan.src = "Iconos/warning.png"; //this works ok
var heart = trashcan.closest(".fave_icon");
heart.style.visibility = "hidden"
}
}
Basically I want to hide the closest element with class fave_icon to trashcan.
On the HTML I have this several times:
<button class="accordion">
<div>
<img src="Iconos/heart.png" onclick="fav(event,this);" alt="Fave" class="fave_icon">
</div>
<div>
<img src="Iconos/tacho.png" onclick="trash(event,this);" alt="Delete" class="delete_icon">
</div>
</button>
If fave_icon is a class then you have to place dot (.) before the class name as part of the selector.
Change var heart = trashcan.closest("fave_icon");
To
var heart = trashcan.closest(".fave_icon");
Based on the code and HTML you have provided you can do something like the following:
function trash(event, trashcan){
event.stopPropagation();
if (trashcan.getAttribute('src') == "Iconos/tacho.png"){
trashcan.src = "Iconos/warning.png"; //this works ok
var heart = trashcan.closest('button').querySelector('.fave_icon');
heart.style.visibility = "hidden";
}
}
<button class="accordion">
<div>
<img src="Iconos/heart.png" onclick="fav(event,this);" alt="Fave" class="fave_icon">
</div>
<div>
<img src="Iconos/tacho.png" onclick="trash(event,this);" alt="Delete" class="delete_icon">
</div>
</button>
From the trash icon, you go up a level to the div, select the previousElementSibling to get the heart's div, and then go down a level to the heart image itself.
Because the element is already included in the event target, you don't need to pass this. Or, even better, if you select the trash image first, you can avoid this entirely and use explicit variable names, which are easier to understand and debug.
But inline event handlers are essentially eval inside HTML markup - they're bad practice and result in poorly factored, hard-to-manage code. Seriously consider attaching your events with JavaScript, instead, eg: https://developer.mozilla.org/en/DOM/element.addEventListener
Another problem is that buttons should not have closing tags. Use a container element instead, like a div.
So, try something like this:
document.querySelectorAll('img[src="Iconos/tacho.png"]').forEach(img => {
img.onclick = () => {
const heartImg = img.parentElement.previousElementSibling.children[0];
heartImg.style.visibility = 'hidden';
};
});
<div class="accordion">
<div>
<img src="Iconos/heart.png" alt="Fave" class="fave_icon">
</div>
<div>
<img src="Iconos/tacho.png" alt="Delete" class="delete_icon">
</div>
</div>
you can add a class to the clicked element and use the general sibling combinator if the two items are adjacent.
document.getElementById("hide")
.addEventListener("click", (event) => {
event.target.classList.add('active');
}, false);
#hide.active~.element {
visibility: hidden;
}
#hide {
cursor: pointer;
}
.accordion {
padding: 15px;
background: lightgrey;
border-bottom: 1px solid grey;
}
.accordion div {
color: black;
margin-right: 20px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/icono/1.3.0/icono.min.css" rel="stylesheet" />
<div class="accordion">
<div class="icono-trash" id="hide"></div>
<div class="element icono-heart"></div>
</div>

Add active class to next sibling

Could someone help me with this, I am a bit stuck with JavaScript. Every time when someone is clicking on the question, the active class name should be added to the next sibling, but how to get that fixed. Only in plain JavaScript please. Any other frameworks do not count.
document.querySelector(".question").addEventListener("click", function() {
if(this.classList.contains("active")) {
this.classList.remove("active");
} else {
this.classList.add("active");
}
});
.faq-block .question,
.faq-block .answer {
border: 1px solid red;
padding: 10px;
}
.answer {
display: none;
}
.active {
display: block;
}
<div class="faq-block">
<div class="question">How do I cancel my order</div>
<div class="answer">Answer</div>
</div>
<div class="faq-block">
<div class="question">I am not getting an internet connection from my second SIM card</div>
<div class="answer">Answer</div>
</div>
<div class="faq-block">
<div class="question">My charger does not work and my device would not charge properly</div>
<div class="answer">Answer</div>
</div>
<div class="faq-block">
<div class="question">My fingerprint scanner is not working and does not respond when touched</div>
<div class="answer">Answer</div>
</div>
Use querySelectorAll ,document.querySelector will only select first matched element.
// get all matched element
var ques = document.querySelectorAll(".question")
// loop over the array to add event to each of the element
ques.forEach(function(item, index) {
// creating closure
(function(i) {
// adding event listerner to each element
ques[i].addEventListener("click", function() {
// this refers to the current selected element
if (this.classList.contains("active")) {
this.classList.remove("active");
} else {
this.classList.add("active");
}
});
}(index)) // passing the index to target element in the array
})
DEMO
Try this
document.querySelector(".question").addEventListener("click", function() {
if(this.classList.contains("active")) {
this.classList.remove("active");
this.nextSibling.classList.add("active")
} else {
this.nextSibling.classList.add("active");
}
});
I don't know why u need to do conditional checking. but to answer your question, you can use nextSibling
A simple adjustment to your CSS will do the trick:
.question.active+.answer {
display: block;
}
This will allow you to set the "active" class on the question, and let it reveal the answer.
Alternatively, use this.parentNode for the class, and then the CSS becomes:
.faq-block.active>.answer {
display: block;
}
This, I would argue, conveys more meaning too.
First of all document.querySelector(".question") returns the first element matching selector(documentation).
If i understood right you wanted to get .active class applied to Answers so here you are:
document.querySelectorAll(".question")
.forEach(element => element.addEventListener("click", function () {
nextNode = this.nextElementSibling;
if(!nextNode.classList.contains("active")){
nextNode.classList.add("active");
}
else{
nextNode.classList.remove('active');
}
})
);
JSFiddle

Removing an element added by ::before pseudo selector

I have the following case: (styling is done in SASS and unnecessary stylings are omitted.)
.header {
...
&::before {
...
position: absolute;
height: 0.5rem;
...
}
}
This creates a bar on top of the application's menu bar. In certain cases this bar has to be removed. I have read questions like these, but with no success. What would be the best way to remove this bar added by the ::before selector?
Only CSS can remove pseudo element, so you need to have an other class that display:none; the before. First declare that class in the CSS :
.header {
...
&::before {
...
position: absolute;
height: 0.5rem;
...
}
&.no-before::before{
display:none;
}
}
Then, when you want to remove it :
$('.header').addClass('no-before'); //Remove before
$('.header').removeClass('no-before'); //Re-add before
The usual way is to create a more specific rule that applies to the element(s) in question (or a later rule with the same specificity), and specify display: none to hide the pseudo in that case.
For example: Here, I want to have an X in front of <span class="foo">, but not if they're in .header:
span.foo::before {
content: 'X ';
}
.header span.foo::before {
display: none;
}
<div>
These have the X:
<span class="foo">span.foo 1</span>
<span class="foo">span.foo 2</span>
<span class="foo">span.foo 3</span>
</div>
<div class="header">
These don't:
<span class="foo">span.foo 4</span>
<span class="foo">span.foo 5</span>
<span class="foo">span.foo 6</span>
</div>
If you are manipulating the DOM by using JavaScript, you can add a class name - for instance .remove-bar - to the element having .header in order to remove the pseudo-element (generated content):
.remove-bar {
&::before { content: none; }
}
Also make sure that it is placed after the previous styles, or use a more specific selector if needed.
For remove special element use this method.
<button onclick="myFunction()">Remove</button>
<div id="myList">
<div> Coffee </div>
<div id="child2" > Tea </div>
<div> Milk </div>
</div>
your JavaScript :
<script>
function myFunction() {
const list = document.getElementById("myList");
if (list.hasChildNodes()) {
list.removeChild(list.children[0]);
}
}
</script>
you can combine above function with this code:
const parent = document.getElementById('myList');
const children = parent.children;
let index = -1;
for (let i = 0; i < children.length; i++) {
if (children[i].id === 'child3') {
index = i;
break;
}
}
alert(index); // 👉️ 2

Categories