Tutorial: Adicionar conclusão automática e sugestões com o SDK .NET

Saiba como implementar a conclusão automática (consultas typeahead e resultados sugeridos) quando um utilizador começa a escrever numa caixa de pesquisa. Neste tutorial, vamos mostrar consultas com conclusão automática e resultados sugeridos separadamente e, em seguida, em conjunto. Um utilizador só pode ter de escrever dois ou três carateres para localizar todos os resultados disponíveis.

Neste tutorial, ficará a saber como:

  • Adicionar sugestões
  • Adicionar realce às sugestões
  • Adicionar conclusão automática
  • Combinar a conclusão automática e sugestões

Descrição Geral

Este tutorial adiciona a conclusão automática e os resultados sugeridos ao tutorial Anterior Adicionar paginação aos resultados da pesquisa .

Pode encontrar uma versão concluída do código neste tutorial no projeto seguinte:

Pré-requisitos

Adicionar sugestões

Vamos começar com o caso mais simples de oferecer alternativas ao utilizador: uma lista pendente de resultados sugeridos.

  1. No ficheiro index.cshtml, altere @id a instrução TextBoxFor para azureautosuggest.

     @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azureautosuggest" }) <input value="" class="searchBoxSubmit" type="submit">
    
  2. Seguindo esta instrução, depois de fechar </div>, introduza este script. Este script tira partido do widget de Conclusão Automática da biblioteca de IU do jQuery de código aberto para apresentar a lista pendente de resultados sugeridos.

    <script>
        $("#azureautosuggest").autocomplete({
            source: "/Home/SuggestAsync?highlights=false&fuzzy=false",
            minLength: 2,
            position: {
                my: "left top",
                at: "left-23 bottom+10"
            }
        });
    </script>
    

    O ID "azureautosuggest" liga o script acima à caixa de pesquisa. A opção de origem do widget está definida como um método Suggest que chama a API Suggest com dois parâmetros de consulta: destaques e fuzzy, ambos definidos como falso nesta instância. Além disso, é necessário um mínimo de dois carateres para acionar a pesquisa.

Adicionar referências a scripts jQuery à vista

  1. Para aceder à biblioteca jQuery, altere a <secção principal> do ficheiro de vista para o seguinte código:

    <head>
        <meta charset="utf-8">
        <title>Typeahead</title>
        <link href="https://code.jquery.com/ui/1.12.1/themes/start/jquery-ui.css"
              rel="stylesheet">
        <script src="https://code.jquery.com/jquery-1.10.2.js"></script>
        <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
    
        <link rel="stylesheet" href="~/css/hotels.css" />
    </head>
    
  2. Uma vez que estamos a introduzir uma nova referência jQuery, também precisamos de remover, ou comentar, a referência jQuery predefinida no ficheiro _Layout.cshtml (na pasta Vistas/Partilhado ). Localize as seguintes linhas e comente a primeira linha de script, conforme mostrado. Esta alteração evita referências em conflito ao jQuery.

    <environment include="Development">
        <!-- <script src="~/lib/jquery/dist/jquery.js"></script> -->
        <script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
        <script src="~/js/site.js" asp-append-version="true"></script>
    </environment>
    

    Agora, podemos utilizar as funções de jQuery de Conclusão Automática predefinidas.

