Reputation: 79
I want to search DOM elements
matching by some text
. Wanted to implement feature similar to Ctrl +F
. Here is what I tried so far:
var elems = document.querySelectorAll("DIV");
elems.forEach(function(e){
if(e.textContent.includes('Foo Bar')){
domEl.push(e);
}
});
but domEl
returns 52 elements
but Ctrl +F
showing only 14
records.
Upvotes: 0
Views: 744
Reputation: 4226
This solution searches children of a container element recursively to collect matching results in a collection
array.
Each result object has two properties: the element containing the textNode where the match occurred and the text node itself. This allows us to later extract whatever information we want about the element (such as its id
property) and/or about the text node (such as its full text).
const
myCollection = [],
searchIn = document.getElementById("container"),
searchFor = "Stack";
collectMatchingDescendants(searchIn, searchFor, myCollection);
printCollectionInfo();
function collectMatchingDescendants(node, text, collection){
if (node.hasChildNodes()) {
let children = node.childNodes;
for (node of children){
if(node.nodeType == 3 && node.nodeValue.includes(text)){
const element = node.parentElement;
const myObject = { element: element, node: node };
collection.push(myObject);
}
else{ collectMatchingDescendants(node, text, collection); }
}
}
}
function printCollectionInfo(){
for(let obj of myCollection){
console.log(`${obj.element.id} matched (text: '${obj.node.nodeValue.trim() }')` );
}
}
*{ border: 1px solid lightgrey; }
<div id="container">
<div id="div1">
Stack Overflow 1
<p id="p1">Stack Overflow 2</p>
<p id="p2">Nothing interesting here</p>
</div>
<div id="div2">
Stack Overflow 3
<p id="p3">Stack Overflow 4
<span id="span1">Stack Overflow 5</span>
</p>
</div>
</div>
Upvotes: 0
Reputation: 23379
Use NodeType to determine the type of node and ignore anything that isn't a text node. Read the comments:
const getElementsContainingText = (query, selector = '*') =>
// Get the list of all elements and filter it
[...document.querySelectorAll(selector)].filter(element =>
// Get a list of all the current elements childNodes
[...element.childNodes]
// Filter out all child nodes that are not plain text
.filter(ele => ele.nodeType === Node.TEXT_NODE)
// Get the content of the text node
.map(ele => ele.textContent)
// Filter out anything that doesn't match the query
.filter(text => text.includes(query))
// Return true if there are any matches
.length > 0
);
console.log(getElementsContainingText('foo', 'div'));
console.log(getElementsContainingText('bar', 'div'));
<div>
bar
<div>
foo
</div>
</div>
You could easily combine the two filter
s and get rid of the map
, but I did it this way so it was easier to explain in the comments, and to keep the lines short.
Upvotes: 2
Reputation: 71
Filter the children of your DIVs to only check #text leafs:
const elems = document.querySelectorAll("DIV");
elems.forEach(function(e) {
for(const node of e.childNodes) {
if(node.nodeName==="#text"&&node.textContent.includes("Foo Bar")) {
domE1.push(e);
return;
}
Keep in mind that this will only select the DIVS that directly contain the searched text:
<div id="a">Foo Bar</div> // a will be selected
<div id="b"><span>Foo Bar</span></div> // b will NOT be selected
<div id="c"><div id="d">Foo Bar</div></div> // c will NOT be selected, but d will
Upvotes: 0