JavaScript only affecting every other element in array [duplicate] - javascript

I attached a codepen example : http://codepen.io/anon/pen/zpmjJ
I just try to change the classname of an html collection array with a classic loop and after many test i can't figure out whats wrong and if i made a common mistake, i have always one item ignored.
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
bout.addEventListener("click",function(){
var newtest = document.getElementsByClassName('test');
for (i=0; i < newtest.length; i++)
{
newtest[i].className="bob";
}
});

The problem here is that you are using document.getElementsByClassName which gives you a live NodeList. You should use document.querySelectorAll('.test') instead.
var newtest = document.querySelectorAll('.test');
for (var i=0; i < newtest.length; i++) {
newtest[i].className = "bob";
}
With live NodeList collection newtest is a reference to a dynamically updated collection of elements (indexed by className test). So after the first loop iteration when you overwrite className with bob the whole collection becomes smaller by one (because newtest[0] no longer has class test). It makes indices shift.
One more problem with your code: don't forget var keywords.
Demo: http://codepen.io/anon/pen/BAFyb

getElementsByClassName is live selector, so when you change something that not matching this selector, it will remove element from scope. So at first iteration it has 3 elements, but when you change class, it remove one element and you leave with 2 elements, so on next iteration it will take i element which is i=1 so it will take second element from top. That's why you get always 1 missing. You may use newtest[0].className="bob"; to loop through, but when you will reach last element, it will be single element, not array any more, so you need to use newtest.className="bob"; for last one

Related

Using shift() brings back the first one every time (javascript)

I have a function that searches for every time ; is found inside a textarea and makes it into a array. See the code below.
function compile() {
// Sets the variable for every line
var compileLineCount = document.getElementById('devInput').value.split(';');
for (let i = 0; i < compileLineCount.length; i++) {
console.log(document.getElementById('devInput').value.split(';').shift(i))
console.log(i)
}
}
But whenever I call the function, it shows the first one every time.
Anyone know how to fix this? Help would be very appreciated.
As per the official shift() document.
The shift() method removes the first element from an array and returns that removed element. This method changes the length of the array.
Hence, It should be like :
var compileLineCount = document.getElementById('devInput').innerHTML.split(';');
for (let i = 0; i <= compileLineCount.length; i++) {
const splittedValue = compileLineCount.shift(i)
console.log(splittedValue)
console.log(i)
}
<p id="devInput">
Hello my name is alpha;Age is 30;I love to help
</p>
I figured out what I did wrong. I forgot shift() also removes the item from the array.
I changed it from .shift(i) to [i].
Thank you jsn00b for the link to the docs
Your shift() method is used incorrectly. According to MDN, it states:
The shift() method removes the first element from an array and returns that removed element.
So, the shift() method doesn't take any parameters. This is why your code isn't working.
To fix this issue, you can get the index from the array using [i], like so.
document.getElementById("devInput").value.split(";")[i];
The only difference in that line is that .shift() is replaced with [i], which gets the element in the array at that specific index.

Why and when for loop ignore some item with html collection

I attached a codepen example : http://codepen.io/anon/pen/zpmjJ
I just try to change the classname of an html collection array with a classic loop and after many test i can't figure out whats wrong and if i made a common mistake, i have always one item ignored.
<div class="test">1</div>
<div class="test">2</div>
<div class="test">3</div>
bout.addEventListener("click",function(){
var newtest = document.getElementsByClassName('test');
for (i=0; i < newtest.length; i++)
{
newtest[i].className="bob";
}
});
The problem here is that you are using document.getElementsByClassName which gives you a live NodeList. You should use document.querySelectorAll('.test') instead.
var newtest = document.querySelectorAll('.test');
for (var i=0; i < newtest.length; i++) {
newtest[i].className = "bob";
}
With live NodeList collection newtest is a reference to a dynamically updated collection of elements (indexed by className test). So after the first loop iteration when you overwrite className with bob the whole collection becomes smaller by one (because newtest[0] no longer has class test). It makes indices shift.
One more problem with your code: don't forget var keywords.
Demo: http://codepen.io/anon/pen/BAFyb
getElementsByClassName is live selector, so when you change something that not matching this selector, it will remove element from scope. So at first iteration it has 3 elements, but when you change class, it remove one element and you leave with 2 elements, so on next iteration it will take i element which is i=1 so it will take second element from top. That's why you get always 1 missing. You may use newtest[0].className="bob"; to loop through, but when you will reach last element, it will be single element, not array any more, so you need to use newtest.className="bob"; for last one

Deleting a child node stops other siblings from being deleted?

