Mysterious mouse event closes jQuery UI dialog
This is obviously SSCCE .
Thus, we are instructed to write the front of the missile launch control system. We choose the Spartan layout considering that it is deadly: just enter a text input field and a button to enter the code:
For security reasons, when you click on the "OK" button, a dialog box appears asking you to confirm:
As a handy hatch, we'll add a key button listener Enterthat will also cause the OK button to be clicked (with $.trigger()
).
Unfortunately, the confirmation dialog is only displayed when the user clicks the "OK" button, but not when they click Enter. When we click Enter, no dialog will appear at all.
Worst of all, after adding some debug messages, it looks like the dialog is indeed displayed for a fraction of a millisecond and then the "Yeap" button is pressed for whatever reason. Therefore, when struck Enter, the rocket launch is immediately confirmed!
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 above code, it is inputKeyListener
bound to keydown
in the document. Binding it closer to keydown
on the text input, as in:
$('#missile-launch-code-input').on('keydown', inputKeyListener);
& hellip; leads to precise behavior.
Update II
This answer suggests that it stopPropagation
is ineffective here because "the event bubble is not really playing here" and explains what preventDefault
to use to "[stop] the key event from accessing other elements of the page (that is, this button)". I am a little confused by these two statements taken together. I thought stopPropagation
this is exactly what is being used to stop the "key event from reaching other elements of the page". Moreover, there are two more confusion.
The first confusion is that the confirmation dialog is div
not the parent of the text input DOM div
, so it is unclear how the keyboard event on text input is div
intercepted by the sibling (non-parent) DOM element. I think this is actually the reason why it is stopPropagation
ineffective, but still it is not clear to me why (regardless of stopPropagation
) the event reaches the confirmation dialog button which is in marriage div
.
The second point of confusion is that if we register an event that we register in the function handler of the "Yeap" button, for example. eg:
buttons: {
"Yeap": function(ev) {
console.log(ev);
& hellip; what we actually see in the console:
& hellip; so it's a mouse event , not a keyboard event that acknowledges the dialog. Considering that (in a scenario where one simple click Enter), only the mouse event we are creating is in inputKeyListener
:
$('#missile-launch-button').click();
& hellip; this means that it is this event that leads to the confirmation of the dialog, and not the keyboard event that we receive by pressingEnter
source to share
This is similar to the case where jQuery's UI is a little too useful for its own good: when opened, dialog
it puts the first button inside it in focus, just in time for the "enter" key event to trigger the button (which is the browser's default behavior when the user presses "enter" when the button has focus.)
Using preventDefault
in inputKeyListener
stops the key event from accessing other elements of the page (i.e. this button). stopPropagation
harmless, but will not have any useful effect either in inputKeyListener
or in missileLaunchButtonClickHandler
, because the event bubble does not really play here.
Here's a demo with no warning of Default or stopPropagation, and an empty button included to innocuously grab autofocus, just to confirm that this is happening:
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
To expand on this, on "Update II": stopPropagation
Prevents event bubbles from spawning up to parent DOM nodes. Typically, for example, an event click bubbles upward from a node that is directly clicked on each parent node.
The reason stopPropagation
here is irrelevant because it is dialog
not the parent of the input element: the event bubble would not reach dialog
. So there is no reason to stop an event bubbling with help stopPropagation
, because it wouldn't trigger anything meaningful anyway.
Events stopped at event.preventDefault
, on the other hand, have nothing to do with the DOM structure - these events don't care if a parent, sibling, grandson, or third cousin is deleted twice; event.preventDefault
simply means "whatever the default browser behavior in this situation, don't do it." So event.preventDefault
on a form, submit stops the submitted form, eg.
In the case described in this question, the browser's default behavior, if the user presses the enter key when the button has focus, is to fire a click event on that button (that is, yes, a mouse event) regardless of whether where the button is in the DOM. So using it event.preventDefault
here prevents the default behavior you want.
source to share
First you need to call missileLaunchButtonClickHandler inside your inputKeyListener function .
After you need to add "preventDefault" to your missileLaunchButtonClickHandler function , because the dialog is automatically closed when you press ENTER. preventDefault does not automatically close the dialog.
Modify the missileLaunchButtonClickHandler function :
function missileLaunchButtonClickHandler(e) {
//e.stopPropagation();
e.preventDefault();
confirm();
}
and change your inputKeyListener to this:
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
}
}
source to share