带有输入搜索框和无序列表的奇怪点击事件模式

Strange click event pattern with input search box and unordered list

我在当前项目中遇到了一个奇怪的模式。我已经尝试搜索 SO 和 Google,但是我不确定究竟要搜索什么。总结我的问题:

我已经设置了一个复制 JSFiddle here。我在 (Windows) Chrome、Waterfox 和 Edge 中遇到过以下行为。基本上,我有一个文本输入作为搜索字段。当输入值发生变化时,将搜索数据库并将有效响应作为列表元素添加到无序列表中。在fiddle中,数据库后端被替换为数组。

当输入更改时,整个列表会被清除,然后根据搜索查询重新填充。当我将列表用作 "menu" 时出现问题 - 我正在监听每个 li 上的 click 事件。当我在框中键入内容然后单击列表项时,不会触发单击事件。当我再次点击列表项时,点击 触发,之后的每次点击都有效(直到我输入另一个查询)。这是奇怪的部分:在触发某个搜索查询的点击事件后,如果我清除输入并再次键入相同的查询,列表项将立即可点击,即使它们刚刚被清除并恢复。

要复制此行为:

  1. 在输入
  2. 中输入"ma"
  3. 单击其中一个结果一次(控制台未打印任何内容)
  4. 再次单击其中一个结果(Clicked! 打印到控制台)
  5. 清除输入并输入其他内容,但不要单击任何结果
  6. 清除输入并再次输入"ma"
  7. 单击其中一个结果。它应该可以立即点击
  8. 输入不同的查询将不会立即得到结果"clickable"

我正在与搜索查询进行不区分大小写的比较,但在第 5 步中键入 "Ma"(或 "MA" 或 "mA")不会使结果立即可点击。

我在三种不同的浏览器中看到过这种行为。如果行为一致,我就不会感到困惑。所以我的问题有两个:

  1. 为什么第一次没有触发点击事件(对于 "new" 查询)?我猜这与输入失去焦点有关。
  2. 为什么在输入最后一个可点击的查询时立即触发点击事件,即使在多次清除无序列表并重新填充之后也是如此?

另外,除了无序列表之外,我应该使用其他东西来提供可点击的搜索结果列表吗?

提前致谢!

根据 Stack Overflow 的 post 编辑器的要求,这里是复制代码:

HTML:

<body>
    <form>
        <input type="text" id="text-input" />
    </form>
    <ul id="results-list">
    </ul>
</body>

JS:

var entries = ["Jason Stewart", "George Bacon", "Martha Stewart", "Cliff Mandarin", "Luke Barnes", "Spandex Assassin"];

$(function() {
    $("#text-input").on("keyup change", function() {
        $("#results-list").html("");
        if ($(this).val().length <= 0) {
          return;
        }
        var query = $(this).val().toUpperCase();
        for (var i = 0; i < entries.length; i++) {
            var entry = entries[i];
            if (entry.toUpperCase().indexOf(query) >= 0) {
                $("#results-list").html($("#results-list").html() + "<li>" + entry + "</li>");
            }
        }
    });

    $("#results-list").on("click", "li", function(evt) {
        console.log("Clicked!");
    });

});

Why does the click event not get triggered the first time (for a "new" query)? I'm guessing it has to do with the input losing focus.

因为您要销毁列表元素并在 change 事件中重新创建它们,如果自从焦点进入文本框以来值已更改,即使焦点离开文本框时也会发生这种情况,即使没有任何更改自上次 keyup 完成工作以来。您单击的元素不再存在;那里有一个相同的替代品。

Why does the click event get triggered instantly when the query that last became clickable is entered, even after the unordered list is cleared several times and repopulated?

因为 change 在这种情况下不会触发;值没有改变。

您可以通过简单地记住您处理的最后一个值而不是替换列表来修复它,除非它被更改。这样,keyup 会更新列表而 change 不会:Updated Fiddle

var entries = ["Jason Stewart", "George Bacon", "Martha Stewart", "Cliff Mandarin", "Luke Barnes", "Spandex Assassin"];

$(function() {
    var lastValue = undefined;                         // **
    $("#text-input").on("keyup change", function() {
        var thisValue = $(this).val();                 // **
        if (lastValue == thisValue) {                  // **
            return;                                    // **
        }                                              // **
        lastValue = thisValue;                         // **
        $("#results-list").html("");
        if (thisValue.length <= 0) {                   // **
          return;
        }
        var query = $(this).val().toUpperCase();
        for (var i = 0; i < entries.length; i++) {
            var entry = entries[i];
            if (entry.toUpperCase().indexOf(query) >= 0) {
                $("#results-list").html($("#results-list").html() + "<li>" + entry + "</li>");
            }
        }
    });

    $("#results-list").on("click", "li", function(evt) {
        console.log("Clicked!");
    });

});

您可能还想将 inputpaste 活动添加到您的活动列表中。


如果您想更好地理解它发生的原因,在填写元素时将事件名称添加到元素中会有所帮助:只需更新您的原始 fiddle(不是我上面的修复)以接受处理函数的 e 参数,并更改添加列表项的行以在文本中包含 e.typeExample

$("#results-list").html($("#results-list").html() + "<li>" + entry + " [" + e.type + "]</li>");

如果您键入 ma,您会看到 keyup,但是当您尝试单击时,您会看到条目被 change 替换。