简体   繁体   English

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

[英]Mysterious mouse event closes jQuery UI dialog

This is obviously a SSCCE . 这显然是一个SSCCE

So we are tasked with writing the front-end of a missile launch control system. 所以我们的任务是编写导弹发射控制系统的前端。 We opt for a Spartan layout given that this is deadly serious: just a text input box and a button to enter the code: 我们选择Spartan布局,因为这是非常严重的:只需一个文本输入框和一个输入代码的按钮:

在此输入图像描述

For safety purposes, upon clicking on the "OK" button we will display a dialog asking the user to confirm: 为安全起见,点击“确定”按钮后,我们将显示一个对话框,要求用户确认:

在此输入图像描述

As a usability finishing touch we add a key listener for the Enter button that will also result in clicking the "OK" button (using $.trigger() ). 作为可用性画龙点睛,我们为Enter按钮添加一个键监听器,这也将导致单击“确定”按钮(使用$.trigger() )。

Unfortunately, the confirmation dialog is only displayed when the user clicks on the "OK" button but not when hitting Enter . 不幸的是,确认对话框仅在用户单击“确定”按钮时显示,但在按Enter键时不显示。 When we hit Enter the dialog does not appear at all. 当我们按Enter键时 ,对话框根本不显示。

Worst still, after adding some debugging messages it appears that the dialog is indeed displayed for a fraction of a millisecond and then for some reason the "Yeap" button is clicked. 最糟糕的是,在添加一些调试消息后,看起来对话框确实显示了几分之一毫秒,然后由于某种原因点击了“Yeap”按钮。 So when Enter is hit, missile launch is immediately confirmed! 因此当Enter命中时,立即确认导弹发射!

Fiddle here . 在这里小提琴

Code below: 代码如下:

 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> 

Update 更新

In the code above the inputKeyListener is bound to keydown on document. 在上面的代码中, inputKeyListener绑定到文档上的keydown Binding it more narrowly to the keydown on the text input, as in: 将它更窄地绑定到文本输入上的keydown ,如:

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

… results in the exact same behavior. ...导致完全相同的行为。

Update II 更新II

This answer suggests that stopPropagation is ineffective here because " event bubbling isn't really in play here " and explains that preventDefault should be used to " [stop] the key event from reaching other page elements (ie that button) ". 这个答案表明stopPropagation在这里是无效的,因为“ 事件冒泡在这里没有真正起作用 ”,并解释了preventDefault应该用于“ [停止]键事件到达其他页面元素(即那个按钮) ”。 I am a bit confused with those two statements taken together. 我对这两个一起的陈述有点困惑。 I thought that stopPropagation is precisely what one uses to stop a " key event from reaching other page elements ". 我认为stopPropagation 正是用来阻止“ 关键事件到达其他页面元素 ”的东西。 Moreover there are two further points of confusion. 此外还有两个混乱点。

The first point of confusion is that the confirmation dialog div is not a parent DOM element of the text input div , so it is unclear how a keyboard event at the text input div is intercepted by a sibling (not parent ) DOM element. 第一个困惑点是确认对话框div不是文本输入div的父DOM元素,因此不清楚文本输入div的键盘事件是如何被兄弟 (而不是 )DOM元素拦截的。 I think this is in fact the reason that stopPropagation is ineffective but still it's not clear to me why (regardless of stopPropagation ) the event reaches the confirmation dialog button which lies in a sibling div . 我认为这实际上是stopPropagation无效的原因,但我仍然不清楚为什么(无论stopPropagation )事件到达确定对话框按钮位于兄弟div

The second point of confusion is that if we log the event we capture in the "Yeap" button function handler, eg like this: 第二个困惑点是,如果我们记录我们在“Yeap”按钮函数处理程序中捕获的事件,例如:

