How to get element by innerText
Asked Answered
S

17

320

How to get tag in html page, if I know what text tag contains. E.g.:

<a ...>SearchingText</a>
Synopsize answered 28/9, 2010 at 13:39 Comment(0)
P
217

You'll have to traverse by hand.

var aTags = document.getElementsByTagName("a");
var searchText = "SearchingText";
var found;

for (var i = 0; i < aTags.length; i++) {
  if (aTags[i].textContent == searchText) {
    found = aTags[i];
    break;
  }
}

// Use `found`.
Periostitis answered 28/9, 2010 at 13:42 Comment(5)
@AutoSponge Actually innerHTML is standard. innerText does not work in FFStunsail
Updated the example, textContent is likely what you want in this case. Thanks, folks :)Periostitis
@AugustLilleaas, what is up with the i < il? What is that doing?Lili
I have found that if you have <span><span>search text</span></span> this method may return the outer span instead of the inner one.Folse
Yeah, this really shouldn't be the accepted answer, considering the number of issues that were found and fixed overtime, with the exception of the most recent issue from 2016.Natelson
A
376

You could use xpath to accomplish this

var xpath = "//a[text()='SearchingText']";
var matchingElement = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;

You can also search of an element containing some text using this xpath:

var xpath = "//a[contains(text(),'Searching')]";
Aciniform answered 26/3, 2015 at 21:26 Comment(15)
This should be the top answer. XPath can do much more, like select node by attribute value, select node sets ... Simple intro: w3schools.com/xml/xpath_syntax.aspCary
Question is, what is the performance penalty for this trickeryMassy
@Massy I think this will be faster than any of the other answers as the xpath is executed by a browser provided algorithm rather than executing in javascript like all the other answers here. It's an interesting question though.Aciniform
Seems Document.evaluate() isn't supposed in IE browserMassy
Why do I get null as a result, if I am here at Stackoverflow and I execute in console: document.evaluate("a[text()='share']", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;? Firefox 63. I see many 'share' links.Ezaria
@AlexanderChzhen you can add '//' to the beginning of the xpath to find all nodes regardless of their parent node. I almost always include that in my xpaths so I'm not sure why I left it out of my answer. Perhaps simplicity? It's definitely faster to only search the root document for an element.Aciniform
@carlin.scott, yes, it works. But why do i need to do this? You wrote the original answer without //.Ezaria
I don't know why, but somehow var xpath = "//a[text()='SearchingText']"; This is not working, but var xpath = "//a[contains(text(),'Searching')]"; this works. Be aware of the excaped character, such as \' \'.Wink
I've found the //a[contains(text(),'needle')] to be a safer option, because any hidden characters / whitespace around the needle will thwart the more explicit [text()='needle'] (which would return null if there's a trailing space, for instance)Annual
You could use the normalize-space xpath method to remove the extra invisible characters. For example, //a[contains(normalize-space(text())='SearchingText']).Aciniform
This only returns a single node, how to return all nodes?Guyenne
@Guyenne You would need to change the call to this: js var matchingElementSet = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); while(element = result.iterateNext()) { // do something with each element } developer.mozilla.org/en-US/docs/Web/API/XPathResult/…Aciniform
Is there any way to make it work on shadow-root elements?Charron
What to do if I don't know the Tag Name I'm searching?Amid
@Amid use * instead of 'a' as in my examples to match against all tagsAciniform
P
217

You'll have to traverse by hand.

var aTags = document.getElementsByTagName("a");
var searchText = "SearchingText";
var found;

for (var i = 0; i < aTags.length; i++) {
  if (aTags[i].textContent == searchText) {
    found = aTags[i];
    break;
  }
}

// Use `found`.
Periostitis answered 28/9, 2010 at 13:42 Comment(5)
@AutoSponge Actually innerHTML is standard. innerText does not work in FFStunsail
Updated the example, textContent is likely what you want in this case. Thanks, folks :)Periostitis
@AugustLilleaas, what is up with the i < il? What is that doing?Lili
I have found that if you have <span><span>search text</span></span> this method may return the outer span instead of the inner one.Folse
Yeah, this really shouldn't be the accepted answer, considering the number of issues that were found and fixed overtime, with the exception of the most recent issue from 2016.Natelson
S
96

