Javascript .querySelector find <div> by innerTEXT
Asked Answered
A

14

261

How can I find DIV with certain text? For example:

<div>
SomeText, text continues.
</div>

Trying to use something like this:

var text = document.querySelector('div[SomeText*]').innerTEXT;
alert(text);

But ofcourse it will not work. How can I do it?

Attainder answered 8/5, 2016 at 9:41 Comment(2)
Even if you could do it it wouldn't be any faster than getting all the divs and filtering them over the innerText property. So why don't you do it manually.Contralto
Possible duplicate : Native javascript equivalent of jQuery :contains() selectorInamorato
V
228

OP's question is about plain JavaScript and not jQuery. Although there are plenty of answers and I like @Pawan Nogariya answer, please check this alternative out.

You can use XPATH in JavaScript. More info on the MDN article here.

The document.evaluate() method evaluates an XPATH query/expression. So you can pass XPATH expressions there, traverse into the HTML document and locate the desired element.

In XPATH you can select an element, by the text node like the following, whch gets the div that has the following text node.

//div[text()="Hello World"]

To get an element that contains some text use the following:

//div[contains(., 'Hello')]

The contains() method in XPATH takes a node as first parameter and the text to search for as second parameter.

Check this plunk here, this is an example use of XPATH in JavaScript

Here is a code snippet:

var headings = document.evaluate("//h1[contains(., 'Hello')]", document, null, XPathResult.ANY_TYPE, null );
var thisHeading = headings.iterateNext();

console.log(thisHeading); // Prints the html element in console
console.log(thisHeading.textContent); // prints the text content in console

thisHeading.innerHTML += "<br />Modified contents";  

As you can see, I can grab the HTML element and modify it as I like.

Vyborg answered 8/5, 2016 at 10:10 Comment(6)
Thank you! Works great! But how to "console.log" the "thisHeading.textContent" if I need to grab only one word from this text? For example: '//div[contains(., \'/You login (.*) times this session/\')]' and then alert(thisHeading.textContent.$1)Attainder
Ok, I do it this way: alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ; Attainder
@passwd, well you can't do that. Regex is not supported in XPATH 1.0 (which .evaluate() uses. Please someone correct me if I am wrong), so firstly, you can't search for something that matches a regular expression. Secondly, the .textContent property returns the text node of the element. If you want to grab a value out of this text you should handle it explicitly, probably by creating some sort of function which matches a regex and returns the matching value in group.For that make a new question on a separate thread.Vyborg
Internet Explorer: No support. But supported in Edge. I'm not sure what that means, version-wise.Sufflate
how should be handled an error in case the element I am looking for is missing?Archaeopteryx
iterateNext() returns null if no element was foundByssinosis
T
164

You could use this pretty simple solution:

Array.from(document.querySelectorAll('div'))
  .find(el => el.textContent === 'SomeText, text continues.');
  1. The Array.from will convert the NodeList to an array (there are multiple methods to do this like the spread operator or slice)

  2. The result now being an array allows for using the Array.find method, you can then put in any predicate. You could also check the textContent with a regex or whatever you like.

Note that Array.from and Array.find are ES2015 features. Te be compatible with older browsers like IE10 without a transpiler:

Array.prototype.slice.call(document.querySelectorAll('div'))
  .filter(function (el) {
    return el.textContent === 'SomeText, text continues.'
  })[0];
Tannin answered 24/8, 2017 at 19:57 Comment(2)
If you'd like to find multiple elements, replace find with filter.Perishing
[].slice.call( ... ) is even simpler 👍Fabric
C
72

Since you have asked it in javascript so you can have something like this

function contains(selector, text) {
  var elements = document.querySelectorAll(selector);
  return Array.prototype.filter.call(elements, function(element){
    return RegExp(text).test(element.textContent);
  });
}

And then call it like this

contains('div', 'sometext'); // find "div" that contain "sometext"
contains('div', /^sometext/); // find "div" that start with "sometext"
contains('div', /sometext$/i); // find "div" that end with "sometext", case-insensitive
Cracking answered 8/5, 2016 at 9:52 Comment(2)
Seems like this works, but in return I am getting only this: [object HTMLDivElement],[object HTMLDivElement]Attainder
Yes you will be getting the divs with matching text in it and then you can call there inner text method something like this foundDivs[0].innerText, that simpleCracking
E
29

