3

I've got a problem I've been stuck with for a while, sorry for spamming the forum a little.

Is there any way I can get an element by it's text content? Here's an example:

<span id="result_5_name" class="market_listing_item_name" style="color: #FFD700;">Item | Anodized Navy</span>

I'm trying to get the element by the text content which is 'Item | Anodized Navy'. Is it possible to do something like:

function getelem {
    var item = document.getElementsByTextContent('Item | Anodized Navy');

    item[0].click();
}

I know this doesn't exist, but it's just so you get the idea. Also, I cannot use by Id or classname, and I can't change the HTML code of the element, so it has to be done with the text content, I don't see another way.

I'd greatly appreciate any feedback,

Thanks!

bram
  • 99
  • 2
  • 11
  • You need to get the content of the text nodes and compare them against you required string. Then you can choose the parent node that you want. – Xotic750 Feb 14 '14 at 22:56

5 Answers5

3

Even though jQuery exposes the :contains pseudo selector, it would be wise to restrict the results first by using the class where possible:

$('.market_listing_item_name:contains("Item | Anodized Navy")')

Narrowing down the initial set of tags to search through improves the performance, otherwise all tags would be considered; also, overlapping tags would match as well.

On more modern browsers you could consider this, based on this answer:

function contains(selector, text) {
    var elements = document.querySelectorAll(selector);
    return [].filter.call(elements, function(element) {
        var contents = element.textContent || element.innerText || '';
        return contents.indexOf(text) != -1;
    });
}

var items = contains('.market_listing_item_name', 'Item | Anodized Navy')

items[0] && items[0].click();
Community
  • 1
  • 1
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
2

Well, since you tagged the question with , you could use the :contains selector:

var item = $(':contains(Item | Anodized Navy)');

Note, however, that this will return every element which contains that text, including the span and all the parent elements of the span. If you'd like to get just the inner-most element (assuming there is only one) just get the last item, like this:

var item = $(':contains(Item | Anodized Navy)').last();

To do the equivalent without using jQuery, you'd have to implement a function like this:

function getElementsByText(text) {
    var spans = document.getElementsByTagName('span'),
        results = [];
    for(var i = 0; i < spans.length; i++) {
        var content = spans[i].textContent || spans[i].innerText || '';
        if (content.indexOf(text) != -1)
            results.push(spans[i]);
    }
    return results;
}

Or possibly like this, depending on exactly what you need:

function getElementsByText(text) {
    function rec(ele, arr)
    {
        if (ele.childNodes.length > 0) 
            for (var i = 0; i < ele.childNodes.length; i++) 
                rec(ele.childNodes[i], arr);
        else if (ele.nodeType == Node.TEXT_NODE && 
                 ele.nodeValue.indexOf(text) != -1) 
            arr.push(ele.parentNode);
        return arr;
    }
    return rec(document.body, []);
}

And then call this function like this:

var item = getElementsByText('Item | Anodized Navy');

Note also in your code, getelem needs to have () after the function name, like this:

function getelem() {
    var item = getElementsByText('Item | Anodized Navy');
    ...
}
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • Thanks alot for this, best comment thus far! Do you know any way to do this without jQuery though? because I thought tampermonkey supported jQuery but it looks like it doesn't so I'm stuck with only javascript now it looks like it, unless I could import a jQuery library or something? – bram Feb 14 '14 at 23:33
  • Thanks so much for your effort, i appreciate it so much! I have a final question before putting you best answer ^^: do I replace the (text) with Item | Anodized Navy? I mean I'm a total noob, so it's not clear to me where to refer to the item in the code :) Also, what do i refer to to click on it? Do I use spans[0].click? Thanks so much again <3 – bram Feb 14 '14 at 23:54
  • Well it doesn't seem to work out for me but you are by far the most complete answer so thank you for this, I'll try fixing it and probably ask more questions around here :) – bram Feb 15 '14 at 00:10
  • @user3302404 Yeah `.click` is a function in jQuery. Your next step would probably be to see if you can find a way to emulate that function in plain JS. But that's a topic for another question. Glad I could help. – p.s.w.g Feb 15 '14 at 00:12
  • 1
    @user3302404: *doesn't work === most complete*? ...Anyway, if you're testing in Firefox, it won't work because FF doesn't support `.innerText`. Depending on the browsers you're supporting, either use `spans[i].textContent.indexOf(...` or `(spans[i].textContent || spans[i].innerText).indexOf(...` – cookie monster Feb 15 '14 at 00:16
  • 1
    @cookiemonster Ahh thanks. I had forgotten about that. Updated to be more cross-browser compliant. – p.s.w.g Feb 15 '14 at 00:21
  • 1
    Now, if the span's `.textContent` is an empty string, it will use `.innerText` which may be `undefined` :) you need `.textContent || .innerText || ''` in case that happens. – Ja͢ck Feb 15 '14 at 00:25
2

You want something like this

HTML

<span id="result_5_name" class="market_listing_item_name" style="color: #FFD700;">Item | Anodized Blue</span>
<span id="result_6_name" class="market_listing_item_name" style="color: #FFD700;">Item | Anodized Navy</span>
<span id="result_7_name" class="market_listing_item_name" style="color: #FFD700;">Item | Anodized Red</span>
<span id="result_8_name" class="market_listing_item_name" style="color: #FFD700;">Item | Anodized Green</span>
<span id="result_9_name" class="market_listing_item_name" style="color: #FFD700;">Item | Anodized Navy</span>

