Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery elements fail to select parent()

It seems elements selected using :contains(sub) with sub containing < or > cannot get to their parents.

The following example should illustrate the problem I'm having in both Safari and Camino (Gecko on Mac):

<html>
 <head>
  <script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
 </head>
 <body>
  <p><strong>bar</strong></p>
  <p><strong>&lt;foo&gt;</strong></p>
  <script type="text/javascript">
alert($('body strong:contains("bar")').length);
alert($('body strong:contains("bar")').parent().length);
alert($('body strong:contains("<foo>")').length);
alert($('body strong:contains("<foo>")').parent().length); // this fails
alert($('body strong').length);
alert($('body strong').parent().length); // two p elements
alert($('body strong').parent().parent().length); // one body
  </script>
 </body>
</html>

Output is:

1
1
1
0
2
2
1

Any ideas why the fourth one is 0 instead of 1, or how I can circumvent this?

This page mentions escaping names in selectors, but that didn't work either (also, I'm not sure if it's applicable).

like image 803
Daniel Beck Avatar asked Sep 28 '10 11:09

Daniel Beck


People also ask

How to use parent selector in jQuery?

The parent() method returns the direct parent element of the selected element. The DOM tree: This method only traverse a single level up the DOM tree. To traverse all the way up to the document's root element (to return grandparents or other ancestors), use the parents() or the parentsUntil() method.

What is parent in jQuery?

The parent() is an inbuilt method in jQuery which is used to find the parent element related to the selected element. This parent() method in jQuery traverse a single level up the selected element and return that element. Syntax: $(selector).parent() Here selector is the selected elements whose parent need to find.

Where can I find grandparents in jQuery?

jQuery parentsUntil() Method The parentsUntil() method returns all ancestor elements between the selector and stop. An ancestor is a parent, grandparent, great-grandparent, and so on.


3 Answers

Disclaimer: This isn't a solution/workaround use Tatu's answer for that, this is just a description of the problem and what's going on to cause the weird behavior for those who are curious.

The root of the problem is here, the regex that jQuery uses to identify an HTML Fragment:

/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/

Which does match your selector:

body strong:contains("<foo>")

You can try it out:

alert(/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/.test('body strong:contains("<foo>")​​​'));​

So it thinks it's an HTML fragment..so overall these are currently equivalent:

$('body strong:contains("<foo>")');
$('<foo>');

Seeing the second is a clearer illustration that it's a document fragment...which has no parent. It takes the 2nd position in the match array, which you can see is just <foo>, again try it out:

alert(/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/.exec('body strong:contains("<foo>")')[1]);​

This results in you ultimately ending up at this code path in jQuery's $(), building a fragment.

So in short yes, I'd consider this a jQuery bug.

like image 191
Nick Craver Avatar answered Oct 23 '22 15:10

Nick Craver


No idea what's causing contains() to fail, but you can use .filter() as an alternative:

alert($('body strong').filter(function() {
    return /<foo>/.test( $(this).text() );
}).parent().length);

That returns 1 as expected. It's ugly, but works.

like image 2
Tatu Ulmanen Avatar answered Oct 23 '22 17:10

Tatu Ulmanen


This is clearly an issue with jQuery's selector parsing. If < and > are present in the selector, jQuery identifies the argument as a document fragment instead of a selector. The result is an element with a tagName of "FOO", and the same selectors would have the same issue:

$('body <foo>')
$('body strong:not(<foo>')

The only difference in your case is that you've used a valid selector and jQuery is identifying it incorrectly.

I made several attempts at a selector-based workaround, but Tatu Ulmanen's was the only one that worked.

EDIT: it seems you can also use .find():

$(document.body).find('strong:contains("<foo>")')
like image 1
Andy E Avatar answered Oct 23 '22 17:10

Andy E