1

I want to write a snippet of code that will traverse the document.body of a page and report all empty block level DOM nodes. This is a pass for SEO/a11y.

I found this article that will seemingly help:

https://css-tricks.com/snippets/jquery/check-for-empty-elements/

$('*').each(function() {
         if ($(this).is(':empty') == "") {
                   //Do Something
         }
});

Question #1: How would I convert .is(':empty') to vanilla js:

function isEmpty(el) {
  // Robust isEmpty logic here
}
document.querySelectorAll('*').filter(el => {
  return isEmpty(el);
});

Question #2: Will this logic traverse all children too?

Question #3: Could we make this querySelector for only blocklevel elements?

Armeen Moon
  • 18,061
  • 35
  • 120
  • 233
  • 1
    On SO, it's important to ask **one** question per question, not three. Question #3 above, in particular, is quite far afield from Question #1. – T.J. Crowder Mar 20 '19 at 16:29
  • 1
    @T.J.Crowder thanks for the semantic clarification on Javascript/Jquery over DOM I've updated the Question's Title. And Yes one question per question, sorry for over stepping the SO rules. Moreover, thanks for giving such a clear answer. Great job! – Armeen Moon Mar 20 '19 at 16:53

2 Answers2

9

The DOM version of "empty" is that there are no child nodes, which you can check in various ways, probably the two most common are:

  • firstChild will be null if there are no child nodes
  • The length of childNodes will be 0 if there are no child nodes

So for instance, since null is falsy and filter works with truthy/falsy values:

const allEmpty = [...document.querySelectorAll('*')].filter(el => !el.firstChild);

(Note that querySelectorAll returns a NodeList and NodeList doesn't have a filter method, so I used spread notation there to create an array from the NodeList [since NodeLists are iterable according to the spec now; but see this answer about ensuring that NodeList is iterable in your environment].)

Question #2: Will this logic traverse all children too?

querySelectorAll('*') selects every element in the document, many of which will be inside others also on the list.

Question #3: Could we make this querySelector for only blocklevel elements?

You can certainly tailor it according to the current specification and the default display browsers apply to them by doing a list of tag, such as querySelectorAll("div, p, h1"/*...*/). But that doesn't tell you what's a block element on that page, since CSS can change the display of elements. To get up-to-date information, you'd need to use getComputedStyle on each element after selecting it and check the display of the result.

Building a list of all of the elements on a page may not be ideal, you might consider a recursive function instead:

function findEmpty(element, results = []) {
    let child = element.firstChild;
    if (!child) {
        results.push(element);
    }
    while (child) {
        if (child.nodeType === Node.ELEMENT_NODE) {
            findEmpty(child, results);
        }
        child = child.nextSibling;
    }
    return results;
}
const allEmpty = findEmpty(document.body);

But this partially comes down to what you consider "empty." jQuery's :empty selector is quite literal: An element is only empty if it doesn't have any child nodes. But your definition may be more loose — for instance, perhaps you want to consider an element that contains only whitespace (directly or in descendant elements) "empty" for your purposes (in which case: !el.textContent.trim()). The first link above is to the DOM information on MDN which gives you lots to explore to determine your own "empty" if necessary.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

You can use children which is different from childNodes which can contain any node including text node. If using childNodes.length on div like <div>Some Text here</div> will be return a non empty array even though there is no child element or html tags

function isEmpty(el) {
  return el.children.length
}
[...document.querySelectorAll('div.parent')].filter(function(el) {
  let k = isEmpty(el);
  console.log(k)
});
<div class='parent'>
  <div>Child 1</div>
  <div>Child 1</div>
  <div>Child 1</div>
  <div>Child 1</div>
</div>

<div class='parent'></div>
brk
  • 48,835
  • 10
  • 56
  • 78