Using the most modern syntax available at the moment, it can be done very cleanly like this:

for (const a of document.querySelectorAll("a")) {
  if (a.textContent.includes("your search term")) {
    console.log(a.textContent)
  }
}

Or with a separate filter:

[...document.querySelectorAll("a")]
   .filter(a => a.textContent.includes("your search term"))
   .forEach(a => console.log(a.textContent))

Naturally, legacy browsers won't handle this, but you can use a transpiler if legacy support is needed.

Somato answered 28/9, 2010 at 13:39 Comment(2)
Much nicer than learning a new parsing language for xpath, and more easily iterable.Majorette
This is perfect! Spreading the querySelectorAll result into an array and then using filter on it like that is perfect vanilla syntax without using messy XPath. A lot easier to understand at first glance, and much more compatible with other things as well. Thanks for the help!Northward
I
48

You can use jQuery :contains() Selector

var element = $( "a:contains('SearchingText')" );
Imogeneimojean answered 28/6, 2015 at 19:42 Comment(1)
I get: Error: <![EX[["Tried to get element with id of \"%s\" but it is not present on the page","a:contains('SearchingText')"]]]> TAAL[1] though I have elements with "SearchingText" in them.Drummond
G
23

Functional approach. Returns array of all matched elements and trims spaces around while checking.

function getElementsByText(str, tag = 'a') {
  return Array.prototype.slice.call(document.getElementsByTagName(tag)).filter(el => el.textContent.trim() === str.trim());
}

Usage

getElementsByText('Text here'); // second parameter is optional tag (default "a")

if you're looking through different tags i.e. span or button

getElementsByText('Text here', 'span');
getElementsByText('Text here', 'button');

The default value tag = 'a' will need Babel for old browsers

Gilletta answered 13/7, 2017 at 19:54 Comment(3)
This is incorrect because it also includes results for all child nodes. I.e. if child node of a will contain str - el will be included into getElementsByText result; which is wrong.Mews
@Mews it depends if that's undesirable. It might be needed to select by text even if it's wrapped in another tag i.e. <span></span>Gilletta
I made document into a passed variable elm so I could narrow down before calling func, and no reason I can't just pass document, but I prefer it that way. Also removed the default tag = 'a'. Great answer though! I love how you used the name convention of existing methods.Excavate
F
22

function findByTextContent(needle, haystack, precise) {
  // needle: String, the string to be found within the elements.
  // haystack: String, a selector to be passed to document.querySelectorAll(),
  //           NodeList, Array - to be iterated over within the function:
  // precise: Boolean, true - searches for that precise string, surrounded by
  //                          word-breaks,
  //                   false - searches for the string occurring anywhere
  var elems;

  // no haystack we quit here, to avoid having to search
  // the entire document:
  if (!haystack) {
    return false;
  }
  // if haystack is a string, we pass it to document.querySelectorAll(),
  // and turn the results into an Array:
  else if ('string' == typeof haystack) {
    elems = [].slice.call(document.querySelectorAll(haystack), 0);
  }
  // if haystack has a length property, we convert it to an Array
  // (if it's already an array, this is pointless, but not harmful):
  else if (haystack.length) {
    elems = [].slice.call(haystack, 0);
  }

  // work out whether we're looking at innerText (IE), or textContent 
  // (in most other browsers)
  var textProp = 'textContent' in document ? 'textContent' : 'innerText',
    // creating a regex depending on whether we want a precise match, or not:
    reg = precise === true ? new RegExp('\\b' + needle + '\\b') : new RegExp(needle),
    // iterating over the elems array:
    found = elems.filter(function(el) {
      // returning the elements in which the text is, or includes,
      // the needle to be found:
      return reg.test(el[textProp]);
    });
  return found.length ? found : false;;
}


findByTextContent('link', document.querySelectorAll('li'), false).forEach(function(elem) {
  elem.style.fontSize = '2em';
});

findByTextContent('link3', 'a').forEach(function(elem) {
  elem.style.color = '#f90';
});
<ul>
  <li><a href="#">link1</a>
  </li>
  <li><a href="#">link2</a>
  </li>
  <li><a href="#">link3</a>
  </li>
  <li><a href="#">link4</a>
  </li>
  <li><a href="#">link5</a>
  </li>
