I have a JS for loop that iterates over all elements with a specific class, and then removes the class. However, whilst the loop works for the first element found, it then stops. I cannot see any errors, I've tried it inside a try/catch, and can't see anything else that might be causing the problem. Does anyone have any suggestions? Thanks :)
let visibleTags = document.getElementsByClassName('show');
console.log(visibleTags.length) // length is 2
for (let index = 0; index < visibleTags.length; index++) {
console.log(index); // 0
visibleTags[index].classList.remove('show'); // removes 'show' from element 0
}
// element 1 still has the 'show' class and was not touched by the loop... ?
visibleTags is a "live" DOM query - the elements within it will change as the DOM changes.
Therefore, when you remove the show class from an element, it simultaneously disappears from visibleTags, since your query was for elements with the show class. Thus, as soon as you remove the class, visibleTags.length drops to 1, and your loop will exit because the loop counter is already at 1.
There's a number of ways to work with this:
One solution to this is to run the loop backwards, so that it starts at visibleTags.length and counts back to zero. This way, you can remove the elements and the length will drop, but you'll then move onto the previous one and the loop carries on.
Another option is to run the loop as a while loop and just keep removing the first item: ie:
while (visibleTags.length) {
visibleTags[0].classList.remove('show');
}
This would be my preferred solution.
Finally, you may opt to create a non-live array of the elements that you can loop through. You probably don't need to do this, but it may be a useful option if you need to loop through the same list of elements again later on (eg maybe to restore the show class).
You shouldn't use indexes, visibleTag is a live collection and you're modifying part of the selection criteria (the show class) so the collection itself will change. Since you want to remove show from everything that has the show class, using a while loop like this is better:
let shown = document.getElementsByClassName('show');
while(shown.length > 0) {
shown[0].classList.remove('show');
}
<div>
<div class="show">1</div>
<div class="show">2</div>
<div class="show">3</div>
<div class="show">4</div>
</div>
This is because document.getElementsByClassName() is referencing the actual array of elements matching your class.
So when iterating and changing its class, the element itself does not belongs anymore to the array, thus the index becomes index-1.
A workaround, if you haven't another path to reach the object, is to rely on another class/selector to retrieve the list of elements:
let visibleTags = document.getElementsByClassName('test');
console.log(visibleTags.length) // length is 2
for (let index = 0; index < visibleTags.length; index++) {
console.log(index); // 0
visibleTags[index].classList.remove('show'); // removes 'show' from element 0
}
.test {
width: 300px;
height: 300px;
border: 1px solid #ccc;
}
.show {
background-color: red;
}
<div>
<div class="show test">1</div>
<div class="show test">2</div>
</div>
Try to use this function:
function removeClassFromElements(className) {
document
.querySelectorAll(`.${className}`)
.forEach(el => el.classList.remove(className));
}
For your case:
removeClassFromElements('show');
You could use querySelectorAll to select all the element with class show.
The Document method querySelectorAll() returns a static (not live) NodeList representing a list of the document's elements that match the specified group of selectors. Read more about this selector here
function removeClass() {
let visibleTags = document.querySelectorAll(".show");
console.log("Number of selected Elements: ", visibleTags.length); // length is 2
for (let index = 0; index < visibleTags.length; index++) {
console.log("Index: ", index); // 0
visibleTags[index].classList.remove("show"); // removes 'show' from element 0
}
}
.show {
background-color: red;
}
<button onclick="removeClass()">Remove Class</button>
<br/>
<br/>
<div>
<div class="show test">1</div>
<div class="show test">2</div>
</div>
Related
I'm very new to JS, and really don't understand a lot of it. Trying to learn as I go.
I'm trying to add some new divs to buttons to style them to look like the rest of the buttons on my site as I cant edit the plugins HTML. I've managed to successfully do this for one button. But it won't work for the other buttons. I've tried to read into it and it looks like because I am using getElementsByClassName its only selecting the first button and not the others.
So I dont know if this is right or not and correct me if it ain't. but I think I need to set up a Node loop? so that getElementsByClassName doesn't just select the first node on the page. However I got no Idea how to set up a node loop and reading about it is just confusing me more.
Can someone help and possibly explain this to me so I can make sense of it for future reference.
Thanks
This is the code I currently have, I just don't know how to make it target all elements with that class rather than just the first element with that class.
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap';
document.getElementsByClassName("dbtb-add-btn-assets")[0].appendChild(btnSwirls);
const btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap';
document.getElementsByClassName("dbtb-add-btn-assets").forEach(element => {
element.appendChild(btnSwirls);
})
learn more about forEach(): https://www.youtube.com/watch?v=SXb5LN_opbA
learn more about arrow functions: https://www.youtube.com/watch?v=h33Srr5J9nY
learn more about var, let, and const: https://www.youtube.com/watch?v=9WIJQDvt4Us
First of all, you are handling an "array" of elements. For that, you'd need a loop to iterate over the array.
you shouldn't be using this
document.getElementsByClassName("dbtb-add-btn-assets")[0] <-- because this part [0] denotes that you are targeting the first element in the array, hence 0, since all arrays start with the index 0; i.e. [0, 1, 2, 3, ...] indices
so for iterating over an array you can either use a (for loop) or a (for of) loop
for loop:
let dbtb_add_btn_assets = document.getElementsByClassName("dbtb-add-btn-assets"); //you are assigning a variable to the array;
for(let i = 0; i < dbtb_add_btn_assets.length; i++) {
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap'; //create btnswirls per iteration of the loop
dbtb_add_btn_assets[i].appendChild(btnSwirls);
}
the i is the current index of the loop, the i++ part of the for loop
will automatically add 1 to itself upon executing the statement inside
the for loop and ends when i is not less than the dbtb_add_btn_assets
length. Length meaning the number of elements inside the array.
for of:
let dbtb_add_btn_assets = document.querySelectorAll('.dbtb-add-btn-assets'); //personally I'd use querySelectorAll instead of getElementsByCLassName just add . for classes # for ids
for(let dbtb of dbtb_add_btn_assets) { //name whatever variable you want to use
var btnSwirls = document.createElement('div');
btnSwirls.id = 'dbtb-button-swirl-wrap';
btnSwirls.className = 'dbtb-button-swirl-wrap'; //create btnswirls per dbtb
dbtb.appendChild(btnSwirls);
}
the for of loop takes the contents from a specified array and put them into a temporary variable, successfully giving access to the individual content/object, and then automatically iterates over each one as you manipulate it however you like inside the loop.
You need to loop over all the elements. You only access the first of many elements using [0].
There are multiple ways to do this.
Here are two ways to do it using a sample application which just toggles a class (adds/ removes a class) every two seconds.
#1 - Use querySelectorAll() and forEach()
You can use querySelectorAll() to get a NodeList of HTML elements which match the given CSS selector .someClass.
Notice that the CSS selector requires a . before a class name.
window.addEventListener("DOMContentLoaded", e => {
const allElements = document.querySelectorAll(".someClass");
// add/ remove class every 2 seconds
setInterval(() => {
// loop over all elements and add/ remove a class
allElements.forEach(element => {
element.classList.toggle("anotherClass");
});
}, 2000)
})
.someClass {
padding: 20;
background-color: black;
color: white;
margin: 5px;
}
.anotherClass {
border: 2px solid red;
}
<div class="someClass">div 1</div>
<div class="someClass">div 2</div>
<div class="someClass">div 3</div>
<div class="someClass">div 4</div>
#2 - Use getElementsByClassName() and for .. of ... loop
Alternatively you can use getElementsByClassName() which returns a HTMLCollection. You can then use a for ... of ... loop to iterate over all the elements in the collection.
Notice that here for the getElementsByClassName() call we MUST NOT use a . before the class name.
window.addEventListener("DOMContentLoaded", e => {
const allElements = document.getElementsByClassName("someClass");
// loop
setInterval(() => {
// loop over all elements and add/ remove a class
for (const element of allElements) {
element.classList.toggle("anotherClass");
}
}, 2000)
})
.someClass {
padding: 20;
background-color: black;
color: white;
margin: 5px;
}
.anotherClass {
border: 2px solid red;
}
<div class="someClass">div 1</div>
<div class="someClass">div 2</div>
<div class="someClass">div 3</div>
<div class="someClass">div 4</div>
Please note: You should use DOMContentLoaded event so you wait till the HTML document is ready before you try to access the DOM.
The Problem:
Hey everyone. I'm trying to create a simple function that identifies the next and previous elements of a current item within the ".length" of elements in a div, and then changes the ID of those two elements. Everything is working except for the part where it tries to identify the previous element at the beginning and the next element at the end.
What I've Tried:
It used to be that it would identify those items by using ".nextElementSibling" and ".previousElementSibling", but I realized that since it starts at the first element within the div then it would begin leaking out and trying to identify the previous element outside of the div. I decided to use a for loop that creates a list of the elements with the specific class name, which works as intended. It begins to run into issues again, though, when it reaches the beginning or the end of the list. I assumed that "[i - 1]" would automatically bring it to the last element if the current was the one at the beginning of the list, and that "[i + 1]" would automatically bring it to the first element if the current was the one at the end of the list. It seems that is not the case.
Is anyone able to help me figure this out? It would be much appreciated.
Note: For the sake of simplicity, I didn't include the JavaScript code that makes it switch between items within the div. That part is fully functional so I don't believe it should affect the underlying concept of this problem.
My Code:
HTML:
<div class="items">
<div id="current-item" class="current-div-item"></div>
<div id="item" class="div-item"></div>
<div id="item" class="div-item"></div>
<div id="item" class="div-item"></div>
<div id="item" class="div-item"></div>
</div>
Javascript:
var divItems = document.getElementsByClassName('div-item'); // Gets number of elements with the specific class.
for (i = 0; i < divItems.length; i++) { // Creates list of elements with the specific class.
if (divItems[i].classList.contains('current-div-item')) { // If it is the current item, then do this:
var next = divItems[i + 1] // Find the next element in the list
var previous = divItems[i - 1] // Find the previous element in the list
next.id = 'next-item' // Change the next element's ID to "next-item"
previous.id = 'previous-item' // Change the previous element's ID to "previous-item"
}
}
You are wanting the items to wrap around that isn't going to happen. For the first item the previous item will be index -1 and for the last item the next index will be 1 larger than the actual number of items in the array.
If you add in a ternary you can get the values to wrap.
var prevIndex = (i === 0) ? divItems.length - 1 : i - 1;
var nextIndex = (i === divItems.length - 1) ? 0 : i + 1;
var next = divItems[prevIndex] // Find the next element in the list
var previous = divItems[nextIndex] // Find the previous element in the list
Based on your HTML code, in logic JS to fetch the all the items based in class it would not fetch the current-div-item as you have written logic to fetch only div-item. So I assume that you also need to change the HTML code. As per my understanding about your requirement I have done some changes and uploading the modified code. Which is working as per you requirement.
HTML:
<div id="current-div-item" class="div-item">Current</div>
<div id="item" class="div-item">Div1</div>
<div id="item" class="div-item">Div2</div>
<div id="item" class="div-item">Div3</div>
<div id="item" class="div-item">Div4</div>
Java Script:
var divItems = document.getElementsByClassName('div-item'); // Gets number of elements with the specific class.
for (i = 0; i < divItems.length; i++) {
if (divItems[i].id=='current-div-item') {
var next;
if (i == divItems.length-1)
next = divItems[0];
else
next = divItems[i + 1];
var previous;
if (i == 0)
previous=divItems[divItems.length-1];
else
previous = divItems[i - 1] // Find the previous element in the list
next.id = 'next-item' // Change the next element's ID to "next-item"
previous.id = 'previous-item' // Change the previous element's ID to "previous-item"
}
}
Attached the screenshot of the modified elements id for your reference
I'm having a for loop to get a certain task done.Now in that for loop,in the last iteration, I need to add the green-color class to all the elements which has the class checkMarks.
This is my code, currently it adds the green-color class only to the first element. Is there a way to do this without having to use another for loop inside the current for loop?
const studentLength = 24;
for(let i=0; i<studentLength; i++){
//something happens here
if(i===studentLength ){ //if last iteration
document.querySelectorAll(".checkMarks").classList.add("green-color");
}
}
You need to iterate the result of querySelectorAll and apply the class to each element:
document.querySelectorAll(".checkMarks").forEach(e => e.classList.add("green-color"));
.green-color {
color: green;
}
<div class="checkMarks">TEST</div>
<div class="checkMarks">TEST2</div>
<div class="checkMarks">TEST3</div>
<div class="checkMarks">TEST4</div>
<div class="checkMarks">TEST5</div>
<div class="checkMarks">TEST6</div>
<div class="checkMarks">TEST7</div>
You need to loop since the All in querySelectorAll returns a nodelist, but you should use forEach on the students too
I wrap in a spread [...] to handle early EDGE browsers
students.forEach(student => { /*something happens here */});
[...document.querySelectorAll(".checkMarks")].forEach(chk => chk.classList.add("green-color"));
If there is only ONE checkMark you can do document.querySelector(".checkMarks").classList.add("green-color") without the all, but vanilla JS does not support adding to a list in one go like the equivalent jQuery $(".checkMarks").addClass("green-color") would
Personally, I would use a for...of loop (ES6):
const divs = document.querySelectorAll(".checkMarks");
for (const div of divs) {
div.classList.add("green-color");
}
.green-color {
color: green;
}
<div class="checkMarks">DIV1</div>
<div class="checkMarks">DIV2</div>
<div class="checkMarks">DIV3</div>
<div class="checkMarks">DIV4</div>
<div class="checkMarks">DIV5</div>
<div class="checkMarks">DIV6</div>
<div class="checkMarks">DIV7</div>
I want to replace a specific div element with a different one, when it has reached 3 clicks on it. That is the only task, I am trying to accomplish with the code.
I have tried looking at some code that does this but all of them replace it with get go, they don't give you a number amount to specify when to replace it with.
Example: <div id="1"></div> has been clicked on 3 times by a user. Once it exceeds that amount replace it with <div id="3"></div>
Changing the id attribute is not a good idea, instead you can use data- attribute like the following way:
var count = 0; // Declare a variable as counter
$('#1').click(function(){
count++; // Increment the couter by 1 in each click
if(count == 3) // Check the counter
$(this).data('id', '3'); // Set the data attribute
console.log($(this).data('id'));
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="1" data-id="1">Click</div>
You could write a JavaScript function that keeps track how often you clicked on a specific DOM element (i. e. the div element with id="1"). As soon as the element was clicked three times, it will be replaced by another DOM element which can be created in JavaScript as well.
var clicks = 0;
function trackClick(el) {
clicks++;
if(clicks === 3) {
var newEl = document.createElement('div');
newEl.textContent = 'Div3';
newEl.id = '3';
el.parentNode.replaceChild(newEl, el);
}
}
<div id="1" onclick="trackClick(this)">Div1</div>
In case you should use a library like jQuery or have another HTML structure, please specify your question to improve this code snippet so that it fits for your purpose.
The main idea is to start listening click events on the first div and count them.
The below code shows this concept. Firstly we put first div into variable to be able to create event listeners on it and also create count variable with initial value: 0. Then pre-make the second div, which will replace the first one later.
And the last part is also obvious: put event listener on a div1 which will increment count and check if it is equal 3 each time click happens.
const div1 = document.querySelector('#id-1');
let count = 0;
// pre-made second div for future replacement
const divToReplace = document.createElement('div');
divToReplace.id = 'id-2';
divToReplace.innerText = 'div 2';
div1.addEventListener('click', () => {
count ++;
if (count === 3) {
div1.parentNode.replaceChild(divToReplace, div1);
}
});
<div id="id-1"> div 1 </div>
Note that this approach is easy to understand, but the code itself is not the best, especially if you will need to reuse that logic. The below example is a bit more complicated - we create a function which takes 2 arguments: one for element to track and another - the element to replace with. Such approach will allow us to reuse functionality if needed.
function replaceAfter3Clicks(elem, newElem) {
let count = 0;
div1.addEventListener('click', () => {
count ++;
if (count === 3) {
elem.parentNode.replaceChild(newElem, elem);
}
});
}
const div1 = document.querySelector('#id-1');
// pre-made second div for future replacement
const div2 = document.createElement('div');
div2.id = 'id-2';
div2.innerText = 'div 2';
replaceAfter3Clicks(div1, div2);
<div id="id-1"> div 1 </div>
If you know, how to use JQuery, just put a click event handler on your div 1. On that handler, increment a click counter to 3. If it reaches 3, replace the div with JQuery again.
If there are multiple divs to replace, use an array of counters instead of a single one, or modify a user-specific data attribute via JQuery.
Using native JavaScript, rather than relying upon library (for all the benefits that might offer), the following approach is possible:
// A named function to handle the 'click' event on the relevant elements;
// the EventObject is passed in, automatically, from EventTarget.addEventListener():
const replaceOn = (event) => {
// caching the element that was clicked (because I'm using an Arrow function
// syntax we can't use 'this' to get the clicked element):
let el = event.target,
// creating a new <div> element:
newNode = document.createElement('div'),
// retrieving the current number of clicks set on the element, after this
// number becomes zero we replace the element. Here we use parseInt() to
// convert the string representation of the number into a base-10 number:
current = parseInt(el.dataset.replaceOn, 10);
// here we update the current number with the decremented number (we use the
// '--' operator to reduce the number by one) and then we update the
// data-replace-on attribute value with the new number:
el.dataset.replaceOn = --current;
// here we discover if that number is now zero:
if (current === 0) {
// if so, we write some content to the created <div> element:
newNode.textContent = "Original element has been replaced.";
// and here we use Element.replaceWith() to replace the current
// 'el' element with the new newNode element:
el.replaceWith(newNode);
}
};
// here we use the [data-replace-on] attribute-selector to search
// through the document for all elements with that attribute, and
// use NodeList.forEach() to iterate over that NodeList:
document.querySelectorAll('[data-replace-on]').forEach(
// using an Arrow function we pass a reference to the current
// Node of the NodeList to the function, and here we use
// EventTarget.addEventListener() to bind the replaceOn function
// (note the deliberate lack of parentheses) to handle the
// 'click' event:
(element) => element.addEventListener('click', replaceOn)
);
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
div {
border: 1px solid #000;
display: inline-block;
padding: 0.5em;
border-radius: 1em;
}
div[data-replace-on] {
cursor: pointer;
}
div[data-replace-on]::before {
content: attr(data-replace-on);
}
<div data-replace-on="3"></div>
<div data-replace-on="13"></div>
<div data-replace-on="1"></div>
<div data-replace-on="21"></div>
<div data-replace-on="1"></div>
<div data-replace-on="6"></div>
<div data-replace-on="4"></div>
References:
CSS:
Attribute-selectors ([attribute=attribute-value]).
JavaScript:
Arrow function syntax.
ChildNode.replaceWith().
document.querySelectorAll().
EventTarget.addEventListener().
NodeList.prototype.forEach().
I have below HTML
<div class="parent">
<span class="child">1</span>
<span class="child">2</span>
<span class="child">3</span>
<span class="child">4</span>
<span class="child">5</span>
</div>
and below CSS
.parent .child {
border : 1px solid black;
display :inline-block;
width:40px;
height:25px;
text-align:center;
cursor:pointer;
}
.mark {
background-color: green;
}
and simple click event for to see selected element a below.
$(".parent > .child").click(function(e){
if(e.shiftKey) {
$(".parent .child").each(function(){
$(this).addClass("mark");
});
}
else {
$(this).addClass("mark");
}
});
Edited : When I click one element and another element with shiftKey , between these two elements should be add class mark. But my code iterate all elements as $(".parent > .child").... I would like to avoid it (I mean if 2 elements between them , I would like to iterate 4 times (inclusive start and element) with my iteration).
My question is can I iterate between two selected elements (inclusive) instead of iterating from thier parent element (in my case I don't want to iterate from parent) ? I know the start and end elements. If so, why I need to iterate all elements and check their status as I want ? JSFiddle link.
For clear question ...
I have 10 HTML element ,assume 3 is start and 6 is end.I would like to iterate as
(for var i=3 ; i <=6 ; i++) {...}
instead of iterating all elements and check their status as
(for var i=1 ; i <=10 ; i++) {
// checking is it between start and end elements
}
Demo
Try this demo. Not sure if it accomplishes what you need. Comment if changes needed.
$(".parent .child").click(function () {
if($(".parent .child.mark:first").length == 1 && !$(this).hasClass('mark')){
firstIndex = $(".parent .child.mark:first").index();
thisIndex = $(this).index();
start = Math.min(thisIndex, firstIndex);
end = Math.max(firstIndex, thisIndex) + 1;
$('.parent .child').slice(start, end).each(function(){
$(this).addClass('mark');
})
} else {
$(this).addClass('mark');
}
});
Not clear from your question but are you saying something like this
Jquery for next element is :
$(".parent .child").click(function(){
$(this).addClass("mark");
$(this).next().addClass('mark');
if($(this).is(':last-child')){
$('.parent .child:first-child').addClass('mark');
}
});
If element is last child then have added class mark to first ,this one was my assumption so far.
Why don't you use this:
$( ".mark" ).nextUntil( ".mark" ).css( "color", "red" );
$( ".mark" )--> this is your first click item
.nextUntil( ".mark" )--> this one is your second click
Hope it will works