【问题标题】:Displaying auto complete results a bit faster更快地显示自动完成结果
【发布时间】:2017-08-05 04:22:03
【问题描述】:

自从我上次访问我们内置在内部工具中的特定功能以来已经有一段时间了。基本上它是一个简单的input 文本框字段,我们允许人们在其中执行搜索。当人们搜索时,我们会显示一个自动完成功能,其中列出了与用户搜索的内容相匹配的 5 个项目。

在这个项目开始的相当长一段时间里,我相信我们的自动完成功能运行得非常快——只要我们在这个字段中点击三个字符,我们就会立即启动自动完成功能——我们正在使用弹性搜索来索引我们的记录。如今,自动完成功能有效,但需要很长时间才能得到一些结果。很多时候,用户可以输入整个搜索字符串,当他/她在点击此页面之前按 Enter 进入我们的搜索结果页面时,最终会出现自动完成功能。好吧,因为用户已经前往搜索结果页面,所以它出现得太晚了。

我想我会发布我们正在做的事情,看看我们是否缺少某些东西或者它是否与硬件相关(因为我们正在使用虚拟机进行搜索)。无论如何,我们有一个基本的输入文本框:

<input runat="server" spellcheck="false" autocomplete="off" id="search" type="text" placeholder="Search..." class="search" maxlength="100" />

在这个搜索框的条目上,我们调用一个函数来进行这样的搜索:

function DoAJAXSearch() {
    var search = $("#search").val().trim();
    var resultsToReturn = 5;
    var module;
    //have they selected a specific module to search on?
    if (search.length > 0) {
        if ($('#ddlEntity').val() === "") {
            module = 0;
        } else {
            module = $('#<%= ddlEntity.ClientID %> option:selected').val();
        }
        $("#search").autocomplete({
            source: function (request, response) {
                $.ajax({
                    type: "POST",
                    contentType: "application/json; charset=utf-8",
                    url: "/Code/WebServices/Search.asmx/DoSearch",
                    dataType: "json",
                    data: '{"searchQuery":"' + search + '","module":"' + module + '","resultsToReturn":"' + resultsToReturn + '"}',
                    success: function (data) {
                        response($.map(data.d, function (item) {
                            return {
                                entity: item.EntityType,
                                value: item.Summary,
                                url: item.EntityLink,
                                picture: item.PicturePath == null ? "/Images/noimagefound.png" : item.PicturePath
                            }
                        }));
                    }
                });
            },
            minLength: 3,
            select: function (event, ui) {
                //log the search back to the database
                LogSearch(module, search, true);
                //end log search back to database

                //open the selected item
                window.open(ui.item.url, "_self");
            },
            open: function () {
            },
            close: function () {
            },
            error: function (xmlHttpRequest, textStatus) {
                alert(textStatus);
            },
            search: function () {
                $(this).addClass('autocompleteloading');
            },
            response: function () {
                $(this).removeClass('autocompleteloading');
            }
        }).data("ui-autocomplete")._renderItem = function (ul, item) {
            ul.addClass("addShadow");
            var face;
            if (item != null) {
                if (item.value.length > 50) {
                    face = item.value.substr(0, 50) + '...';
                } else {
                    face = item.value;
                }
                return $("<li>")
                    .append("<a title='" + item.value + "' href='" + item.url + "'><div style='float:left;width:100%;'><div class='dPicture'><img width='48px' height='48px' src='" + item.picture + "' /></div><div class='dPicture'><div class='dPictureFields'><b>" + item.entity + "</b></div><div class='dPictureFields'>" + face + "</div></div></div><div class='clear-fix'></div></a></li>")
                    .appendTo(ul);
            }
            return false;
        };
    }
}

基本上,正如您在此处看到的,我们允许用户搜索所有模块或特定模块(如果他们选择了一个)。我们只在输入 3 个字符时调用此 Web 方法:“DoSearch”。

