5

我正在尝试在单击日期时制作 jQuery ui 日历进行 ajax 调用,但几天前我遇到了问题。我发现了一段应该可以做到这一点的代码,但我发现它使用了 jQuery 自定义选择器。该代码给了我一个错误,所以我开始深入研究自定义选择器以了解有关它们的更多信息。到目前为止,我还无法找出为什么我会出现这种奇怪的行为。

这是一张希望能把事情弄清楚的图片,我会在它之后解释更多 在此处输入图像描述

我在控制台输入

$('.ui-datepicker-calendar td a:test(3)')

正如你所看到的,我 meta2 和 stack2 是未定义的,还有一件更奇怪的事情,为什么 index2 返回一个 #document ,它应该包含元素数组的索引。

此外,元素 (el2) 甚至不是正确的元素。看看,我打电话

$('.ui-datepicker-calendar td a:test(3)')

这应该从日历中选择所有日期,并且在第一个循环中,console.log 应该打印出这个

<td class=" ui-datepicker-week-end " data-handler="selectDay" data-event="click" data-month="8" data-year="2012"><a class="ui-state-default" href="#">1</a></td>

但相反,我得到了整个文档中的第一个“a”标签,在这种情况下,它是上个月的标签(如图所示)。

如果有人可以对这种情况有所了解,请这样做。哦,还有一件事我忘记了

meta2 ,它应该包含这个

[
    ':test(argument)', // full selector
    'test',            // only selector
    '',                // quotes used
    'argument'         // parameters
]

在我的情况下,它又是未定义的......

我将分享我的 javascript 代码,希望对您有所帮助

<script>
    $(function()
    {
        $.expr[":"].test = function(el2,index2,meta2,stack2)
        {
            debugger;
            console.log(el2);
            console.log(index2);
            console.log(meta2);
            console.log(stack2);
        }
    })

    $(function()
    {
        function getJsonDate(year, month)
        {
            $.getJSON('dates.php?year='+year+'&month='+month, function(data)
            {
                var i = 0;
                for (i = 0; i < data.data.length; i++)
                {
                    debugger;
                    var myDay = data.data[i]['d'];
                    $('.ui-datepicker-calendar td a:exactly('+data.data[i]['d']+')')
                    .css({color: '#f00'})
                    .attr('href',data.data[i]['link'])
                    .parent().attr('onclick','');
                }
            });
        }
        $.expr[":"].exactly = function(el, index, meta, stack) 
        {
            debugger;
            console.log(el);
            console.log(index);
            console.log(meta);
            console.log(stack);
            var s = meta[3];
            if (!s) return false;
            return eval("/^" + s + "$/i").test($(el).text());
        };
        $('#datepicker').datepicker(
        {
            inline: true,
            onSelect: function(dateText, inst) 
            {
                Date.prototype.toString = function () {return isNaN (this) ? 'NaN' : [this.getDate(), this.getMonth(), this.getFullYear()].join('/')}
                d = new Date(dateText);
                getJsonDate(d.getFullYear(), d.getMonth()+1);
            },
            onChangeMonthYear: function(year, month, inst) 
            {
                //alert(year);
                //alert(month);
                getJsonDate(year, month);
            }
        });
    });
</script>
4

2 回答 2

6

最短的解释是“jQuery 1.8.0 中有一个错误,升级到 1.8.1 进行修复”,但这并不能完全回答所有问题。

jQuery 1.8.x 有一个显着升级的“Sizzle”引擎,它用于选择器。作为此更改的一部分,调用自定义选择器的方式已更改,但此外,处理许多选择器的方式已更改。关于规则处理顺序的各种假设不再成立,等等。在各种用例中它也明显更快。

即使升级到 1.8.1,在处理您提供的示例时,您仍然会看到与在 1.7.2(1.8.x 之前的系列中的最新版本)中所做的方式有很大不同。这解释了您在选择“页面上的第一个 <a> 元素”时看到的内容。也就是说:您对自定义选择器如何工作的期望不是它们实际如何工作,如果您允许循环继续(而不是在第一次迭代时调用“调试器”),您会看到它实际上正在通过所有<a> 元素)。简而言之:Sizzle 不保证将调用各种规则的顺序,只保证结果将匹配所有规则。

如果您确定您的自定义规则将比其他规则效率低(可能是因为您确定其他规则会严重减少匹配元素的数量),您可以通过选择它们来强制它们首先运行,然后调用 .find () 仅在该元素子集上,例如:

$(".ui-datepicker-calendar").find("td a:test(3)");

至于未定义的“堆栈”,正如 Kevin B 指出的那样,尽管 1.8.1 更新恢复了向后兼容性,但 API 已经发生了变化,看起来“堆栈”只是不再传递给伪。实际上,这是有道理的,因为调用测试的顺序发生了变化。也就是说:当你到达堆栈时,堆栈是空的,因为“查看是否有任何 <a> 元素与这个伪选择器匹配”是第一个被处理的规则。测试应该始终是独立的,因此堆栈无论如何都不会非常有用(可能只会导致混乱)。

那么如果 1.8.1 恢复了向后兼容,那么创建伪选择器的前向兼容方法是什么?正如您在Sizzle 的伪选择器的文档中看到的那样,从 jQuery 1.8 开始,创建伪选择器的首选方法是“createPseudo”方法($.expr.createPseudo),它更喜欢使用闭包而不是“元“ 争论。因此,对于您的特定示例,执行它们的“新”方法将是:

$.expr[":"].test = $.expr.createPseudo(function( tomatch )
{
        return function( el2 )
        {
            debugger;
            console.log(el2); // not much else to see here
        };
})

其中“tomatch”是 :test(...) 选择器的参数。如您所见,您正在寻找的额外参数在这种新语法中不再需要。至于更有用的东西:

