4

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?

Stephen Watkins
  • 25,047
  • 15
  • 66
  • 100
  • Looks to me as a garbage collector problem, and IE's garbage collector seems effective than other browsers'. Let's see if I can elaborate.. – Fabrício Matté Aug 25 '12 at 04:35

2 Answers2

7

First let's highlight the important parts:

  1. (first click) Takes the source element and put it inside the target element;
  2. (second click) Empties the target element and appends a new child (p.dummy) to it, effectively removing source from the DOM;
  3. 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:

enter image description here

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.

Community
  • 1
  • 1
Fabrício Matté
  • 69,329
  • 26
  • 129
  • 166
  • Had to get up and fix this as I realized the issue was not JScript's GC but rather its `innerHTML`. If anyone has the ECMAScript or JScript implementation spec to add, feel free to. – Fabrício Matté Aug 25 '12 at 06:49
  • +1 Very thorough investigation to answer the question. I also first thought it was a Garbage Collection issue; but, it makes sense that it would be an innerHTML quirk with IE seeing that other element properties still exist. – Stephen Watkins Aug 25 '12 at 17:58
  • 1
    @StephenWatkins Yes, I also did some more testing before stating that - I stored the `.contents()` of the element (which includes `textNode`s) to make sure that it wasn't an issue with IE's GC -- the `textNode`s were still cached but got "emptied" with the `innerHTML`. – Fabrício Matté Aug 25 '12 at 18:05
2

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);
Richard Rout
  • 1,296
  • 1
  • 15
  • 28
  • This could be clarified: `targetEl.html('

    Transferring...

    ');` *on the second invocation*. Of course, it could just be I was being a little brain dead earlier.
    – JayC Aug 25 '12 at 05:19
  • But then it wouldn't say "Transferring" during the process on the first click? – Richard Rout Aug 25 '12 at 05:23
  • Actually, even my suggestion to clarify is wrong... First invocation doesn't do anything to any item with class ".source" because you haven't touched any such item yet. *Any invocation after the timer has transpired* would remove the sourceEl because then it's actually in targetEl's innerHTML (like Fabrico said). – JayC Aug 25 '12 at 05:33
  • (Fabrico mentions the second click... which would normally be the second invocation, unless you're click happy). – JayC Aug 25 '12 at 05:36
  • rofl @JayC, yes, the second invocation. That's when the `source` element is inside the element which gets the `innerHTML` overwritten. – Fabrício Matté Aug 25 '12 at 05:38
  • Just made an edit to fix for trigger happy people. Not the most ideal solution, but at least it works for this scenario. – Richard Rout Aug 25 '12 at 05:50
  • -1 I appreciate the answer. However, as @FabrícioMatté points out in his answer, this is actually *not* IE behaving correctly by garbage collecting the element's reference (I first thought this as well). Instead, it's a "quirk" with IE's handling of innerHTML. See his answer for more detail. – Stephen Watkins Aug 25 '12 at 18:05