DoSearch 是 asmx 文件中的一个方法(因为这是一个 Web 表单项目)。它很长,我知道很丑,但它的重点是捕捉用户输入的内容并建立我们的弹性搜索结果。尽管我很讨厌它,但它目前是我们所拥有的:

    public List<ElasticSearchResult> DoSearch(string searchQuery, int module, int resultsToReturn = 0)
            {
                //get tag
                //user can add a tag to the search, "SearchQuery [C++]" looking for anything with SearchQuery that is tagged C++
                string tag = string.Empty;
                int pos = searchQuery.IndexOf('[', 0);
                if (pos > -1)
                {
                    int pos2 = searchQuery.IndexOf(']', pos);

                    if (pos2 > -1)
                    {
                        //we've got [ ]
                        tag = searchQuery.Substring(pos + 1, pos2 - pos - 1).ToLower();
                    }

                    if (pos > 0)
                        searchQuery = searchQuery.Substring(0, pos - 1);
                }
                //The search engine only sees /t, not a tab, so replace it with a space
                searchQuery = searchQuery.Replace("\t", " ");

                //Reserved characters:     + - && || ! ( ) { } [ ] ^ " ~ * ? : \ /
                //Remove reserved characters the user won't use
                var reserverCharacters = new List<string>
                {
                    "+",
                    "&&",
                    "||",
                    "(",
                    ")",
                    "{",
                    "}",
                    "[",
                    "]",
                    "^",
                    "~",
                    ":",
                    "\\",
                    "/"
                };
                searchQuery = reserverCharacters.Aggregate(searchQuery,
                    (current, character) => current.Replace(character, ""));

                //To prevent search errors, trim * and whitespace
                searchQuery = searchQuery.Trim(' ', '*'); 
                //Define the descriptors so they can be reused throughout this function

                var companySearchDescriptor = new SearchDescriptor<ElasticSearchCompany>();

                companySearchDescriptor
                    .Size(35)
                    .Query(q => q
                        .QueryString(query => query
                            .AnalyzeWildcard()
                            .OnFieldsWithBoost(b => b
                                .Add(f => f.Summary, 1.6)
                                .Add(f => f.Summary.Suffix("ngram"), 1.5)
                                .Add(f => f.Description, 1.0)
                                .Add(f => f.City.Suffix("ngram"), 1.1)
                                .Add(f => f.City, 1.0)
                                .Add(f => f.Tags, 1.0))
                            .Query(searchQuery)));

                var contactSearchDescriptor = new SearchDescriptor<ElasticSearchContact>();
                contactSearchDescriptor
                    .Size(35)
                    .Query(q => q
                        .QueryString(query => query
                            .AnalyzeWildcard()
                            .OnFieldsWithBoost(b => b
                                .Add(f => f.Summary, 1.6)
                                .Add(f => f.Summary.Suffix("ngram"), 1.5)
                                .Add(f => f.Description, 1.0)
                                .Add(f => f.Email, 1.1)
                                .Add(f => f.Email.Suffix("ngram"), 1.0)
                                .Add(f => f.Title, 1.1)
                                .Add(f => f.Title.Suffix("ngram"), 1.0)
                                .Add(f => f.Tags, 1.0))
                            .Query(searchQuery)));
                var userSearchDescriptor = new SearchDescriptor<ElasticSearchUser>();
                userSearchDescriptor
                    .Size(35)
                    .Query(q => q
                        .QueryString(query => query
                            .AnalyzeWildcard()
                            .OnFieldsWithBoost(b => b
                                .Add(f => f.Summary, 1.6)
                                .Add(f => f.Summary.Suffix("ngram"), 1.5)
                                .Add(f => f.Description, 1.0)
                                .Add(f => f.Email, 1.1)
                                .Add(f => f.Email.Suffix("ngram"), 1.0)
                                .Add(f => f.Title, 1.1)
                                .Add(f => f.Title.Suffix("ngram"), 1.0)
                                .Add(f => f.Tags, 1.0))
                            .Query(searchQuery)));

                var corporationSearchDescriptor = new SearchDescriptor<ElasticSearchCorporation>();
                corporationSearchDescriptor
                    .Size(35)
                    .Query(q => q
                        .QueryString(query => query
                            .AnalyzeWildcard()
                            .OnFieldsWithBoost(b => b
                                .Add(f => f.Summary, 1.6)
                                .Add(f => f.Summary.Suffix("ngram"), 1.5)
                                .Add(f => f.Description, 1.0)
                                .Add(f => f.Tags, 1.0))
                            .Query(searchQuery)));

                var documentSearchDescriptor = new SearchDescriptor<ElasticSearchDocument>();
                documentSearchDescriptor
                    .Size(35)
                    .Query(q => q
                        .QueryString(query => query
                            .AnalyzeWildcard()
                            .OnFieldsWithBoost(b => b
                                .Add(f => f.Summary, 2.0)
                                .Add(f => f.Summary.Suffix("ngram"), 1.5)
                                .Add(f => f.Title, 1.4)
                                .Add(f => f.Title.Suffix("ngram"), 1.0)
                                .Add(f => f.Description, 1.0)
                                .Add(f => f.Tags, 1.0))
                            .Query(searchQuery)));

                var checklistSearchDescriptor = new SearchDescriptor<ElasticSearchChecklist>();
                checklistSearchDescriptor
                    .Size(35)
                    .Query(q => q
                        .QueryString(query => query
                            .AnalyzeWildcard()
                            .OnFieldsWithBoost(b => b
                                .Add(f => f.Summary, 2.0)
                                .Add(f => f.Summary.Suffix("ngram"), 1.9)
                                .Add(f => f.Summary.Suffix("ngram_no_punct"), 1.9)
                                .Add(f => f.Id, 2.0)
                                .Add(f => f.Milestone, 1.6)
                                .Add(f => f.RecordName, 1.5)
                                .Add(f => f.RecordName.Suffix("ngram"), 1.4)
                                .Add(f => f.RecordName.Suffix("ngram_no_punct"), 1.4)
                                .Add(f => f.ParentName, 1.3)
                                .Add(f => f.ParentName.Suffix("ngram"), 1.2)
                                .Add(f => f.ParentName.Suffix("ngram_no_punct"), 1.2)
                                .Add(f => f.Description, 1.0)
                                .Add(f => f.Tags, 1.0))
                            .Query(searchQuery)));

                var issueSearchDescriptor = new SearchDescriptor<ElasticSearchIssue>();
                issueSearchDescriptor
                    .Size(35)
                    .Query(q => q
                        .QueryString(query => query
                            .AnalyzeWildcard()
                            .OnFieldsWithBoost(b => b
                                .Add(f => f.Summary, 1.3)
                                .Add(f => f.Summary.Suffix("ngram"), 1.2)
                                .Add(f => f.Description, 1.0)
                                .Add(f => f.IssueNumber, 2.0)
                                .Add(f => f.LessonLearned, 1.0)
                                .Add(f => f.Resolution, 1.0)
                                .Add(f => f.Type, 1.0)
                                .Add(f => f.Tags, 1.0))
                            .Query(searchQuery)));

//SIMILIAR CODE FOR ALL OTHER MODULES BASED ON ISSUE ABOVE 
//TOO MUCH CODE TO POST

                //Need the client before you can make a call to the search servers
                var esClient = Code.Utilities.GetElasticClient();
                if (esClient == null) return null; 
                IMultiSearchResponse result;

                //Each case(other than default) handles if the user chose to only search for a single type of module
                switch (module)
                {
                    case (int) Module.Company:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchCompany>(a => companySearchDescriptor));
                        break;
                    }
                    case (int) Module.Contact:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchContact>(a => contactSearchDescriptor));
                        break;
                    }
                    case (int) Module.Corporate:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchCorporation>(a => corporationSearchDescriptor));
                        break;
                    }
                    case (int) Module.Checklist:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchChecklist>(a => checklistSearchDescriptor));
                        break;
                    }
                    case (int) Module.Document:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchDocument>(a => documentSearchDescriptor));
                        break;
                    }
                    case (int) Module.Issue:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchIssue>(a => issueSearchDescriptor));
                        break;
                    }
                    case (int) Module.TaskReminder:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchTaskReminder>(a => taskSearchDescriptor));
                        break;
                    }
                    case (int) Module.LessonLearned:
                    {
                        result =
                            esClient.MultiSearch(search => search
                                .Search<ElasticSearchLessonLearned>(a => lessonLearnedSearchDescriptor));
                        break;
                    }
                    case (int) Module.Program:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchProgram>(a => programSearchDescriptor));
                        break;
                    }
                    case (int) Module.Project:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchProject>(a => projectSearchDescriptor));
                        break;
                    }
                    case (int) Module.SubProject:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchSubProject>(a => subprojectSearchDescriptor));
                        break;
                    }
                    case (int) Module.ServiceOrder:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchServiceOrder>(a => serviceOrderDescriptor));
                        break;
                    }
                    case (int) Module.Meeting:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchMeeting>(a => meetingSearchDescriptor));
                        break;
                    }
                    case (int) Module.Tip:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchTip>(a => tipSearchDescriptor));
                        break;
                    }
                    case (int) Module.Incident:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchIncident>(a => incidentSearchDescriptor));
                        break;
                    }
                    case (int) Module.Material:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchMaterial>(a => materialSearchDescriptor));
                        break;
                    }
                    case (int) Module.Training:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchTraining>(a => trainingSearchDescriptor));
                        break;
                    }
                    case (int) Module.User:
                    {
                        result = esClient.MultiSearch(search => search
                            .Search<ElasticSearchUser>(a => userSearchDescriptor));
                        break;
                    }
                    default: //Handles the "All" type search
                    {
                        //LessonLearned results are Issues, don't want to search them twice in "All" type search
                        if (MySession.Current.User.IsAdmin)
                        {
                            //allow user search
                            result = esClient.MultiSearch(search => search
                                .Search<ElasticSearchCompany>(a => companySearchDescriptor)
                                .Search<ElasticSearchContact>(a => contactSearchDescriptor)
                                .Search<ElasticSearchUser>(a => userSearchDescriptor)
                                .Search<ElasticSearchCorporation>(a => corporationSearchDescriptor)
                                .Search<ElasticSearchChecklist>(a => checklistSearchDescriptor)
                                .Search<ElasticSearchDocument>(a => documentSearchDescriptor)
                                .Search<ElasticSearchIssue>(a => issueSearchDescriptor)
                                .Search<ElasticSearchTaskReminder>(a => taskSearchDescriptor)
                                .Search<ElasticSearchProgram>(a => programSearchDescriptor)
                                .Search<ElasticSearchProject>(a => projectSearchDescriptor)
                                .Search<ElasticSearchSubProject>(a => subprojectSearchDescriptor)
                                .Search<ElasticSearchServiceOrder>(a => serviceOrderDescriptor)
                                .Search<ElasticSearchMeeting>(a => meetingSearchDescriptor)
                                .Search<ElasticSearchTraining>(a => trainingSearchDescriptor)
                                .Search<ElasticSearchIncident>(a => incidentSearchDescriptor)
                                .Search<ElasticSearchMaterial>(a => materialSearchDescriptor)
                                .Search<ElasticSearchTip>(a => tipSearchDescriptor));
                        }
                        else
                        {
                            result = esClient.MultiSearch(search => search
                                    .Search<ElasticSearchCompany>(a => companySearchDescriptor)
                                    .Search<ElasticSearchContact>(a => contactSearchDescriptor)
                                    .Search<ElasticSearchCorporation>(a => corporationSearchDescriptor)
                                    .Search<ElasticSearchChecklist>(a => checklistSearchDescriptor)
                                    .Search<ElasticSearchDocument>(a => documentSearchDescriptor)
                                    .Search<ElasticSearchIssue>(a => issueSearchDescriptor)
                                    .Search<ElasticSearchTaskReminder>(a => taskSearchDescriptor)
                                    .Search<ElasticSearchProgram>(a => programSearchDescriptor)
                                    .Search<ElasticSearchProject>(a => projectSearchDescriptor)
                                    .Search<ElasticSearchSubProject>(a => subprojectSearchDescriptor)
                                    .Search<ElasticSearchServiceOrder>(a => serviceOrderDescriptor)
                                    .Search<ElasticSearchMeeting>(a => meetingSearchDescriptor)
                                    .Search<ElasticSearchTraining>(a => trainingSearchDescriptor)
                                    .Search<ElasticSearchIncident>(a => incidentSearchDescriptor)
                                    .Search<ElasticSearchMaterial>(a => materialSearchDescriptor)
                                    .Search<ElasticSearchTip>(a => tipSearchDescriptor)
                            );
                        }
                        break;
                    }
                }

                List<ElasticSearchResult> esSearchResultList =
                    ParseSearchResults(result, MySession.Current.User.IsAdmin).ToList();

                if (!string.IsNullOrEmpty(tag) && tag.Length > 0)
                    esSearchResultList = esSearchResultList.Where(x => x.Tags.Any(a => a == tag)).ToList();

                //we need to reserve the order of the result 
                //so we added a new property called sort order which we will update below
                int counter = 0;
                foreach (var esr in esSearchResultList)
                {
                    esr.SortOrder = counter;
                    counter++;
                }

                //Remove any results the user doesn't have permission to see
                var entityResultList = new List<ElasticSearchResult>();
                //this is a inteermediate place holder for records
                var filteredResultList = new List<ElasticSearchResult>();
                //this will have the final result after filtering 

                //get the search entity list
                var entityList = CachedData.EntitySearchList;
                using (var db = DataCenterAccess.NewConnection())
                {
                    //loop thru the entity search to filter what the current user can /cannot see
                    foreach (var entity in entityList)
                    {
                        //get user permission for current entity
                        Answer permission =
                            (Answer) MySession.Current.Permission.GetPermission(entity.EntityID, (int) ActionEnum.View);

                        if (permission == Answer.Yes)
                        {
                            //if yes get all records for that entity type
                            entityResultList = esSearchResultList.Where(x => x.EntityId == entity.EntityID).ToList();
                        }
                        else if (permission == Answer.Originator)
                        {
                            //if originator ; get only record the user can see for that entity type
                            entityResultList =
                                esSearchResultList.Where(
                                    x =>
                                        x.EntityId == entity.EntityID &&
                                        x.AddedByUserId == MySession.Current.User.UserId).ToList();
                        }

                        //add the result to the final result list
                        filteredResultList.AddRange(entityResultList);
                    }

                    //now we need to isolate the records that are private based on the team record security
                    var privateRecords = filteredResultList.Where(x => x.Private).ToList();

                    //also make sure the filtered list does not have any of the private records
                    filteredResultList = filteredResultList.Where(x => !x.Private).ToList();

                    if (privateRecords.Any())
                    {
                        //if we have any private records
                        //we need to loop thru and see if the user is on a team that has access to the record
                        foreach (var esr in privateRecords)
                        {
                            if (RIMSBL.Code.DAL.UserHasTeamPermission(db, esr.EntityId, esr.RecordId,
                                MySession.Current.User.UserId))
                                filteredResultList.Add(esr);
                        }
                    }
                }
                    //0 means return all. If not 0, then limit to that number of search results
                if (resultsToReturn != 0)
                {
                    filteredResultList = filteredResultList.OrderBy(x => x.SortOrder).Take(resultsToReturn).ToList();
                }
                return filteredResultList;
            }