$.expr[":"].exactly = $.expr.createPseudo(function( s )
{
    return function(el)
    {
        if(!s) return false;
        return eval("/^" + s + "$/i").test($(el).text());
    };
});

这个版本的“exactly”应该兼容1.8+,是首选*的做事方式。

我认为即使 jQuery 版本/api 出现了问题,您提供的代码仍然不会完全符合您的要求,因为 datepicker 可能会根据插件的一时兴起而重建。仍然有一小段时间您可以判断所需元素确实按预期突出显示,因此选择器本身似乎正在工作。

下面是一个完整的示例,基于您提供的示例。您可以通过更改 1.7.2、1.8.0 和 1.8.1 之间使用的 jQuery 版本来查看行为差异。为了跨版本的兼容性,对 $.expr.createPseudo 的测试已添加到伪选择器函数分配中。注意所有的“调试器;” 语句已被注释掉,因为在 datepicker 中的所有日期链接的每次迭代中都有一个断点变得相当乏味,并且 getJSON 调用已被模拟以允许测试是独立的。

<html>
<head>
    <title>jQuery custom selector, "undefined"</title>
    <!-- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"></script> -->
    <!-- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.js"></script> -->
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.js"></script>
    <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.js"></script>
    <link rel="stylesheet"
        href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/base/jquery-ui.css" />
    <script>
    $(function()
    {
        $.expr[":"].test = $.expr.createPseudo ?
            $.expr.createPseudo(function( tomatch )
            {
                return function( el2 )
                {
//                  debugger;
                    console.log(el2);
                };
            }) :
            function(el2,index2,meta2,stack2)
            {
    //          debugger;
                console.log(el2);
                console.log(index2);
                console.log(meta2);
                console.log(stack2);
            };
    })

    $(function()
    {
        function getJsonDate(year, month)
        {
            //$.getJSON('dates.php?year='+year+'&month='+month, function(data)
            //{
                var data = {data:[
                    {d:1,link:"a"},
                    {d:15,link:"b"},
                    {d:25,link:"c"}
                ]};
                var i = 0;
                for (i = 0; i < data.data.length; i++)
                {
//                  debugger;
                    var myDay = data.data[i]['d'];
                    $('.ui-datepicker-calendar td a:exactly('+data.data[i]['d']+')').
                        css({color: '#f00'}).
                        attr('href',data.data[i]['link']).
                        parent().attr('onclick','');
                }
            //});
        }

        $.expr[":"].exactly = $.expr.createPseudo ?
            $.expr.createPseudo(function( s )
            {
                return function(el)
                {
                    if(!s) return false;
                    return eval("/^" + s + "$/i").test($(el).text());
                };
            }) :
            function(el, index, meta, stack) 
            {
//              debugger;
                console.log(el);
                console.log(index);
                console.log(meta);
                console.log(stack);
                var s = meta[3];
                if (!s) return false;
                return eval("/^" + s + "$/i").test($(el).text());
            };

        $('#datepicker').datepicker(
        {
            inline: true,
            onSelect: function(dateText, inst) 
            {
                Date.prototype.toString = function () {
                    return isNaN (this) ?
                        'NaN' :
                        [this.getDate(), this.getMonth(), this.getFullYear()].join('/')
                }
                d = new Date(dateText);
                getJsonDate(d.getFullYear(), d.getMonth()+1);
            },
            onChangeMonthYear: function(year, month, inst) 
            {
                //alert(year);
                //alert(month);
                getJsonDate(year, month);
                return false;
            }
        });
    });
    </script>
    <script>
    (function($){$(function(){
        $("<input />").
            attr({type:"button", value: "run test selector"}).
            click(function(){
                $(".ui-datepicker-calendar td:test(3) a");

                // Or, if you are certain that your test will be less-efficient than an exclusion based
                // on parents, you could do:
                //  $(".ui-datepicker-calendar").find("td a:test(3)");
            }).
            appendTo("body");
    })}(window.jQuery));
    </script>
</head>
<body>
    <a href="#ignoreThisLink">.</a>
    <a href="#ignoreThisToo">.</a>
    <p>
        (first, click the input box to cause the datepicker to initialise)
    </p>
    <input type="text" id="datepicker" />
</body>
</html>

我希望这有助于阐明一些事情。

*我说这是“首选”方法,但您仍在使用“eval”。这是非常不鼓励的,因为它很慢并且可能导致意外/令人惊讶/危险的结果。基于字符串构建正则表达式的更好方法是

return (new RegExp("^" + s + "$", "i")).test($(el).text());

尽管这也有问题,因为“s”可能包含 RegExp 特殊字符。但是,在这种特殊情况下,甚至不需要正则表达式,并且可以通过以下方式更有效地测试事物:

return String(s).toUpperCase() === $(el).text().toUpperCase();

您甚至可以通过将转换滚动到闭包中来节省更多,为您提供以下全部功能:

$.expr[":"].exactly = $.expr.createPseudo(function( s )
{
        if(!s) return function(){ return false; };
        s = String(s).toUpperCase();

        return function(el)
        {
            return (s === $(el).text().toUpperCase());
        };
});
于 2012-09-12T20:13:57.360 回答
4

jquery ui datepicker 具有这种功能的钩子。与其尝试定位构成日期的 DOM 元素,不如绑定到选择一个的行为。jsFiddle

$('#datepicker').datepicker({
    onSelect: function(dateText, inst){
        //awesome ajax stuff based on dateText
    }
});​

编辑评论:如果您需要设置特定日期的样式,那么您应该通过在绘制之前应用自定义类来定位它。jsFiddle

$('#datepicker').datepicker({
    beforeShowDay: function(date){
        return [true, 'date-' + date.getDate() ];
    }
});
于 2012-09-11T21:00:58.663 回答