136

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?

passwd
  • 1,693
  • 2
  • 10
  • 19
  • 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. – Redu May 08 '16 at 09:45
  • Possible duplicate : [Native javascript equivalent of jQuery :contains() selector](http://stackoverflow.com/questions/17799236/native-javascript-equivalent-of-jquery-contains-selector/17799291#17799291) – Shady Alset May 08 '16 at 11:30

13 Answers13

130

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.

wyu
  • 1,622
  • 13
  • 31
gdyrrahitis
  • 4,340
  • 3
  • 20
  • 36
  • 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) – passwd May 08 '16 at 11:46
  • Ok, I do it this way: `alert(thisHeading.textContent.replace(/.*You have login (.*) times.*/,'$1')) ; ` – passwd May 08 '16 at 12:09
  • @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. – gdyrrahitis May 08 '16 at 12:13
  • Internet Explorer: No support. But supported in Edge. I'm not sure what that means, version-wise. – Rolf Oct 12 '17 at 16:17
  • 1
    how should be handled an error in case the element I am looking for is missing? – nenito Apr 02 '18 at 09:12
  • iterateNext() returns null if no element was found – Pietro Coelho May 06 '21 at 20:21
93

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];
Niels
  • 1,069
  • 8
  • 10
48

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
Pawan Nogariya
  • 7,162
  • 12
  • 43
  • 89
  • 1
    Seems like this works, but in return I am getting only this: `[object HTMLDivElement],[object HTMLDivElement]` – passwd May 08 '16 at 11:52
  • 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 simple – Pawan Nogariya May 09 '16 at 08:05
22

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>
Andrew Willems
  • 9,768
  • 8
  • 39
  • 58
  • 4
    I love this. Clean, concise and comprehensible -- all at the same time. – ba_ul Dec 10 '18 at 03:59
  • 3
    Horribly inefficient surely? Think how large `innerHTML` is for your top-most `
    `s. You should filter out `div`s that contain children first. Also suspect `document.getElementsByTagName('div')` may be faster but I would benchmark to be sure.
    – Timmmm Jan 10 '20 at 13:39
  • 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, thanks – gsalgadotoledo Mar 13 '20 at 20:45
12

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.

Redu
  • 19,106
  • 4
  • 44
  • 59
5

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.

Steve Botello
  • 141
  • 1
  • 5
3

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.

keymap
  • 393
  • 3
  • 13
3

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;
    };
  }
Vadorequest
  • 12,672
  • 17
  • 87
  • 183
2

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.

Vigilante
  • 91
  • 1
  • 7
2

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
}
Steven Spungin
  • 17,551
  • 4
  • 57
  • 56
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)
b_laoshi
  • 355
  • 5
  • 11
1

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';
}
Jan Kyu Peblik
  • 1,140
  • 11
  • 17
0

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)))

}