Compartilhar via


Cutting Edge

Continue

Dino Esposito

Dino EspositoDesde os primórdios da Web, a maioria das páginas apresenta uma caixa de pesquisa para ajudá-lo a localizar rapidamente o conteúdo da página em si ou no site. Uma função de pesquisa bem feita é obrigatória em sites maiores. Isso ajuda os usuários a localizar o que desejam rápida e facilmente, ignorando a arquitetura e o mapa do site.

Em um site de compra, por exemplo, você talvez queira usar cadeias de consulta para pesquisar produtos, ofertas ou notícias e alertas. Em um site criado para algo como uma equipe esportiva profissional, a função de pesquisa deve ser capaz de encontrar notícias, resultados, nomes de atleta, biografias e assim por diante. A estrutura de dados no qual uma função de pesquisa precisa funcionar nunca é óbvia. É claramente específicas do aplicativo.

Em vez de reinventar a roda toda vez, considere o uso de um mecanismo de pesquisa de texto completo local, como o Lucene.Net para fazer backup de sua função de pesquisa. Um mecanismo como o Lucene.Net indexa um monte de documentos baseados em cadeia de caracteres e analisa qualquer cadeia de consulta no índice. Dessa forma, o mecanismo permite que você especifique combinações complexas de cadeias de consulta. Para muitos sites e páginas, o Lucene.Net pode ser um exagero, mas eles ainda precisam de algum tipo de pesquisa que é mais inteligente do que colocar uma lista interminável de itens em uma lista suspensa.

Esse artigo apresentará uma pequena estrutura para preenchimento automático criado em torno do Twitter typeahead.js. Essa estrutura não é mágica, mas realmente simplifica o uso do preenchimento automático em páginas da Web. O aspecto mais interessante dessa estrutura é que ele permite combinar vários conjuntos de dados para consultar na mesma página e recuperar informações diferentes, porém relacionadas.

Configurando o Typeahead.js

Nesse artigo, eu esclarecerei os fundamentos do uso do typeahead em um cenário realista utilizando a versão do typeahead encontrada no NuGet ao digitar "typeahead", conforme mostrado na Figura 1. Quando você pesquisa no Google typeahead, é possível executar em referências anteriores, arquivos JavaScript mais antigos ou apenas versões bifurcadas do código do projeto original. A documentação também está incorreta.

O pacote do NuGet para Typeahead.js do Twitter
Figura 1 O pacote do NuGet para Typeahead.js do Twitter

O arquivo de pacote contém todos os pacotes que compõem a biblioteca, incluindo o mecanismo Bloodhound para gerenciar dicas no navegador local. Para configurar um modo de exibição Razor ou página da Web para usar typeahead.js, basta estar familiarizado com a sintaxe do jQuery e ativar o plug-in no campo de entrada selecionado. Veja um exemplo:

<form action="@Url.Action("Query", "Home")" method="post">

  <input type="hidden" id="queryCode" name="queryCode" />

    <input type="text" name="queryString" id="queryString">

    <button id="queryButton" type="submit">Get</button>

</form>

<form action="@Url.Action("Query", "Home")" method="post">
  <input type="hidden" id="queryCode" name="queryCode" />
    <input type="text" name="queryString" id="queryString">
    <button id="queryButton" type="submit">Get</button>
</form>

É importante observar que para usar o preenchimento automático em uma página da Web para algo útil, também é necessário um campo amigo oculto para coletar algum tipo de ID exclusivo para a dica selecionado. Há muitos cenários em que você pode considerar o uso de um campo de entrada de preenchimento automático. O cenário que tenho interesse usa o preenchimento automático para substituir uma lista suspensa infinita. Ele permite a pesquisa de estilo do Bing leve no seu site, sem exigir a ajuda de mecanismos de texto completo, como o Lucene.Net.

Código de script para ter na exibição

Para usar typeahead.js, consulte jQuery 1.9.1 ou superior e o script typeahead. A quantidade mínima de código que você precisará no modo de exibição é mostrada aqui:

$('#queryString').typeahead(
  null,
  {
    displayKey: 'value',
    source: hints.ttAdapter()
  }
});

Dessa forma, você pode realizar todas as configurações padrão e instruir o mecanismo para usar a propriedade de valor nos dados retornados para preencher a lista suspensa. Um valor nomeado da propriedade deve para existir em quaisquer dados que você filtrar. Em teoria, você pode configurar o preenchimento automático em qualquer matriz de dados do JavaScript. Na prática, porém, o preenchimento automático faz sentido principalmente quando dados são baixados de uma fonte remota.

Baixar de uma fonte remota apresenta muitos problemas — da mesma origem da política do navegador, pré-busca e cache — para mencionar alguns. Twitter typeahead.js vem com um mecanismo de sugestão chamado Bloodhound. Isso transparentemente faz a maior parte desse trabalho para você. Se você obtiver o arquivo JavaScript de grupo do NuGet, é possível iniciar apenas chamando no Bloodhound sem se preocupar com seu download e instalação. A variável de dicas no trecho de código anterior resulta do seguinte em vez da inicialização padrão do Bloodhound:

var hints = new Bloodhound({
  datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
  queryTokenizer: Bloodhound.tokenizers.whitespace,
  remote: "/hint/s?query=%QUERY"
});
hints.initialize();

Observe o atributo remoto. Isso é apenas o ponto de extremidade do servidor responsável por retornar dicas para exibir na lista suspensa. Além disso, observe a sintaxe %QUERY. Isso indica que a cadeia de caracteres no campo de entrada está sendo enviada para o servidor para obter dicas. Em outras palavras, %QUERY é um espaço reservado para qualquer texto que estiver no campo de entrada. Por padrão, typeahead.js começa a obter dicas assim que você digitar um único caractere. Se você desejar esperar que os caracteres estejam no buffer antes do preenchimento automático ser iniciado, adicione um objeto de configurações como o primeiro argumento do plug-in:

$('#queryString').typeahead(
  {
    minLength: 2
  },
  {
    displayKey: 'value',
    source: hints.ttAdapter()
  }
});

Quando o buffer estiver cheio o suficiente para iniciar chamadas remotas, Bloodhound começa a funcionar. Ele baixa dados JSON e os adapta para exibição. Nesse ponto, você tem um mecanismo de preenchimento automático com mal funcionamento que exibe sugestões com base em alguma lógica no servidor. No entanto, há muito mais o que fazer antes de poder usar o preenchimento automático efetivamente em uma página real.

Usar Typeahead.js com inicialização

Qualquer plug-in suficientemente complexo precisa de um pouco de CSS para parecer bem. Typeahead.js não é exceção. O plug-in vem com sua própria interface do usuário padrão, mas você pode querer aplicar algumas correções, especialmente se você usá-lo com o Twitter Bootstrap. Você também poderá personalizar alguns atributos visuais, como cores e preenchimento. A Figura 2 lista algumas classes CSS que deseja usar para personalizar a aparência do componente typeahead.js.

Figura 2 As classes CSS para editar para poder personalizar o componente Typeahead.js

Classe CSS Descrição
twitter-typeahead Define o campo de entrada em que o usuário digita dicas.
tt-hint Define de texto que representa o delta entre o que você digitou e a primeira dica. Essa classe é usada somente quando a propriedade de dica é definida como true (false por padrão).
tt-dropdown-menu Define o pop-up suspenso onde as dicas são listadas.
tt-cursor Define as sugestões destacadas na caixa suspensa.
tt-highlight Define a parte do texto que corresponde à cadeia de consulta.

A Figura 3 dá uma ideia do que você pode obter de classes CSS personalizadas. Você também pode personalizar o comportamento geral do plug-in do ponto de vista funcional.

Classes CSS personalizadas podem obter efeitos diferentes para seu aplicativo
Figura 3 Classes CSS personalizadas podem obter efeitos diferentes para seu aplicativo

Adicionar lógica no lado do cliente

Um campo de preenchimento automático é mais rápido do que qualquer menu suspenso longo. Quando o número de itens para escolher for centenas, qualquer lista suspensa clássica é lenta. Portanto, se você planeja usar o campo de entrada de preenchimento automático para selecionar um valor específico — digamos, o nome de um produto ou um cliente — um plug-in typeahead.js simples não é suficiente. Um código de script adicional é necessário que associa ao evento selecionado do plug-in:

$('#queryString').on('typeahead:selected', 
  function (e, datum) {
  $("#queryCode").val(datum.id);
});

O principal benefício de entrada de preenchimento automático é os usuários digitarem um nome inteligível e o sistema recuperar o código exclusivo afiliado ou ID. Esse recurso precisa ser codificado explicitamente. No manipulador de eventos selecionado, você recupera as informações de ID do objeto de referência e as armazena com segurança em um campo oculto. Quando o formulário ao qual pertencem os campos de entrada de preenchimento automático for postado, a ID selecionada também é postada. O formato do objeto de referência — o item de dados selecionado na lista suspensa — depende do formato dos dados que você recebe do lado do servidor.

E o texto que está sendo exibido no campo de entrada? Nesse cenário, você provavelmente não precisará ter qualquer texto significativo exibido no campo de entrada. A entrada relevante para outras operações é o que você armazena no campo oculto. O que você exibir lá cabe a você. Apenas observe se você especifica nas configurações do plug-in uma propriedade displayKey, porque o valor dessa propriedade será exibido automaticamente no campo de entrada. Em qualquer taxa, você pode definir qualquer valor programaticamente:

$("#queryString").val(datum.label);

Em alguns casos, a caixa de texto de preenchimento automático é o único elemento no formulário HTML. Isso significa que talvez você queira processar qualquer dado selecionado assim que ele for selecionado. Adicionando a seguinte linha ao manipulador de eventos selecionado typeahead.js, é possível simular um clique no botão de envio do formulário:

$("#queryButton").click();