我不确定问题是否与数据有关,因为我们只处理数十万条记录中的某些内容(跨所有模块)。我们要做的一件事是,当您在搜索文本框中输入字母时,我们会不断调用 DoSearch,完成后它会返回一个状态码。因此,如果您输入 7 个字符,则会在第 3 个字符上调用搜索,直到第 7 个字符(总共 5 次),如 chrome 控制台所示:

我唯一能想到的另一件事是也许我们只需要更好的硬件。该服务器统计:

我知道搜索服务器内存不足,但在我请求其他硬件之前,我想看看我是否缺少任何东西。

【问题讨论】:

  • 优化的第一条规则是“衡量它”。您是否尝试过测量究竟需要多少时间?后续请求是否实际上会减慢第一个请求(即仅测量 3 个字母的响应时间与 3 个字母的响应时间,然后您继续输入)?需要测量的明显的事情是:switch (module) 下的 MultiSearch 的时间,ParseSearchResults 的跟踪标记过滤的时间(顺便说一句,你不能把它移到 ElasticSearch 吗?),以及最终块的时间可能访问D B。然后您(和我们)可以看到需要进一步关注的内容。
  • 我要做的第一件事是使用 Chrome DevTools 来确定您的瓶颈是在后端还是前端。 (序列化结果或渲染它们)。我们可以查看其中一个DoSearch 电话的详细信息吗?