This solution does the following:

  • Uses the ES6 spread operator to convert the NodeList of all divs to an array.

  • Provides output if the div contains the query string, not just if it exactly equals the query string (which happens for some of the other answers). e.g. It should provide output not just for 'SomeText' but also for 'SomeText, text continues'.

  • Outputs the entire div contents, not just the query string. e.g. For 'SomeText, text continues' it should output that whole string, not just 'SomeText'.

  • Allows for multiple divs to contain the string, not just a single div.

[...document.querySelectorAll('div')]      // get all the divs in an array
  .map(div => div.innerHTML)               // get their contents
  .filter(txt => txt.includes('SomeText')) // keep only those containing the query
  .forEach(txt => console.log(txt));       // output the entire contents of those
<div>SomeText, text continues.</div>
<div>Not in this div.</div>
<div>Here is more SomeText.</div>
Eriha answered 12/4, 2018 at 3:32 Comment(3)
I love this. Clean, concise and comprehensible -- all at the same time.Ial
Horribly inefficient surely? Think how large innerHTML is for your top-most <div>s. You should filter out divs that contain children first. Also suspect document.getElementsByTagName('div') may be faster but I would benchmark to be sure.Mitsue
This is great for me, I can set a good selector at the begining because I already know that it only can be in a table, cool, thanksUnnamed
U
19

Coming across this in 2021, I found using XPATH too complicated (need to learn something else) for something that should be rather simple.

Came up with this:

function querySelectorIncludesText (selector, text){
  return Array.from(document.querySelectorAll(selector))
    .find(el => el.textContent.includes(text));
}

Usage:

querySelectorIncludesText('button', 'Send')

Note that I decided to use includes and not a strict comparison, because that's what I really needed, feel free to adapt.

You might need those polyfills if you want to support all browsers:

  /**
   * String.prototype.includes() polyfill
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
   * @see https://vanillajstoolkit.com/polyfills/stringincludes/
   */
  if (!String.prototype.includes) {
    String.prototype.includes = function (search, start) {
      'use strict';

      if (search instanceof RegExp) {
        throw TypeError('first argument must not be a RegExp');
      }
      if (start === undefined) {
        start = 0;
      }
      return this.indexOf(search, start) !== -1;
    };
  }
Uranometry answered 5/5, 2021 at 9:46 Comment(0)
C
11

You best see if you have a parent element of the div you are querying. If so get the parent element and perform an element.querySelectorAll("div"). Once you get the nodeList apply a filter on it over the innerText property. Assume that a parent element of the div that we are querying has an id of container. You can normally access container directly from the id but let's do it the proper way.

var conty = document.getElementById("container"),
     divs = conty.querySelectorAll("div"),
    myDiv = [...divs].filter(e => e.innerText === "SomeText");

So that's it.

Contralto answered 8/5, 2016 at 9:53 Comment(1)
This worked for me but with innerHTML instead of innerTextPrimrose
M
6

If you don't want to use jquery or something like that then you can try this:

function findByText(rootElement, text){
    var filter = {
        acceptNode: function(node){
            // look for nodes that are text_nodes and include the following string.
            if(node.nodeType === document.TEXT_NODE && node.nodeValue.includes(text)){
                 return NodeFilter.FILTER_ACCEPT;
            }
            return NodeFilter.FILTER_REJECT;
        }
    }
    var nodes = [];
    var walker = document.createTreeWalker(rootElement, NodeFilter.SHOW_TEXT, filter, false);
    while(walker.nextNode()){
       //give me the element containing the node
       nodes.push(walker.currentNode.parentNode);
    }
    return nodes;
}

//call it like
var nodes = findByText(document.body,'SomeText');
//then do what you will with nodes[];
for(var i = 0; i < nodes.length; i++){ 
    //do something with nodes[i]
} 

Once you have the nodes in an array that contain the text you can do something with them. Like alert each one or print to console. One caveat is that this may not necessarily grab divs per se, this will grab the parent of the textnode that has the text you are looking for.

Minnick answered 8/5, 2016 at 10:28 Comment(0)
A
4

Google has this as a top result for For those who need to find a node with certain text. By way of update, a nodelist is now iterable in modern browsers without having to convert it to an array.

The solution can use forEach like so.

