I obtain an active copy of an HTML5 <template> using function importNode():
function getTemplate() {
var t = document.getElementById("example");
return document.importNode(t.content,true);
}
After this, I fill the dynamic data,
var t = fillTemplate({
id:"test",
text:"Enter test data"
});
and finally, I append the node into the target container:
var c = document.getElementById("container");
var result = c.appendChild(t);
My problem: the result node has all its content stripped off: I can't access the component elements of the template in the result node. Actually, the result node contains no child nodes at all once the appendChild operation has been performed.
I expect that the return value of appendChild should point to the node that has been inserted into the container and which is now part of the active document. Any explanation why this is not the case?
Here is the jsfiddle (tested in Chrome 53):
https://jsfiddle.net/rplantiko/mv2rbhym/
It is due to the fact that you don't manipulate a Node but a DocumentFragment.
If you want get the number of Nodes inserted, you should perform the call on the parent container (c in your example) then you'll get the right answer (5).
But if you want to count only the child elements you added, you should not use childNodes, but the property of the ParentNode interface children:
c.childNodes.length // = 5
c.children.length // = 2
After being appended to the container c, the DocumentFragment t has no children any more.
From the MDN documentation:
Various other methods can take a document fragment as an argument
(e.g., any Node interface methods such as Node.appendChild and
Node.insertBefore), in which case the children of the fragment are
appended or inserted at the location in the DOM where you insert the
document fragment, not the fragment itself. The fragment itself
continues to exist (in memory) but now has no children.
The DOM 3 W3C recommendation is also clear:
Furthermore, various operations -- such as inserting nodes as children
of another Node -- may take DocumentFragment objects as arguments;
this results in all the child nodes of the DocumentFragment being
moved to the child list of this node.
Related
is Node interface just a concept ? What is the difference between element interface and node interface ? when Programmatically used for i need examples please !!
What’s a node? it is the name of any type of object in the DOM tree. It could be one of the built-in DOM elements such as the document itself, document.head or document.body. A node could be an HTML tag specified in the HTML such as , ,, or it could be a comment node, text node… In fact, a node is any DOM object and every node has a parent, every node is allowed to have one or more children or even zero children.
What’s an element? An element is a specific type of node, one that can be directly specified in the HTML with an HTML tag and can have properties like an id or a class. can have children, etc.
Nodes vs Elements: Nodes are all the different components that a webpage is made up of and elements are one type of node.
You can create a DOM Node in a web page as follows:
var node=document.createTextNode('A Node');
You can create a paragraph element as follows:
var p=document.createElement('p')
To attach the node to the element:
p.appendChild(node);
ref : https://developer.mozilla.org/en-US/docs/Web/API/Node
I am trying to execute below snippet
let frag = document.createDocumentFragment();
let divElement = document.createElement('div');
divElement.insertAdjacentElement('afterend', frag);
I am getting the below error.
Uncaught TypeError: Failed to execute 'insertAdjacentElement' on 'Element': parameter 2 is not of type 'Element'.
What is the restriction here? reference about error details would be much appreciated.
Please suggest an alternative to do it efficiently.
Fragments can be composed of multiple elements, not just single elements. In such a case, if the technique in your question worked, insertAdjacentElement would be inserting multiple elements, not just a single element. (It's not called insertAdjacentElements)
It just isn't allowed. Unlike appendChild, insertAdjacentElement can't take a fragment as an argument.
You could iterate through all children of the fragment and insert them afterend:
// Create fragment, append elements
const fragment = document.createDocumentFragment();
fragment.appendChild(document.createElement('span')).textContent = 'span1';
fragment.appendChild(document.createElement('span')).textContent = 'span2';
fragment.appendChild(document.createElement('span')).textContent = 'span3';
// First cast fragment.children from an HTMLCollection to an Array
// Since insertAdjacentElement will remove the element from fragment.children
// It will mutate on each loop causing issue
const childrenAsArray = Array.from(fragment.children);
// Iterate through fragment
for (const element of childrenAsArray ) {
divElement.insertAdjacentElement('beforeend', element);
}
<div id="divElement"></div>
Or you could call insertAdjacentElement directly instead of putting the elements into a fragment first.
createDocumentFragment is never part of main dom.In dom tree document fragment is replaced by its child. Also it cannot inherit from element base class. So it is not a valid element too. The syntax of insertAdjacentElement is targetElement.insertAdjacentElement(position, element) where element need to be a valid one as described by the above link.
Hence this error
I've read the following very good article:
Difference between Node object and Element object?
as to clear out the difference between a node object and an element object. I 've understood that
element objects are a subset of node objects.
So, after that I've come upon the following query: In which way may I iterate through all the node objects? By using document.getElementsByTagName('*') ,I think I am getting all the element objects since all of them have value 1 for their .nodeType property. Am I right and if yes, how may I include into my results all these nodes that are not elements?
Thank you
I don't believe there's any standard DOM function that will return every Node (as opposed to Element) in the entire document.
You would probably have to use recursive DOM traversal to find them all with something like this:
function getAllNodes(parent, nodes) {
parent = parent || document;
nodes = nodes || [];
var child = parent.firstChild;
while (child) {
nodes.push(child);
if (child.hasChildNodes) {
getAllNodes(child, nodes);
}
child = child.nextSibling;
}
return nodes;
}
As written you can just write var nodes = getAllNodes() and it'll automatically start at the document root.
The answer is in your question. You use document.getElementsByTagName. Let me repeat that for you. getElementsByTagName.
Non-element nodes don't have a tag name, an identifier, or anything like that. The only way to designate them is by where they are in the document structure.
You can get a reference to an Element and then browse its childNodes, as has been suggested already.
Another way is to use an XPath, which, unlike CSS selectors, is able to point directly to any Node, including text and comment Nodes.
document.evaluate("*", document.documentElement, null, XPathResult. UNORDERED_NODE_ITERATOR_TYPE)
The first log returns a full li element while the second one returns an empty DocumentFragment. Why? I couldn't find any information about that behavior in any documentation.
var main = document.getElementById('main');
var fooTemplate = document.getElementById('my-template');
var foo = fooTemplate.content.cloneNode(true);
console.log(foo);
main.appendChild(foo);
console.log(foo);
<template id="my-template">
<li>foo</li>
</template>
<ul id="main">
</ul>
From the MDN docs on DocumentFragment
Various other methods can take a document fragment as an argument (e.g., any Node interface methods such as Node.appendChild and Node.insertBefore), in which case the children of the fragment are appended or inserted, not the fragment itself.
foo = fooTemplate.content.cloneNode(true) copies the document fragment to foo.
main.appendChild(foo) moves the contents of the foo document fragment into main. foo remains a document fragment, and all the nodes have moved so it's empty.
If you want to keep a reference to the DOM nodes after appending them, you need to store the childNodes, but if you just reference the nodeList, it'll be empty, so you'll need to convert it to an Array:
var nodes = Array.prototype.slice.call(foo.childNodes);
console.log(nodes);
main.appendChild(foo);
console.log(nodes);
No libraries please. Beyond my control.
I'm appending a document fragment to the dom. THIS ALL WORKS. No problemo.
How do I retain/retrieve the node list after the fragment is appended? Relevant code:
var frag = document.createDocumentFragment();
//add some content to the fragment
element.appendChild(frag);
Again, this works! I do NOT need a troubleshoot on how to add things to the dom!
If I set var e = element.appendChild(frag);, everything gets appended normally, but e = an empty document fragment.
I'm looking for some smooth magic voodoo here. Don't send me looping over the entire dom. The content could be anything, one or many nodes with or without children. If there's some trick with querySelectorAll or something that would be acceptable.
Thanks!
EDIT
Upon further poking, it appears that e above is in fact a returned reference to the frag var, that is empty after appending it to the dom. It's much like the elements were neatly slid off of the fragment into the dom, and the fragment just lays around empty.
It's exactly what you've described; when the elements are appended to an element, they're removed from the fragment to prevent memory leaks from lingering references.
One way to get those child nodes is to make a shallow copy of the fragment's childNodes before doing appendChild():
// make shallow copy, use Array.prototype to get .slice() goodness
var children = [].slice.call(frag.childNodes, 0);
element.appendChild(frag);
return children;
If you're looking just after appending it, the lastChild is the way to go. Use it like this:
var e = element.lastChild;
More info in SitePoint
From doc of DocumentFragment:
Various other methods can take a document fragment as an argument (e.g., any Node interface methods such as appendChild and insertBefore), in which case the children of the fragment are appended or inserted, not the fragment itself.
So appendChild method return the documentFragment but it's empty because its child nodes are inserted in your element object and (from appendChild doc):
[...] a node can't be in two points of the document simultaneously. So if the node already has a parent, it is first removed, then appended at the new position.
Now...
You can store in a cache array your children and after return it (or see Jack solution :-))
Like Jack's solution, but if you are using esnext you could use Array.from as
const children = Array.from(frag.children);
element.appendChild(frag);
return children;
If you have single main element in your document fragment, you can use .firstElementChild and that's all. To make multiple copies use .cloneNode(true).
Example - https://codepen.io/SergeiMinaev/pen/abLzQjZ
Thanks to #The Sloth's comment, I found this solution, which finally solved it for me:
var node = element.lastElementChild
In my case, element.lastChild returned a text node instead of the appended node.