2

I want to select elements that contain text regardless of their type. I don't want to select their ascendants. I tried this $(":contains('Twilight')").css('border-left', '1px solid purple'); but that selects elements with the text and it's ancestor elements up the dom tree.

Below is some HTML for example. I want to select the ones marked with <!-- THIS -->, nothing else. How do I do that?

<div>                                       <!-- no -->
    <h1>Magical ponies</h1>                 <!-- no -->
    <ul>                                    <!-- no -->
        <li>Fluttershy</li>                 <!-- no -->
        <li>Pinkie pie</li>                 <!-- no -->
        <li>Twilight sparkle</li>           <!-- THIS -->
        <li>Hurdurrdurr</li>                <!-- no -->
    </ul> 
</div>
<div>Twilight is a movie too</div>          <!-- THIS -->
<p>Hurr                                     <!-- no -->
    <span>Twilight ZONE</span>              <!-- THIS -->
Durr</p>

Here's a jsfiddle to play with: http://jsfiddle.net/34C7Z/ - I specifically want the solution to be jquery and not native JS.

Joel Peltonen
  • 13,025
  • 6
  • 64
  • 100
  • 1
    Some of the solutions use a `*`, or no selector (which is the same as *). You should try to change that to some elements. If you know its always a div, li or p, select those like this `$('p.div.li'):filter('selectorHere')`. Will speed things up, wildcard selectors are slow – Martijn Nov 19 '13 at 15:20
  • 1
    You'll need comma's to separate the possible tags. Now you're just referencing a `

    ` element, which will most likely not return any results.
    – Flater Nov 19 '13 at 15:40

3 Answers3

1

The only solution I see :

$("*").filter(function(){
    return $(this).clone()    //clone the element
    .children() //select all the children
    .remove()   //remove all the children
    .end()  //again go back to selected element
    .text() //get the text
    .indexOf("Twilight") >= 0;
}).css('border-left', '1px solid purple');

I took the filter function body from here : Using .text() to retrieve only text not nested in child tags

The thing is that contains selector uses text jQuery function that returns takes everything following the element and stripping html tags. This is why you have to do it programmatically, jQuery has no function or selector returning only text not in nested children.

You should change the $("*") selector at the beginning.

Community
  • 1
  • 1
OlivierH
  • 3,875
  • 1
  • 19
  • 32
0

Check it (with a little help from this SO answer):

$('*').contents().filter(function(){
    return this.data && this.data.match(/Twilight/g);
}).parent().css('border-left','1px solid purple');

I tested it on a slightly different version of your HTML that includes another use case: rather than assuming the text is the last node in the descendants list, it accounts for sibling nodes to exist next to the text/CDATA.

Oh, and as a bonus, you get regex, and don't have to consume more memory by duplicating nodes - this is simply traversal.

And the updated fiddle: http://jsfiddle.net/34C7Z/23/


Regarding the comment about "double applying/checking" below - in which case if a matched element contains another matched element, both would have the css applied - this will make sure only the relative top-most is matched (could use some cleaning up):

var v = $('*').contents().filter(function(){
    return this.data && this.data.match(/Twilight/g);
}).parent();
v.children().each(function(){
    v = v.has(this)?v.not(this):v; // meh, modifies the original, w/e
});
v.css('border-left', '1px solid purple');

And the fiddle for this version of the solution: http://jsfiddle.net/34C7Z/26/

Community
  • 1
  • 1
zamnuts
  • 9,492
  • 3
  • 39
  • 46
-2

Well, this is about as far as I got before giving up. No idea why, but jQuery just doesn't care if the .not selector is there or if it isn't.

var e = $(document);   
e.find("*").css('border-left', '1px solid purple');
var val = e.not(':contains("Twilight")');
val.css('border-left', '7px solid red');

Here's a fiddle.

  • This won't work if the parent AND the child contains Twilight. But still a good idea, we could think about a recursive function using this. – OlivierH Nov 19 '13 at 15:19
  • That's true. You're right. I tried it with the `
      ` and it no worky. :(
    – usernolongerregistered Nov 19 '13 at 15:19
  • I love when people downvote, but don't explain why. It's my favorite. – usernolongerregistered Nov 19 '13 at 15:37
  • i didn't downvote, but perhaps this could be a reason why: http://jsfiddle.net/34C7Z/25/ doesn't work in all cases! – zamnuts Nov 19 '13 at 16:15
  • I realize it doesn't work in all cases, that was established through the comments, though it does answer the question with the stipulation that OP gave, meaning it isn't a "useless" question as the downvote would imply, it's just not perfect. I was going to work on a more adequate solution, but I've given up. It's a shame that even this community has trolls :\ – usernolongerregistered Nov 19 '13 at 16:18