神秘的鼠标事件关闭了jQuery UI对话框

Mar*_*tus 3 javascript jquery jquery-ui jquery-ui-dialog

这显然是一个SSCCE.

所以我们的任务是编写导弹发射控制系统的前端.我们选择Spartan布局,因为这是非常严重的:只需一个文本输入框和一个输入代码的按钮:

在此输入图像描述

为安全起见,点击"确定"按钮后,我们将显示一个对话框,要求用户确认:

在此输入图像描述

作为可用性画龙点睛,我们为Enter按钮添加了一个按键监听器,这也会导致单击"确定"按钮(使用$.trigger()).

不幸的是,确认对话框仅在用户点击"确定"按钮时显示,但在点击时不显示Enter.当我们点击Enter对话框时根本没有出现.

最糟糕的是,在添加一些调试消息后,看起来对话框确实显示了几分之一毫秒,然后由于某种原因点击了"Yeap"按钮.所以当Enter被击中时,立即确认导弹发射!

小提琴这里.

代码如下:

function inputKeyListener(evt) {
  console.log('key listener - triggered key code is: ' + evt.keyCode);
  if (evt.keyCode === $.ui.keyCode.ENTER) {
    evt.stopPropagation();
    $('#missile-launch-button').click(); // Directly calling confirm() doesn't work either
  }
}

function missileLaunchButtonClickHandler(e) {
  e.stopPropagation();
  confirm();
}

function confirm() {
  var launchCode = $('#missile-launch-code-input').val();
  const dialog = $('#missile-launch-confirmation-modal');
  dialog.dialog({
    closeOnEscape: false,
    dialogClass: 'no-close',
    open: function(event, ui) {
      console.log('confirm :: open is called');
    },
    close: function() {
      console.log('confirm :: close is called');
    },
    resizable: false,
    height: "auto",
    width: 400,
    modal: true,
    buttons: {
      "Yeap": function() {
        console.log('Confirmation button was clicked');
        $(this).dialog("close");
        console.log('missile launch with code [' + launchCode + '] was confirmed!');
      },
      "Maybe not just yet": function(ev) {
        console.log('Abort button was clicked');
        $(this).dialog("close");
        console.log('Armageddon was averted');
      }
    }
  });

  dialog.dialog('open');
  console.log('by this time the dialog should be displayed');
}


$('#missile-launch-confirmation-modal').dialog({
  autoOpen: false
});


$('#missile-launch-button').click(missileLaunchButtonClickHandler);

$(document).on('keydown', inputKeyListener);
Run Code Online (Sandbox Code Playgroud)
<link rel='stylesheet' href='https://code.jquery.com/ui/1.11.4/themes/vader/jquery-ui.css'>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

<div id='missile-launch-confirmation-modal' title='Confirm missile launch' </div>
  <span class="ui-icon ui-icon-alert" style="float:left; margin:12px 12px 20px 0;"></span> Are you sure you want to unleash nuclear Armageddon?
</div>
</div>
<div>
  <div>
    <div>Enter missile launch code:</div>
    <div>
      <input id='missile-launch-code-input' type='text' autofocus/>
    </div>
    <div>
      <button id='missile-launch-button' type='button'>OK</button>
    </div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

更新

在上面的代码inputKeyListener中绑定到keydown文档.将它更窄地绑定到keydown文本输入上,如:

$('#missile-launch-code-input').on('keydown', inputKeyListener);
Run Code Online (Sandbox Code Playgroud)

...导致完全相同的行为.

更新II

这个答案表明stopPropagation这里效果不佳,因为" 事件冒泡在这里没有真正发挥作用 ",并解释说preventDefault应该用来" [停止]关键事件到达其他页面元素(即那个按钮) ".我对这两个一起的陈述有点困惑.我认为stopPropagation正是什么人用来"停止到达其他网页元素的关键事件 ".此外还有两个混乱点.

第一个困惑点是确认对话框div不是文本输入的父DOM元素div,因此不清楚文本输入中的键盘事件如何div兄弟(而不是)DOM元素拦截.我认为这实际上stopPropagation是无效的原因,但我仍然不清楚为什么(不管stopPropagation)事件到达兄弟姐妹的确认对话框按钮div.

第二个困惑点是,如果我们记录我们在"Yeap"按钮函数处理程序中捕获的事件,例如:

