I write a lot of jQuery plugins and have custom jQuery selectors I use all the time like :focusable
and :closeto
to provide commonly used filters.
e.g. :focusable looks like this
jQuery.extend(jQuery.expr[':'], {
focusable: function (el, index, selector) {
return $(el).is('a, button, :input[type!=hidden], [tabindex]');
};
});
and is used like any other selector:
$(':focusable').css('color', 'red'); // color all focusable elements red
I notice none of the jQuery selectors available can navigate back up ancestors. I gather that is because they were designed to follow the basic CSS selector rules which drill down.
Take this example: which finds the label for an input that has focus:
$('input:focus').closest('.form-group').find('.label');
I need the equivalent type of complex selectors for plugins, so it would be useful to provide such a selector as a single string (so they can be provided as options to the plugin).
e.g. something like:
$('input:focus < .form-group .label');
or
$('input:focus:closest(.form-group) .label');
Note: Please assume more complex operations and that ancestor navigation is required (I realize this particular example can be done with has
, but that does not help).
e.g. it also needs to support this:
options.selector = ':closest(".form-group") .label';
$('input').click(function(){
var label = $(this).find(options.selector);
});
Is it possible to extend jQuery selectors to extend search behavior (and not just add more boolean filters)? How do you extend custom search behavior?
Update:
It appears a complete custom selector (like <
) would not be as easy as adding a pseudo selector to jQuery's Sizzle parser. I am currently looking at this Sizzle documentation, but I am finding inconsistencies with the jQuery version. (e.g. no Sizzle.selectors.order
property exists at runtime).
For reference, jQuery stores Sizzle
on its jQuery.find
property and Sizzle.selectors
on its jQuery.expr
property.
so far I have added this:
jQuery.expr.match.closest = /^:(?:closest)$/;
jQuery.expr.find.closest = function (match, context, isXML){
console.log("jQuery.expr.find.closest");
};
and call it with a simple test: http://jsfiddle.net/z3vwk1ko/2/
but it never gets to the console.log statement and I still get "Syntax error, unrecognized expression: unsupported pseudo: closest"
. On tracing inside jQuery it is trying to apply it as a filter
instead of a find
, so I am missing some key part.
Update 2:
The processing for selectors works right-to-left (see extract from jQuery 1.11.1 below) so if the last argument does not existing in the context, it aborts early. This means navigating upwards will not occur with the current jQuery Sizzle code in the common case where we want to look for an element in another DOM branch of an ancestor:
// Fetch a seed set for right-to-left matching
i = matchExpr["needsContext"].test(selector) ? 0 : tokens.length;
while (i--) {
token = tokens[i];
// Abort if we hit a combinator
if (Expr.relative[(type = token.type)]) {
break;
}
if ((find = Expr.find[type])) {
// Search, expanding context for leading sibling combinators
if ((seed = find(
token.matches[0].replace(runescape, funescape),
rsibling.test(tokens[0].type) && testContext(context.parentNode) || context
))) {
// If seed is empty or no tokens remain, we can return early
tokens.splice(i, 1);
selector = seed.length && toSelector(tokens);
if (!selector) {
push.apply(results, seed);
return results;
}
break;
}
}
}
I was surprised to see this, but realise now that it made the rule engine much easier to write. It does mean however we need to make sure the right-hand end of a selector is as specific as possible, as that is evaluated first. Everything that happens after that is just progressive pruning of the result set (I had always assumed the first items in the selector had to be more specific to increase efficiency).
See Question&Answers more detail:
os