Reputation: 4546
Here are some examples, which I would expect to work with $
:
$(':target', element); // Should return element
$(':parent', element); // Should return parent of element
$(':parent textarea', element); // Should return textareas within parent of element
The reason I don't want to use the parent
method is because the selector will be dynamic, and I don't want to have to parse it.
Edit:
Here's a concrete example.
HTML:
<div>
<span data-hide=":parent span:eq(1)">Span 1</span>
<span>Span 2</span>
</div>
JavaScript:
$('[data-hide]').on('click', function()
{
var selector = $(this).attr('data-hide');
$(selector, $(this)).hide();
});
If the 2nd parameter of $
(called context
) and :parent
pseudo selector worked the way I expected, clicking "Span 1" would hide "Span 2".
But it doesn't. So how can I write this function so that any reasonable absolute or relative selector can be used?
Upvotes: 0
Views: 796
Reputation: 93611
I had a similar requirement (basically search up then down the DOM using a single string selector), and after digging into how selectors work I found this was not possible with normal selectors as the evaluation of the selector elements is right-to-left, whereas you need left-to-right to make your suggestion work. I was able to make a custom selector that could have a :this
reference, which can let you do similar searches including :has(:this)
. I have repeated my answer here from Is it possible to create custom jQuery selectors that navigate ancestors? e.g. a :closest or :parents selector
Based on numerous comments, and one detailed explanation on why this is impossible, it occurred to me that the aim I wanted could be met with a $(document).find()
, but with some concept of targeted elements. That is, some way to target the original query elements, within the selector.
To that end I came up with the following, a :this
selector, which works like this (no pun intended):
// Find all labels under .level3 classes that have the .starthere class beneath them
$('.starthere').findThis('.level3:has(:this) .label')
This allows us to now, effectively, search up the DOM then down into adjacent branches in a single selector string! i.e. it does the same job this does (but in a single selector):
$('.starthere').parents('.level3').find('.label')
1 - Add a new jQuery.findThis
method
2 - If the selector has :this
, substitute an id search and search from document
instead
3 - If the selector does not contain a :this
process normally using the original find
4 - Test with selector like $('.target').find('.ancestor:has(:this) .label')
to select a label within the ancestor(s) of the targetted element(s)
This is the revised version, based on comments, that does not replace the existing find and uses a generated unique id.
// Add findThis method to jQuery (with a custom :this check)
jQuery.fn.findThis = function (selector) {
// If we have a :this selector
if (selector.indexOf(':this') > 0) {
var ret = $();
for (var i = 0; i < this.length; i++) {
var el = this[i];
var id = el.id;
// If not id already, put in a temp (unique) id
el.id = 'id'+ new Date().getTime();
var selector2 = selector.replace(':this', '#' + el.id);
ret = ret.add(jQuery(selector2, document));
// restore any original id
el.id = id;
}
ret.selector = selector;
return ret;
}
// do a normal find instead
return this.find(selector);
}
// Test case
$(function () {
$('.starthere').findThis('.level3:has(:this) .label').css({
color: 'red'
});
});
Known issues:
This leaves a blank id
attribute on targetted elements that did not have an id
attribute to begin with (this causes no problem, but is not as neat as I would like)
Because of the way it has to search from document, it can only emulate parents()
and not closest()
, but I have a feeling I can use a similar approach and add a :closest()
pseudo selector to this code.
This was based on 6 hours slaving over jQuery/Sizzle source code, so be gentle. Always happy to hear of ways to improve this replacement find
as I am new to the internals of jQuery :)
You can then solve your original problem with something like this:
<div>
<span data-hide="div:has(:this) span:eq(1)">Span 1</span>
<span>Span 2</span>
</div>
JavaScript:
$('[data-hide]').on('click', function()
{
var selector = $(this).attr('data-hide');
$(this).findThis(selector).hide();
});
Upvotes: 1