Adicionar a ação Sugerir ao controlador

  1. No controlador principal, adicione a ação SuggestAsync (após a ação PageAsync ).

    public async Task<ActionResult> SuggestAsync(bool highlights, bool fuzzy, string term)
    {
        InitSearch();
    
        // Setup the suggest parameters.
        var options = new SuggestOptions()
        {
            UseFuzzyMatching = fuzzy,
            Size = 8,
        };
    
        if (highlights)
        {
            options.HighlightPreTag = "<b>";
            options.HighlightPostTag = "</b>";
        }
    
        // Only one suggester can be specified per index. It is defined in the index schema.
        // The name of the suggester is set when the suggester is specified by other API calls.
        // The suggester for the hotel database is called "sg", and simply searches the hotel name.
        var suggestResult = await _searchClient.SuggestAsync<Hotel>(term, "sg", options).ConfigureAwait(false);
    
        // Convert the suggested query results to a list that can be displayed in the client.
        List<string> suggestions = suggestResult.Value.Results.Select(x => x.Text).ToList();
    
        // Return the list of suggestions.
        return new JsonResult(suggestions);
    }
    

    O parâmetro Tamanho especifica o número de resultados a devolver (se não for especificado, a predefinição é 5). É especificado um sugeridor no índice de pesquisa quando o índice é criado. No índice de hotéis de exemplo alojado pela Microsoft, o nome do sugeridor é "sg" e procura correspondências sugeridas exclusivamente no campo HotelName .

    A correspondência difusa permite que "quase falhas" sejam incluídas na saída, até uma distância de edição. Se o parâmetro de destaques estiver definido como verdadeiro, as etiquetas HTML a negrito são adicionadas à saída. Vamos definir ambos os parâmetros como verdadeiros na secção seguinte.

  2. Poderá obter alguns erros de sintaxe. Se for o caso, adicione as duas seguintes instruções using à parte superior do ficheiro.

    using System.Collections.Generic;
    using System.Linq;
    
  3. Execute a aplicação. Obtém um intervalo de opções quando introduz "po", por exemplo? Agora, experimente "pa".

    Escrever *po* revela duas sugestões

    Tenha em atenção que as letras introduzidas têm de iniciar uma palavra e não ser simplesmente incluídas na palavra.

  4. No script de vista, defina &difuso como verdadeiro e execute a aplicação novamente. Agora, introduza "po". Repare que a pesquisa pressupõe que recebeu uma letra errada.

    Escrever *pa* com o conjunto difuso como verdadeiro

    Se estiver interessado, a sintaxe de consulta Lucene no Azure Cognitive Search descreve a lógica utilizada em pesquisas difusas em detalhe.

Adicionar realce às sugestões

Podemos melhorar o aspeto das sugestões para o utilizador ao definir o parâmetro de destaques como verdadeiro. No entanto, primeiro temos de adicionar algum código à vista para apresentar o texto a negrito.

  1. Na vista (index.cshtml), adicione o seguinte script após o "azureautosuggest" script descrito anteriormente.

    <script>
        var updateTextbox = function (event, ui) {
            var result = ui.item.value.replace(/<\/?[^>]+(>|$)/g, "");
            $("#azuresuggesthighlights").val(result);
            return false;
        };
    
        $("#azuresuggesthighlights").autocomplete({
            html: true,
            source: "/home/suggest?highlights=true&fuzzy=false&",
            minLength: 2,
            position: {
                my: "left top",
                at: "left-23 bottom+10"
            },
            select: updateTextbox,
            focus: updateTextbox
        }).data("ui-autocomplete")._renderItem = function (ul, item) {
            return $("<li></li>")
                .data("item.autocomplete", item)
                .append("<a>" + item.label + "</a>")
                .appendTo(ul);
        };
    </script>
    
  2. Agora, altere o ID da caixa de texto para que seja apresentado da seguinte forma.

    @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azuresuggesthighlights" }) <input value="" class="searchBoxSubmit" type="submit">
    
  3. Execute a aplicação novamente e deverá ver o texto introduzido a negrito nas sugestões. Experimente escrever "pa".

    Escrever *pa* com realce

    A lógica utilizada no script de realce acima não é infalível. Se introduzir um termo que aparece duas vezes com o mesmo nome, os resultados a negrito não são exatamente o que pretende. Experimente escrever "mo".

    Uma das perguntas que um programador precisa de responder é quando é que um script está a funcionar "suficientemente bem" e quando é que as suas peculiaridades devem ser abordadas. Não vamos avançar mais neste tutorial, mas encontrar um algoritmo preciso é algo a ter em conta se o realce não for eficaz para os seus dados. Para obter mais informações, veja Realce de acesso.