I'm trying to remove all the child nodes with class .FormErrorMessage from element #LoginFormErrorWrapper. These are XUL "description" elements if that makes a difference.
Using this code:
var Messages = document.getElementById('LoginFormErrorWrapper').getElementsByClassName("FormErrorMessage");
for (var i = 0; i < Messages.length; i++) {
var node = Messages[i];
document.getElementById('LoginFormErrorWrapper').removeChild(node);
}
Only the first element is deleted. Messages contains all of the elements, but once the first child gets deleted, the loop stops. If I comment out the removeChild line and alert the node, it alerts all of the nodes. Why does it stop looping after the first child is deleted? The parent still exists.
getElementsByClassName returns a live NodeList. So while you remove an element from the DOM it is also removed from the NodeList, causing various side effects.
You can avoid them by converting the NodeList to an Array
var Messages = [].slice.call(document.getElementById('LoginFormErrorWrapper').getElementsByClassName("FormErrorMessage"));

How to reverse the ordering of list items in an unordered list

Let's say I have an unordered list, like this:
<ul id="birds">
<li>Bald Eagle</li>
<li>Falcon</li>
<li>Condor</li>
<li>Albatross</li>
<li>Parrot</li>
</ul>
With plain JavaScript (not including jQuery), I would like to be able to do reverse the order of this list - so that it matches the following expected "output":
<ul id="birds">
<li>Parrot</li>
<li>Albatross</li>
<li>Condor</li>
<li>Falcon</li>
<li>Bald Eagle</li>
</ul>
This is the JavaScript I have attempted to code, to try and achieve this goal:
var birdList = document.getElementById("birds").getElementsByTagName("li");
var birdListReverse = birdList.reverse();
for(var i = 0; i < birdList.length; i++)
{
birdList[i] = birdListReverse[i];
}
I would think that somehow over-writing the current list items with the ones in the "reversed" list would be sufficient, but not to be. Would I have to somehow manipulate each nodeValue in the list?
I would like to do this when a button is clicked - that is the easy part, it is just getting the technique to reverse the list that has hampered me a bit.
An HTMLCollection is only a read-only view of nodes. In order to manipulate nodes, you have to use one of the various DOM manipulation methods. In your case, there happens to be a shortcut; all you need do is to append the child nodes in reverse order.
var birds = document.getElementById("birds");
var i = birds.childNodes.length;
while (i--)
birds.appendChild(birds.childNodes[i]);
Edit: What's happening is that appendChild removes the node from the document before appending it. This has two effects: a) it saves you from having to make a copy of the list of nodes and removing them before appending them in reverse order, and b) it reverses the order of nodes as it goes, since it appends the last child first and the first child list.
var birds = document.getElementById('birds');
for(var i = 0, bll = birds.childNodes.length; i < bll; i++) {
birds.insertBefore(birds.childNodes[i], birds.firstChild);
}
You can do it like this (in plain javascript). You can just cycle through the li tags in reverse order and append them to the parent. This will have the natural effect of reversing their order. You get the last one, append it to the end of the list, get the 2nd to last one, append it to the list. Get the 3rd to last one, append it to the end of the list and so on and all the items end up reversed. Note, you can use appendChild on something that is already in the DOM and it will be moved from it's current location automatically (e.g. removed before appended).
var birds = document.getElementById("birds");
var birdList = birds.getElementsByTagName("li");
for (var i = birdList.length - 1; i >= 0; i--) {
birds.appendChild(birdList[i]);
}​
Working example: http://jsfiddle.net/jfriend00/GLvxV/
const list = document.querySelector("ul");
Array.from(list.children).reverse().forEach(element =>list.appendChild(element));
Manipulating the birdList doesn't re-order the elements because birdList is just a collection - a NodeList - which is independent of the DOM itself.
You actually need to get a reference to the <ul id="birds"> element and then use a combination of removeChild(...) and appendChild(...) - maybe insertBefore().
Untested, but try:
var parent = document.getElementById("birds");
var birdList = parent.getElementsByTagName("li");
for(var i = birdList.length-1; i > 0; --i) {
parent.appendChild(birdList[i];
}
You can also do this. It works as follows:
Grab all the bird list items (the <li> elements) and return them as a NodeList
Convert the NodeList into an an array and reverse the array (since items in arrays can be reversed, but not items in NodeLists)
Append each bird list item from the array into the original list container (the <ul> element)
Code:
const birdsContainer = document.getElementById("birds");
const birdList = birds.getElementsByTagName("li");
[].slice.call(birdList)
.reverse()
.forEach(bird => birdsContainer.appendChild(bird));
There's actually no need for JavaScript. If you were to use the semantically correct <ol> (ordered list) element here (since ordering does obviously matter to you), then you can use the reversed attribute to do the exact same thing without any script-dependencies! To reverse the order of the items visually, we'd sprinkle in two lines of CSS with flex-direction:
ol[reversed] {
display: flex;
flex-direction: column-reverse;
}
<ol reversed>
<li>Bald Eagle</li>
<li>Falcon</li>
<li>Condor</li>
<li>Albatross</li>
<li>Parrot</li>
</ol>

How do i display various div values using divs with the same id?

Ok to start of i currently have 4 divs with the same id for example:
<div id='name'></div>
<div id='name'></div>
<div id='name'></div>
<div id='name'></div>
and im currently using a javascript function to display the value of the div's for example:
function divCheck(){
alert(document.getElementById('name').innerHTML);
}
the problem im having is when i call the function it only displays the value of the first div.
My goal is to display the values of all the divs and place it into a Textarea input.
I will really truly appreciate it. In advance thank you.
Use class instead of id, and use getElementsByClassName
ID is for use once, and is generally for large item (div etc) which has to be pretty unique, or is to be individually accessed, when you need to access combinations or even apply CSS properties on grpups of html elements without having to type them again and for each id, use class, and apply the common properties to that class, use ID for unique properties.
Similarly here use class, as you can see the function is get*Elements*ByClassName, means it returns a group, and this is what class is for. For this kind of use, use class instead of ID.
As others have said, use classes instead of ids. Each id must be unique. You cannot have more than one object with the same id. Here's how it looks with a class name instead:
<div class='name'></div>
<div class='name'></div>
<div class='name'></div>
<div class='name'></div>
And, here's how you get all objects with a given class name and iterate over them:
function divCheck() {
var elems = document.getElementsByClassName('name');
for (var i = 0; i < elems.length; i++) {
alert(elems[i].innerHTML);
}
}
Unfortunately, getElementsByClassName() was not supported by IE until IE9 so you will have to use a javascript shim that implements it a different way when it doesn't already exist. Or, use a pre-built library like Sizzle or jQuery that already support this type of functionality in older browsers.
Hey why don't you use the class instead of Ids. Give dynamic classNames like class="className-'+id+'"
And the call them using :
$('div[class^="className-'+id+'""]')
Hope it will be useful.
P.S Avoid using same ids for elements.
Since you seem to be after a getElementsByClassName function independant of any library, try the following. It tries querySelectorAll first, if not available it tries getElementsByClassName, and finally does an old school iterate over elements approach.
It will also accept multiple class names and always returns a static NodeList or array of the matched elements (per querySelectorAll). Note that getElemensByClassName returns a live NodeList, so the result must be converted to an array otherwise it might behave a little differently if elements are being added or removed from the document.
/*
Selector must be per CSS period notation, using attribute notation
(i.e. [class~=cName]) won't work for non qSA browsers:
single class: .cName
multiple class: .cName0.cName1.cName2
If no root element provided, use document
First tries querySelectorAll,
If not available replaces periods '.' with spaces
and tries host getElementsByClassName
If not available, splits on spaces, builds a RegExp
for each class name, gets every element inside the
root and tests for each class.
Could remove duplicate class names for last method but
unlikely to occur so probably a waste of time.
Tested in:
Firefox 5.0 (qSA, gEBCN, old)
IE 8 (old method only, doesn't support qSA or gEBCN)
Chrome 14 (qSA, gEBCN, old)
*/
function getByClassName(cName, root) {
root = root || document;
var reClasses = [], classMatch;
var set = [], node, nodes;
// Use qSA if available, returns a static list
if (root.querySelectorAll) {
return root.querySelectorAll(cName);
}
// Replace '.' in selector with spaces and trim
// leading and trailing whitespace for following methods
cName = cName.replace(/\./g, ' ').replace(/^\s+/,'').replace(/\s+$/,'');
// Use gEBCN if available
if (root.getElementsByClassName) {
nodes = root.getElementsByClassName(cName);
// gEBCN usually returns a live list, make it static to be
// consistent with other methods
for (var i=0, iLen=nodes.length; i<iLen; i++) {
set[i] = nodes[i];
}
return set;
}
// Do it the long way... trim leading space also
nodes = root.getElementsByTagName('*');
cName = cName.split(/\s+/);
// Create a RegExp array of the class names to search on
// Could filter for dupes but unlikely to be worth it
for (var j = 0, jLen = cName.length; j < jLen; j++) {
reClasses[j] = new RegExp('(^|\\s+)' + cName[j] + '\\s+|$');
}
// Test each element for each class name
for (var m = 0, mLen = nodes.length; m < mLen; m++) {
node = nodes[m];
classMatch = true;
// Stop testing class names when get a match
for (var n = 0, nLen = reClasses.length; n < nLen && classMatch; n++) {
classMatch = node.className && reClasses[n].test(node.className);
}
if (classMatch) {
set.push(node);
}
}
return set;
}

Categories