Why use cloneNode when I can clone a node using querySelector? - javascript

Below two function does same thing - first they copy an element then clear the body and then add the element back. My question is which one is better and in what conditions cloneNode using function or the other one will not work ?
Using querySelector
function noahArc(animal) {
// Preserve the poor animal and its children
var arc = document.querySelector(animal);
// Flood the entire body with rage, water and thunder
document.body.innerHTML = "";
// Restore the preserved animal into the post-apocalyptic world
document.body.appendChild(arc);
}
noahArc('.asd');
Using cloneNode
function noahArc(animal) {
// Preserve the poor animal and its children
var arc = document.getElementsByClassName(animal)[0].cloneNode(true);
// Flood the entire body with rage, water and thunder
document.body.innerHTML = "";
// Restore the preserved animal into the post-apocalyptic world
document.body.appendChild(arc);
}
noahArc('asd');

First, for the avoidance of doubt: querySelector does not clone elements. It just gives you a reference to the element that already exists. In a non-buggy browser, that reference remains valid even if you wipe the conents of body after you get the reference. It just means the element isn't in the document anymore.
Second, your edit and various comments suggesting there's a difference in your code depending on whether you use querySelector or getElementsByClassName to select the element are incorrect. In the code shown, it makes no difference whatsoever. The only difference of consequence in your examples is whether you clone the node.
So, looking at the two examples:
Your first example will fail in some versions of IE because of a bug it has where it wipes the contents of the descendant element when you assign to the innerHTML of its ancestor even when you have a reference to the descendant element. It shouldn't fail, but it will. (This bug cost me hours of debugging time sometime last year...) I don't think Edge has this bug. I've just verified it's still an issue with IE11 using this test:
function noahArc(animal) {
var arc = document.querySelector(animal);
document.body.innerHTML = "";
document.body.appendChild(arc);
}
noahArc('.asd');
console.log("Done, you should still see 'this is the div' above, but you won't in IE");
<div class="asd">this is the div</div>
Other than that, neither is "better." It depends on what you want to do.
Your first example attempts to keep the same element in the document (which may have event handlers attached to it). It doesn't make a copy, and only works (on browsers where it works) because the original is removed from the document by assigning to its ancestor's innerHTML.
Your second example creates a copy of the element (which won't have event handlers on it).
Which you use depends on what you want to achieve, and what browsers you want to support.

Related

Why can't I use element.remove() on an element created with createElement() if I do something in between in the DOM? [duplicate]

When creating elements via code, I have encountered an issue where modifying the innerHTML property of an element breaks any references to other elements that are injected into the modified element prior to the modification.
I have a test case here: http://jsfiddle.net/mJ7bF/1/ in which I would expect the link1 reference to behave exactly as link2 does.
This second test case is the same code, but instead of using the innerHTML property to add the <br> tag, I create the line break with an object. This test behaves as expected: http://jsfiddle.net/K4c9a/2/
My question is not regarding this specific code, but the concept behind it: what happens to the link1 reference in that first test case? If it doesn't refer to the HTML/DOM node that is visible when the cont node is injected into the document, what DOES it refer to, and how does this fit in with the ByReference nature of javascript objects?
few things here.
first of all. strings are immutable hence doing element.innerHTML += "<br>" acts as a complete read and rewrite.
second, why that is bad:
aside from performance, mootools (and jquery, for that matter) assigns special unique sequential uids to all referenced elements. you reference an element by calling a selector on it or creating it etc.
then consider that SPECIFIC element with uid say 5. the uid is linked to a special object called Storage that sits behind a closure (so its private). it has the uid as key.
element storage then works on a element.store("key", value") and element.retrieve("key")
and finally, why that matters: events are stored into element storage (eg, Storage[5]['events']) - do element.retrieve("events") and explore that in fireBug if you're curious.
when you rewrite the innerHTML the old element stops existing. it is then recreated but the event handler AND the reference to the function that you bound earlier will no longer work as it will now get a NEW uid.
that's about it, hope it makes sense.
to add a br just do new Element("br").inject(element) instead or create a templated fragment for the lot (fastest) and add in 1 big chunk, adding events after.
HTML is represented internally by a DOM object structure. Kind of like a Tree class in traditional programming languages. If you set innerHTML, the previous nodes in the parent node are destroyed, the new innerHTML is parsed, and new objects are created. The references are no longer the same.
div
|-- a..
The div object above contains an Anchor object as a child. Now set a variable link1 as a reference to the address of this Anchor object. Then the .innerHTML is += "<br />", which means all of the nodes of div are removed, and recreated dynamically based on the parsed result of the new value of .innerHTML. Now the old reference is no longer valid because the Anchor tag was re-created as a new object instance.

What is the difference between getElementById and simply using an element's ID as a variable?

Can someone tell me the difference between calling an HTML elment with id="myDomObect"?:
var myObj = document.getElementById('myDomObect');
&
var myObj = myDomObect;
Use the first form or a wrapper such as jQuery. The second form,
var myObj = myDomObect;
Translates to
var myObj = window["myDomObect"];
This "works" because of an old, old hack in which ID's were exposed as global window properties (IIRC this was a misfeature from the start) and thus we are still blessed with the behavior 20 years later.. and yes, it will work in the very latest Chrome.
However, such a shorthand should not be used for multiple reasons:
It will not work as originally written in "strict mode" (but it will work with the second form)
It does not convey the operation - namely that a DOM element is requested/fetched (by ID).
It does not work for IDs that collide with window properties; eg. <div id=history></div> would result in "unexpected behavior" if accessed this way. (This doesn't affect getElementById code that correctly uses local var variables in a function.)
Behavior is not defined when duplicate IDs exist in the document (which is allowed); the behavior for getElementById has been codified in by DOM 4: "getElementById(elementId) method must return the first element [with the ID], in tree order.."
See also:
Do DOM tree elements with ids become global variables?
The first is how the "real" DOM API works (another option is document.querySelector("#myDomObject")). The second is how browsers have started implementing automatic hoisting of id'd elements, since ids are supposed to be unique. In a twist of "what were you thinking", this can lead to hilarious conflicts where variables that have the same name as HTML elements with an id don't take precedence and the variable you though you were using is suddenly an HTML element.

setting innerHTML property breaks references to child elements

The issue is similar to the one described here, as far as I can tell. However, I am not using mootools and my question and results are different.
This is a test page to demonstrate the issue: http://jsfiddle.net/S7rtU/2/.
I add elements to a container using createElement and appendChild. As I add each, I also store a reference to it in a private array (elems).
Then, I decide to clear the container, and do so by setting container.innerHTML = ''.
(In my example, I set it first to a pending message, and then 3s later, using setTimeout, I clear it. This is to give time to observe the changes to the DOM.)
Then, I try to repopulate the container from the elems array, calling container.appendChild for each of the stored elements.
I have tested on the browsers I have at hand: Firefox 17.0.1, Chrome 23.0.1271.97, Safari 3.1.2, IE 6 and IE 7. All except IE 6 & 7 will actually restore those elements. So they are successfully stored in memory and references not destroyed. Furthermore, the event handlers registered to those elements still work.
In IE, the elements do not reappear.
What I have read about this issue, including the other SO question, seem to suggest that references are supposed to be broken when you modify innerHTML on the container. Event handlers are also supposed to be broken. However the 3 modern browsers I tested do not break the references, nor the event handlers.
Of course, to make this work in IE I can use something like this, but this is significant extra processing if there are lots of elements:
function explicitClearContainer() {
var e;
// Explicitly remove all elements
for (var i = 0; i < elems.length; ++i) {
e = elems[i];
// Update the reference to the removed Node
elems[i] = e.parentNode.removeChild(e);
}
}
My question is what is known about this behaviour, what can be expected in different environments, what are the pitfalls of using this sort of technique?
I would appreciate any comments.
The innerHTML property was only "standardised" in HTML5, which really just documents common browser behaviour for many features. Things such as innerHTML have been implemented differently in different browsers and will continue to be different for some time, so best to avoid using it if it's causing problems.
There are other approaches to clearing the child nodes of an element, e.g.:
function removeContent(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
Which should avoid your issues with innerHTML and is a bit less code than your version. You can also replace element with a shallow clone of itself, but that may cause other issues.

Element reference breaks on modification of innerHTML property of container

When creating elements via code, I have encountered an issue where modifying the innerHTML property of an element breaks any references to other elements that are injected into the modified element prior to the modification.
I have a test case here: http://jsfiddle.net/mJ7bF/1/ in which I would expect the link1 reference to behave exactly as link2 does.
This second test case is the same code, but instead of using the innerHTML property to add the <br> tag, I create the line break with an object. This test behaves as expected: http://jsfiddle.net/K4c9a/2/
My question is not regarding this specific code, but the concept behind it: what happens to the link1 reference in that first test case? If it doesn't refer to the HTML/DOM node that is visible when the cont node is injected into the document, what DOES it refer to, and how does this fit in with the ByReference nature of javascript objects?
few things here.
first of all. strings are immutable hence doing element.innerHTML += "<br>" acts as a complete read and rewrite.
second, why that is bad:
aside from performance, mootools (and jquery, for that matter) assigns special unique sequential uids to all referenced elements. you reference an element by calling a selector on it or creating it etc.
then consider that SPECIFIC element with uid say 5. the uid is linked to a special object called Storage that sits behind a closure (so its private). it has the uid as key.
element storage then works on a element.store("key", value") and element.retrieve("key")
and finally, why that matters: events are stored into element storage (eg, Storage[5]['events']) - do element.retrieve("events") and explore that in fireBug if you're curious.
when you rewrite the innerHTML the old element stops existing. it is then recreated but the event handler AND the reference to the function that you bound earlier will no longer work as it will now get a NEW uid.
that's about it, hope it makes sense.
to add a br just do new Element("br").inject(element) instead or create a templated fragment for the lot (fastest) and add in 1 big chunk, adding events after.
HTML is represented internally by a DOM object structure. Kind of like a Tree class in traditional programming languages. If you set innerHTML, the previous nodes in the parent node are destroyed, the new innerHTML is parsed, and new objects are created. The references are no longer the same.
div
|-- a..
The div object above contains an Anchor object as a child. Now set a variable link1 as a reference to the address of this Anchor object. Then the .innerHTML is += "<br />", which means all of the nodes of div are removed, and recreated dynamically based on the parsed result of the new value of .innerHTML. Now the old reference is no longer valid because the Anchor tag was re-created as a new object instance.

Are an element's dimensions readable immediately upon adding it to the DOM tree?

Suppose I dynamically create a DIV using document.createElement, add some inner text, and want to determine its height and width. I can see that before adding the DIV to the DOM tree, properties such as offsetWidth and offsetHeight are 0, which is expected. Once it is programmatically added to the tree, can I expect those properties to be immediately "ready", or are there environments where repaint/reflow may be delayed and occur later?
textDiv = document.createElement("div");
textDiv.innerHTML = "some dynamically created string";
console.log(textDiv.offsetWidth); // 0
document.body.appendChild(textDiv);
console.log(textDiv.offsetWidth); // OK?
I think the answer would be something like "the JavaScript interpreter is single-threaded and the repaint/reflow event triggered by appendChild is part of that thread and finishes before continuing on to the next statement"... but can someone check my understanding?
This Opera article has a lot of what I'm looking for but it's not clear how Opera-specific it is. In any case it does seem like the application developer can assume any required repaint/reflow correctly occurs when taking measurements such as by accessing offsetWidth.
As soon as your element is added to the DOM, it should be available for access as if it were there from the beginning. Plain, simple DOM manipulation is synchronous.
Be careful when you start introducing timeouts, intervals, and Ajax.
As far as i know, they are readable only after they are added to DOM tree after all javascript functions or properties work on elements that are already there in the DOM tree.
I tried both cases and following turned out to be working fine:
<SCRIPT>
window.onload = function() {
textDiv = document.createElement("div");
textDiv.innerHTML = "some dynamically created string";
alert(textDiv.offsetWidth); // 0
document.body.appendChild(textDiv);
alert(textDiv.offsetWidth); // OK?
};

Categories