Question

I'm trying to figure out a selector for the following markup. I want all nodes, except the node which contains a blockquote. The <blockquote> will always be an immediate child of a <div> which will be an immediate child of the <body> tag.

Example HTML:

<ul>
    <!-- Lots of HTML -->
</ul>
<div>
    <p>
        <!-- Lots of other HTML -->
    </p>
</div>
<div>
    <blockquote>Some text I don't care about</blockquote>
</div>

Expected Result:

<ul><!-- Lots of HTML --></ul>
<div><p><!-- Lots of other HTML --></p></div>

Attempted Selector:

document.querySelectorAll("body > :not(div > blockquote)")

I tried the above, but I can't put an immediate child selector inside the :not() selector. Here is a fiddle of the failed attempt.

I am not currently and nor can I use jQuery.

Was it helpful?

Solution

You cannot do this natively. There is no way of filtering elements by their children in any current selector specification. (This may come in a future selectors specification, however.)

You will therefore need to do it by doing the filtering yourself. This isn't particularly difficult. One way would be by selecting all the relevant elements, then filtering out the ones you don't want:

Array.prototype.slice.call(document.querySelectorAll("body > *"))
    .filter(function(el) {
        return !el.querySelector(':scope > blockquote');
    })

(jsFiddle)

This turns the selection into an array, then uses the Array#filter method to get rid of all those that contain a blockquote element.

Obviously this has far worse performance than a native selector, but it is the only way to do what you want in current JS.

As BoltClock rightly points out, :scope is not yet an official standard. I was lulled into a false sense of security by the fact that Chrome does support it: other browsers as yet do not. If you want to exclude any top level element that contains a blockquote element at any level, the best way to do this is:

return !el.querySelector('blockquote');
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top