Bootstrap 每次打开新模态时,模态内部代码都会成倍增加

Bootstrap modal inside code is multiplying every time a new modal opens

你好,所以我为我建立的聊天创建了一个房间密码验证,我使用 Bootstrap 的 4 模式向用户显示密码输入。

我正在使用 jQuery 打开模式,就像这样 $("#password-validation").modal();。它很好用。但是在你关闭模态框,重新打开它并提交输入后,“如果用户点击回车按钮”里面的代码执行了多次而不是on。

// #room-password-btn is the enter button
$("#room-password-btn").click(function(){ 
  console.log("Execute")
}); 

举个例子:如果您(打开模态按钮并关闭模态)* 3 然后单击您将在控制台中看到的输入按钮:

// first open and close
// second open and close
Execute // third open, enter
Execute
Execute

问题的工作模型:https://codepen.io/PachUp/pen/LYGQozz(尝试关闭它并按回车键,您会看到多条消息而不是一条消息,关闭得越多,看到的消息就越多。我相信如果我解决了这个问题,问题就解决了)。

模态:

<div class="modal fade" id="password-validation" tabindex="-1" role="dialog" aria-labelledby="room-password-val-aria" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header text-center">
        <h4 class="modal-title w-100 font-weight-bold">Enter the room password</h4>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body mx-3">
        <div class="md-form mb-5">
          <label data-error="wrong" data-success="right">Room password</label>
          <input class="form-control" id="room-password-val">
        </div>
      </div>
      <div class="modal-footer d-flex justify-content-center">
        <button class="btn btn-default" id="room-password-btn">Enter</button>
      </div>
    </div>
  </div>
</div>

完整的 JS:

    corrent_pass = false;
    room_pass = ""
    $.ajax({
        type: "POST",
        url: "/validate",
        data: new_room,
        success: function(pass){
            room_pass = pass; // pass is the room password
        }
    });
    
    console.log("room pass: " + room_pass)
    console.log(room_pass.length)
    if(room_pass != "False"){ 
        $("#password-validation").modal('toggle');
        $('#password-validation').on('shown.bs.modal', function () {
            $('#room-password-val').trigger('focus');
            $("#room-password-btn").mousedown(function(){
                console.log("Execute")
                user_password = $("#room-password-val").val()
                console.log(user_password)
                console.log(room_pass)
                if(user_password == room_pass){
                    corrent_pass = false
                    $("#messages").html("");
                    $("#password-validation .close").click()
                    toastr["success"]("Entering you into the room!", "Corrent password")
                    console.log("Joining a room")
                    socket.emit('leave', {"room" : room})
                    socket.emit("join", {"room" : new_room})
                    room = new_room
                }
                else{
                    toastr["error"]("Sorry, you have entered the wrong password. Try again.", "Wrong password")
                }
            });
        });
    }
    else{
        console.log("no password")
        $("#messages").html("");
        console.log("Joining a room")
        socket.emit('leave', {"room" : room})
        socket.emit("join", {"room" : new_room})
        var room_msg = "You have joined " + new_room
        toastr["success"](room_msg, "Room joined")
        room = new_room
    }
}

你已经检查了你的模式是否在这一行之后 $("#password-validation").modal(); 通过这样做:

console.log($('#password-validation').is(':visible')) // false every time
console.log($('#password-validation').hasClass('in')) // false every time

所以这两行在显示模态之前首先执行。您可以在此事件显示模态后进行检查:

$('#password-validation').on('shown.bs.modal', function () {
    // will only come inside after the modal is shown
    console.log($('#password-validation').is(':visible')) // true every time if shown
    console.log($('#password-validation').hasClass('show')) // true every time if shown
});

而且仔细看,$('#password-validation').hasClass('in')这在bootstrap 4中是行不通的。所以你可以通过我在事件中提到的这种方式来检查是否显示了模态。

我想这会解决你的问题。

Working JSFiddle(我用简单的 alert() 替换了您的 toastr 内容,并注释掉了 socket 内容)。

问题是您正在添加基于可重复用户的事件侦听器 activity。例如:

