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 ;-)
Related
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.
I'm working with phylogentic trees and I want an object for the tree itself and then an object for each species, 4 species total. I'm trying to have the tree contain the species objects under tree.leaf and then assign an array of attributes to each species but through the tree object, because I'm randomizing the order of the species so I can't depend on species names but I can use leaf placement(Hope that makes sense). I'm having trouble updating the html, a div inside a table though.
Simplified Version:
var tree = new Object();
var speciesA = new Object();
tree.leaf1 = speciesA;
//Not sure if this next line assigns to speciesA or what exactly happens
tree.leaf1.attributes = new Array("Attr1","Attr2",etc);
var count = 1;
for(attr in speciesA.attributes)
{
//There are 4 divs per speices to display attributes
document.getElementById("A"+String(count)).innerhtml = speciesA.attributes[attr];
count++;// used to specify divs ie A1 = attribute1, A2 = attribute2 etc
}
So I guess my main question is will this work/do what I think it does?
If needed I can pastebin my html and full js files.
What you have should work, but it can be written a bit cleaner. I would suggest this:
var tree = {
leaf1: {attributes: ["Attr1", "Attr2"]}
};
var attributes = tree.leaf1.attributes;
for (var i = 0; i < attributes.length; i++) {
document.getElementById("A"+(i+1)).innerHTML = attributes[i];
}
Things I changed:
Used a javascript literal to make the definition a lot more compact
Used {} and [] for defining arrays and objects rather than new Object() and new Array().
Used for (var i = 0; i < xxx.length; i++) syntax to iterate array elements only, not all properties. This is the "safe" way to iterate elements of an array.
Remove the String(count) as it is not needed. Javascript will auto-convert a number to a string when adding to another string.
Cached the value of the attributes array to save having to deep reference it each time.
Removed separate count variable as the for index can be used
To answer one of your other questions, when you do this:
tree.leaf1 = speciesA;
you have assigned a "reference" to speciesA to tree.left1. A reference is like a pointer. It is not a copy. So, the both refer to exactly the same object. Any change you make to speciesA or to tree.leaf1 is make a change to the exact same object.
So, when you then do this:
//Not sure if this next line assigns to speciesA or what exactly happens
tree.leaf1.attributes = new Array("Attr1","Attr2",etc);
you are indeed modifying the speciesA object since speciesA and tree.leaf1 point to the same object.
In javascript, arrays, objects and strings are assigned by reference. That means that when you assign one to a variable, it just points to the original object. A copy is not made. So, change the object via either either one will change the other (since they both point to the same object). Strings are immutable (a string is never actually changed). Things that feel like modifications to a string always just return a new string so this aspect of javascript doesn't affect strings so much. But, it is very important to know that arrays and objects are assigned by reference.
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';
}
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".
I'm not sure if I'm doing something wrong here or not, but I need to base the status of several objects off of the status of another. So I'm basically iterating over the parents, then getting their children and iterating over them to get their children etc. However, I don't know how to add jquery objects on to a jquery result object.
My code looks something like this (where bubbleDown is said result object):
while (i < bubbleDown.length) {
var curRow = bubbleDown[i];
child = curRow.getAttribute('class') //etc
parent = curRow.getAttribute('class') //etc
bubbleDown.add($('.' + child + ':not(.' + parent.replace(' ', '.') + ')'));
i++;
}
I've hidden some logic due to it not being applicable to my question. Anyway, it doesn't work, and not because the jquery fails to return objects. Can anyone help?
The simplest approach is that .add() takes a selector already, just use that overload and keep updating the reference to the object to use the one .add() returns:
var newBubbleDown = $(bubbleDown);
while (i < bubbleDown.length) {
var curRow = bubbleDown[i];
child = curRow.getAttribute('class') //etc
parent = curRow.getAttribute('class') //etc
newBubbleDown = newBubbleDown.add('.' + child + ':not(.' + parent.replace(' ', '.') + ')');
i++;
}
//use newBubbleDown
I can't simplify it any further since I'm not sure of your logic outside, e.g. where i comes from, what child and parent are used for, etc. But just call .add(selector), without feeding it a jQuery object and you're all set.
We're using a new object here since .add() returns a reference to a new jQuery object (one you're not using), so each .add() adds elements, creates a jQuery object containing them, then it's thrown away on the next loop. Instead you need to update the reference so it keeps accumulating elements like you want. I tend to change the .add() implementation to act on the current array in most of my projects because it's more useful that way which would make your original code work, for example).
Note: this will only add elements to the newBubbleDown jQuery object, but your :not() use makes me wonder if this will suit your needs, it wouldn't remove any elements in any case, it'll only find the elements matching that selector and add them to the array. If you need to exclude elements already in the array, you'll need .filter().
you have to do this on a jquery-object item:
var curRow = $(bubbleDown[i]);
or you can use the each() method: (while index holds the current item index)
bubbleDown.each(function(index){
var child = $(this).attr('class')...
...
bubbleDown.add(...);
});
use var to init your variables to avoid problems with IE for example.