是否可以创建导航祖先的自定义jQuery选择器?例如a:最近或:父母选择器

Gon*_*ing 17 javascript jquery jquery-selectors sizzle

我编写了很多jQuery插件,并且我一直使用自定义jQuery选择器,:focusable:closeto提供常用的过滤器.

例如:focusable看起来像这样

jQuery.extend(jQuery.expr[':'], {
    focusable: function (el, index, selector) {
        return $(el).is('a, button, :input[type!=hidden], [tabindex]');
    };
});
Run Code Online (Sandbox Code Playgroud)

并像任何其他选择器一样使用:

$(':focusable').css('color', 'red');  // color all focusable elements red
Run Code Online (Sandbox Code Playgroud)

我注意到没有一个可用的jQuery选择器可以导航回祖先.我认为这是因为它们的设计遵循了向下钻取的基本CSS选择器规则.

以此示例:找到具有焦点的输入的标签:

$('input:focus').closest('.form-group').find('.label');
Run Code Online (Sandbox Code Playgroud)

我需要用于插件的等效类型的复杂选择器,因此将这样的选择器作为单个字符串提供是有用的(因此它们可以作为插件的选项提供).

例如:

$('input:focus < .form-group .label');
Run Code Online (Sandbox Code Playgroud)

要么

$('input:focus:closest(.form-group) .label');
Run Code Online (Sandbox Code Playgroud)

注:请假设更复杂的操作和祖先导航需要(我知道这个特殊的例子是可以做到的has,但这并不能帮助).

例如它还需要支持这个:

options.selector = ':closest(".form-group") .label';

$('input').click(function(){
    var label = $(this).find(options.selector);
});
Run Code Online (Sandbox Code Playgroud)

是否可以扩展jQuery选择器来扩展搜索行为(而不仅仅是添加更多的布尔过滤器)?如何扩展自定义搜索行为?

更新:

看起来一个完整的自定义选择器(比如<)就不像在jQuery的Sizzle解析器中添加伪选择器那么容易.我目前正在查看这个Sizzle文档,但我发现jQuery版本不一致.(例如Sizzle.selectors.order,运行时不存在属性).

作为参考,jQuery的商店Sizzle在其jQuery.find性能和Sizzle.selectors它的jQuery.expr属性.

到目前为止我添加了这个:

 jQuery.expr.match.closest = /^:(?:closest)$/;
 jQuery.expr.find.closest = function (match, context, isXML){
     console.log("jQuery.expr.find.closest");
 };
Run Code Online (Sandbox Code Playgroud)

并通过一个简单的测试来调用它:http://jsfiddle.net/z3vwk1ko/2/

但它永远不会到达console.log语句,我仍然得到"Syntax error, unrecognized expression: unsupported pseudo: closest".在jQuery中进行跟踪时,它试图将其应用为a filter而不是a find,因此我错过了一些关键部分.

更新2:

选择器的处理从右向左工作(参见下面jQuery 1.11.1的摘录),因此如果上一个参数在上下文中不存在,则它会提前中止.这意味着在我们想要在祖先的另一个DOM分支中查找元素的常见情况下,当前的jQuery Sizzle代码不会向上导航:

// 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我很惊讶地看到这一点,但现在意识到它使得规则引擎更容易编写.但这确实意味着我们需要确保选择器的右端尽可能具体,因为首先要对其进行评估.之后发生的一切都只是对结果集进行渐进式修剪(我一直认为选择器中的第一项必须更具体,以提高效率).

Gon*_*ing 7

基于众多评论,以及为什么这是不可能的一个详细解释,我想到我想要的目标可以通过一个目标元素的$(document).find()概念来满足.也就是说,某些方法可以在选择器中定位原始查询元素.

为此,我提出了以下一个:this选择器,它的工作原理如下(没有双关语):

// Find all labels under .level3 classes that have the .starthere class beneath them
$('.starthere').findThis('.level3:has(:this) .label')
Run Code Online (Sandbox Code Playgroud)

这使我们现在可以有效地搜索DOM,然后在单个选择器字符串中向下搜索相邻的分支!即它做同样的工作(但在一个选择器中):

$('.starthere').parents('.level3').find('.label')
Run Code Online (Sandbox Code Playgroud)

脚步:

1 - 添加新jQuery.findThis方法

2 -如果选择了:this,替换一个ID搜索和查起document,而不是

3 - 如果选择器不包含:this正常使用原稿的过程find

4 - 使用选择器$('.target').find('.ancestor:has(:this) .label')进行测试,以便在目标元素的祖先中选择一个标签

这是基于注释的修订版本,它不会替换现有查找并使用生成的唯一ID.

JSFiddle:http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/36/

// 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'
    });
});
Run Code Online (Sandbox Code Playgroud)

已知的问题:

  • 这会id在没有id开始属性的目标元素上留下一个空白属性(这不会导致问题,但不像我想的那样整洁)

  • 由于它必须从文档搜索的方式,它只能模拟parents()而不能closest(),但我有一种感觉,我可以使用类似的方法,并:closest()为此代码添加一个伪选择器.


第一个版本如下:

1 - 保存jQuery的find方法以供参考

2 - 替换新jQuery.find方法

3 -如果选择了:this,替换一个ID搜索和查起document,而不是

4 - 如果选择器不包含:this正常使用原件的过程find

5 - 使用选择器$('.target').find('.ancestor:has(:this)')进行测试,以选择目标元素的祖先

JSFiddle:http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/24/

// Save the original jQuery find we are replacing
jQuery.fn.findorig = jQuery.fn.find

// Replace jQuery find with a custom :this hook
jQuery.fn.find = function (selector) {
    // If we have a :this selector
    if (selector.indexOf(':this') > 0) {
        var self = this;
        var ret = $();
        for (var i = 0; i < this.length; i++) {
            // Save any existing id on the targetted element
            var id = self[i].id;
            if (!id) {
                // If not id already, put in a temp (unique) one
                self[i].id = 'findme123';
            }
            var selector2 = selector.replace(':this', '#findme123');
            ret = ret.add(jQuery(selector2, document));
            // restore any original id
            self[i].id = id;
        }
        ret.selector = selector;
        return ret;
    }
    return this.findorig(selector);
}

// Test case
$(function () {
   $('.starthere').find('.level3:has(:this)').css({
        color: 'red'
    });
});
Run Code Online (Sandbox Code Playgroud)

这是基于jQuery/Sizzle源代码的6个小时,所以要温和.总是很高兴听到改进这个替换的方法,find因为我是jQuery的内部新手:)

它现在意味着我可以解决如何做原始标签示例的初始问题:

options.selector = ".form-group:has(:this) .label";

$('input').click(function(){
    var label = $(this).find(options.selector);
});
Run Code Online (Sandbox Code Playgroud)

例如http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/25/