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><foo></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).
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.
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.
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.
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.
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.
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>")')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With