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

时间:2017-05-16 20:11:35

标签: javascript jquery jquery-ui jquery-ui-dialog

这显然是SSCCE

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

enter image description here

出于安全考虑,点击“确定”按钮后,我们将显示一个对话框,要求用户确认:

enter image description here

作为可用性完成触摸,我们为 Enter 按钮添加一个关键监听器,这也将导致单击“确定”按钮(使用$.trigger())。

不幸的是,确认对话框仅在用户点击“确定”按钮时显示,但在点击 Enter 时不显示。当我们点击 Enter 时,对话框根本不会出现。

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

小提琴here

以下代码:

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

更新

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

$('#missile-launch-code-input').on('keydown', inputKeyListener);

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

更新II

This answer表示此处stopPropagation无效,因为“事件冒泡在这里并没有发挥作用”并解释preventDefault应该用于“ [停止]关键事件到达其他页面元素(即该按钮)“。我对这两个一起的陈述有点困惑。我认为stopPropagation 正好用于阻止“键事件到达其他页面元素”的内容。此外还有两个混乱点。

第一个困惑点是确认对话框div不是文本输入div的父DOM元素,因此不清楚文本输入中的键盘事件{{1}被兄弟(不是)DOM元素拦截。我认为这实际上是div无效的原因,但我仍然不清楚为什么(无论stopPropagation)事件到达确认对话框按钮,该按钮位于兄弟stopPropagation

第二个困惑点是,如果我们记录我们在“Yeap”按钮功能处理程序中捕获的事件,例如像这样:

div

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

enter image description here

...所以它是鼠标事件,而不是确认对话框的键盘事件。鉴于(在一个简单命中 Enter 的情况下)我们生成的唯一鼠标事件位于buttons: { "Yeap": function(ev) { console.log(ev);

inputKeyListener

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

2 个答案:

答案 0 :(得分:6)

这似乎是jQuery UI对自己的好处略微有用的情况:当dialog打开时,它会将第一个按钮置于焦点内,正好赶上&#34;输入& #34;用于触发按钮的键事件(当用户点击&#34时,这是浏览器的默认行为;当按钮处于焦点时,输入&#34;)

preventDefault中使用inputKeyListener会阻止关键事件到达其他页面元素(即该按钮)。 stopPropagation是无害的,但在inputKeyListenermissileLaunchButtonClickHandler中没有任何有用的效果,因为事件冒泡在这里并没有真正起作用。

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

&#13;
&#13;
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);
&#13;
<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>
&#13;
&#13;
&#13;

on event.preventDefault vs event.stopPropagation

为了扩展这一点,每个&#34;更新II&#34;:stopPropagation可防止事件冒泡到父DOM节点。通常,例如,单击事件从直接通过每个父节点单击的节点向上冒泡。

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

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

在此问题中描述的情况下,如果用户点击&#34;输入&#34;当按钮处于焦点时,按键是触发该按钮上的单击事件(即,是,鼠标事件),而不管该按钮在DOM中的位置。因此,在此处使用event.preventDefault会阻止默认行为,这就是您想要的。

答案 1 :(得分:1)

首先,您需要在 inputKeyListener 函数中调用 missileLaunchButtonClickHandler

在您需要添加&#34; preventDefault&#34;到你的 missileLaunchButtonClickHandler 函数,因为当你按Enter键时对话框会自动关闭。 preventDefault 会避免对话框自动关闭。

missileLaunchButtonClickHandler 功能更改为:

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

并将 inputKeyListener 修改为:

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