for loop condition evaluation - javascript

I have this HTML code
<div id="first">
<div id="second">
</div>
</div>
I want to append a child to each existing div.
When I use this code
function appendC() {
var divsVar = document.getElementsByTagName("div");
for (var i = 0; i < divsVar.length; i++) {
var new_div = document.createElement("div");
div[i].appendChild(new_div);
}
}
the function it goes into an infinite loop. But I can't understand why.
When the function is called, it assigns to var divsVar an array with 2 divs in my example. Never executed again, unless I call again the function. But the divsVar.length is changed in every loop. How is this possible?? The divsVar.length should stay as constant??
If I use a temp variable like that
function appendC() {
var divsVar = document.getElementsByTagName("div");
var _temp = divsVar.length;
for (var i = 0; i < _temp; i++) {
var new_div = document.createElement("div");
div[i].appendChild(new_div);
}
}
it work like a charm.

The .getElementsByTagName() method returns a live HTMLCollection.
You can either turn the collection into an array (e.g. [].slice.call(collection)), or use another method such as .querySelectorAll() which returns a static NodeList.
https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByTagName
https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll

getElementsByTagName() returns an object containing a live HTMLCollection. This is updated whenever you update the dom. In your for loop, after every iteration, the length property is evaluated again on the current dom.
Because you are adding divs to the dom, this loop will run forever.
The reason this works with a _temp variable is because you only evaluate the length of the HTMLCollection once.
I think the best solution would be to evaluate the length beforehand, just like your last example.

getElementsByTagName() method returns a live HTMLCollection of elements with the given tag name.
This means that even though you're not calling it again, it's still being updated. To fix this simply use
document.querySelectorAll("div")
which returnsA non-live NodeList containing one Element object for each element that matches at least one of the specified selectors.

Related

GetElementsByTagName - No HTML

I did a getElementsByTagName("li") in my DOMDocument, but I don't have the inner HTML of each li elements, just the unlike a getElementById. How to get the HTML for a getElementsByTagName?
The word Elements in getElementsByTagName is plural.
It doesn't return an element, it returns a NodeList, which is like an array.
Loop over it (for (var i = 0; i < nodelist.length; i++)) and deal with each member in turn.
getElementsByTagName returns an array of elements. You have to loop through it to extract the innerHTML (make sure you really want the HTML, and not just the text!)
for (var i=0; i<x.length; i++) { //x = the list of elements
var derp = x[i].innerHTML;
}
Or if you wanna be fancy, use the prototype method forEach and call.
[].forEach.call(document.getElementsByTagName("li"), function(element) {
element.innerHTML;
});
I suggest replacing innerHTML with textContent however, since you said you wanted text.

How can I replace one class with another on all elements, using just the DOM?