</ul>

Of course, a somewhat simpler way still is:

var textProp = 'textContent' in document ? 'textContent' : 'innerText';

// directly converting the found 'a' elements into an Array,
// then iterating over that array with Array.prototype.forEach():
[].slice.call(document.querySelectorAll('a'), 0).forEach(function(aEl) {
  // if the text of the aEl Node contains the text 'link1':
  if (aEl[textProp].indexOf('link1') > -1) {
    // we update its style:
    aEl.style.fontSize = '2em';
    aEl.style.color = '#f90';
  }
});
<ul>
  <li><a href="#">link1</a>
  </li>
  <li><a href="#">link2</a>
  </li>
  <li><a href="#">link3</a>
  </li>
  <li><a href="#">link4</a>
  </li>
  <li><a href="#">link5</a>
  </li>
</ul>

References:

Flameout answered 27/10, 2014 at 19:46 Comment(0)
Z
16

Simply pass your substring into the following line:

Outer HTML

document.documentElement.outerHTML.includes('substring')

Inner HTML

document.documentElement.innerHTML.includes('substring')

You can use these to search through the entire document and retrieve the elements that contain your search term:

function get_elements_by_inner(word) {
    res = []
    elems = [...document.getElementsByTagName('a')];
    elems.forEach((elem) => { 
        if(elem.outerHTML.includes(word)) {
            res.push(elem)
        }
    })
    return(res)
}

Usage:

How many times is the user "T3rm1" mentioned on this page?

get_elements_by_inner("T3rm1").length

1

How many times is jQuery mentioned?

get_elements_by_inner("jQuery").length

3

Get all elements containing the word "Cybernetic":

get_elements_by_inner("Cybernetic")

enter image description here

Zygapophysis answered 13/3, 2019 at 19:44 Comment(2)
This returns true or false but not the element.Roadhouse
You can use the truth condition to iterate through the retrieved elements and grab anything you need from those elements. See updated answer.Zygapophysis
K
15

To get the filter method from user1106925 working in <=IE11 if needed

You can replace the spread operator with:

[].slice.call(document.querySelectorAll("a"))

and the includes call with a.textContent.match("your search term")

which works pretty neatly:

[].slice.call(document.querySelectorAll("a"))
   .filter(a => a.textContent.match("your search term"))
   .forEach(a => console.log(a.textContent))
Karmakarmadharaya answered 23/4, 2020 at 7:2 Comment(1)
I like this method. You can also Array.from instead of [].slice.call. For example: Array.from(document.querySelectorAll('a'))Capelin
E
9

You can do this, not sure if this is recommended but it works for me.

[... document.querySelectorAll('a')].filter(el => el.textContent.includes('sometext'));
Elite answered 5/3, 2022 at 10:13 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Watercool
B
6

I found the use of the newer syntax a little bit shorter compared to the others answer. So here's my proposal:

const callback = element => element.innerHTML == 'My research'

const elements = Array.from(document.getElementsByTagName('a'))
// [a, a, a, ...]

const result = elements.filter(callback)

console.log(result)
// [a]

JSfiddle.net

Bays answered 20/4, 2017 at 7:33 Comment(0)
T
6
document.querySelectorAll('a').forEach(function (item) {
    if (item.innerText == 'SearchingText') {
        console.dir(item);
    }
});
Terrorist answered 22/7, 2022 at 10:11 Comment(1)
Please don't post code-only answers but add a little textual explanation about how and why your approach works and what makes it different from the other answers given. You may also have a look at our "How to write a good answer" entry.Girard
W
5

You can use a TreeWalker to go over the DOM nodes, and locate all text nodes that contain the text, and return their parents:

const findNodeByContent = (text, root = document.body) => {
  const treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);

  const nodeList = [];

  while (treeWalker.nextNode()) {
    const node = treeWalker.currentNode;

    if (node.nodeType === Node.TEXT_NODE && node.textContent.includes(text)) {
      nodeList.push(node.parentNode);
    }
  };

  return nodeList;
}

const result = findNodeByContent('SearchingText');

console.log(result);
<a ...>SearchingText</a>
Wishywashy answered 12/10, 2019 at 18:23 Comment(0)
N
2

