JQuery UI Autocomplete: завершить то, что я напечатал после размытия, но до завершения запроса ajax? - PullRequest
3 голосов
/ 23 июня 2011

Допустим, мой виджет autocomplete имеет 2 возможных варианта: "JavaScript" и "jQuery". Если я нажму "j" на входе, оба результата появятся. Теперь допустим, что я набираю «q», а затем сразу нажимаю «Tab». Есть только один возможный результат для «jq», и это «jQuery», но виджет в любом случае выбирает «JavaScript», потому что запрос ajax еще не вернулся, а «JavaScript» был тем, что все еще было выделено, когда я нажимал Tab.

Есть ли способ "исправить" это? то есть, я думаю, что должно произойти, я набираю «jq», нажимаю «tab», он оставляет «jq» на входе до тех пор, пока запрос не завершится, а затем, когда это произойдет, выбирает первый вариант.


Все еще не удается заставить это работать. Я пытался записать, когда он искал, и запретил выбор, как Andrew , но loading никогда не кажется правдой, поэтому я не могу отменить его ... на самом деле, я думаю, что это потому, что delay не был найден, а следующий поиск даже не начался ... Мне нужно что-то связать с событием нажатия клавиши.


Хорошо. Думаю, я почти понял:

$('#myselector').autocomplete({
    source: function(request, response) {
        var data = {};
        $(this.element).data('keyHandled', true).data('lastTerm', request.term);

        // *snip*

        $.ajax({
            url: '/ajax/major_city',
            dataType: 'json',
            data: data,
            success: function(data, textStatus, $xhr) {
                response(data);
            },
            error: function($xhr, textStatus) {
                response([]);
            },
        });
    },
    select: function(event, ui) {
        if(!$(this).data('keyHandled')) {
            // TODO perform another search for just one value and set the input immediately
            return false;
        }
        this.value = ui.item.value;
        $(this).trigger('change');
        return false;
    },
    minLength: 0,
    autoFocus: true,
    delay: 250
})
.keydown(function(event) {
    if($(this).val() !== $(this).data('lastTerm')) {
        $(this).data('keyHandled', false);
    }
});

Похоже, это правильно "отменяет" выбор, если пользователь вводил вкладку после ввода набора, но до того, как задержка была достигнута / начался следующий поиск. Теперь мы можем выполнить еще один ajax-вызов, чтобы получить единственное значение «наилучшего соответствия» на основе того, что находится на входе, а затем установить его. Не нужно снова открывать окно поиска.

Ответы [ 2 ]

1 голос
/ 25 июня 2011

Редактировать 4

Сделал это функцией.

(function($){
    $.fn.ajaxselect = function(options) {
        var settings = {
            delay: 300,
            data: function(term) {
                return {term:term};
            },
            url: '',
            select: function(item) {},
            html: true,
            minLength: 0,
            autoSelect: true
        };
        if(options) {
            $.extend(settings, options);
        }
        $(this).autocomplete({
            source: function(request, response) {
                var data = settings.data.call(this.element[0], request.term);
                $.ajax({
                    url: settings.url,
                    dataType: 'json',
                    data: data,
                    success: function(data, textStatus, $xhr) {
                        response(data);
                    },
                    error: function($xhr, textStatus) {
                        response([]);
                    }
                });
            },
            focus: function(event, ui) {
                return false;
            },
            search: function(event, ui) {
                $(this).data('lastSearch', this.value);
            },
            select: function(event, ui) {
                if($(this).val() === $(this).data('lastSearch')) {
                    if(settings.select.call(this, ui.item) !== false) {
                        $(this).val(ui.item.value);
                    }
                    $(this).data('selectedValue',$(this).val()).trigger('change');
                } 
                return false;
            },
            minLength: settings.minLength,
            autoFocus: settings.autoSelect,
            delay: settings.delay,
            html: settings.html
        }).bind('change.ajaxselect', function() {
            $(this).toggleClass('selected', $(this).val() === $(this).data('selectedValue'));
        });

        if(settings.autoSelect) {
            $(this).bind('autocompletechange', function(event, ui) {
                if($(this).val() !== $(this).data('selectedValue') && this.value.length > 0) {
                    var self = this;
                    var data = $.extend({autoSelect:1},settings.data.call(this, this.value));
                    $(this).addClass('.ui-autocomplete-loading');
                    $.ajax({
                        url: settings.url,
                        dataType: 'json',
                        data: data,
                        success: function(data, textStatus, $xhr) {
                            if(data.length >= 1) {
                                var item = $.ui.autocomplete.prototype._normalize(data)[0];
                                if(settings.select.call(self, item) !== false) {
                                    $(self).val(item.value);
                                }
                                $(self).data('selectedValue',$(self).val()).trigger('change');
                            }
                        },
                        complete: function($xhr, textStatus) {
                            $(self).removeClass('.ui-autocomplete-loading');
                        }
                    });
                }
            });
        }

        if(!settings.minLength) {
            $(this).bind('focus.ajaxselect', function() {
                if(!$(this).autocomplete('widget').is(':visible')) {
                    $(this).autocomplete('search','');
                }
            });
        }

        return $(this);
    };
})(jQuery);

Использование:

$('#yourselector').ajaxselect({
    url: '/ajax/city'
});

Теперь работает довольно хорошо.

1 голос
/ 23 июня 2011

Я предполагаю, что автозаполнение настроено с параметром autoFocus, установленным на true. Имея это в виду, вы можете сделать что-то вроде этого:

/* Keep track of whether or not we're in an AJAX request*/
var loading = false;

$("input").autocomplete({
    source: function(request, response) {
        /* Beginning of an AJAX request; set loading to true */
        loading = true;

        $.ajax({
            /* (snip) Options for your AJAX request. */
            success: function(data) {
                /* Loading complete. */
                loading = false;
                response(data);
            }
        });
    },
    autoFocus: true,
    select: function () {
        /* prevent the user from selecting an item if we're still loading matches */
        return !loading;
    }
}).bind("keydown", function(event) {
    if (event.keyCode === $.ui.keyCode.TAB) {
        /* Don't let the user tab out if we're still loading matches: */
        return !loading;
    }
});

Отслеживайте, выполняется ли вызов AJAX. Если это так, не позволяйте пользователю выбирать вариант из раскрывающегося списка до тех пор, пока этот запрос не будет выполнен и не будут найдены самые последние совпадения.

Единственная часть вашего вопроса, которая может не удовлетворить это, - последняя часть (и я могу неправильно понять, что вы здесь просите):

[...], а затем, когда это происходит, выбирает первый вариант.

Это, вероятно, возможно, но с точки зрения юзабилити мне кажется более разумным запретить выбор элемента, пока не появятся самые свежие элементы.

В конце концов, тот факт, что для виджета автозаполнения имеется только один результат, не означает, что пользователь захочет выбрать его, когда он наконец появится.

Вероятно, это проще увидеть на примере: http://jsfiddle.net/RsSTa/. Попробуйте ввести «J», а затем «JQ». Вы не сможете продолжить работу с предметами из устаревшего AJAX-вызова.

...