Adicionar conclusão automática

Outra variação, ligeiramente diferente das sugestões, é a conclusão automática (por vezes denominada "type-ahead") que conclui um termo de consulta. Mais uma vez, vamos começar com a implementação mais simples, antes de melhorar a experiência do utilizador.

  1. Introduza o seguinte script na vista, seguindo os scripts anteriores.

    <script>
        $("#azureautocompletebasic").autocomplete({
            source: "/Home/Autocomplete",
            minLength: 2,
            position: {
                my: "left top",
                at: "left-23 bottom+10"
            }
        });
    </script>
    
  2. Agora, altere o ID da caixa de texto, para que seja apresentado da seguinte forma.

    @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azureautocompletebasic" }) <input value="" class="searchBoxSubmit" type="submit">
    
  3. No controlador doméstico, introduza a ação AutocompleteAsync após a ação SuggestAsync .

    public async Task<ActionResult> AutoCompleteAsync(string term)
    {
        InitSearch();
    
        // Setup the autocomplete parameters.
        var ap = new AutocompleteOptions()
        {
            Mode = AutocompleteMode.OneTermWithContext,
            Size = 6
        };
        var autocompleteResult = await _searchClient.AutocompleteAsync(term, "sg", ap).ConfigureAwait(false);
    
        // Convert the autocompleteResult results to a list that can be displayed in the client.
        List<string> autocomplete = autocompleteResult.Value.Results.Select(x => x.Text).ToList();
    
        return new JsonResult(autocomplete);
    }
    

    Repare que estamos a utilizar a mesma função de sugestão , denominada "sg", na pesquisa de preenchimento automático, tal como fizemos para obter sugestões (por isso, estamos apenas a tentar preencher automaticamente os nomes dos hotéis).

    Existe um intervalo de definições de AutocompleteMode e estamos a utilizar o OneTermWithContext. Veja API de Conclusão Automática para obter uma descrição das opções adicionais.

  4. Execute a aplicação. Repare como o intervalo de opções apresentado na lista pendente são palavras simples. Experimente escrever palavras a partir de "re". Repare como o número de opções reduz à medida que são digitadas mais letras.

    Escrever com a conclusão automática básica

    Tal como está, o script de sugestões que executou anteriormente é provavelmente mais útil do que este script de conclusão automática. Para tornar a conclusão automática mais simples, considere utilizá-la com resultados sugeridos.

Combinar a conclusão automática e sugestões

Combinar a conclusão automática e sugestões é a mais complexa das nossas opções e, provavelmente, proporciona a melhor experiência de utilizador. O que queremos é apresentar, inline com o texto que está a ser escrito, é a primeira opção de Azure Cognitive Search para a conclusão automática do texto. Além disso, queremos uma série de sugestões como uma lista pendente.