Javascript

function walkTheDOM(node, func) {
    func(node);
    node = node.firstChild;
    while (node) {
        walkTheDOM(node, func);
        node = node.nextSibling;
    }
}

function getElementsByText(node, text) {
    var results = [];

    walkTheDOM(node, function (currentNode) {
        if (currentNode.nodeType === 3 && currentNode.nodeValue === text) {
            results.push(currentNode.parentNode);
        }
    });

    return results;
}

console.log(getElementsByText(document, 'Item | Anodized Navy'));

Output

[span#result_6_name.market_listing_item_name, span#result_9_name.market_listing_item_name]

On jsFiddle

Additional 1, as we talked about treeWalker

function getElementsByText(node, text) {
    var results = [],
        treeWalker = document.createTreeWalker(
        node,
        NodeFilter.SHOW_TEXT, {
            acceptNode: function (node) {
                return NodeFilter.FILTER_ACCEPT;
            }
        }, false);

    while (treeWalker.nextNode()) {
        if (treeWalker.currentNode.nodeValue === text) {
            results.push(treeWalker.currentNode.parentNode);
        }
    }

    return results;
}

console.log(getElementsByText(document, 'Item | Anodized Navy'));

On jsFiddle

Additional 2, as we talked about getElementsByTagName('*')

function getElementsByText(node, text) {
    var results = [],
        tags = node.getElementsByTagName('*'),
        tagsLength = tags.length,
        tagsIndex,
        currentTag,
        childNodes,
        childNodesLength,
        ChildNodesIndex,
        currentChildNode;

    for (tagsIndex = 0; tagsIndex < tagsLength; tagsIndex += 1) {
        currentTag = tags[tagsIndex];
        childNodes = currentTag.childNodes;
        childNodesLength = childNodes.length;
        for (ChildNodesIndex = 0; ChildNodesIndex < childNodesLength; ChildNodesIndex += 1) {
            currentChildNode = childNodes[ChildNodesIndex];
            if (currentChildNode.nodeType === 3 && currentChildNode.nodeValue === text) {
                results.push(currentTag);
            }
        }
    }

    return results;
}

console.log(getElementsByText(document, 'Item | Anodized Navy'));

On jsFiddle

Note that these solutions are the same but they are different to all the contains solutions given here. Because these solutions actually give you the nodes that have a matching text node as a child. The simple markup that I have used does not demonstrate this, but you will soon notice the difference when your markup is highly nested.

And finally here is a jsPerf of these 3 solutions.

Xotic750
  • 22,914
  • 8
  • 57
  • 79
  • For this purpose, `document.getElementsByTagName('*')` would make the code a whole lot simpler. – Ja͢ck Feb 15 '14 at 00:19
  • Yes, I used very simple (not nested) markup for this demonstration. I would be quite happy for you to demonstrate though? I could have used `treeWalker` but this code is cross browser. – Xotic750 Feb 15 '14 at 00:32
  • Using `getElementsByTagName('*')` is cross browser too :) – Ja͢ck Feb 15 '14 at 00:37
  • I was not suggesting that it wasn't. :) – Xotic750 Feb 15 '14 at 00:39
  • I didn't say that you did :) my point was that rolling your own traversal code is rarely necessary. – Ja͢ck Feb 15 '14 at 00:42
  • Noone else has demonstrated a `getElementsByTagName('*')` solution, go on, take the challenge! :P – Xotic750 Feb 15 '14 at 00:44
  • I've already written my own answer, but using the more modern `document.querySelector()` :) – Ja͢ck Feb 15 '14 at 00:53
  • Indeed, though our solutions do not perform exactly the same tasks. I see p.s.w.g has now also added a variation of the DOM walker to his answer. Perhaps he will go a step further and add a `treeWalker` and a `getElementsByTagName('*')` solution too? :) – Xotic750 Feb 15 '14 at 01:00
  • @Jack Has that simplified things? :) – Xotic750 Feb 15 '14 at 01:55
  • Not sure why you're iterating over the child nodes if you can just use textContent (and friends) to find the text value. – Ja͢ck Feb 15 '14 at 02:05
  • Consider this [jsFiddle](http://jsfiddle.net/Xotic750/NrDED/), see the difference? I have given an alternative, it depends on how you interpret `Is there any way to getElementBy the text content?` Is that not also the reason why p.s.w.g added his DOM walker, because it depends on interpretation? – Xotic750 Feb 15 '14 at 02:21
2

Maybe I'm late in answering this, but I didn't see the question tagged with JQuery, so here's a pure JavaScript solution:

var myID = getElementsByTextContent('Item | Anodized Navy');

function getElementsByTextContent (elText){
    var spanID;
    var allSpans = document.getElementsByTagName("span"); 
    for (var i = 0; i < allSpans.length; i++) { 
        var spanText = allSpans[i].innerHTML;
        if(spanText == elText ){
            spanID = allSpans[i].id;
        }
    }
    return spanID;
}
edwh
  • 58
  • 1
  • 5
0

You can simply use Jquery's :contains()

If you want to select all spans containing the text "mytext", use

var elements=$('span:contains("mytext"));

Please mark as answer if I helped you.

Thank you :)

Ananthan Unni
  • 1,304
  • 9
  • 23