Suponha que um usuário começa a digitar no campo de entrada, faz com que a lista suspensa seja exibida e interrompe de digitação sem fazer uma seleção. O que você faz quando ele retoma a digitação? Naturalmente, você aguardaria que ele digitasse novamente até fazer uma seleção. Depois que o usuário fizer uma seleção, algum código é armazenado. Portanto, quando a edição recomeça, essa seleção deve ser cancelada. Você precisa de uma variável local para fazer isso:

var typeaheadItemSelected = false;
$('#queryString').on('typeahead:selected', function (e, datum) {
  $("#queryCode").val(datum.id);
  typeaheadItemSelected = true;
});

Também é necessário um manipulador para o evento de foco do campo de entrada para redefinir todos os dados armazenados:

$('#queryString').on('input', function () {
  if (typeaheadItemSelected) {
    typeaheadItemSelected = false;
    $('#queryString').val(''); 
    $("#queryCode").val('');
  }
});

O principal objetivo dessa lógica extra no lado do cliente é garantir que o campo de entrada de preenchimento automático funcione como uma lista suspensa.

O lado do servidor de preenchimento automático

Tudo o que qualquer código no lado do cliente pode fazer depende estritamente os dados retornados do servidor. No mínimo, o ponto de extremidade do servidor é apenas uma URL que retorna dados JSON. Quando você tiver um ponto de extremidade que serve uma coleção de objetos Product, por exemplo, você pode usar a propriedade displayKey do typeahead.js para executar algum tipo de associação de dados na lista suspensa de dicas. Em sua forma mais simples, um método de controlador retornando JSON pode ser assim:

public JsonResult P(string query)
{
  var productHints = _service.GetMatchingProducts(query);
  return Json(productHints, JsonRequestBehavior.AllowGet);
}

Se o campo de entrada de preenchimento automático deve exibir dicas para itens de dados homogêneos, essa é uma abordagem ideal. No lado do cliente, na verdade, você pode facilmente aproveitar o mecanismo de modelagem incorporado no typeahead.js e organizar exibições de sugestão personalizadas:

$('#queryString').typeahead(
null,
{
  templates: {
    suggestion: Handlebars.compile('<b>({{Id}}</b>: {{notes}}')
  },
  source: hints.ttAdapter()
});

A propriedade de modelos substitui displayKey e define um layout personalizado para o conteúdo da lista suspensa. A lista suspensa na Figura 3 resulta do trecho de código anterior. Ao organizar um modelo, você talvez queira usar um mecanismo de modelo local, como Handlebars (handlebarsjs.com). Você deve vincular Handlebars ao projeto separadamente do typeahead.js. Usar Handlebars é opcional. Você pode sempre formatar um modelo HTML através do código JavaScript manual ou até mesmo retornar um objeto do servidor com HTML pré-formatado.

As coisas ficam mais complicadas quando sua entrada de preenchimento automático deve retornar dicas heterogêneas, como produtos ou ofertas. Nesse caso, você deve retornar uma matriz de algum tipo de dados intermediário que contém informações suficientes para que o usuário escolha. O preenchimento automático obtido com esse artigo oferece uma classe AutoCompleteItem básica, como mostrado aqui:

public class AutoCompleteItem
{
  public String label { get; set; }
  public String id { get; set; }
  public String value { get; set; }
}

A propriedade id contém uma ID exclusiva. Quando postada, é significativo para o controlador de recebimento. Ele normalmente feito de duas partes — a ID real e um identificador que corresponde exclusivamente a ID para um dos conjuntos de dados (produtos ou ofertas) possivelmente sendo retornado na consulta. O valor da propriedade é o conteúdo de cadeia de caracteres para mostrar na lista suspensa. A outra propriedade é um tipo de propriedade de carga para qualquer outra coisa que seja necessária. A propriedade de valor também pode conter cadeias de caracteres HTML organizadas no lado do servidor para layouts de sugestão personalizados. O código do servidor também é responsável pela execução de todas as consultas necessárias e manipular dados em uma coleção de objetos AutoCompleteItem.

Conclusão

A facilidade de uso é mais importante diariamente em sites da Web modernos. É bem-vinda na face pública do site, mas ainda é mais crítica em back-ends do site da Web em que as informações reais são inseridas. No lado do administrador de um dos meus sites da Web, uma vez tive uma lista suspensa com mais de 700 itens. Funcionou, mas era lenta. Eu substituí com preenchimento automático e agora é muitíssimo rápido. Eu organizei um projeto GitHub para esse e outros utilitários em bit.ly/1zubJea.


Dino Esposito é o co-autor de “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) e “Programming ASP.NET MVC 5” (Microsoft Press, 2014). Um evangelista técnico para as plataformas Microsoft .NET Framework e Android na JetBrains e palestrante frequente em eventos do setor em todo mundo, Esposito compartilha sua visão do software em software2cents.wordpress.com e no Twitter em twitter.com/despos.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Jon Arne Saeteras