问题
Our users complain that when they press the enter key after pasting or typing values in a jQuery autocomplete widget the form is submitted.
It's extremely annoying them when they copy-paste a value that exists in the autocomplete options the autocomplete widget opens to show that single value, they press Enter to accept that value but then the form is submitted before they finished filling all the fields because (by default and we don't want to change it) the widget won't select the first option in the menu.
<form>Type C and press Enter:
<input id="autocomplete" />
<input type="submit" value="submit" />
</form>
$('form').submit(function () {
alert('You submitted the form');
return false;
});
$('#autocomplete').autocomplete({
source: ["c#", "c", "c++", "java", "php", "coldfusion"]
});
DEMO of the problem
How can we change that clicking Enter will only close the autocomplete suggestions?
回答1:
It seems like jQuery UI didn't left a backdoor to customize the widget out of the box, so what you can do is override the autocomplete
function to register a callback for the onkeypress
event, capture the Enter
and stop the propagation so it won't submit the form if the widget is open=visible.
Here how it goes:
function cancelAutocompleteSumbission(e) {
// Make sure this is a nodeElement and the button pressed was Enter-Return
if (!this.nodeType || e.which != 13)
return;
// If the widget is visible we simply want to close the widget.
if ($(this).autocomplete('widget').is(':visible')) {
$(this).autocomplete('close');
return false;
}
}
// Making a private scope to avoid naming collision.
$.fn.autocomplete = (function () {
// Cache the old autocomplete function.
var oldAutocomplete = $.fn.autocomplete;
// This will be the new autocomplete function.
return function () {
// If the first argument isn't "destroy" which
// should restore the input to it's initial state.
if (!/^destroy$/i.test(arguments[0]))
// Attach event to the input which will prevent Enter submission as
// explained above.
this.keypress(cancelAutocompleteSumbission);
// We need to restore the input to it's initial state,
// detach the keypress callback.
else
this.off('keypress', cancelAutocompleteSumbission);
// Call the cached function with the give "this" scope and paramteres.
return oldAutocomplete.apply(this, arguments);
};
})();
Live DEMO
Notes:
- To change all the autocomplete widgets you need to use jQuery's prototype, $.fn is an alias to $.prototype.
- Also you need to change
$.fn.autocomplete
before you use it it or the changes you made won't apply to those widget. this
inside theautocomplete
function is actually a jQuery object so you don't need to wrap it with$(this)
- You might say, Hey you keep register the very same callback for the
keypress
event. Well, that's exactly what I'm doing and why I wrote the callback as a named function. If you pass the same callback toaddEventListener
it will register it only once. MDN, Specifications - Adding code to a javascript function programmatically
回答2:
I might have a simpler solution by using jQuery autocomplete's autocompleteclose
and autocompleteopen
events.
See the code below:
var flag = 0; //global variable
$("#autocomplete").on({
autocompleteclose: function (event, ui) {
flag = 1;
//set flag back to 0 with a short delay so the next keypress can submit form
setTimeout(function () {
flag = 0;
}, 100);
},
//if the autocomplete widget is open, don't submit form on keypress
autocompleteopen: function (event, ui) {
flag = 1;
}
});
$('body').keypress(function(e) {
if (e.keyCode == '13') {
if (flag != 0) {
e.preventDefault();
} else {
//submit form
}
}
});
var flag = 0; //global variable
$("#autocomplete").on({
autocompleteclose: function (event, ui) {
flag = 1;
//set flag back to 0 with a short delay so the next keypress can submit form
setTimeout(function () {
flag = 0;
}, 100);
},
//if the autocomplete widget is open, don't submit form on keypress
autocompleteopen: function (event, ui) {
flag = 1;
}
});
$('body').keypress(function (e) {
if (e.keyCode == '13') {
if (flag != 0) {
e.preventDefault();
} else {
//submit form
}
}
});
$('form').submit(function () {
alert('You submitted the form');
return false;
});
$('#autocomplete').autocomplete({
source: ["c#", "c", "c++", "java", "php", "coldfusion", "javascript", "asp", "ruby"]
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="//code.jquery.com/ui/1.10.3/jquery-ui.js"></script>
<link rel="stylesheet" type="text/css" href="//code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css">
<form>Type C and press Enter:
<input id="autocomplete" />
<input type="submit" value="submit" />
</form>
回答3:
I was somehow against overriding the jqueryui implementation and did the following:
- in the close event of the autocomplete i set a flag "doNotSubmit" when enter was pressed
- in your case then i'd bound a submit event listener to the form which checks the doNotSubmit flag and acts accordingly.
The basic idea behind it, is that jqueryui's close event is triggered before keyup or submit events and gives you the keycode. So you can in another place (keyup, submit, etc.) consume that one unwanted enter or other keypress.
回答4:
This question is similar to this one. While the solutions on that page are straight forward, they depend on the ID's of elements on the page. I use autocomplete on lots of pages so I prefer gdoron's approach (on this page). Thanks to him for doing the heavy lifting.
However, I think there is a bug in his code. If you go to an autocomplete field that already has content and type a return, it will submit the form. Here is a fix (the change is the second "if block in cancelAutocompleteSumbission):
function cancelAutocompleteSumbission(e) {
// Make sure this is a nodeElement and the button pressed was Enter-Return
if (!this.nodeType || e.which != 13)
return;
if (!$(this).autocomplete('widget').is(':visible') && e.which === 13){
return false;
}
// If the widget is visible we simply want to close the widget.
if ($(this).autocomplete('widget').is(':visible')) {
$(this).autocomplete('close');
return false;
}
}
// Making a private scope to avoid naming collision.
$.fn.autocomplete = (function () {
// Cache the old autocomplete function.
var oldAutocomplete = $.fn.autocomplete;
// This will be the new autocomplete function.
return function () {
// If the first argument isn't "destroy" which
// should restore the input to it's initial state.
if (!/^destroy$/i.test(arguments[0]))
// Attach event to the input which will prevent Enter submission as
// explained above.
this.keypress(cancelAutocompleteSumbission);
// We need to restore the input to it's initial state,
// detach the keypress callback.
else
this.off('keypress', cancelAutocompleteSumbission);
// Call the cached function with the give "this" scope and paramteres.
return oldAutocomplete.apply(this, arguments);
};
})();
Live Demo
来源:https://stackoverflow.com/questions/18749924/modify-jquery-autocomplete-not-to-submit-eagerly-on-enter