I just want to change a classname to another one.
I've tried using
document.getElementsByClassName("current").setAttribute("class", "none");
but it doesn't work. I'm new at javascript.
Explanation
document.getElementsByClassName returns an HTMLCollection, not just an array of elements. That means that the collection is live, so in this specific situation, it retains the requirement that it will always hold all elements with the class "current".
Coincidentally, you are removing the very class that the collection depends on, therefore updating the collection. It would be totally different if you were setting the value property (for example) in the loop - the collection wouldn't be affected, because the class "current" wasn't removed. It would also be totally different if you were adding a class, such as el.className += " none";, but that isn't the case.
A great description from the MDN docs:
HTMLCollections in the HTML DOM are live; they are automatically updated when the underlying document is changed.
Approach 1
An easy way to overcome all this pandemonium is by looping backwards.
var els = document.getElementsByClassName('current'),
i = els.length;
while (i--) {
els[i].className = 'none';
}
DEMO: http://jsfiddle.net/fAJgT/
(the code in the demo has a setTimeout, simply so you can see the original border color at first, then after 1.5 seconds, see it change)
This works because it modifies the last item in the collection - when it is modified (and automatically removed), move onto the item before it. So it doesn't suffer any consequences of the automatic removal.
An alternate setup, doing the same thing, is:
for (i = els.length; i >= 0; i--) {
Approach 2
Another answer made me realize you could just continually operate on the first item found. When you remove the specific class, the element is removed from the collection, so you're guaranteed that the first item is always a fresh item in the collection. Therefore, checking the length property should be a safe condition to check. Here's an example:
var els = document.getElementsByClassName('current');
while (els.length) {
els[0].className = 'none';
}
DEMO: http://jsfiddle.net/EJLXe/
This is basically saying "while there's still items in the collection, modify the first one (which will be removed after modified)". I really wouldn't recommend ever using that method though, because it only works specifically because you end up modifying the collection. This would infinitely loop if you were not removing the specific class, or with a normal array or a non-live collection (without spliceing).
Approach 3
Another option is to slice (shallow copy) the collection into an array and loop through that normally. But I don't see any reason/improvement to do that. Here's an example anyways:
var els = document.getElementsByClassName('current'),
sliced = Array.prototype.slice.call(els), i;
for (i = 0; i < sliced.length; i++) {
sliced[i].className = 'none';
}
DEMO: http://jsfiddle.net/LHe95/2/
Approach 4
Finally, you could use document.querySelector - it returns a non-live NodeList (therefore you can loop like normal), and even has better support in browsers than document.getElementsByClassName does. Here's an example:
var els = document.querySelectorAll('.current'),
i;
for (i = 0; i < els.length; i++) {
els[i].className = 'none';
}
DEMO: http://jsfiddle.net/xq8Xr/
References:
document.getElementsByClassName: https://developer.mozilla.org/en-US/docs/DOM/document.getElementsByClassName
HTMLCollection: https://developer.mozilla.org/en-US/docs/DOM/HTMLCollection
slice: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/slice
querySelectorAll: https://developer.mozilla.org/en-US/docs/DOM/Document.querySelectorAll
document.getElementsByClassName("current") returns a HTMLCollection, not an HTMLElement. You can access elements in the list the same way you would access an array.
The following will change the class of the first element in the HTMLCollection.
document.getElementsByClassName("current")[0].setAttribute("class", "none");
However, you do not have to use the setAttribute method, you can just set the className property of the element.
element.className = 'none';
Please note that HTMLCollection are live, wich means that as soon as the element will not have the current class name anymore, the element will be removed from the array, so to avoid any issues you could iterate over it using the following approach:
var list = document.getElementsByClassName("current");
while (list.length) {
list[0].className = 'none';
}

Select all ID's or Class's with getElementBy

I have a script that I'm running and I want to it to select all the ID's or Class's instead of just the first one.
<script type = "text/Javascript" >
function color(){
var d=document.getElementsByClassName("black")[0];
d.setAttribute("style", "background-color:#333;");
}
</script>
The [0] in document.getElementsByClassName("black")[0] means that you're actually discarding all but the first, after selecting them. Use a loop if you want to iterate over the value returned by gEBCN.
Use document.getElementsByTagName('*') if you want all elements.
function color() {
var allElements = document.getElementsByTagName('*');
for (var i=0; i<allElements.length; i++) {
allElements[i].setAttribute('style', 'background-color:#333;');
}
}
To get all the elements with a certain ID, you can rely on querySelectorAll, as in
var d = document.querySelectorAll("#temp");
Keep in mind some things:
It's semantically wrong, even if not syntactically, to have multiple elements with the same id.
querySelectorAll return a NodeList object, not a live collection. This means that if you add another element with id "temp", that won't be in the collection you got and you have to call querySelectorAll again.
querySelectorAll isn't supported by IE7 and previous versions.
You already have an answer for getElementsByClassName.

call a function with a specific index value of nodelist

Is it possible to call a function on specific index value of nodelist which is storing div like following :
var txtElem = txtdiv.getElementsByTagName("div");
the thing i want is that i am storing list of divisions in txtElem nodelist now i want to call a function on click event of the 3rd div stored in nodelist. The divisions are created dynamically and they don't have any id so they are not accessible by id.
from what you asked, it seems like this will do:
function toPseudoArray(nodeList) {
var ar = [];
for(var i in nodeList)
if(nodeList[i].nextSibling) // or for that case any other way to find if this is an element
ar.push(nodeList[i]);
return ar;
}
Pass your nodeList to this function, use what it returns as an array that contains your elements, and only your elements.
By the way, you could directly call function on a specific element simply using my_fab_function(txtElem[0]); -- of course, until you don't exceed the count.
The question is quite unclear. Seeing the jQuery tag, these come to my mind:
A way to call a jQuery function on a specified index using .eq():
var n = 1; //the index you need
$(txtElem).eq(n).css('color', 'red');
Simple Javascript to get the DOM element:
var n = 1; //the index you need
var elem = txtElem[n]; //elem will hold the DOM element
//call simple DOM methods on it:
var s = elem.innerHTML;
//you can also call jQuery functions on it:
$(elem).css('color', 'red');
By the way txtElem is not an object, it is a NodeList, an "array-like object".

Appending elements to DOM in a loop structure

Once the page has been loaded, I would like to append an additional element for each existing elements on the page.
I tried something like this:
var divs=document.getElementsByTagName('div');
for(i=0;i<divs.length;i++){
newDiv=document.createElement('div');
divs[i].appendChild(newDiv);
}
Just a warning this will actually freezes the browser because the divs variable is dynamic and divs.length just gets larger and larger each time the loop goes.
Is there a way to determine the number of tags when the DOM is normally loaded for the first time and have a chance to work with the elements statically.
I can't there of another solution so far.
Thanks so much.
Dennis!
The problem is that DOM collections are live, and when the underlying document structure is changed, it will be reflected automatically on the collection, that's why when the length property is accessed it will contain a new length, a common approach is to cache the length before starting the loop:
var divs=document.getElementsByTagName('div');
for(var i = 0, len = divs.length;i<len;i++){
var newDiv = document.createElement('div');
divs[i].appendChild(newDiv);
}
Also notice that you should declare all your variables with the var statement, otherwise it might become global.
Edit: In this case, since you are appending child nodes of the same tagName, the collection will be modified, and the indexes will no longer match, after the first iteration, the index 1 will refer to the newDiv object from the previous iteration, as #Casey recommends it will be safer to convert the collection to a plain array before traversing it.
I use the following function:
function toArray(obj) {
var array = [];
// iterate backwards ensuring that length is an UInt32
for (var i = obj.length >>> 0; i--;) {
array[i] = obj[i];
}
return array;
}
//...
var divs = toArray(document.getElementsByTagName('div'));
//...
Like you said, the divs variable is dynamic, so you have to convert it into an array (which is static) before you use it.
var nodeList = document.getElementsByTagName('div');
var divs = [];
for (var i = 0; i < nodeList.length; i++)
divs.push(nodeList[i]);
// loop again and append the other divs
Another (more elegant) way to do this is:
var divs = Array.prototype.slice.call(document.getElementsByTagName('div'));
But alas, this method does not work in IE.
Using jQuery, this is pretty straight forward. You can get a reference to all the existing divs or any other element on the page and then append a new element very easily without needing to create an explicit loop. Hope this help.
$('div').each(function(){
var newDiv = document.createElement('div');
$(this).append(newDiv);
});
document.getElementsByTagName() does NOT return a plain array, but an instance of HtmlCollection, which behaves like an array, but in fact presents some kind of view to all elements with the given element name in the document.
So, whenever you insert something into the DOM, the length property of divs will be updated too - of course.
So, besides other answers here, this behaviour should make sense now ;-)

Categories