Laurent T
Laurent T

Reputation: 1098

Dynamically remove elements in JavaScript

I'm using plain old JavaScript (no jQuery or other).

I have an HTML5 app where I want to delete canvas from a section element:

<section id="thumbnails">
    <h1>My Bookmarks:</h1>
    <canvas height="60px" width="100px" data-time="2.1167500019073486"></canvas>
    <canvas height="60px" width="100px" data-time="3.60492205619812"></canvas>
    <canvas height="60px" width="100px" data-time="4.558893203735352"></canvas>
    <canvas height="60px" width="100px" data-time="5.411310195922852"></canvas>
</section>

The code that I use is the following:

document.getElementById('videos').addEventListener('change',function(e){
    var canvasElements=document.getElementById('thumbnails').getElementsByTagName('canvas');
    for(var i=0;i<canvasElements.length;i++){
        document.getElementById('thumbnails').removeChild(canvasElements[i]);
        console.log('removed canvas');
    }
},true);

However when the code runs, not all canvas are removed from that section and I don't even see the log statement in the console.

Go on this page, take some thumbnails, then switch to another video: http://laurent-tonon.com/VideoFinal/

I really don't get it...

Upvotes: 0

Views: 9949

Answers (4)

jfriend00
jfriend00

Reputation: 707158

The problem is that getElementsByTagName returns a dynamic nodeList that will change beneath you when you remove the items. You can work around that by processing the array in reverse order so the only part of the nodeList that changes when you remove an item is the part you've already processed, not the part you still have to go:

document.getElementById('videos').addEventListener('change',function(e){
    var canvasElements=document.getElementById('thumbnails').getElementsByTagName('canvas');
    for (var i = canvasElements.length - 1; i >= 0; i--) {
        canvasElements[i].parentNode.removeChild(canvasElements[i]);
        console.log('removed canvas');
    }
},true);

Processing it in forward order like you did causes you to remove every other item and leaves half the items left. You remove the first item. It gets removed from the nodeList moving every item in the nodeList down one slot. You then advance your index to [1] and you remove what was originally the third item (but is now in the 2nd slot in the array). The item that was originally the second item is now in the first slot in the array and you will never end up removing it.

Reversing the order you process the nodeList works around that problem since the ones that get removed from the array are at the end and don't change the position of the ones you still have to process.


I know this wasn't a jQuery question and is perfectly solvable with the above plain javascript, but it's times like this where I'm reminded how easy some things are in jQuery:

$("#thumbnails canvas").remove();

Upvotes: 7

Esailija
Esailija

Reputation: 140210

Since you are already relying on html5 capable browser, why not use some updated methods:

[].slice.call( document.querySelectorAll( "#thumbnails canvas" ) ).forEach(
    function(v){
    v.parentNode.removeChild(v);
    }
);

Upvotes: 3

vzwick
vzwick

Reputation: 11044

Why so complicated? How about either

document.getElementById('videos').addEventListener('change',function(e){
    var canvasElements=document.getElementById('thumbnails');
    while (canvasElements.hasChildNodes())
    {
        canvasElements.removeChild(canvasElements.lastChild);
    }
});

or, even more simple

document.getElementById('videos').addEventListener('change',function(e){
    document.getElementById('thumbnails').innerHTML = '';
});

?

Upvotes: 1

alex
alex

Reputation: 490153

I believe the problem is getElementsByTagName() returns a live nodeList, which causes problems when you are removing the elements and referring to them by their index.

Try this...

while(canvasElements.length) {
    canvasElements[0].parentNode.removeChild(canvasElements[0]);
    console.log('removed canvas');
}

jsFiddle.

Upvotes: 2

Related Questions