buttons: {
       "Yeap": function(ev) {
       console.log(ev); 

… what we in fact see in the console is: ...我们实际上在控制台中看到的是:

在此输入图像描述

… so it is a mouse event, not a keyboard event that confirms the dialog. ...所以这是一个鼠标事件,而不是确认对话框的键盘事件。 Given that (in the scenario where one simple hits Enter ) the only mouse event we are generating is in the inputKeyListener : 鉴于(在一个简单命中Enter的场景中)我们生成的唯一鼠标事件在inputKeyListener

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

… this means that it is this event that results in the confirmation of the dialog, not the keyboard event we get by hitting Enter ...这意味着正是这个事件导致对话框的确认,而不是我们通过按Enter键获得的键盘事件

This appears to be a case of jQuery UI being slightly too helpful for its own good: when a dialog opens, it puts the first button inside it into focus, just in time for the "enter" key event to trigger the button (which is the browser's default behavior when the user hits "enter" while a button is in focus.) 这似乎是jQuery UI对它自身的好处略微有用的情况:当一个dialog打开时,它将第一个按钮置于焦点内,正好赶上“enter”键事件触发按钮(这是当按钮处于焦点时用户点击“enter”时的浏览器默认行为。)

Using preventDefault in your inputKeyListener stops the key event from reaching other page elements (ie that button). inputKeyListener使用preventDefault inputKeyListener阻止键事件到达其他页面元素(即该按钮)。 stopPropagation is harmless but will have no useful effect in either inputKeyListener or missileLaunchButtonClickHandler because event bubbling isn't really in play here. stopPropagation是无害的,但在inputKeyListenermissileLaunchButtonClickHandler没有任何有用的效果,因为事件冒泡在这里并没有真正起作用。

Here's a demo with no preventDefault or stopPropagation, and a dummy button included to harmlessly catch the autofocus, just to confirm that this is what's happening: 这是一个没有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); 
 <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> 

on event.preventDefault vs event.stopPropagation 在event.preventDefault vs event.stopPropagation上

To expand on this, per "Update II": stopPropagation prevents events bubbling up to parent DOM nodes. 为了扩展这一点,根据“Update II”: stopPropagation可防止事件冒泡到父DOM节点。 Normally, for example, click events bubble upwards from the node directly clicked on through every parent node. 通常,例如,单击事件从直接通过每个父节点单击的节点向上冒泡。

The reason stopPropagation is irrelevant here is because dialog is not a parent of the input element: event bubbling wouldn't have reached dialog . stopPropagation与此无关的原因是因为dialog不是input元素的父元素:事件冒泡不会到达dialog So there's no reason to stop the event bubbling with stopPropagation , because it wouldn't have triggered anything meaningful anyway. 所以没有理由用stopPropagation来阻止事件冒泡,因为无论如何它都不会触发任何有意义的事情。

The events stopped by event.preventDefault , by contrast, have nothing to do with the DOM structure -- these events don't care if parent, sibling, grandchild, or third cousin twice removed; 相反, event.preventDefault停止的事件与DOM结构无关 - 这些事件不关心父母,兄弟,孙子或第三代表兄弟两次被删除; event.preventDefault simply means "whatever the default behavior of the browser would have been in this situation, don't do that." event.preventDefault只是意味着“无论浏览器的默认行为是什么,都不要这样做。” So event.preventDefault on a form submit stops the form being submitted, for example. 因此,表单提交上的event.preventDefault停止正在提交的表单。

In the case described in this question, the default browser behavior if the user hits the "enter" key while a button is in focus is to trigger a click event (which is, yes, a mouse event) on that button, regardless of where the button is in the DOM. 在此问题中描述的情况下,如果用户在按钮处于焦点时点击“输入”键,则默认浏览器行为是触发该按钮上的单击事件(即,是,鼠标事件),无论在何处按钮位于DOM中。 So using event.preventDefault here prevents that default behavior, which is what you want. 所以在这里使用event.preventDefault可以防止这种默认行为,这就是你想要的。

At first, you need to call the missileLaunchButtonClickHandler inside your inputKeyListener function. 首先,你需要调用missileLaunchButtonClickHandlerinputKeyListener函数中。

After you need to add a "preventDefault" to your missileLaunchButtonClickHandler function because the dialog is closing automatically when you hit ENTER. 在您需要向missileLaunchButtonClickHandler函数添加“preventDefault”之后,因为当您按Enter时对话框会自动关闭。 The preventDefault avoid the dialog to close automatically. preventDefault避免对话框自动关闭。

Change your missileLaunchButtonClickHandler function to this: missileLaunchButtonClickHandler函数更改为:

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

and modify your inputKeyListener to this: 并将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
       }
     }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM