4

This question is essentially what I'm asking but with the restriction of not removing events for the descendant nodes.

In this fiddle I attempt to remove attached listeners by removing it from the DOM and placing it back.

function removeListeners(el) {
    var par = el.parentNode;
    var place = el.nextSibling;
    par.removeChild(el);
    par.insertBefore(el, place);
}

Unfortunately this didn't work, you can still click to get the background to change (which is actually good, I never knew you could attach events without the element being in the DOM).

Given that discovery I tried this

function removeListeners(el) {
    var par = el.parentNode;
    var clone = el.cloneNode(false);
    par.replaceChild(clone, el);

    for (var index = 0; index < el.childNodes.length; ++index)
        clone.appendChild(el.childNodes[index]);
}

Which tries to do a shallow clone then copy all the decendants back in, but it doesn't copy all the children.

The aforementioned question's answer said that "[if you want to have the children keep their listeners] you'll have to resort to explicitly removing listeners one at a time". I don't know how you would implement that.
I assume he means getting a list of the event types and removing the listeners for each in turn (like jQuery can). Not only would that be impossible for custom events but vanilla JS has no such functionality (you must specify the function to remove).

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Hashbrown
  • 12,091
  • 8
  • 72
  • 95

2 Answers2

5

Node.childNodes is not an Array, but a NodeList (see NodeList on MDN). It is updated dynamically when the DOM is updated.

In your case you were emptying the childNodes collection as you were moving the elements to a new parentNode.

You can either copy the childNodes into an array using Array.prototype.slice or iterate backwards.

function removeListeners(el) {
    var par = el.parentNode;
    var clone = el.cloneNode(false);
    par.replaceChild(clone, el);

    for (var index = el.childNodes.length - 1; index >= 0; --index)
        clone.insertBefore(el.childNodes[index], clone.childNodes[0]);
}
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • that makes sense. So it would've worked if I iterated backwards? [I tried it](http://jsfiddle.net/hq8vx/2/), youre right, upvoted – Hashbrown Sep 06 '13 at 06:01
  • You could have iterated backwards, but then it would have been a pain to insert the elements in the right order in your new clone. Copying the childNodes to an Array as you did here is easier to understand. – Julian Descottes Sep 06 '13 at 06:08
  • oh, and by bug I meant in my code (not of the browser, obviously). In which case it was definitely a bug – Hashbrown Sep 06 '13 at 06:42
  • Ok, my mistake. Added your implementation to my answer. – Julian Descottes Sep 06 '13 at 07:11
0

I managed to get my second attempt working.
And then, thanks to @JulianDescottes noting of my oversight, I've made this more concise version.

Here's the working code.

function removeListeners(el) {
    //only replace the ancestor element
    var clone = el.cloneNode(false);

    //copy children backwards because of the removal
    for (var index = el.childNodes.length - 1; index >= 0; --index)
        clone.insertBefore(el.childNodes[index], clone.firstChild);

    //insert back into DOM
    el.parentNode.replaceChild(clone, el);
}

I was hoping for something more native, I fear there are unforeseen consequences using this method, I don't like removing things from the DOM and expecting them to work properly after.
At least it is more efficient than current suggestions, and if anyone comes up with something nicer I would be obliged (and thankful) to accept your answer.

Hashbrown
  • 12,091
  • 8
  • 72
  • 95