if(room_pass != "False"){ 
    // ...
    // Adding an event handler for the modal shown event based on 
    // previous user activity
    $('#password-validation').on('shown.bs.modal', function () {
        // ... 
        // Adding another event handler, inside a previous event handler
        $("#room-password-btn").mousedown(function(){

但是添加事件处理程序不会替换任何先前已附加的事件处理程序 - 它们堆叠在一起,即使它是完全相同的事件的完全相同的处理程序。因此,如果您重复添加处理程序的条件,您将在旧处理程序之上添加一个新处理程序。当触发时,它们将全部 运行 在一起。

每次关闭模式时,都会添加另一个处理程序在它打开时执行某些操作,这又会添加另一个处理程序在 mousedown 上执行某些操作。第一次关闭它时,您有一组处理程序可以做一些事情。如果你打开它再关闭它,你添加了一个新的集合,当事件被触发时它们将全部 运行。

更好的方法是仅添加一次处理程序,而不受任何用户行为或条件的影响。这样您就可以确定它们只连接了一次。如果您希望处理程序执行的操作取决于用户的操作,您应该测试处理程序内的状态,而不是相反。

所以在这种情况下:

// Add event handlers, independent of any other activity
$('#password-validation').on('hidden.bs.modal', function () {
    console.log("Closed")
    // ...
});

$('#password-validation').on('shown.bs.modal', function () {
    $('#room-password-val').trigger('focus');
});

$("#room-password-btn").mousedown(function(){
    // ...
});

// ... 

if (room_pass != "False") {
    $("#password-validation").modal('toggle');

} else{
    console.log("Joining a room")
    // ...
}

请注意,如果需要,您也可以 remove handlers using .off(),在某些复杂的情况下,这可能是可行的方法 - 在状态更改时添加和删除处理程序。但是这里不需要这么复杂。

更新

根据评论,实际问题出在一些未显示的代码中,这些代码处理用户单击聊天室菜单的操作,该菜单是从数据库动态填充的。我在上面描述了如何处理这个问题的一般情况 - 测试事件处理程序内的状态,以便您可以采取适当的措施。这是一个示例 - 不是基于您的实际 HTML 或代码,因为我没有。

HTML 示例:

<ul class="dropdown-menu">
    <li><a href="/rooms/chat1" data-room="room1">Chat 1</a></li>
    <li><a href="/rooms/chat2" data-room="room2">Chat 2</a></li>
    <li><a href="/rooms/chat3" data-room="room3">Chat 3</a></li>
</ul>

JS:

// Password will be set in AJAX below
var room_pass;

// Register a handler on .dropdown-menu, even if it has no elements
// on page load.  Every click will be filtered to check it it was 
// on an <a>.  This way you can dynamically add contents to the menu,
// and still handle those clicks.
$('.dropdown-menu').on('click', 'a', function(e) {

    // Don't actually follow the link 
    e.preventDefault();

    // In the handler, "$(this)" is the event target.  So you can 
    // find which link was clicked like this:
    var href = $(this).attr('href');
    
    // Or its text:
    var text = $(this).text();

    // Or maybe it has data attributes
    var room = $(this).data('room');

    // Not sure I understand your comments but maybe you want something
    // like this, where you pass something about the clicked link to the
    // AJAX query
    $.ajax({
        type: "POST",
        url: "/validate",
        data: room,
        success: function(pass) {
            room_pass = pass;
        }
    });
});

次要旁注:

  • 使用mousedown 处理密码输入意味着用户无法使用键盘提交。这不是好的用户体验,它看起来像一个表单,所以点击回车应该可以,当然还有可访问性问题。要解决此问题,您需要在模态中实际添加一个 <form>,并在该表单上将 mousedown 处理程序替换为 submit 处理程序。

  • corrent_pass 中的错字,可能是 correct?只要始终如一地使用它并不重要,但拼写错误是给未来的自己带来很多困惑的好方法! :-)

你可以通过一行代码解决这个问题 off 正确的 after your #password-validation 事件监听器它基本上做了什么 unbind/removes 触发后的事件监听器,这样你就不会再次打开模态后会触发一堆事件

$('#password-validation').on('shown.bs.modal', function () {
$('#room-password-val').trigger('focus');
$("#password-validation").off("shown.bs.modal");

这里是jsfiddle

Bootstrap 现在有一个 .one 方法,它只会在触发器上触发一次回调,然后再解除绑定。

$('#password-validation').one('shown.bs.modal', function () {
   $('#room-password-val').trigger('focus');
}