标签: jquery search autocomplete


【解决方案1】:

首先,您必须衡量它是服务器还是客户端(浏览器)。在服务器代码中添加一个断点并单步执行以获取想法。

那么。要防止对服务器的多个请求,请在您的自动完成初始化中添加 delay 选项:

$("#search").autocomplete({
    delay: 550,
    //continued...
});

这种方式自动完成功能仅在用户暂停输入 550 毫秒或更长时间时才有效。试验一下这个值。

另外,我注意到您在 DoAJAXSearch 函数中每次都重新初始化自动完成,这是预期的行为吗?这个函数DoAJAXSearch 什么时候被准确调用? 每次击键?怪不得那么慢。 Automcomplete 应该只在页面加载时初始化一次。

类似这样的:

$(function() { //this is a shortcut for "run this code when document is ready"
    $("#search").autocomplete({
        source: function (request, response) {
            var search = $("#search").val().trim();
            var resultsToReturn = 5;
            var module;
            if ($('#ddlEntity').val() === "") {
                module = 0;
            } else {
                module = $('#<%= ddlEntity.ClientID %> option:selected').val();
            }
            $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                url: "/Code/WebServices/Search.asmx/DoSearch",
                dataType: "json",
                data: '{"searchQuery":"' + search + '","module":"' + module + '","resultsToReturn":"' + resultsToReturn + '"}',
                success: function (data) {
                    response($.map(data.d, function (item) {
                        return {
                            entity: item.EntityType,
                            value: item.Summary,
                            url: item.EntityLink,
                            picture: item.PicturePath == null ? "/Images/noimagefound.png" : item.PicturePath
                        }
                    }));
                }
            });
        },
        minLength: 3,
        delat: 550,
        select: function (event, ui) {
            //log the search back to the database
            LogSearch(module, search, true);
            //end log search back to database

            //open the selected item
            window.open(ui.item.url, "_self");
        },
        open: function () {
        },
        close: function () {
        },
        error: function (xmlHttpRequest, textStatus) {
            alert(textStatus);
        },
        search: function () {
            $(this).addClass('autocompleteloading');
        },
        response: function () {
            $(this).removeClass('autocompleteloading');
        }
    }).data("ui-autocomplete")._renderItem = function (ul, item) {
        ul.addClass("addShadow");
        var face;
        if (item != null) {
            if (item.value.length > 50) {
                face = item.value.substr(0, 50) + '...';
            } else {
                face = item.value;
            }
            return $("<li>")
                .append("<a title='" + item.value + "' href='" + item.url + "'><div style='float:left;width:100%;'><div class='dPicture'><img width='48px' height='48px' src='" + item.picture + "' /></div><div class='dPicture'><div class='dPictureFields'><b>" + item.entity + "</b></div><div class='dPictureFields'>" + face + "</div></div></div><div class='clear-fix'></div></a></li>")
                .appendTo(ul);
        }
        return false;
    };
});