Existem bibliotecas que oferecem esta funcionalidade , muitas vezes denominadas "conclusão automática inline" ou um nome semelhante. No entanto, vamos implementar esta funcionalidade nativamente para que possa explorar as APIs. Vamos começar a trabalhar no controlador primeiro neste exemplo.

  1. Adicione uma ação ao controlador que devolve apenas um resultado de conclusão automática, juntamente com um número especificado de sugestões. Vamos chamar a esta ação Conclusão AutomáticaAndSuggestAsync. No controlador doméstico, adicione a seguinte ação, seguindo as outras novas ações.

    public async Task<ActionResult> AutoCompleteAndSuggestAsync(string term)
    {
        InitSearch();
    
        // Setup the type-ahead search parameters.
        var ap = new AutocompleteOptions()
        {
            Mode = AutocompleteMode.OneTermWithContext,
            Size = 1,
        };
        var autocompleteResult = await _searchClient.AutocompleteAsync(term, "sg", ap);
    
        // Setup the suggest search parameters.
        var sp = new SuggestOptions()
        {
            Size = 8,
        };
    
        // Only one suggester can be specified per index. The name of the suggester is set when the suggester is specified by other API calls.
        // The suggester for the hotel database is called "sg" and simply searches the hotel name.
        var suggestResult = await _searchClient.SuggestAsync<Hotel>(term, "sg", sp).ConfigureAwait(false);
    
        // Create an empty list.
        var results = new List<string>();
    
        if (autocompleteResult.Value.Results.Count > 0)
        {
            // Add the top result for type-ahead.
            results.Add(autocompleteResult.Value.Results[0].Text);
        }
        else
        {
            // There were no type-ahead suggestions, so add an empty string.
            results.Add("");
        }
    
        for (int n = 0; n < suggestResult.Value.Results.Count; n++)
        {
            // Now add the suggestions.
            results.Add(suggestResult.Value.Results[n].Text);
        }
    
        // Return the list.
        return new JsonResult(results);
    }
    

    É devolvida uma opção de conclusão automática na parte superior da lista de resultados , seguida de todas as sugestões.

  2. Na vista, em primeiro lugar, implementamos um truque para que uma palavra de conclusão automática cinzenta clara seja composta diretamente em texto mais a negrito que está a ser introduzido pelo utilizador. O HTML inclui o posicionamento relativo para esta finalidade. Altere a instrução TextBoxFor (e as instruções div> adjacentes<) para o seguinte, observando que uma segunda caixa de pesquisa identificada por baixo está mesmo abaixo da nossa caixa de pesquisa normal, ao retirar esta caixa de pesquisa de 39 píxeis da sua localização predefinida!

    <div id="underneath" class="searchBox" style="position: relative; left: 0; top: 0">
    </div>
    
    <div id="searchinput" class="searchBoxForm" style="position: relative; left: 0; top: -39px">
        @Html.TextBoxFor(m => m.searchText, new { @class = "searchBox", @id = "azureautocomplete" }) <input value="" class="searchBoxSubmit" type="submit">
    </div>
    

    Repare que vamos alterar o ID novamente para azureautocomplete neste caso.

  3. Também na vista, introduza o seguinte script, depois de todos os scripts que introduziu até agora. O script é longo e complexo devido à variedade de comportamentos de entrada que processa.

    <script>
        $('#azureautocomplete').autocomplete({
            delay: 500,
            minLength: 2,
            position: {
                my: "left top",
                at: "left-23 bottom+10"
            },
    
            // Use Ajax to set up a "success" function.
            source: function (request, response) {
                var controllerUrl = "/Home/AutoCompleteAndSuggestAsync?term=" + $("#azureautocomplete").val();
                $.ajax({
                    url: controllerUrl,
                    dataType: "json",
                    success: function (data) {
                        if (data && data.length > 0) {
    
                            // Show the autocomplete suggestion.
                            document.getElementById("underneath").innerHTML = data[0];
    
                            // Remove the top suggestion as it is used for inline autocomplete.
                            var array = new Array();
                            for (var n = 1; n < data.length; n++) {
                                array[n - 1] = data[n];
                            }
    
                            // Show the drop-down list of suggestions.
                            response(array);
                        } else {
    
                            // Nothing is returned, so clear the autocomplete suggestion.
                            document.getElementById("underneath").innerHTML = "";
                        }
                    }
                });
            }
        });
    
        // Complete on TAB.
        // Clear on ESC.
        // Clear if backspace to less than 2 characters.
        // Clear if any arrow key hit as user is navigating the suggestions.
        $("#azureautocomplete").keydown(function (evt) {
    
            var suggestedText = document.getElementById("underneath").innerHTML;
            if (evt.keyCode === 9 /* TAB */ && suggestedText.length > 0) {
                $("#azureautocomplete").val(suggestedText);
                return false;
            } else if (evt.keyCode === 27 /* ESC */) {
                document.getElementById("underneath").innerHTML = "";
                $("#azureautocomplete").val("");
            } else if (evt.keyCode === 8 /* Backspace */) {
                if ($("#azureautocomplete").val().length < 2) {
                    document.getElementById("underneath").innerHTML = "";
                }
            } else if (evt.keyCode >= 37 && evt.keyCode <= 40 /* Any arrow key */) {
                document.getElementById("underneath").innerHTML = "";
            }
        });
    
        // Character replace function.
        function setCharAt(str, index, chr) {
            if (index > str.length - 1) return str;
            return str.substr(0, index) + chr + str.substr(index + 1);
        }
    
        // This function is needed to clear the "underneath" text when the user clicks on a suggestion, and to
        // correct the case of the autocomplete option when it does not match the case of the user input.
        // The interval function is activated with the input, blur, change, or focus events.
        $("#azureautocomplete").on("input blur change focus", function (e) {
    
            // Set a 2 second interval duration.
            var intervalDuration = 2000, 
                interval = setInterval(function () {
    
                    // Compare the autocorrect suggestion with the actual typed string.
                    var inputText = document.getElementById("azureautocomplete").value;
                    var autoText = document.getElementById("underneath").innerHTML;
    
                    // If the typed string is longer than the suggestion, then clear the suggestion.
                    if (inputText.length > autoText.length) {
                        document.getElementById("underneath").innerHTML = "";
                    } else {
    
                        // If the strings match, change the case of the suggestion to match the case of the typed input.
                        if (autoText.toLowerCase().startsWith(inputText.toLowerCase())) {
                            for (var n = 0; n < inputText.length; n++) {
                                autoText = setCharAt(autoText, n, inputText[n]);
                            }
                            document.getElementById("underneath").innerHTML = autoText;
    
                        } else {
                            // The strings do not match, so clear the suggestion.
                            document.getElementById("underneath").innerHTML = "";
                        }
                    }
    
                    // If the element loses focus, stop the interval checking.
                    if (!$input.is(':focus')) clearInterval(interval);
    
                }, intervalDuration);
        });
    </script>
    

    Repare como a função de intervalo é utilizada para limpar o texto subjacente quando já não corresponde ao que o utilizador está a escrever e também para definir as mesmas maiúsculas ou minúsculas que o utilizador está a escrever (como "pa" corresponde a "PA", "pA", "Pa" ao procurar), para que o texto sobreposto seja organizado.

    Leia os comentários no script para obter uma compreensão mais completa.

  4. Por fim, temos de fazer um pequeno ajuste a duas classes HTML para torná-las transparentes. Adicione a seguinte linha às classes searchBoxForm e searchBox , no ficheiro hotels.css.

    background: rgba(0,0,0,0);
    
  5. Agora, execute a aplicação. Introduza "pa" na caixa de pesquisa. Você recebe "palácio" como a sugestão de conclusão automática, juntamente com dois hotéis que contêm "pa"?

    Escrever com preenchimento automático inline e sugestões

  6. Experimente fazer tabbing para aceitar a sugestão de preenchimento automático e experimente selecionar sugestões com as teclas de seta e a tecla de tabulação e tente novamente com o rato e um único clique. Verifique se o script processa todas estas situações corretamente.

    Pode decidir que é mais simples carregar numa biblioteca que oferece esta funcionalidade por si, mas agora sabe, pelo menos, uma forma de obter a conclusão automática inline para funcionar.

Conclusões

Considere as seguintes conclusões deste projeto:

  • A conclusão automática (também conhecida como "type-ahead") e as sugestões podem permitir que o utilizador escreva apenas algumas chaves para localizar exatamente o que pretende.
  • A conclusão automática e as sugestões que funcionam em conjunto podem proporcionar uma experiência de utilizador avançada.
  • Teste sempre as funções de conclusão automática com todas as formas de entrada.
  • A utilização da função setInterval pode ser útil para verificar e corrigir elementos da IU.

Passos seguintes

No próximo tutorial, vamos ver outra forma de melhorar a experiência do utilizador, utilizando facetas para restringir pesquisas com um único clique.