2

I try to understand how to remove all children elements without specified attribute for example:

This is the html:

<div class="container">
  <span></span>
  <div></div>
  <strong data-*></strong>
  <p></p>
  <div data-*></div>
</div>

What i want is to remove all element inside the container and without data-* attribute.

data-*

The attribute can be data-anything (data-color, data-border, etc).

I'm looking javascript only solution.

Filburt
  • 17,626
  • 12
  • 64
  • 115
Aviel Fedida
  • 4,004
  • 9
  • 54
  • 88
  • If you're using jQuery, now would be the time to speak up ? – adeneo Mar 06 '14 at 20:32
  • Can you show your real HTML? – Brad Mar 06 '14 at 20:34
  • 2
    I'm not using jQuery for this project. – Aviel Fedida Mar 06 '14 at 20:35
  • If you were using jQuery, this would have been the solution: http://stackoverflow.com/questions/12199008/find-html-based-on-partial-attribute Just posting it in case someone finds it in a search. – epascarello Mar 06 '14 at 20:36
  • It's possible, but it would be extremely slow. (Check all elements on page, load attributes into an array, search the array for `data-`, and finally remove the element if there's no match) – Mooseman Mar 06 '14 at 20:36

5 Answers5

4

Using .filter(), .some() and .forEach() should do the trick.

var els = document.querySelectorAll(".container *");

[].filter.call(els, function(el) {
    return ![].some.call(el.attributes, function(attr) {
        return /^data/i.test(attr.name);
    });
}).forEach(function(el) {
    el.parentNode.removeChild(el);
});

You'll need patches for old browsers, but you probably knew that already.


If like me, you like reusable functions:

var els = document.querySelectorAll(".container *");

[].filter.call(els, els_with_no_data)
  .forEach(remove_nodes);

function remove_nodes(el) {
    el.parentNode.removeChild(el);
}

function has_data_attrs(attr) {
    return /^data/i.test(attr.name);
}

function els_with_no_data(el) {
    return ![].some.call(el.attributes, has_data_attrs)
}

And then using Array generics (in supported browsers, otherwise polyfiilled):

var els = document.querySelectorAll(".container *");

Array.filter(els, els_with_no_data)
     .forEach(remove_nodes);

function remove_nodes(el) {
    el.parentNode.removeChild(el);
}

function has_data_attrs(attr) {
    return /^data/i.test(attr.name);
}

function els_with_no_data(el) {
    return !Array.some(el.attributes, has_data_attrs)
}
cookie monster
  • 10,671
  • 4
  • 31
  • 45
  • 2
    `>_<` That's exactly what I wanted to write. – kirilloid Mar 06 '14 at 20:41
  • Gotcha! You need to reverse the collection. Otherwise you may have `null` accessing `el.parentNode`, since `qSA` place parents first. – kirilloid Mar 06 '14 at 20:45
  • @kirilloid: Hmmm... I don't understand. Even if the parent is removed first, there shouldn't be anything preventing the child from being removed as well (though it would be unnecessary). Or am I missing something? – cookie monster Mar 06 '14 at 20:47
  • Hmmm. Maybe, I overthought it. – kirilloid Mar 06 '14 at 20:48
  • @kirilloid: Still, it could be a worthwhile optimization to have nodes check to see if they still descend from the `.container` before checking their attributes and removing them. Once their ancestor is removed, there's not much point in removing it. – cookie monster Mar 06 '14 at 20:49
  • 1
    Yes, I was wrong. Detached DOM nodes do not become `null`s automatically for their children. For the sake of optimization reordering may be interesting. Actually you'll need to walk nodes "by yourself" then. – kirilloid Mar 06 '14 at 20:51
  • Well, someone didn't like this answer. Not enough jQuery? Or did I miss something in my implementation? – cookie monster Mar 06 '14 at 20:53
  • 1
    @kirilloid: If we had an outer loop, or if there was only one `container`, we could do `if (container.compareDocumentPosition(el) & 16) {` – cookie monster Mar 06 '14 at 20:55
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/49181/discussion-between-kirilloid-and-cookie-monster) – kirilloid Mar 06 '14 at 20:59
  • @cookiemonster Thank you i think your solution is the most descriptive and flexible. – Aviel Fedida Mar 06 '14 at 22:03
2

This should do it

var parent = document.querySelector('.container');
var elems  = parent.querySelectorAll('*');

for (var i=elems.length; i--;) {
    var attr    = elems[i].attributes,
        hasData = false;

    for (var j = attr.length; j--;) {
        if ( attr[j].name.indexOf('data') === 0 ) {
            hasData = true;
            break;
        }
    }

    if ( ! hasData ) parent.removeChild(elems[i]);
}

FIDDLE

querySelector(All) will have to be polyfilled if you need to support IE7 and below.

adeneo
  • 312,895
  • 29
  • 395
  • 388
2

There is probbaly better ways, just had a few minutes to waste so thought I would play with the answer.

var container = document.getElementsByClassName("container")[0];
var children = container.childNodes;

for (var i=children.length-1;i--) {

    var child = children[i];    
    if (child.nodeType===1) {  //make sure it is an element, not a text node
        var hasMatch = false;
        var attrs = child.attributes;  //get the attributes of the element
        for(var j=0; j<attrs.length;j++){  //loop through
            if(attrs[j].nodeName.indexOf("data-")===0) {  //if it starts we have a match
               hasMatch = true;
               break;
            }
        }
        if (!hasMatch) {
            container.removeChild(child);  //if no match, remove it
        }
    }
}
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • 1
    +1 I'll upvote that, it's basically the same I came up with, and not all the overly clever stupidness of the other answers. – adeneo Mar 06 '14 at 21:23
2

Reworked solution of @cookie monster:

(function r (nodelist) {
    // reduceRight is used only to walk in reverse direction
    [].reduceRight.call(nodelist, function (dummy, e) {
        if ([].every.call(e.attributes, function(a) {
            return !/^data/i.test(a.name);
        })) return e.parentNode.removeChild(e);
        r(e.children);
    }, "filler");
}(document.getElementsByClassName("container")[0].children));
kirilloid
  • 14,011
  • 6
  • 38
  • 52
1

Try this:

var objects = document.querySelectorAll('.container *');
var parent = document.getElementsByClassName('container')[0];

for (var i = objects.length - 1; i >= 0; i--) {
    if (objects[i].attributes[0]) {
        if (objects[i].attributes[0].name.indexOf('data-') == -1) {
            parent.removeChild(objects[i]);
        }
    } else {
        parent.removeChild(objects[i]);
    }
}

Demo

Rahil Wazir
  • 10,007
  • 11
  • 42
  • 64