buttons: {
       "Yeap": function(ev) {
       console.log(ev); 
Run Code Online (Sandbox Code Playgroud)

...我们实际上在控制台中看到的是:

在此输入图像描述

...所以这是一个鼠标事件,而不是确认对话框的键盘事件.鉴于(在一次简单命中的情况下Enter)我们生成的唯一鼠标事件是inputKeyListener:

$('#missile-launch-button').click();
Run Code Online (Sandbox Code Playgroud)

...这意味着正是这个事件导致了对话框的确认,而不是我们通过点击获得的键盘事件 Enter

Dan*_*eck 6

这似乎是jQuery UI对其自身的好处略微有用的情况:当dialog打开时,它将其中的第一个按钮置于焦点,正好赶上"enter"键事件触发按钮(这是当按钮处于焦点时用户点击"enter"时的浏览器默认行为.)

使用preventDefault您的inputKeyListener到达其他网页元素(即按钮)停止的关键事件. stopPropagation是无害的,但在任何一方都没有任何有用的效果,inputKeyListener或者missileLaunchButtonClickHandler因为事件冒泡在这里并没有真正起作用.

这是一个没有preventDefault或stopPropagation的演示,并且包含一个虚拟按钮以无害地捕获自动对焦,只是为了确认这是发生了什么:

function inputKeyListener(evt) {
  console.log('key listener - triggered key code is: ' + evt.keyCode);
  if (evt.keyCode === $.ui.keyCode.ENTER) {
    // $('#missile-launch-button').click(); // Directly calling confirm() doesn't work either
    confirm(); // Does too!
  }
}

function missileLaunchButtonClickHandler(e) {
  confirm();
}

function confirm() {
  var launchCode = $('#missile-launch-code-input').val();
  const dialog = $('#missile-launch-confirmation-modal');
  dialog.dialog({
    closeOnEscape: false,
    dialogClass: 'no-close',
    open: function(event, ui) {
      console.log('confirm :: open is called');
    },
    close: function() {
      console.log('confirm :: close is called');
    },
    resizable: false,
    height: "auto",
    width: 400,
    modal: true,
    buttons: {
      "Hmmmm": function() {
        console.log('First button inside the dialog was clicked.');
      },
      "Yeap": function() {
        console.log('Confirmation button was clicked');
        $(this).dialog("close");
        console.log('missile launch with code [' + launchCode + '] was confirmed!');
      },
      "Maybe not just yet": function(ev) {
        console.log('Abort button was clicked');
        $(this).dialog("close");
        console.log('Armageddon was averted');
      }
    }
  });

  dialog.dialog('open');
  console.log('by this time the dialog should be displayed');
}


$('#missile-launch-confirmation-modal').dialog({
  autoOpen: false
});


$('#missile-launch-button').click(missileLaunchButtonClickHandler);

$(document).on('keydown', inputKeyListener);
Run Code Online (Sandbox Code Playgroud)
<link rel='stylesheet' href='https://code.jquery.com/ui/1.11.4/themes/vader/jquery-ui.css'>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

<div id='missile-launch-confirmation-modal' title='Confirm missile launch' </div>
  <span class="ui-icon ui-icon-alert" style="float:left; margin:12px 12px 20px 0;"></span> Are you sure you want to unleash nuclear Armageddon?
</div>
</div>
<div>
  <div>
    <div>Enter missile launch code:</div>
    <div>
      <input id='missile-launch-code-input' type='text' autofocus/>
    </div>
    <div>
      <button id='missile-launch-button' type='button'>OK</button>
    </div>
  </div>
</div>
Run Code Online (Sandbox Code Playgroud)

在event.preventDefault vs event.stopPropagation上

为了扩展这一点,根据"Update II": stopPropagation防止事件冒泡到父DOM节点.通常,例如,单击事件从直接通过每个父节点单击的节点向上冒泡.

原因stopPropagation与此无关,因为dialog它不是input元素的父元素:事件冒泡不会达到dialog.因此,没有理由阻止事件冒泡stopPropagation,因为无论如何它都不会触发任何有意义的事情.

event.preventDefault相反,停止的事件与DOM结构无关 - 这些事件并不关心父母,兄弟,孙子或第三代表兄弟两次被删除; event.preventDefault只是意味着"无论浏览器的默认行为是什么,都不要这样做." 因此event.preventDefault,在表单提交上会停止提交的表单,例如.

在此问题中描述的情况下,如果用户在按钮处于焦点时点击"输入"键,则默认浏览器行为是触发该按钮上的单击事件(即,是,鼠标事件),无论在何处按钮位于DOM中.所以event.preventDefault在这里使用可以防止这种默认行为,这就是你想要的.