JavaScript - Looping array less than I should - javascript

I'm trying to loop an array which contains a list of elements returned by ClassName, but I can't loop all of them, because of the next situation:
var list = document.getElementsByClassName('class');
for (var i = 0; i < list.length; i++) {
var theClass = list[i].className; //once got list[i].
theClass = theClass.replace('class', '');
list[i].className = theClass; //twice got list[i].
}
If the size of the list is = 4, I just can loop two times, because I'm getting twice each position per loop. Do you know what I can do and why it happens? Thank you.

The data structure returned by getElementsByClassName is Array-like and dynamic based on the DOM. Once you replace the class on the list item in question, you end up losing an item per iteration.
To fix this, you can take a copy of the returned values first before operating on them, or work backwards.
Take a copy:
var list = document.getElementByClassName('class')
var realList = []
Array.prototype.push.apply(realList, list)
for (var i = 0; i < realList.length; i++) {
// do changes as you have already
}
Working backwards:
var list = document.getElementsByClassName('class')
for (i=list.length - 1; i >= 0; i--) {
// do changes to list[i]
}
Another poster briefly mentioned a while loop which also works, but then their answer disappeared (I don't want to take credit for this!):
var list = document.getElementsByClassName('class')
while (list.length != 0) {
// do changes to list[0]
}
If you write out what happens in your initial code, you can see the problem more clearly:
Iteration 1: i=0, list=[a,b,c,d], length = 4, list[i]=a
Iteration 2: i=1, list=[b,c,d], length = 3, list[i]=c
Before Iteration 3: list=[b,d], i=2, length = 2, loop breaks
Now writing out what happens when using the reverse loop:
Iteration 1: i=3, list=[a,b,c,d], length = 4, list[i]=d
Iteration 2: i=2, list=[a,b,c], length = 3, list[i]=c
Iteration 3: i=1, list=[a,b], length = 2, list[i]=b
Iteration 4: i=0, list=[a], length = 1, list[i]=a
All these solutions are variations on this solution of avoiding using i to reference the middle parts of the array-like result value of getElementsByClassName so that the dynamic nature of it is dealt with.

Related

Javascript: What is the use case for negative loops?

I normally use for loops which count positively.
var i;
for (i = 0; i < cars.length; i++) {
text += cars[i] + "<br>";
}
I am curious to know in what use case would counting backwards be used?
Say you have an array of 10 numbers.
const myArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
Your task is to delete the ones that are divisible by 2 and convert to a string with an a the others (for example 1 will be 1a). (For the sake of the argument let´s say no map or filter functions exist).
If you parse the array from the start for (i = 0; i < myArray .length; i++), everytime you delete an item with for example splice the array length changes and no all items will be correctly parsed.
BUT if you starts from the end this will be not a problem.
Here a example:
When you retrieve data from a database for example blogposts, the data will be most of the time an array with objects in it and render them via a loop on your website the last data that was inserted in the database will be display at the top. Maybe you want to show the oldest blogpost so you could just change your loop to count backwards.
I hope you get the point what I mean, that's just a example
If you were attempting to loop over items in any array-like object (using the .length of the array as your loop boundary) and delete them, you'd find that if you counted incrementally, the loop would not run the correct amount of times because the length would change each time you deleted an item:
// Get all the divs into a collection
let divs = document.getElementsByTagName("div");
// Set up a loop that will go as many times as there are items in the collection
for(var i = 0; i < divs.length; i++){
divs[i].remove(); // Remove the div being iterated from the page
}
<div>One</div>
<div>Two</div>
<div>Three</div>
<div>Four</div>
<div>Five</div>
But, if you remove the elements at the end of the collection and work backwards, the length is adjustment doesn't get out of sync with the contents of the collection:
// Get all the divs into a collection
let divs = document.getElementsByTagName("div");
// Set up a loop that will go as many times as there are items in the collection
// But count backwards to keep the length from causing issues.
for(var i = divs.length - 1; i > -1 ; i--){
divs[i].remove(); // Remove the div being iterated from the page
}
<div>One</div>
<div>Two</div>
<div>Three</div>
<div>Four</div>
<div>Five</div>

JavaScript .children HTML collection length issues when looping

I'm attempting to loop through an HTMLCollection produced from JavaScript's .children property using the following code:
var articles = data.children;
var newsGrid = document.getElementById('js-news__grid');
for (var i = 0; i < articles.length; i++) {
articles[i].classList.add('loading');
newsGrid.appendChild(articles[i]);
};
The data variable is a fragment of a successful XHR. When I run the loop, it only appends the first child and the loop ends, and when running console.log(articles); before the loop, it shows 2 HTML elements (like it should) but only has a length of 1. If I remove the loop and run console.log(articles); it shows the 2 HTML elements like before, BUT it now has a length of 2.
I've left out my XHR code for the sake of simplicity and due to the fact that the HTMLCollection that is produced from data.children looks correct. Here are the log messages:
[article.news__item, article.news__item]
0: article.news__item
1: article.news__item
length: 2
__proto__: HTMLCollection
[article.news__item, article.news__item]
0: article.news__item
length: 1
__proto__: HTMLCollection
The problem is .children is a live collection, which will get updated as you move each child out of the container.
In your case, there are 2 children for data so articles.length is 2 when the loop is started, but after the first iteration you have relocated the first child which means the articles object is now contains only 1 element and i is 2 now the loop condition i < articles.length fails.
So one easy solution is to use a reverse loop
var articles = data.children;
var newsGrid = document.getElementById('js-news__grid');
for (var i = articles.length - 1; i >= 0; i--) {
articles[i].classList.add('loading');
newsGrid.appendChild(articles[i]);
};
Another solution will be is to convert articles to a normal array
var articles = [].slice.call(data.children);
Another approach as suggested by RobG is
var articles = data.children;
var newsGrid = document.getElementById('js-news__grid');
while (articles.length) {
articles[0].classList.add('loading');
newsGrid.appendChild(articles[0]);
};

JavaScript remove all elements with name

I am trying to use JavaScript to remove all the elements with a certian name, but it is only removing the first one.
My code is:
var ele= document.getElementsByName("javascriptaudio");
for(var i=0;i<ele.length;i++)
{
ele[i].parentNode.removeChild(ele[i]);
}
Can anyone tell me what is wrong?
Thanks
I don't have enough rep to comment on Álvaro G. Vicario. The reason that it works is that the element is removed from ele when it is removed from the DOM. Weird.
The following code should work equally well:
var ele= document.getElementsByName("javascriptaudio");
len = ele.length;
parentNode = ele[0].parentNode;
for(var i=0; i<len; i++)
{
parentNode.removeChild(ele[0]);
}
Try removing them backwards:
var ele = document.getElementsByName("javascriptaudio");
for(var i=ele.length-1;i>=0;i--)
{
ele[i].parentNode.removeChild(ele[i]);
}
The problem is that removing elements from ele shifts indexes: if you have 5 items (0 to 4) and remove item 0 you then have 4 items ranging from 0 to 3 (4 becomes 3, 3 becomes 2, etc.); you should then remove item 0 but your i variable has already incremented to 1.
you need to save length of array outside the loop because it is evaluated with each pass if you place it inside loop criteria. That way you will have correct amount of iterations. Furthermore you need to delete first item in loop each time because with each pass you lose 1 item so you will be outside of range at the end of process.
var ele = document.getElementsByName("javascriptaudio");
var elementsCount = ele.length;
for(var i=0;i<ele.length;i++){
ele[0].parentNode.removeChild(ele[0]);
}
Try the following jQuery code:
$("[name=javascriptaudio]").remove();

How to compare Array value to result of 'for' loop in javascript

I have an empty array (called zoomthumbsarray) which gets values pushed to it whilst a 'for' loop is running. This 'for' loop is checking if a thumbnail image is present in the backend against the particular product the user is viewing. If there is an image it gets added into a vertical slider. The current issue is there are non colour specific images (like lifestyle shots) that are being added into the slider multiple times.
So I need to check if the image found in the for loop is currently stored in the array. If it is present, the image has already been generated and I don't want it to get pulled into the slider again. If it hasn't then the image will get added.
Below is the code I am working on. I would presume indexOf would be used but can't get this to work.
Any help would be really appreciated.
var zoomthumbsarray = [] // Empty array which gets populated by .push below during loop
for (var i = 0; i < storeImgsArr.length; i++) { // storeImgsArr finds the quantity of attributes present against the product. This loops and increments counter if there is another attibute image
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) { // Loop and increment counter if there is a Large image
zoomthumbsarray.push(storeImgsArr[i].images.imgS[e].slice(-16)); // Slices off last 16 characters of image path i.e. _navy_xsmall.jpg or 46983_xsalt1.jpg and pushes this into 'zoomthumbsarray' array
// if statement sits here to build the html to add the image to the slider
}
}
zoomthumbsarray = [] // Resets array to zero
ANSWER
As answered by Chris I used $.unique to only keep unique values in the array.
Then wrap an if statement around the code to build the thumb image html if the array === 0 or if the current image isn't already in the array.
Updated code below:
var zoomthumbsarray = [] // Empty array which gets populated by .push below during loop
for (var i = 0; i < storeImgsArr.length; i++) { // storeImgsArr finds the quantity of attributes present against the product. This loops and increments counter if there is another attibute image
if (zoomthumbsarray === 0 || zoomthumbsarray.indexOf(storeImgsArr[i].images.imgS[e].slice(-16)) < 0) { // If statement is true if array === 0 or if the current image isn't already in the array
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) { // Loop and increment counter if there is a Large image
zoomthumbsarray.push(storeImgsArr[i].images.imgS[e].slice(-16)); // Slices off last 16 characters of image path i.e. _navy_xsmall.jpg or 46983_xsalt1.jpg and pushes this into 'zoomthumbsarray' array
zoomthumbsarray = $.unique(zoomthumbsarray); //Keeps only unique elements
// if statement sits here to build the html to add the image to the slider
}
}
}
zoomthumbsarray = [] // Resets array to zero
Some cheap and dirty ideas:
Using underscore/lodash:
zoomthumbsarray = _.uniq(zoomthumbsarray); //Keeps only unique elements
jQuery has one as well:
zoomthumbsarray = $.unique(zoomthumbsarray); //Keeps only unique elements
then you loop through the array and build HTML.
Update:
There's something a bit odd about the rest of the JS. Might this work (if you're using a new enough browser)?
var zoomthumbsarray = [];
storeImgsArr
.map(function(item) { return item.images.imgS; })
.forEach(function(imgS) {
zoomthumbsarray = zoomthumbsarray.concat(imgS.map(function(imagePath) {
return imagePath.slice(-16);
}));
});
zoomthumbsarray = $.unique(zoomthumbsarray);
I have tried indexOf (see first if statement below) but this doesn't work.
As #elclanrs said, indexOf does return the index in the array not a boolean. You only will need to see if it's >= 0 to test whether an image is already contained in the array.
var zoomthumbsarray = [];
for (var i = 0; i < storeImgsArr.length; i++) {
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) {
var image = storeImgsArr[i].images.imgS[e].slice(-16);
if (zoomthumbsarray.indexOf(image) < 0) { // not yet in the array
zoomthumbsarray.push();
// and build the html to add the image to the slider
}
}
}
If you have really lots of images and notice this starts slowing the page down, then there are too many images in your page anyway. No, joke aside; …then check the optimisation by #Ivey.
instead of using an array you can use an object to store the images as keys and a dummy value (possibly true). then you can extract the keys from this object.
var images = {};
for (var i = 0; i < storeImgsArr.length; i++) {
for (var e = 0; e < storeImgsArr[i].images.imgL.length; e++) {
images[storeImgsArr[i].images.imgS[e].slice(-16))] = true;
}
}
var zoomthumbsarray = [];
for(var k in images) {
zoomthumbsarray.push(k);
// build the html to add the image to the slider
}
EDIT: Added build html comment

