Question

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?

Was it helpful?

Solution

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 the autocomplete 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 to addEventListener it will register it only once. MDN, Specifications
  • Adding code to a javascript function programmatically

OTHER TIPS

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>

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.

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top