Why is element empty in IE after being removed from the DOM? - javascript

The following HTML and JavaScript is taken from parts of this jsFiddle:
http://jsfiddle.net/stephenjwatkins/2j3ZB/3/
HTML:
<p class="source">
Source
</p>
<div id="target">
<p class="dummy">
Target
</p>
</div>
<button id="transfer-button">Transfer</button>
JavaScript:
var sourceEl = $('.source');
var targetEl = $('#target');
$('#transfer-button').click(function() {
targetEl.html('<p class="dummy">Transferring...</p>');
setTimeout(function() {
// Source element will be empty on second attempt to append
targetEl.html('').append(sourceEl);
}, 750);
return false;
});​
Note that the setTimeout and dummy text is there simply for a visual indicator.
As one can see, after the source element is appended and removed once from the DOM, IE (all versions) will add an empty element to the DOM upon any further appends; whereas, all other browsers will add the correct, non-empty element.
Another aspect that adds to the confusion is that the sourceEl still has element information (e.g. sourceEl.attr('class') will return "source").
I know of methods to mitigate the issue (e.g. sourceEl.clone()), but it would be nice to have a better understanding as to why IE is behaving differently to avoid any related problems in the future.
What is causing the source element to be uniquely empty in IE after once replacing the element?

First let's highlight the important parts:
(first click) Takes the source element and put it inside the target element;
(second click) Empties the target element and appends a new child (p.dummy) to it, effectively removing source from the DOM;
Empties the target element and tries to re-append source, which is no longer present in the DOM.
At first look, this wouldn't work in any browser as the source element has already been removed from the DOM. The "magic" here is JavaScript's Garbage Collector. Browsers see that sourceEl is still scoped (inside the setTimeout closure) and do not trash the referenced DOM element inside of the sourceEl jQuery object.
The issue here is not JScript (Microsft's Javascript implementation)'s Garbage Collector, but rather how JScript handles the DOM parsing when setting an element's innerHTML.
Other browsers will simply detach all childNodes (whose will be collected by GC when there are no more active references) and parse the passed html string into DOM elements appending them to the DOM. Jscript, on the other hard, will also erase the detached childNodes' innerHTML/childNodes. Check this fiddle for an illustration.
The element, in fact, does still exist in IE and is appended to the DOM:
It just has no childNodes anymore.
To prevent this kind of behavior, .clone() the element (as mentioned in the question) or .detach() it before calling .html() on its parent, if you intend to re-use the element instead of "overwriting" it.
Here's a fiddle using .detach() before the element is overwritten, works fine in all browsers.

In my mind, IE is behaving correctly, and the other browsers are magical for working.
It all stems from when you call the line:
targetEl.html('<p class="dummy">Transferring...</p>');
This removes the sourceEl element from the page. So it no longer exists. I guess other browsers are remembering the DOM object as there is still a variable referencing it. But IE recognizes this as no longer existing on the page, so it loses the reference.
As you mentioned, I would recommend cloning the object when you click. This creates a new object in JavaScript. Luckily, overwriting same variable works.
sourceEl = sourceEl.clone();
http://jsfiddle.net/AbJQE/3/
edit You can also remove any possibly existing original source objects before you insert this new one. This fixes the problem for trigger happy clickers:
setTimeout(function() {
$('.source').remove();
targetEl.html('').append(sourceEl);
}, 750);

Related

Permanent slowdown after adding and removing HTML elements: What causes it and how to fix it?

I am making a project in Javascript that is mostly on run on a canvas, but also involves dynamically creating a lot of menus in the DOM. I am not using JQuery, instead I am using a custom function that creates an element using document.createElement and then adds it to a parent with appendChild. To remove elements, I use element.parentNode.removeChild(element). Elements created can be of any type; inputs, images, and so on.
The problem is that after creating and removing elements a lot of times, the page starts to slow down significantly, and this worsens steadily the more elements are created and removed, even though there is no point where an especially large number of elements exist at once. The Javascript does not slow down, though; the main update loop is fine, but mouse events and everything related to manipulating the DOM becomes slow until the page is reloaded.
I have experienced similar issues before, but have generally ignored them because they did not involve creating and removing large numbers of elements to the same degree as this one.
The only guess I have for a cause is that elements created by document.createElement continue to exist in memory even if their reference is cleared and they are removed from the visible part of the DOM. Or perhaps removing a parent element does not properly remove all of its children, even though they seem to be gone.
My question is: Are created elements retained by the DOM even when they are not visible and no JS variable points to them, and can this be the cause of slowdown? If so, how do I destroy a DOM element properly?
As it is described here:
The removed child node still exists in memory, but is no longer part
of the DOM. With the first syntax-form shown, you may reuse the
removed node later in your code, via the oldChild object reference.
In the second syntax-form however, there is no oldChild reference
kept, so assuming your code has not kept any other reference to the
node elsewhere, it will immediately become unusable and irretrievable,
and will usually be automatically deleted from memory after a short
time.
And you can try something described as here:
var garbageBin;
window.onload = function ()
{
if (typeof(garbageBin) === 'undefined')
{
//Here we are creating a 'garbage bin' object to temporarily
//store elements that are to be discarded
garbageBin = document.createElement('div');
garbageBin.style.display = 'none'; //Make sure it is not displayed
document.body.appendChild(garbageBin);
}
function discardElement(element)
{
//The way this works is due to the phenomenon whereby child nodes
//of an object with it's innerHTML emptied are removed from memory
//Move the element to the garbage bin element
garbageBin.appendChild(element);
//Empty the garbage bin
garbageBin.innerHTML = "";
}
}
Where you can delete your dom element like:
discardElement(yourDomElement);

Express item.parentElement.remove() in a way that is supported by IE

From what I understand, the
remove()
function is not supported in IE. I have a JS function that creates a div and appends it to an existing list.The div contains another div styled as a button (this is the 'item' in the title, that's what I called it when I got it from HTML), which, on click, removes its parentNode (and consequently itself) from the DOM (by means of remove()), though it still 'exists' in that JavaScript can read it's data and stuff. I need a way to remove it from the DOM, as well as all of it's child elements. Setting it's innerHTML equal to nothing will not work, nor will setting it's display to none. Any idea how to do this in a way compatible with IE?
Any help appreciated, please no jQuery or other frameworks.
Anytime you would use .remove(), you can always just use .removeChild() instead with slightly different code around it.
So, if you wanted to do to remove the parent of a given node:
item.parentElement.remove();
Then, you can instead do this:
var p = item.parentNode;
p.parentNode.removeChild(p);
If you want to put this in a utility function, you can do this:
function removeNode(node) {
node.parentNode.removechild(node);
}
And, in your case, instead of item.parentElement.remove() you would call it like this:
removeNode(item.parentNode);
Note, I'm using parentNode instead of parentElement since you appear to want IE compatibility with older versions of IE. parentNode and parentElement are not exactly the same, but it's very rare where parentNode would be different than parentElement (in fact, I can't think of a case in a normal DOM where it wouldn't be appropriate to use parentNode). See Difference between DOM parentNode and parentElement for a discussion of the differences.

'document' vs. 'content.document'

I'm trying to write a Firefox extension that adds elements to the loaded page. So far, I get the root element of the document via
var domBody = content.document.getElementsByTagName("BODY").item(0);
and create the new elements via
var newDiv = content.document.createElement("div");
and everything worked quite well, actually. But the problems came when I added a button with on onclick attribute. While the button is correctly displayed, I get an error. I already asked asked here, and the answer with document.createElement() (without content) works.
But if I remove the 'content.' everywhere, the real trouble starts. Firstly, domBody is null/undefined, no matter how I try to access it, e.g. document.body (And actually I add all elements _after_the document is fully loaded. At least I think so). And secondly, all other elements look differently. It's seem the style information, e.g., element.style.width="300px" are no longer considered.
In short, with 'content.document' everything looks good, but the button.onclick throws an error. with only 'document' the button works, but the elements are no longer correctly displayed. Does anybody see a solution for that.
It should work fine if you use addEventListener [MDN] (at least this is what I used). I read somewhere (I will search for it) that you cannot attach event listener via properties when creating elements in chrome code.
You still should use content.document.createElement though:
Page = function(...) {
...
};
Page.prototype = {
...
addButton : function() {
var b = content.document.createElement('button');
b.addEventListener('click', function() {
alert('OnClick');
}, false);
},
...
};
I would store a reference to content.document somewhere btw.
The existing answer doesn't have a real explanation and there are too many comments already, so I'll add another answer. When you access the content document then you are not accessing it directly - for security reasons you access it through a wrapper that exposes only actual DOM methods/properties and hides anything that the page's JavaScript might have added. This has the side-effect that properties like onclick won't work (this is actually the first point in the list of limitations of XPCNativeWrapper). You should use addEventListener instead. This has the additional advantage that more than one event listener can coexist, e.g. the web page won't remove your event listener by setting onclick itself.
Side-note: your script executes in the browser window, so document is the XUL document containing the browser's user interface. There is no <body> element because XUL documents don't have one. And adding a button won't affect the page in the selected tab, only mess up the browser's user interface. The global variable content refers to the window object of the currently selected tab so that's your entry point if you want to work with it.

Checking if node exists in Javascript

Here's the deal - I've got a load of elements on a page, and I'm using Javascript to remove some of them (this.parentNode.removeChild(this)) which is working great. However, if I have a variable referring to this node, then remove the node, the variable does NOT lose it's value! But if I then try and perform some other actions on this element, I get errors!
Example:
var element = document.getElementById('ooolookatmeimanelement');
element.parentNode.removeChild(element);
alert(element);
I still get "[Object HTMLblahblahblah]" in the alert, rather than null or undefined - anyone got any ideas how I can check to see if the node has been removed? It's probably something really simple that I'm oblivious to!
If you remove the node, remove the references too. E.g. in your code, assign null to element:
element = null;
Or if this is not possible, you can always check the value of parentNode. It should give null if the element is not part of the DOM:
if(element.parentNode === null) {
// element is not part of the DOM
}
But this does not make much sense to me (might depend on the context though): If you removed an element, then you know that you have removed it. Why would you do any further operations on it?
You can also use document.body.contains(element); or $.contains(body, element).
Checking for the parentNode is not reliable; if what is removed is an ancestor of the element, instead of the element itself, then parentNode will still return the parent, while the element is not in the DOM anymore.
var ancestor = document.querySelector('div');
var ghost = document.querySelector('br');
ancestor.parentNode.removeChild(ancestor);
console.log(ghost.id + ' son of ' + ghost.parentNode.id);
console.log(ghost.id + (document.body.contains(ghost) || ' dont') + ' exists in DOM');
<div id="ancestorToRemove">
<p id="a bitch">
<br id="you">
</p>
</div>
.removeChild() only removes the element from the DOM, and not from memory.
Hence it still exists until it's garbage collected. However you're holding a reference to it, so that won't happen.
Another point that may save time if you're needing to run JavaScript triggered by onClick which does something to itself, you can do this:
onClick="if(!document.getElementById(this.id))return false; YOUR CODE HERE"
For me, this came up when I had a DIV with several children, one of which was a "remove this div" button (actually a DIV made to look like a button). When clicked, this 'button' would remove the DIV from the DOM, as expected, but then would call the onClick event of the DIV itself (which no longer exists). This caused problems. By adding this to my DIV's onClick, I prevented running the code which refers to the DIV after its removal.

How to overwrite html element from javascript?

I have HTML page with some HTML element with ID="logo". I need to create JS script (with no external libs calls) that will overwrite that html element with other HTML element like "<div id=logo> stuff inside </div>".
Most of the time, it's just the content you want to replace, not the element itself. If you actually replace the element, you'll find that event handlers attached to it are no longer attached (because they were attached to the old one).
Replacing its content
Replacing the element's content is easy:
var element;
element = document.getElementById("logo");
if (element) {
element.innerHTML = "-new content-";
}
The innerHTML property has only recently been standardized, but is supported by all major browsers (probably most minor ones, too). (See notes below about innerHTML and alternatives.)
Replacing the element iself
Actually replacing the element itself is a little harder, but not much:
var element, newElement, parent;
// Get the original element
element = document.getElementById("logo");
// Assuming it exists...
if (element) {
// Get its parent
parent = element.parentNode;
// Create the new element
newElement = document.createElement('div');
// Set its ID and content
newElement.id = "logo";
newElement.innerHTML = "-new content here-";
// Insert the new one in front of the old one (this temporarily
// creates an invalid DOM tree [two elements with the same ID],
// but that's harmless because we're about to fix that).
parent.insertBefore(newElement, element);
// Remove the original
parent.removeChild(element);
}
Notes on innerHTML and other DOM manipulation techiques
There are a number of wrinkles around using innerHTML in certain browsers, mostly around tables and forms. If you can possibly use a library like jQuery, Prototype, etc., I'd do so, as they've got workarounds for those issues built-in.
Alternatively, you can use the various other DOM methods rather than innerHTML (the same ones I used for creating the div and adding/removing, above). Note that in most browsers, doing any significant amount of markup by doing a bunch of createElement, appendChild, etc., calls rather than using innerHTML will be dramatically slower. Parsing HTML into their internal structures and displaying it is fundamentally what browsers do, and so they're highly optimized to do that. When you go through the DOM interface, you're going through a layer built on top of their internal structures and not getting the advantage of their optimizations. Sometimes you have to do it that way, but mostly, innerHTML is your friend.
Do you really need to 'replace' the element or can you just toggle its visibility? This is a technique that's much simpler and will be more efficient. Most importantly it keeps the content (html) separated from the behavior (javascript).
function toggle() {
document.getElementById("logo").style.display="none";
document.getElementById("element_to_show").style.display="block";
}
see T.J.'s answer if you actually want to replace the element.
Do you really need to 'replace' the element or can you just toggle its visibility? This is a technique that's much simpler and will be more efficient. Most importantly it keeps the content (html) separated from the behavior (javascript).

Categories

Resources