Javascript indexing array issue

Hi I have an array that hold the following numbers, however when I loop though the eachNode function(which iterates 13 times) it repeats all the list elements 13 times. I tested everything but it still produces an error, I'm I executing the for loop correctly?
list[61,67,78]
var len = list.length;
fd.graph.eachNode(function (node) { // loops thru all node id's in graph (13)
for (var i = 0; i < len; ++i) {
if (i in list) {
var nody = list[i]; // I put the number in a variable
var nodess = fd.graph.getNode(nody); //this takes the number and matches it with a node id, it "odjectify" it
if (node.id != nodess.id) { // if the list nodes are not the same
node.setData('alpha', 0); //
node.eachAdjacency(function (adj) { // this make the unmatched nodes disappear
adj.setData('alpha', 0, 'end');
});
}
}
}
});
This line is unneeded:
if (i in list)
The in keyword returns true if its right operand contains the property specified by its left operand. When using this with arrays, it returns unexpected results. The behavior of this keyword is insignificant in this context, so you should simply take it out.
Moreover, you need to create the list array like this:
var list = [61, 67, 78];
...however, when I loop though eachNode (which iterates 13 times) it repeats all the list elements 13 times
It doesn't, it in fact iterates over eachNode 13 times. You also made a for loop which will traverse the list array by its length.
Now that you've given me more detail as to what you want, here is the updated code. I hope it works for you:
fd.graph.eachNode(function (node) {
var flag = false;
for (var i = 0; i < len; ++i)
{
var nody = list[i];
var nodess = fd.graph.getNode(nody);
if (node.id == nodess.id) {
flag = true; break;
}
}
if (flag)
{
node.setData('alpha', 0);
node.eachAdjacency(function (adj) {
adj.setData('alpha', 0, 'end');
});
}
});
This is the behavior by design:
You loop over the graph (13 times as you say), then inside each iteration you loop over your array (3 items).
If you only want to loop once over your array, just move it out of the outer loop

Categories

Resources