Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent Jquery autocomplete options from closing after each selection

I am using Jquery UI's autocomplete for multiple selectable values. All is good except the list of options closes after each selection. I would like the options to stay open until the user decides they are done making selections. I have looked through the documentation and I don't see any way to keep the options open. Any ideas?

<meta charset="utf-8">

<script>
$(function() {
    var availableTags = [
        "ActionScript",
        "AppleScript",
        "Asp",
        "BASIC",
        "C",
        "C++",
        "Clojure",
        "COBOL",
        "ColdFusion",
        "Erlang",
        "Fortran",
        "Groovy",
        "Haskell",
        "Java",
        "JavaScript",
        "Lisp",
        "Perl",
        "PHP",
        "Python",
        "Ruby",
        "Scala",
        "Scheme"
    ];
    function split( val ) {
        return val.split( /,\s*/ );
    }
    function extractLast( term ) {
        return split( term ).pop();
    }

    $( "#tags" ).autocomplete({
        minLength: 0,
        source: function( request, response ) {
            // delegate back to autocomplete, but extract the last term
            response( $.ui.autocomplete.filter(
                availableTags, extractLast( request.term ) ) );
        },
        focus: function() {
            // prevent value inserted on focus
            return false;
        },
        select: function( event, ui ) {
            var terms = split( this.value );
            // remove the current input
            terms.pop();
            // add the selected item
            terms.push( ui.item.value );
            // add placeholder to get the comma-and-space at the end
            terms.push( "" );
            this.value = terms.join( ", " );
            return false;
        }
    });
});
</script>
Tag programming languages:
like image 820
Rusty Avatar asked Oct 13 '10 15:10

Rusty


4 Answers

You can do something like this: first define a variable named readyToClose and set it to be false at the begining. And when you want to close your menu on next selection, set this variable to the true. And also we should reimplement the close method of JQuery UI.

Here I reimplemented the close method of JQuery UI in your code, not in the source file! This is the same thing we do to render the list in custom way (e.g. http://jqueryui.com/demos/autocomplete/custom-data.html )

var readyToClose = false;
$( "#tags" ).autocomplete({
    minLength: 0,
    source: function( request, response ) {
        // delegate back to autocomplete, but extract the last term
        response( $.ui.autocomplete.filter(
            availableTags, extractLast( request.term ) ) );
    },
    focus: function() {
        // prevent value inserted on focus
        return false;
    },
    select: function( event, ui ) {
        var terms = split( this.value );
        // remove the current input
        terms.pop();
        // add the selected item
        terms.push( ui.item.value );
        // add placeholder to get the comma-and-space at the end
        terms.push( "" );
        this.value = terms.join( ", " );
        return false;
    }
}).data( "autocomplete" ).close = function(e){
    if(readyToClose)
        clearTimeout(this.closing), this.menu.element.is(":visible") && (this.menu.element.hide(), this.menu.deactivate(), this._trigger("close", e));
    else
        return false;    
};

Note: In newer versions of jQuery (i.e. 1.9.0), replace "autocomplete" with "uiAutocomplete", as in:

$("#tags")
    .autocomplete({...})
    .data("uiAutocomplete").close = ...
like image 81
Mehran Avatar answered Nov 15 '22 23:11

Mehran


I know it's an old question that may no longer by relevant to the OP, but for the sake of completeness, a cleaner solution would be to extend the autocomplete widget and overriding _close, as well as extending the event object in the select event handler. This allows you to perform custom logic to determine whether the menu should be closed or not on a case by case (event by event) basis. See also http://learn.jquery.com/jquery-ui/widget-factory/extending-widgets/

//override the autocomplete widget
jQuery.widget( "ui.autocomplete", jQuery.ui.autocomplete, {
    _close: function( event ) {
        if(event!== undefined && event.keepOpen===true) {
            //trigger new search with current value
            this.search( null, event );
            return true;
        }
        //otherwise invoke the original
        return this._super( event );
    }
});

$('ac').autocomplete(
    {
        ...custom options...
        select: function( event, ui ) {
            ...custom logic...
            if(menu should remain open) {
                //extend original event with special flag to keep dropdown open
                //the o-event continues to be passed through the chain of listeners
                //and will end up being processed during _close()
                jQuery.extend(event.originalEvent,{keepOpen:true});
                //modify value as required
                jQuery(this).val(...);
                return false; //prevent selected value from being set,
                              //i.e. keeping modified value above
            }
        }
    }
);

In the above jQuery.extend(event.originalEvent,{keepOpen:true}); is used to add a special keepOpen property to event.originalEvent. Since extend modifies the original object (first argument), any subsequent use of the same event.originalEvent will have this property available. Having read and stepped through the code it ends up being the same object that is referenced by event in the _close method. This code will break should this change in future, however, it is a lot simpler to maintain than the equivalent of what ingredient_15939 suggested 18 months ago.

like image 26
user3482702 Avatar answered Nov 15 '22 22:11

user3482702


The jQuery UI team thinks this is bad UX practice: http://forum.jquery.com/topic/enhanced-autocomplete-interest-in-getting-this-into-jqueryui#14737000001125152

So no built-in way to cancel it. You can try re-triggering the autocomplete after the select & close events. And probably you should cache the results array so that no new requests are made (if it's in Ajax mode).

Didn't get into the details though - I have convinced my UI designer that we don't need this for this version :)

like image 28
Steponas Dauginis Avatar answered Nov 15 '22 22:11

Steponas Dauginis


I needed this same ability. IMO the authors could have easily given us the option. What I did was exclude autocomplete from the UI bundle, and include an edited copy of the separate file: jquery.ui.autocomplete.js (get it from the "development-bundle\ui" folder).

I added a couple of new options, closeOnSelect and updateElement (both boolean default true), as follows (at the top of file):

$.widget("ui.autocomplete", {
  options: {

  ...

  closeOnSelect: true, // add this line - controls list closing.
  updateElement: true  // add this line - controls updating input box.
},

Then search for the string "selected:" in the code (there will be only one occurrence). Within this function, replace these last few lines:

if ( false !== self._trigger( "select", event, { item: item } ) ) {
    self.element.val( item.value );
}
// reset the term after the select event
// this allows custom select handling to work properly
self.term = self.element.val();

self.close( event );
self.selectedItem = item;

With this:

if (false !== self._trigger("select", event, { item: item })) {

  if (self.options.updateElement) {
    self.element.val(item.value);
    self.term = self.element.val(); // Ensure term string is same.
  }
  if (self.options.removeOnSelect) { // Remove menu item if option is true.
    console.log(ui);
  } else {
    self.selectedItem = item;
  }
  if (self.options.closeOnSelect) self.close(event); // Close menu if option is true.

} else { // Returned false.
  self.term = self.element.val(); // Ensure term string is same, in case callback changed element value.
}

Then search for string "focus: function" (again, only one occurrence). Within that function, replace the line:

self.element.val(item.value);

With this:

if (self.options.updateElement) self.element.val(item.value);

Now, when you create the autocomplete, simply set those two options as you like:

$("#txtsearch").autocomplete({
  closeOnSelect: false, // Keep list open when item selected.
  updateElement: false, // Don't change the input box contents.
  // etc...

This works perfectly for me. Regardless of whether it's deemed "bad UI practice" by the original authors, everyone's needs are different and options should be there! :)

like image 27
ingredient_15939 Avatar answered Nov 15 '22 23:11

ingredient_15939