Автозаполнение пользовательского интерфейса jQuery: завершить то, что я набрал после размытия, но до завершения запроса ajax?

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

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


Все еще есть проблемы с тем, чтобы заставить это работать. Я пытался записывать, когда он искал, и запрещал выбор, например Эндрю делает, но 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, чтобы получить одно значение «наилучшего совпадения» на основе входных данных, а затем установить его. Нет необходимости снова открывать окно поиска.


person mpen    schedule 23.06.2011    source источник
comment
На самом деле есть пример обработки нажатия клавиши Tab в примере с автозаполнением. jqueryui.com/demos/autocomplete/#multiple-remote . Вам это как-то помогает?   -  person Jay    schedule 23.06.2011
comment
@Jayraq: Нет, не с этой проблемой, но это может помочь с другой будущей проблемой.   -  person mpen    schedule 24.06.2011


Ответы (2)


Я предполагаю, что у вас настроено автозаполнение с параметром 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.

person Andrew Whitaker    schedule 23.06.2011
comment
Причина, по которой я хочу сделать это таким образом, заключается в том, что в моем веб-приложении одни и те же пользователи будут постоянно вводить одни и те же данные. Они узнают, что содержит список, введя одно и то же несколько раз, и они будут знать, что jq не вызовет конфликтов и для него будет только один результат. Почему я должен заставлять их ждать загрузки списка только для того, чтобы они могли нажать Enter или Tab, как только это произойдет? - person mpen; 23.06.2011
comment
Кроме того, ваш пример, похоже, не работает так, как вы предлагаете, если только я что-то не понимаю. Я нажал j, дождался загрузки списка, затем сразу же нажал q и tab, и он по-прежнему выбирает javascript. - person mpen; 23.06.2011
comment
Тем не менее, ваш пример дал мне некоторые идеи... Вероятно, я смогу заполнить ввод вручную, когда вызов ajax завершится... Мне просто нужно проверить, что окно все еще не открыто/они все еще печатают. Вы правы насчет того, что я установил autoFocus на true. - person mpen; 23.06.2011
comment
@Mark: Похоже, ты прав; если вы печатаете достаточно быстро, вы можете обойти это. Также похоже, что это поведение зависит от браузера. Я посмотрю, а также приму во внимание ваш первый комментарий. - person Andrew Whitaker; 23.06.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'
});

Сейчас работает довольно хорошо.

person mpen    schedule 25.06.2011