var elList = document.querySelectorAll(".some .selector");
elList.forEach(function(el) {
    if (el.innerHTML.indexOf("needle") !== -1) {
        // Do what you like with el
        // The needle is case sensitive
    }
});

This worked for me to do a find/replace text inside a nodelist when a normal selector could not choose just one node so I had to filter each node one by one to check it for the needle.

Ackler answered 6/11, 2017 at 22:59 Comment(0)
T
4

Use XPath and document.evaluate(), and make sure to use text() and not . for the contains() argument, or else you will have the entire HTML, or outermost div element matched.

var headings = document.evaluate("//h1[contains(text(), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

or ignore leading and trailing whitespace

var headings = document.evaluate("//h1[contains(normalize-space(text()), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

or match all tag types (div, h1, p, etc.)

var headings = document.evaluate("//*[contains(text(), 'Hello')]", document, null, XPathResult.ANY_TYPE, null );

Then iterate

let thisHeading;
while(thisHeading = headings.iterateNext()){
    // thisHeading contains matched node
}
Touchy answered 11/12, 2017 at 15:54 Comment(2)
Can this method be used to add a class to an element? e.g. thisheading.setAttribute('class', "esubject")Dawes
Once you have the element, sure. However, it is better to use element.classList.add("esubject") though :)Touchy
T
4

I was looking for a way to do something similar using a Regex, and decided to build something of my own that I wanted to share if others are looking for a similar solution.

function getElementsByTextContent(tag, regex) {
  const results = Array.from(document.querySelectorAll(tag))
        .reduce((acc, el) => {
          if (el.textContent && el.textContent.match(regex) !== null) {
            acc.push(el);
          }
          return acc;
        }, []);
  return results;
}
Twotone answered 6/6, 2021 at 17:47 Comment(0)
E
2

Here's the XPath approach but with a minimum of XPath jargon.

Regular selection based on element attribute values (for comparison):

// for matching <element class="foo bar baz">...</element> by 'bar'
var things = document.querySelectorAll('[class*="bar"]');
for (var i = 0; i < things.length; i++) {
    things[i].style.outline = '1px solid red';
}

XPath selection based on text within element.

// for matching <element>foo bar baz</element> by 'bar'
var things = document.evaluate('//*[contains(text(),"bar")]',document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
for (var i = 0; i < things.snapshotLength; i++) {
    things.snapshotItem(i).style.outline = '1px solid red';
}

And here's with case-insensitivity since text is more volatile:

// for matching <element>foo bar baz</element> by 'bar' case-insensitively
var things = document.evaluate('//*[contains(translate(text(),"ABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwxyz"),"bar")]',document,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
for (var i = 0; i < things.snapshotLength; i++) {
    things.snapshotItem(i).style.outline = '1px solid red';
}
Eastlake answered 22/8, 2019 at 22:34 Comment(0)
B
2

There are lots of great solutions here already. However, to provide a more streamlined solution and one more in keeping with the idea of a querySelector behavior and syntax, I opted for a solution that extends Object with a couple prototype functions. Both of these functions use regular expressions for matching text, however, a string can be provided as a loose search parameter.

Simply implement the following functions:

// find all elements with inner text matching a given regular expression
// args: 
//      selector: string query selector to use for identifying elements on which we 
//                should check innerText
//      regex: A regular expression for matching innerText; if a string is provided,
//             a case-insensitive search is performed for any element containing the string.
Object.prototype.queryInnerTextAll = function(selector, regex) {
    if (typeof(regex) === 'string') regex = new RegExp(regex, 'i'); 
    const elements = [...this.querySelectorAll(selector)];
    const rtn = elements.filter((e)=>{
        return e.innerText.match(regex);
    });
    
    return rtn.length === 0 ? null : rtn
}

// find the first element with inner text matching a given regular expression
// args: 
//      selector: string query selector to use for identifying elements on which we 
//                should check innerText
//      regex: A regular expression for matching innerText; if a string is provided,
//             a case-insensitive search is performed for any element containing the string.
Object.prototype.queryInnerText = function(selector, text){
    return this.queryInnerTextAll(selector, text)[0];
}

With these functions implemented, you can now make calls as follows:

  • document.queryInnerTextAll('div.link', 'go');
    This would find all divs containing the link class with the word go in the innerText (eg. Go Left or GO down or go right or It's Good)
  • document.queryInnerText('div.link', 'go');
    This would work exactly as the example above except it would return only the first matching element.
  • document.queryInnerTextAll('a', /^Next$/);
    Find all links with the exact text Next (case-sensitive). This will exclude links that contain the word Next along with other text.
  • document.queryInnerText('a', /next/i);
    Find the first link that contains the word next, regardless of case (eg. Next Page or Go to next)
  • e = document.querySelector('#page');
    e.queryInnerText('button', /Continue/);
    This performs a search within a container element for a button containing the text, Continue (case-sensitive). (eg. Continue or Continue to Next but not continue)
Baldhead answered 31/7, 2020 at 18:40 Comment(0)
C
1

I had similar problem.

Function that return all element which include text from arg.

This works for me:

function getElementsByText(document, str, tag = '*') {
return [...document.querySelectorAll(tag)]
    .filter(
        el => (el.text && el.text.includes(str))
            || (el.children.length === 0 && el.outerText && el.outerText.includes(str)))

}

Comfortable answered 27/10, 2019 at 21:0 Comment(0)
W
1

Since there are no limits to the length of text in a data attribute, use data attributes! And then you can use regular css selectors to select your element(s) like the OP wants.

for (const element of document.querySelectorAll("*")) {
  element.dataset.myInnerText = element.innerText;
}

document.querySelector("*[data-my-inner-text='Different text.']").style.color="blue";
<div>SomeText, text continues.</div>
<div>Different text.</div>

Ideally you do the data attribute setting part on document load and narrow down the querySelectorAll selector a bit for performance.

Warrior answered 25/2, 2020 at 22:58 Comment(9)
this could significantly increase the document size unnecessarilyWhoops
Yeah but adding data to every single tag that has text is a really bad approach, I would just delete the answer, since there are a few others that are widely accepted and upvoted as a better way to do it by just checking the text and not adding data to do soWhoops
not gonna delete. IMHO this is the only answer that does exactly what the op asks and allows using actual css selectors to find text. It's the only answer that enables putting a selector in an actual css file like *[data-my-inner-text='Different text.'] while also allowing use of native querySelector. May increase the "size" of the document in memory but actual queries should be fastest using this method because of the way browser engines optimize for DOM queries.Warrior
OP did not ask for CSS Selector. He asked for: "Javascript .querySelector find <div> by innerTEXT" but did not specify CSS selector was a req. Increasing page size is bad for Google Rank, bad for Speed optimization, and adding data opens up potential for bugs, like quotes / special chars potentially breaking the HTML, etc. Other answers in this thread already accomplish the OP's request to FIND any DIV containing a TEXT. That's why you're getting downvotes. You can leave your answer up, but I would expect downvotes for an inefficient answer that has downsides, unwanted effects, & risksWhoops
Well I guess it depends how you interpret the OP's question. Document.querySelector() only accepts a valid css selector as argument, so taken literally, mine is the only answer that allows you to find an element by innerText using querySelector. All other answers involve creating some wrapper function around querySelector, not directly using a single call to querySelector. "Increasing page size" - my method obviously does not increase the downloaded html size, rather it only increases the page size in memory, should have no affect whatsoever on Google Rank.Warrior
Also, "bad for Speed optimization" - really depends on page structure and how this solution is applied. Using my method, actual queries would be faster than any other method here. But initial page processing/rendering speed might be slowed, yes. It's more a question of where you want the speed hit - once on initial page load, or during queries. If you're doing a lot of querying, then there is a scenario where my method would be the superior choice...Warrior
for my use case it is an interesting workaround - I plan not add it to all elements - just only to the elements which need :) (only a few with the initial selector - for example about 3 or 4) - and then I can use plain css selectorsCatalonia
With this "patching" method there was no need to change my other scripts (in my case ActionClick) which are depending on css selectors. I just fire a patch commant ActionPatch for some items and I can use it :) see github.com/muescha/dot_hammerspoon/commit/… and github.com/muescha/dot_hammerspoon/commit/…Catalonia
Jesus Christ! innerText would return the content of the node and its descendants, triggers a re-draw for every call as it applies CSS transformation to the contents. It will ignore hidden text or potentially alter it through CSS rules which is not what OP wants. You are better off using textContent which is also faster. Even better, use nodeValue and test for null. Even best stay away from messing with the DOM.Sufflate

© 2022 - 2024 — McMap. All rights reserved.