user4898587
user4898587

Reputation:

How to get elements that have at least one direct child text node

I'm trying to write a function that returns all elements with an immediate child text node -

function getAllElementsWithDirectTextNode() {
    var matchingElements = [];
    var allElements = document.body.getElementsByTagName('*');
    for (var i = 0, n = allElements.length; i < n; i++) {
         if (allElements[i].childNodes !== null) {
             matchingElements.push(allElements[i]);
         }
    }
    console.log(allElements);           
}

However, this is not working. I think it has to do with the 5th line (the if statement). If the element has no direct text node children but does have a direct element child that has its own child text, I do not want to include it.

EDIT: I tried Praveen Kumar's solution with the following DOM, and the console prints all the elements in the body. However, I was expecting to only see div.div1, span.span2, div.div3, and button. Am I doing something wrong?

<div class="div1">
   this is direct text
   <span class="span1"></span>
</div>
<div class="div2">
    <span class="span2">this is span text</span>
</div>
<article></article>
<div class="div3">
    <span class="span3"></span>
    this is direct text, child #2
</div>

Upvotes: 3

Views: 774

Answers (5)

Sebastian Simon
Sebastian Simon

Reputation: 19535

The answers provided so far are fine, but a bit dated. There are some modern ECMAScript and DOM features you can use nowadays:

const getAllElementsWithDirectTextNode = () => Array.from(document.body.querySelectorAll("*"))
  .filter(({ childNodes }) => Array.from(childNodes)
    .some(({ nodeType, textContent }) => nodeType === Node.TEXT_NODE && textContent.trim()))

The .trim check is there to test if removing white space from both ends of the text node still produces a non-empty string. Remove it if you also want to include white space only results:

const getAllElementsWithDirectTextNode = () => Array.from(document.body.querySelectorAll("*"))
  .filter(({ childNodes }) => Array.from(childNodes)
    .some(({ nodeType }) => nodeType === Node.TEXT_NODE))

The Node.prototype.childNodes getter will never return null, so any comparison with null is superfluous.

Upvotes: -1

santon
santon

Reputation: 1834

jsfiddle with NodeIterator

MDN Reference

$(function () {
var textNodeIterator = document.createNodeIterator(
document.body,
NodeFilter.SHOW_TEXT,

function (node) {
    if (is_ignorable(node)) return NodeFilter.FILTER_REJECT;
    return NodeFilter.FILTER_ACCEPT;
});
var matchingElements = [];
var currentNode;

while (currentNode = textNodeIterator.nextNode()) {
    //console.log(currentNode);
    if (currentNode) {
        //console.log(currentNode.nodeType + "-" + currentNode.textContent);
        if (!isParentAlreadyMatched(currentNode.parentNode)) matchingElements.push(currentNode.parentNode);
    }
}
console.log(matchingElements);

function is_all_ws(nod) {
    // Use ECMA-262 Edition 3 String and RegExp features
    return !(/[^\t\n\r ]/.test(nod.textContent));
}

function is_ignorable(nod) {
    return (nod.nodeType == 8) || // A comment node
    ((nod.nodeType == 3) && is_all_ws(nod)); // a text node, all ws
}

function isParentAlreadyMatched(parentNode) {
    for (var i = 0; i < matchingElements.length; i++) {
        if (matchingElements[i] === parentNode) return true;
    }
    return false;
}
});

Upvotes: 1

santon
santon

Reputation: 1834

jsfiddle with a for loop solution

 function getAllElementsWithDirectTextNode() {
    var matchingElements = [];
    var allElements = document.body.getElementsByTagName('*');
    for (var i = 0; i < allElements.length; i++) {
        for (var j=0; j < allElements[i].childNodes.length; j++) {
            if (allElements[i].childNodes[j].nodeType === Node.TEXT_NODE) {
                if (is_all_ws(allElements[i].childNodes[j])) continue;
                matchingElements.push(allElements[i]);
                break;
            }
        }
    }
    console.log(matchingElements);
}

function is_all_ws(nod) {
   // Use ECMA-262 Edition 3 String and RegExp features
   return !(/[^\t\n\r ]/.test(nod.textContent));
}

Upvotes: 1

Barmar
Barmar

Reputation: 782499

You need to test whether any of the children are text nodes. So you need to loop through all the children, testing their type.

function getAllElementsWithDirectTextNode() {
  var matchingElements = [];
  var allElements = document.body.getElementsByTagName('*');
  for (var i = 0, n = allElements.length; i < n; i++) {
    if (allElements[i].childNodes !== null) {
      var children = allElements[i].childNodes;
      for (var j = 0, m = children.length; j < m; j++) {
        if (children[j].nodeType == Node.TEXT_NODE) {
          matchingElements.push(allElements[i]);
          break; // don't need to check the remaining children
        }
      }
    }
  }
  console.log(allElements);
}

Upvotes: 1

Praveen Kumar Purushothaman
Praveen Kumar Purushothaman

Reputation: 167240

You need to check if they have a childNode with nodeType of Node.TEXT_NODE:

function getAllElementsWithDirectTextNode() {
  var matchingElements = [];
  var allElements = document.body.getElementsByTagName('*');
  for (var i = 0, n = allElements.length; i < n; i++) {
    if (allElements[i].childNodes !== null) {
      for (var j in allElements[i].childNodes)
        if (allElements[i].childNodes[j].nodeType == Node.TEXT_NODE) {
          matchingElements.push(allElements[i]);
          break;
        }
    }
  }
  console.log(allElements);           
}

Upvotes: 3

Related Questions