Construir um elemento de formulário de preenchimento automático com Atlas Search e JavaScript
Avalie esse Tutorial
Ao desenvolver um aplicativo da Web, uma experiência de usuário de qualidade pode fazer ou quebrar seu aplicativo. Um recurso comum do aplicativo é permitir que os usuários insiram texto em uma barra de pesquisa para encontrar uma informação específica. Em vez de fazer com que o usuário insira informações e espere que sejam válidas, você pode ajudá-los a encontrar o que procuram, oferecendo sugestões de preenchimento automático enquanto digitam.
Então, o que pode dar errado?
Se seus usuários forem como eu, eles cometerão vários erros de ortografia para cada palavra do texto. Se você estiver criando um campo de preenchimento automático usando expressões regulares em seus dados, programar para contabilizar erros ortográficos e dedos gordos é difícil!
Neste tutorial, veremos como criar um aplicativo Web simples que apresenta sugestões de preenchimento automático para o usuário. Essas sugestões podem ser criadas facilmente usando os recursos de pesquisa de texto completo disponíveis no Atlas Search.
Para ter uma ideia melhor do que queremos realizar, dê uma olhada na seguinte imagem animada:
Na imagem acima, você notará que eu não apenas cometi erros de ortografia, mas também usei uma palavra que aparecia em qualquer lugar dentro do campo para qualquer documento em uma collection.
Iremos ignorar as etapas básicas de configuração do Node.js ou do MongoDB e presumiremos que você já tem alguns itens instalados, configurados e prontos para Go:
- MongoDB Atlas com um cluster M0 ou superior, com configurações de lista segura de usuário e rede estabelecidas
- Node.js instalado e configurado
- Um bancodedados de receitas com uma coleção dereceitas estabelecida
Usaremos o Atlas Search dentro do MongoDB Atlas. Para seguir este tutorial, a collection(dentro do banco de dados decomidas) esperará documentos assim:
1 { 2 "_id": "5e5421451c9d440000e7ca13", 3 "name": "chocolate chip cookies", 4 "ingredients": [ 5 "sugar", 6 "flour", 7 "chocolate" 8 ] 9 }
Certifique-se de criar vários documentos em sua coleção dereceitas, alguns dos quais com nomes semelhantes. No meu exemplo, usei "grilled cheese", "five cheese lasagna", e "baked salmon".
Antes de começarmos a criar um front-end ou back-end, precisamos preparar nossa collection para pesquisa criando um índice de pesquisa especial.
Na guiaColeções do seu cluster, encontre a collectionde receitas e escolha a guiaÍndices de pesquisa.
Você provavelmente não terá um índice do Atlas Search criado ainda, portanto, precisaremos criar um.
Por padrão, o Atlas Search mapeia dinamicamente todos os campos em uma collection. Isso significa que todos os campos em nosso documento serão verificados em relação aos nossos termos de pesquisa. Isso é ótimo para criar collection em que o esquema pode se desenvolver e você deseja pesquisar em muitos campos diferentes. No entanto, também pode consumir muitos recursos. Para nosso aplicativo, na verdade, só queremos pesquisar por um campo específico, o campo "nome" em nossos documentos de receita. Para fazer isso, escolha "Criar índice de pesquisa" e altere o código para o seguinte:
1 { 2 "mappings": { 3 "dynamic": false, 4 "fields": { 5 "name": [ 6 { 7 "foldDiacritics": false, 8 "maxGrams": 7, 9 "minGrams": 3, 10 "tokenization": "edgeGram", 11 "type": "autocomplete" 12 } 13 ] 14 } 15 } 16 }
No exemplo acima, estamos criando um índice no campo
name
dentro de nossos documentos utilizando um índice de preenchimento automático. Quaisquer campos que não sejam explicitamente mapeados, como a arrayingredients
, não serão pesquisados.Para este exemplo, podemos colar o JSON utilizando o Editor JSON.
Agora, clique em "Criar índice". É isso ai! Basta dar ao MongoDB Atlas alguns minutos para criar seu índice de pesquisa.
Se quiser saber mais sobre os índices de preenchimento automático do Atlas Search e as várias estratégias de tokenização que podem ser usadas, você pode encontrar informações na documentação oficialdo.
Neste momento, devemos ter nossa coleção de dados de receitas, bem como um índice do Atlas Search criado nesses dados para o campo
name
. Agora estamos prontos para criar um backend que irá interagir com nossos dados usando o driver Node.js do MongoDB.Vamos apenas retocar o aspecto de introdução ao MongoDB desse aplicativo de backend. Se você quiser ler algo mais detalhado, confira a série de tutoriaisdeLauron Scheefer sobre o assunto.
Em seu computador, crie um novo diretório de projeto com um arquivomain.js dentro desse diretório. Usando a linha de comando, execute os seguintes comandos:
1 npm init -y 2 npm install mongodb express body-parser cors --save
Os comandos acima inicializarão o arquivopackage.json e instalarão cada uma das dependências do nosso projeto para criar uma API RESTful que interage com o MongoDB.
Dentro do arquivo main.js, adicione o seguinte código:
1 const { MongoClient, ObjectID } = require("mongodb"); 2 const Express = require("express"); 3 const Cors = require("cors"); 4 const BodyParser = require("body-parser"); 5 const { request } = require("express"); 6 7 const client = new MongoClient(process.env["ATLAS_URI"]); 8 const server = Express(); 9 10 server.use(BodyParser.json()); 11 server.use(BodyParser.urlencoded({ extended: true })); 12 server.use(Cors()); 13 14 var collection; 15 16 server.get("/search", async (request, response) => {}); 17 server.get("/get/:id", async (request, response) => {}); 18 19 server.listen("3000", async () => { 20 try { 21 await client.connect(); 22 collection = client.db("food").collection("recipes"); 23 } catch (e) { 24 console.error(e); 25 } 26 });
Lembre-se quando eu disse que gostaria de começar a usar o MongoDB? Foi verdade, mas, se você estiver copiando e colando o código acima, certifique-se de substituir a seguinte linha em seu código:
1 const client = new MongoClient(process.env["ATLAS_URI"]);
Armazeno minhas informações do MongoDB Atlas em uma variável de ambiente em vez de codificá-las no aplicativo. Se você deseja fazer o mesmo, crie uma variável de ambiente em seu computador chamado
ATLAS_URI
e defina-a para sua connection string do MongoDB. Essa connection string terá a seguinte aparência:1 mongodb+srv://<username>:<password>@cluster0-yyarb.mongodb.net/<dbname>?retryWrites=true&w=majority
O que nos chama a atenção para este exemplo são os endpoints
/search
e /get/:id
da API. O primeiro endpoint aproveitará o Atlas Search, enquanto o segundo endpoint obterá o documento com base em seu valor_id
. Isso é útil caso você queira pesquisar documentos e obter todas as informações sobre o documento selecionado.Então, vamos expandir o endpoint para pesquisa:
1 server.get("/search", async (request, response) => { 2 try { 3 let result = await collection.aggregate([ 4 { 5 "$search": { 6 "autocomplete": { 7 "query": `${request.query.query}`, 8 "path": "name", 9 "fuzzy": { 10 "maxEdits": 2, 11 "prefixLength": 3 12 } 13 } 14 } 15 } 16 ]).toArray(); 17 response.send(result); 18 } catch (e) { 19 response.status(500).send({ message: e.message }); 20 } 21 });
No código acima, estamos criando um pipeline de agregação com um único estágio
$search
, que será abastecido pelo nosso índice do Atlas Search. Ele usará os dados fornecidos pelo usuário como a query e o operadorautocomplete
para proporcionar a eles essa experiência de digitação antecipada. Em um cenário de produção, podemos querer fazer validação adicional nos dados fornecidos pelo usuário, mas tudo bem para este exemplo. Observe também que, ao usar o Atlas Search, um aggregation pipeline é necessário e o operador$search
deve ser o primeiro estágio desse pipeline.O campo
path
de name
representa o campo dentro de nossos documentos em que queremos pesquisar. Lembre-se de quename
também é o campo que definimos em nosso índice.É aqui que a diversão começa!
Estamos fazendo uma busca confusa. Isso significa que estamos encontrando cadeias de caracteres que são semelhantes, mas não necessariamente iguais, ao termo de pesquisa. Lembra quando escrevi incorretamente
cheese
ao digitar chease
em vez disso? O maxEdits
campo representa quantos caracteres consecutivos devem corresponder. No meu exemplo, havia apenas um, mas e se eu o escrevesse incorretamente como cheaze
, onde os az
caracteres não estão corretos ?O campo
prefixLength
indica o número de caracteres no início de cada termo no resultado que deve corresponder exatamente. Em nosso exemplo, três caracteres no início de cada termo devem corresponder.Isso tudo é muito poderoso, considerando o tipo de confusão que seu código pareceria usando expressões regulares ou
$text
.Você pode encontrar mais informações sobre o que pode ser usado com o operador
autocomplete
na documentação.Então, vamos tratar do nosso outro endpoint:
1 server.get("/get/:id", async (request, response) => { 2 try { 3 let result = await collection.findOne({ "_id": ObjectID(request.params.id) }); 4 response.send(result); 5 } catch (e) { 6 response.status(500).send({ message: e.message }); 7 } 8 });
O código acima não é nada sofisticado. Estamos pegando um hash de ID fornecido pelo usuário, convertendo-o em um ID de objeto adequado e, em seguida, encontrando um único documento.
Você pode testar esse aplicativo servindo-o primeiro com
node main.js
e, em seguida, usando uma ferramenta como o Postman no http://localhost:3000/search?query= ou http://localhost:3000/get/ urls .Agora que temos um back-end para trabalhar, podemos cuidar do front-end que melhora a experiência geral do usuário. Há várias maneiras de criar um formulário de preenchimento automático, mas, neste exemplo, o jQuery fará o trabalho pesado.
Crie um novo projeto com um arquivoindex.html nele. Abra o arquivo e inclua o seguinte:
1 2 <html> 3 <head> 4 <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> 5 <script src="//code.jquery.com/jquery-1.12.4.js"></script> 6 <script src="//code.jquery.com/ui/1.12.1/jquery-ui.js"></script> 7 </head> 8 <body> 9 <div class="ui-widget"> 10 <label for="recipe">Recipe:</label><br /> 11 <input id="recipe"> 12 <ul id="ingredients"></ul> 13 </div> 14 <script> 15 $(document).ready(function () {}); 16 </script> 17 </body> 18 </html>
A marcação acima não faz muito de nada. Estamos apenas importando as dependências do jQuery e definindo o elemento do formulário que mostrará o preenchimento automático. Depois de clicar no elemento de preenchimento automático, os dados preencherão nossa lista de ingredientes na lista.
Dentro da
<script>
tag podemos adicionar o seguinte:1 <script> 2 $(document).ready(function () { 3 $("#recipe").autocomplete({ 4 source: async function(request, response) { 5 let data = await fetch(`http://localhost:3000/search?query=${request.term}`) 6 .then(results => results.json()) 7 .then(results => results.map(result => { 8 return { label: result.name, value: result.name, id: result._id }; 9 })); 10 response(data); 11 }, 12 minLength: 2, 13 select: function(event, ui) { 14 fetch(`http://localhost:3000/get/${ui.item.id}`) 15 .then(result => result.json()) 16 .then(result => { 17 $("#ingredients").empty(); 18 result.ingredients.forEach(ingredient => { 19 $("#ingredients").append(`<li>${ingredient}</li>`); 20 }); 21 }); 22 } 23 }); 24 }); 25 </script>
Algumas coisas estão acontecendo no código acima.
Dentro da função
autocomplete
, definimos um source
para de onde vêm nossos dados e um select
para o que acontece quando selecionamos algo de nossa lista. Também definimos um minLength
para não sobrecarregarmos nosso backend e banco de dados a cada tecla digitada.Se examinarmos mais de perto a função
source
, temos o seguinte:1 source: async function(request, response) { 2 let data = await fetch(`http://localhost:3000/search?query=${request.term}`) 3 .then(results => results.json()) 4 .then(results => results.map(result => { 5 return { label: result.name, value: result.name, id: result._id }; 6 })); 7 response(data); 8 },
Estamos fazendo um
fetch
em nosso backend e, em seguida, formatando os resultados em algo que o plug-in jQuery reconhece. Se quiser saber mais sobre como fazer solicitações HTTP com JavaScript, você pode conferir um tutorial anterior que criei intitulado Execute HTTP Requests in JavaScript Applications.Na função
select
, podemos analisar melhor o que está acontecendo:1 select: function(event, ui) { 2 fetch(`http://localhost:3000/get/${ui.item.id}`) 3 .then(result => result.json()) 4 .then(result => { 5 $("#ingredients").empty(); 6 result.ingredients.forEach(ingredient => { 7 $("#ingredients").append(`<li>${ingredient}</li>`); 8 }); 9 }); 10 }
Estamos fazendo uma segunda solicitação para nosso outro endpoint da API. Em seguida, estamos liberando a lista de ingredientes em nossa página e preenchendo-os novamente com os novos ingredientes.
Ao executar este aplicativo, certifique-se de que o back-end também esteja em execução, caso contrário, seu front-end não terá nada para se comunicar.
Para atender ao aplicativo front-end, há algumas opções que não se limitam ao que está abaixo:
- Use Python para criar um
SimpleHTTPServer
.
O meu favorito é usar o pacote
serve
. Se você instalá-lo, poderá executarserve
a partir da linha de comando no diretório de trabalho do seu projeto.Com o projeto servindo com
serve
, ele deve estar acessível em http://localhost:5000 em seu navegador da web.Você acabou de ver como aproveitar o MongoDB Atlas Search para sugerir dados aos usuários em um formulário de preenchimento automático. Atlas Search é ótimo porque usa linguagem natural para pesquisar nos campos do documento para evitar que você tenha que escrever expressões regulares longas e complicadas ou lógica de aplicativo.
Não se esqueça de que fizemos nossa pesquisa usando o operador
$search
em um pipeline de agregação. Isso significa que você pode adicionar outros estágios ao seu pipeline para fazer algumas coisas realmente extravagantes. Por exemplo, após o estágio do pipeline de$search
, você pode usar um $in
na matriz de ingredientes para limitar os resultados apenas a receitas de chocolate. Além disso, você pode usar outros operadores legais no estágio$search
, além do operadorautocomplete
. Por exemplo, você pode usar o operadornear
para pesquisa numérica e geoespacial ou operadores como compound
e wildcard
para outras tarefas. Mais informações sobre esses operadores podem ser encontradas na documentação.Relacionado
Tutorial
Usar a OpenAI e o MongoDB Atlas para melhorar a funcionalidade de pesquisa
Sep 18, 2024 | 5 min read
Tutorial
Comece a usar o MongoDB Atlas sem servidor, AWS CDK e AWS sem servidor
Aug 09, 2024 | 18 min read