While it's possible to get by the inner text, I think you are heading the wrong way. Is that inner string dynamically generated? If so, you can give the tag a class or -- better yet -- ID when the text goes in there. If it's static, then it's even easier.

Nardone answered 28/9, 2010 at 13:44 Comment(0)
M
2

This does the job.
Returns an array of nodes containing text.

function get_nodes_containing_text(selector, text) {
    const elements = [...document.querySelectorAll(selector)];

    return elements.filter(
      (element) =>
        element.childNodes[0]
        && element.childNodes[0].nodeValue
        && RegExp(text, "u").test(element.childNodes[0].nodeValue.trim())
    );
  }
Mews answered 13/10, 2019 at 15:13 Comment(0)
C
2

const el = Array.from(document.body.querySelectorAll('a')).find(elm => elm.textContent.toLowerCase().includes('searching text'));
const el2 = document.evaluate('//a[contains(text(), "text5")]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
console.log(el, el2);
<a href="#">text1</a>
<a href="#">text2</a>
<a href="#">Searching Text</a>
<a href="#">text3</a>
<a href="#">text4</a>
<a href="#">text5</a>
Cythiacyto answered 18/10, 2022 at 21:53 Comment(0)
I
0

I think you'll need to be a bit more specific for us to help you.

  1. How are you finding this? Javascript? PHP? Perl?
  2. Can you apply an ID attribute to the tag?

If the text is unique (or really, if it's not, but you'd have to run through an array) you could run a regular expression to find it. Using PHP's preg_match() would work for that.

If you're using Javascript and can insert an ID attribute, then you can use getElementById('id'). You can then access the returned element's attributes through the DOM: https://developer.mozilla.org/en/DOM/element.1.

Interplay answered 28/9, 2010 at 13:59 Comment(0)
N
0

I've just needed a way to get the element that contains a specific text and this is what I came up with.

Use document.getElementsByInnerText() to get multiple elements (multiple elements might have the same exact text), and use document.getElementByInnerText() to get just one element (first match).

Also, you can localize the search by using an element (e.g. someElement.getElementByInnerText()) instead of document.

You might need to tweak it in order to make it cross-browser or satisfy your needs.

I think the code is self-explanatory, so I'll leave it as it is.

HTMLElement.prototype.getElementsByInnerText = function (text, escape) {
    var nodes  = this.querySelectorAll("*");
    var matches = [];
    for (var i = 0; i < nodes.length; i++) {
        if (nodes[i].innerText == text) {
            matches.push(nodes[i]);
        }
    }
    if (escape) {
        return matches;
    }
    var result = [];
    for (var i = 0; i < matches.length; i++) {
        var filter = matches[i].getElementsByInnerText(text, true);
        if (filter.length == 0) {
            result.push(matches[i]);
        }
    }
    return result;
};
document.getElementsByInnerText = HTMLElement.prototype.getElementsByInnerText;

HTMLElement.prototype.getElementByInnerText = function (text) {
    var result = this.getElementsByInnerText(text);
    if (result.length == 0) return null;
    return result[0];
}
document.getElementByInnerText = HTMLElement.prototype.getElementByInnerText;

console.log(document.getElementsByInnerText("Text1"));
console.log(document.getElementsByInnerText("Text2"));
console.log(document.getElementsByInnerText("Text4"));
console.log(document.getElementsByInnerText("Text6"));

console.log(document.getElementByInnerText("Text1"));
console.log(document.getElementByInnerText("Text2"));
console.log(document.getElementByInnerText("Text4"));
console.log(document.getElementByInnerText("Text6"));
<table>
    <tr>
        <td>Text1</td>
    </tr>
    <tr>
        <td>Text2</td>
    </tr>
    <tr>
        <td>
            <a href="#">Text2</a>
        </td>
    </tr>
    <tr>
        <td>
            <a href="#"><span>Text3</span></a>
        </td>
    </tr>
    <tr>
        <td>
            <a href="#">Special <span>Text4</span></a>
        </td>
    </tr>
    <tr>
        <td>
            Text5
            <a href="#">Text6</a>
            Text7
        </td>
    </tr>
</table>
Nation answered 14/12, 2017 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.