PS。 16gig 服务器完全没问题,不应该成为 asp.net 的瓶颈。

【讨论】:

  • 好的,我可以添加延迟...但是关于初始化。我的关键事件是调用这个函数 DoAjaxSearch。如果我删除自动完成的初始化,那么这个方法会怎样?此外,自动完成功能中有一个源代码。如果我没有在 DoAjaxSearch 中包含这个,如果没有自动完成,这个方法会是什么样子?
  • @JonH 你根本不需要DoAJAXSearch 函数,因为.autocomplete 将它自己的按键处理程序绑定到文本框。将您的逻辑移至function (request, response) 函数体,例如在调用实际$.ajax 之前从下拉列表等中提取数据。并在 document.ready 上初始化自动完成
  • 我不确定我是否关注,您可以编辑您的答案并使用我的功能并进行修改。假设我从两个不同的地方调用此函数,一个是 $('#search').on('input keyup paste', function () { DoAJAXSearch(); });,另一个是下拉列表中的更改事件。
  • 但是如果我的下拉值改变了值或者我的文本框改变了值,自动完成怎么知道呢?此外,在我的下拉更改事件中,我目前正在调用此 DoAjaxSearch...我现在也将更改它吗?
  • 我认为它可能需要一些时间才能被授予。但我确实接受了你的回答并获得了赏金。请耐心等待。
猜你喜欢
  • 1970-01-01
  • 2013-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多