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

时间:2014-09-08 09:43:13

标签: javascript jquery jquery-selectors sizzle

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

e.g。 :focusable看起来像这样

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

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

$(':focusable').css('color', 'red');  // color all focusable elements red

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

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

$('input:focus').closest('.form-group').find('.label');

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

e.g。类似的东西:

$('input:focus < .form-group .label');

$('input:focus:closest(.form-group) .label');

注意:请假设更复杂的操作,并且需要 的祖先导航(我意识到这个特定的例子可以用has完成,但这没有帮助。)

e.g。它还需要支持这个:

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

$('input').click(function(){
    var label = $(this).find(options.selector);
});

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

更新

看起来完整的自定义选择器(如<)并不像向jQuery的Sizzle解析器添加伪选择器那么容易。我目前正在查看此Sizzle documentation,但我发现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");
 };

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

但它永远不会进入console.log语句,我仍然得到"Syntax error, unrecognized expression: unsupported pseudo: closest"。在jQuery中进行跟踪时,它会尝试将其应用为filter而不是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;
        }
    }
}

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

2 个答案:

答案 0 :(得分:7)

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

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

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

这使我们现在可以有效地搜索DOM,然后在单个选择器字符串中搜索到相邻的分支!即它执行相同的工作(但在单个选择器中):< / p>

$('.starthere').parents('.level3').find('.label')

步骤:

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'
    });
});

已知问题:

  • 这会在没有id属性的目标元素上留下空白id属性(这不会​​导致问题,但不像我想的那样整洁)< / p>

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


以下第一个版本:

1 - 保存jQuery&#39; :closest()方法以供参考

2 - 替换新的find方法

3 - 如果选择器有jQuery.find,请替换ID搜索并从:this搜索

4 - 如果选择器不包含document进程,通常使用原始:this

5 - 使用find之类的选择器进行测试以选择目标元素的祖先

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

$('.target').find('.ancestor:has(:this)')

这是基于对jQuery / Sizzle源代码的6个小时的奴役,所以要温和。我很高兴听到有关改进此替换// 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' }); }); 的方法,因为我是jQuery内部的新手:)

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

find

e.g。 http://jsfiddle.net/TrueBlueAussie/z3vwk1ko/25/

答案 1 :(得分:3)

不可能,我会说明原因

要创建自定义选择器,您应该需要以下内容:

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

选择器逻辑允许您从当前选择中选择子集元素,例如,如果$(“div”)选择页面上的所有div,则以下两个示例将选择所有没有子元素的div:

$("div:empty")
$("div").filter(":empty")

根据jQuery允许你编写自定义选择器的方式,没有办法返回原始集之外的元素,你只能在被问到时为集合中的每个元素选择true或false。例如,“:contains”选择器以这种方式在jQuery的2.1.1版本上实现:

function ( text ) {
        return function( elem ) {
            return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
        };
}

但是... (也许你想先看最后一次编辑) 也许,你会发现一个有用的给定元素的所有祖先的选择器,例如,想象一下这个场景:

<element1>
  <div>
    <span id="myelementid"></span>
  </div>
  <div>
    <p>Lorem ipsum sir amet dolor... </p>
  </div>
</element1>
<element2>
</element2>

并且,你想要涉及myelementid的div父元素,element1和所有祖先等等,你可以使用“has”选择器,这样:

$("*:has('#myelementid')")

第一个选择器将返回所有元素,通过“has”过滤将返回所有具有#myelementid作为后代,ergo,#myelementid的所有祖先的元素。 如果你担心性能(也许你应该),你需要用更具体的东西替换'*',也许你正在寻找div

$("div:has('#myelementid')")

或者对于给定的班级

$(".agivenclass:has('#myelementid')")

我希望这会有所帮助

修改

你可以使用jquery的parents()“selector”,因为它返回元素的所有祖先,甚至包括文档的根元素(即html标记),这样:

$('#myelementid').parents()

如果要过滤某些元素(例如div),可以使用:

$('#myelementid').parents(null, "div")

或者这个:

$('#myelementid').parents().filter("div")