I have a treeview with drag and drop functionality. Upon completion of drag and drop I need to find the count of the child nodes of the new parent node. So for that I'm using the following
lines
var childnodelength=elem.parentNode.parentNode.childNodes.length;
alert(childnodelength);
But I always get the same value of 4 irrespective of the no. of childs, with the above alert.
I've also tried in the following way.
alert(elem.getElementsByTagName("A")[0].childNodes.length);
Above line always gives me 1 irrespective of the no. of childs. I am not sure if I am referring correctly in both the ways.
And hence I'm unable to find the no. of child nodes.
Please could someone help me with this?
Thanks
Keep in mind that childNodes only looks one level down, so the value will be the same so long as you have the same number of immediate children. Perhaps what you wanted was to count all the children all the way down the tree?
If you want all the child elements, you can use getElementsByTagName("*"). Note that the use of the "*" argument doesn't work in IE5.5, which could also pose trouble for more recent versions of IE when running in quirks mode.
Another good thing to know about childNodes.length is that it may return a different value in IE than other browsers because of different ways of counting text nodes . . . If you want to exclude text nodes and only count elements you can loop through the childNodes and check the value of nodeType (1 = ELEMENT_NODE, 2 = ATTRIBUTE_NODE, 3 = TEXT_NODE, etc.) You can also use children instead of childNodes if you only want element nodes, but in IE this incorrectly counts comment nodes, and in Firefox it wasn't supported until version 3.5.
Perhaps the child node is a DIV or Table that wraps up the nodes you need to count?
Related
When clearing HTML elements such as select boxes, tables, or lists, is it better/faster to remove the nodes (e.g., select.options.remove(i), table.deleteRow(i)) or just empty the innerHTML (e.g., select.innerHTML = "")? Or does it matter?
An example case would be reinitializing a table. A specific select field's value should load different values for a subsequent HTML table. When the select value changes, the table needs to be reinitialized.
In IE you cannot set the innerHTML of a select element. So for a cross-browser solution the only way is to add/remove child nodes.
I made a new test that isn't broken.
http://jsperf.com/innerhtml-vs-removechild/67
It's not perfect either since part of the sample setup is part of the test so this might skew the results.
This gives that innerHTML is faster but I don't know by how much.
Per this conversation here: What is the best way to empty an node in JavaScript
It appears that the while (elm.firstChild) {elm.removeChild(elm.firstChild);} approach got the the best results across browsers in this test.
(Would have put this as a comment instead of an answer, but the comments are coming in awful fast, so I didn't want it to get lost.)
Case 1:-
Run on 1000 Child DOM Element to Remove.
Which one is Faster?
Remove Child (5,676,264 ops/sec ±1.46%) --> Faster
innerHTML = "" (4,359,867 ops/sec ±1.46%) --> Slower
Case 2:-
Which one is able to remove event handler of child Nodes?
Remove Child --> Able to remove event handler
innerHTML = "" --> Not able to remove event handler
Result:-
Seems It's better to used Remove Child for removing any child nodes.
Ref:-
https://www.measurethat.net/Benchmarks/Show/6910/0/innerhtml-vs-removechild-vs-remove
https://rusingh.com/javascript-benchmark-removechild-vs-innerhtml/
So much confusion, so few answers. I'm trying to loop through the DOM, looking for a specific node by id, however, this code has several problems for which I have no explanation. First, the length of the childNodes list comes up as '5'. Two "ul"'s, two "id"'s, if those count...and one for luck?
Second, it dies at if(y[i].hasAttribute('id')===true). Firebug says this is not a function. I have no reason to not believe it, but am not sure why it isn't.
Thank you for any help.
<div id="list">
<ul id="first"></ul>
<ul id="second"></ul>
</div>
<script>
var comments=document.getElementById('list')
var y=comments.childNodes;
var count=y.length
for(i=0;i<count;i++)
{
document.write(y.length);
if(y[i].hasAttribute('id')===true)
{ document.write('here!');}
}
</script>
the childNodes attribute contains all nodes in the DOM, which specifically means, it includes text nodes. you have 3 of them - the newline/linefeed characters inside your div.
you can test for element children using the nodeType attribute ( see eg. here; 1represents ELEMENT_NODE, 3 stands for TEXT_NODE).
If you use a tool like Firebug and inspect the DOM itself, you would see all the children of an element and the difference between .children and .childNodes .
It's by hunting around in the DOM that I discovered why there are so many things that at first appear to be duplicates of each other, but are definitely not. The Mozilla developers site developer.mozilla.org is also a wealth of information.
So I'm trying to control the stacking of equally-sized elements via z-index.
Now an idea that I came across recently to avoid going through z-indices and improve performance times by hopefully to avoiding browser reflows is instead order layers via the order I append things to the parent.
So if I have a container div that holds all the stacking divs, and linked list that mirrors the order, referencing the stacking divs, then I reorder the divs based on user input. Then instead of updating the z-indices, I would recreate the div element and just append everything in order. So something like this:
var from = nodeBeforeFrom; // Input
var target = nodeBeforeTarget; // Input
var linkedlist = input; // var linkedlist contains all the stacking divs
linkedlist.moveElement(div1, div2); //Move div1 to after div2
var container = document.createElement('div');
linkedlist.reorder; //
var cur = linkedlist.first;
while (cur.next) {
container.appendChild(cur)
cur = cur.next;
}
document.removeChild(oldContainer);
document.appendChild(container);
// This is meant as pseudocode so forgive an errors in regards to the specifics
So my questions are the following:
Would this reduce browser reflows from n reflows to just 1 or 2 (where n is the number of divs)? If I understand it right, changing the z-index of a single element should cause either a browser repaint or a reflow.
Will the second approach work and stack elements in the order you append them?
Is there a way to move childs around using the DOM's child node structure already so I don't have to create a separate linked list? I only see removeChild and appendChild functions that I can use at the moment.
And yes performance is an issue since I'm planning on using this for graphics and html5 stuff. So where I can save I would like to save.
Well great it seems I've answered my own question after playing around with things and some research thanks to the good people at Opera. Pretty much yeah it's faster to perform updates on hidden/unseen elements on the browser, then to add it to the DOM tree. I got my confirmation from here. The actual trick is to set the hidden CSS tag, perform all the operations that affect display, then set hidden back to true and that reduces your browser reflows from O(n) to just 2 total.
Also this method for avoiding z-index certainly works. I unfortunately still haven't found a way to access the childNodes linked list for DOM elements. However, taking a closer look at the specification, it turns out that childNodes for DOM nodes is read-only, which likely means it's not possible unless there's some vague hack around it.
This is the same question as this:
Referring to a div inside a div with the same ID as another inside another
except for one thing.
The reason there are two elements with the same ID is because I'm adding rows to a table, and I'm doing that by making a hidden div with the contents of the row as a template. I make a new div, copy the innerhtml of the template to my new div, and then I just want to edit bits of it, but all the bits have the same ID as the template.
I could dynamically create the row element by element but it's a VERY complex row, and there's only a few things that need to be changed, so it's a lot easier to just copy from a template and change the few things I need to.
So how do I refer to the elements in my copy, rather than the template?
I don't want to mess up the template itself, or I'll never be able to get at the bits for a second use.
Or is there another simpler way to solve the problem?
It will probably just be easiest when manipulating the innerHtml to do a replace on the IDs for that row. Maybe something like...
var copiedRow = templateRow.innerHTML.replace(/id=/g,"$1copy")
This will make the copied divs be prefixed with "copy". You can develop this further for the case that you have multiple copies by keeping a counter and adding that count variable to the replace() call.
When you want to make a template and use it multiple times its best to make it of DOM, in a documentFragment for example.
That way it doesn't respond to document.getElementById() calls in the "live" DOM.
I made an example here: http://jsfiddle.net/PM5544/MXHRr/
id's should be unique on the page.
PM5544...
In reality, there's no use to change the ID to something unique, even though your document may not be valid.
Browsers' selector engines treat IDs pretty much the same as class names. Thus, you may use
document.querySelector('#myCopy #idToLookFor');
to get the copy.
IDs on a page are supposed to be unique, even when you clone them from a template.
If you dynamically create content on your page, then you must change the id of your newly cloned elements to something else. If you want to access all cloned elements, but not the template, you can add a class to them, so you can refer to all elements with that class:
var clonedElement = template.cloneNode(yes); // make a deep copy
clonedElement.setAttribute("id", "somethingElse"); // change the id
clonedElement.setAttribute("class",
clonedElement.getAttribute("class") + " cloned"
);
To access all cloned elements by classname, you can use the getElementsByClassName method (available in newer browsers) or look at this answer for a more in-depth solution: How to getElementByClass instead of GetElementById with Javascript?
Alternatively, if you have jQuery available, you can do this is far less lines of code:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned").appendTo("#someDiv");
The class lookup is even simpler:
$(".cloned").doSomethingWithTheseElements();
Try to avoid using IDs in the child elements of the cloned structure, as all ids of the cloned element should be changed before adding the clone to the page. Instead, you can refer to the parent element using the new id and traverse the rest of the structure using classnames. Class names do not need to be unique, so you can just leave them as they are.
If you really must use ID's (or unique "name" attributes in form fields), I can strongly suggest using a framework like jQuery or Prototype to handle the DOM traversal; otherwise, it is quite a burden to resolve all the cross-browser issues. Here is an example of some changes deeper in the structure, using jQuery:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned") // add a cloned class to the top element
.find("#foo").attr("id","bar").end() // find and modify a child element
.appendTo("#someDiv"); // finally, add the node to the page
Check out my ugly but functional cheese. I wrote a function that works like getelementbyid, but you give it a start node instead of the document. Works like a charm. It may be inefficient but I have great faith in the microprocessors running today's browsers' javascript engines.
function getelement(node, findid)
{
if (node)
if (node.id)
if (node.id == findid)
return node;
node = node.firstChild;
while(node)
{
var r = getelement(node, findid);
if (r != null)
return r;
node = node.nextSibling;
}
return null;
}
When you copy the row, don't you end up having a reference to it? At that point can't you change the ID?
Here's what I'm trying to do: I have a bookmarklet that is looking for elements in the current page (which can be any site) and dispatch a click event on the ones that match. I have that part working.
In some cases though, nothing matches automatically and I want to be able to show (by hovering it) what element should be activated and then save some info about it in localStorage. The next time I'm using the bookmarklet on that page, I want to retrieve that info to identify the element in the DOM and then dispatch a click event.
The question is: what information should I save to be able to identify it? (in most cases, since it will always be possible to create a case where it doesn't work)
In the best case, said-element will have an id value and I'm good to go. In some other cases, it won't and I'd like to see your suggestions as to what info and what method I should use to get it back.
So far my idea is to save some of the element's properties and traverse the DOM to find elements that match everything. Not all properties will work (e.g. clientWidth will depend on the size of the browser) and not all types of elements will have all properties (e.g. a div node won't have a src value), which means that on one hand, I can't blindly save all properties, but on the other, I need to either choose a limited list of properties that will work for any kinds of element (at the risk of losing some useful info) or have different cases for different elements (which doesn't sound super great).
Things I was thinking I could use:
id of course
className, tagName would help, though className is likely to not be a clear match in some cases
innerHTML should work in a lot of cases if the content is text
src should work in most cases if the content is an image
the hierarchy of ancestors (but that can get messy)
...?
So, my question is a bit "how would you go about this?", not necessarily code.
Thanks!
You could do what #brendan said. You can also make up a jQuery-style selector string for each element in the DOM by figuring out the element's "index" in terms of its place in its parent's list of child nodes, and then building that up by walking up the DOM to the body tag.
What you'd end up with is something that looks like
body > :nth-child(3) > :nth-child(0) > :nth-child(4)
Of course if the DOM changes that won't work so good. You could add class names etc, but as you said yourself things like this are inherently fragile if you don't have a good "id" to start with, one that's put there at page creation time by whatever logic knows what's supposed to be in the page in the first place.
an approach would be using name, tagName and className-combination. innerHTML could may be too big.
another approach would be to look for child elements of your choosen element which have an id.
check for id => check for childs with id => check for name, tagName and className-combination (if => tell user to choose a different item :-)
What about finding all elements without an ID and assigning them a unique id. Then you could always use id.
What about using the index (integer) of the element within the DOM? You could loop through every element on page load and set a custom attribute to the index...
var els = document.getElementsByTagName("*");
for(var i = 0, l = els.length; i < l; i++) {
els[i].customIndex = i;
}