Skip to main content

Criando um filtro de busca customizável com JavaScript funcional


Tutoriais utilizando bibliotecas e frameworks para state management se tornaram comuns. Apesar disso, já que muita gente usa muitos frameworks diferentes, muitas vezes precisamos falar sobre a plataforma que permite isso tudo: a odiadíssima ferramenta que é o JavaScript, na sua forma mais pura — conforme descrito com documentações excelentes como a da MDN.

Esse tutorial também pode ser útil para quem está iniciando em JavaScript e desenvolvimento em geral já começar com alguma ideia do que é programação funcional. O termo está se tornando popular e quem não subir nesse barco pode ficar para trás em pouco tempo.

Não vamos adentrar nos detalhes do que é programação funcional neste artigo — isso é assunto para outras horas de conversa. Aqui, ficaremos no rápido e simples: alcançar nosso objetivo, evitando mutações dos dados e estado global.

Iniciando o projeto

Pelo bem da simplicidade, vamos manter tudo num único arquivo, que vai ter essa cara:

<!DOCTYPE HTML> <html lang='pt'> 
<head> 
<meta charset='utf-8'> 
<title>Um simples exemplo de filtro de busca.</title> 
</head> 
<body> 
<h1>Um simples exemplo de filtro de busca.</h1> 
<input type='text' id='search-filter' placeholder='Filtre sua busca por aqui.'> 
<div id='options'></div> 
<script> 
// Code goes here 
</script> 
</body> 
</html> 

Antes de qualquer coisa, vamos criar um scope wrapper, uma pequena camada de encapsulamento que impede que o nosso código fique visível fora do escopo do nosso filtro. É simplesmente uma função sem nome, executada no momento em que é definida:

<body> <h1>Um simples exemplo de filtro de busca.</h1>
<input type='text' id='search-filter' placeholder='Filtre sua busca por aqui.'>
<div id='options'></div>
<script>
;(() => {
// safe coding!
})()
</script>
</body>


Essa resposta no StackOverflow (em inglês) explica resumidamente por que devemos usar scope wrappers, ou self-executing functions, as funções autoexecutantes.

Selecionando os elementos

O arquivo que nós criamos não tem nada demais: um título, nosso elemento de entrada — no qual digitaremos para filtrar os resultados — e uma área para incluir as opções, que é o que vamos fazer em seguida.

Primeiro, selecionaremos os elementos que queremos:


Sempre gosto de imprimir algo dinamicamente nas áreas, só para garantir — não leva nem trinta segundos!

Adicionamos isso…

E vemos esse resultado:

Agora que sabemos que o DOM está sendo manipulado como esperado, vamos pensar em algumas melhorias para esse código simples. Mas ele tem só 4 linhas, você me pergunta. A resposta, ao melhor estilo de filme de kung fu dos anos 70, é que fazemos agora para não ter que fazer no futuro.

Para começar, já dá para ver um problema aí: toda vez que eu quiser selecionar num novo elemento, vou ter que digitar document.querySelector. Já que não pretendo usar jQuery, vamos criar uma função que encurta esse nosso caminho.

Ela não deve ser usada mais muitas vezes neste tutorial, mas pode ser usada no futuro, o que possivelmente justifica aplicar os princípios DRY nesse pequeno snippet.

O que a função $ faz aqui é simples: ela recebe um parâmetro, selector, e retorna o elemento, da mesma forma que document.querySelector. E assim, caso um dia queiramos adicionar jQuery (por favor, não…), ele não conflitará com nosso código graças ao scope wrapper que definimos mais cedo.

Criando a lista modificável


Se você usa um framework de monitoramento de estado como React ou Vue, já sabe como criar uma lista que se atualize conforme você alterar um objeto JavaScript. Esse seria o cenário ideal — onde só criamos ou removemos itens de um array para atualizar o DOM, mas, pela simplicidade do nosso código, vamos criar uma função que recria a parte específica do documento em que vamos trabalhar.

A função poderia acessar um array definido no nosso escopo atual e atualizar acessa os elementos selecionados usando nossa recém criada função $, atualizando a vista do usuário a partir desses elementos. Essencialmente, nós queremos fazer isso, mas se o código crescer, um array definido fora do escopo da função pode tornar as coisas confusas. Compare:


Neste exemplo, nosso trabalho é cumprido. Definimos uma lista de opções, e uma função que me permite atualizá-la. Qual é o problema?

O primeiro é que dependemos do objeto global list e o segundo, de $. Se um desses objetos mudar, teremos uma função quebrada. Em outro caso problemático, list é alterado e nos esquecemos de chamar updateElements. updateElements também pode simplesmente não estar acessível do escopo atual, caso list seja movido.

Entrem pureza e iteradores

Vamos criar uma função que solucione esses problemas.

Para isso, precisaremos utilizar dois conceitos: um específico das linguagens de programação modernas, outro do conceito de programação funcional: iteradores e pureza.

Iteráveis são objetos que contêm zero ou mais elementos e podem ser iterados — isto é, ter seus itens trabalhados individualmente. Uma função ou método que trabalhe sobre um iterável é um iterador. Iteradores em JavaScript são métodos como find, filter, map, reduce, forEach.
Espera aí, o que exatamente aconteceu ali?! Vamos por etapas.
A função elementList:
Recebe um parâmetro, itemList;
Aplica map a itemList, transformando cada item em um elemento <li>;
Aplica reduce ao resultado da transformação, reduzindo o array de <li>a um elemento <ul> contendo os <li>s;
Retorna o valor reduzido.

Fora da função, adicionamos o novo elemento elementList(optionList) ao DOM.

A primeira vantagem desse método é a pureza: em programação funcional, pureza de uma função é a característica de que 
1) ela não faz uso de, nem altera, nenhum elemento externo a ela, e 2) dados os mesmos parâmetros, retorna sempre o mesmo resultado. elementList, ao exigir itemList (em vez de alterar um objeto global, como no primeiro exemplo), respeita esse princípio.

Outro princípio que era violado e não é mais é a imutabilidade, adquirida, nesse caso, graças aos iteradores: um valor é imutável quando, após definido, não pode ser alterado. No primeiro exemplo, confiávamos na alteração do array list para atualizar a visão do usuário; no segundo, recebemos a lista cada vez que precisamos atualizar a visão.

Já que a função não tem efeitos colaterais (não altera nada alheio a ela), não há necessidade de nomeá-la como um verbo, afinal, não aplica uma ação: o relevante para o mundo externo é o valor retornado, que é um objeto, e não a ação praticada. Por isso, a função foi nomeada elementList, e não createElementList ou algo parecido.

Respeitando a esses dois princípios, diminuimos as chances de passarmos o valor errado para uma função, recebendo resultados inesperados.

Nos dois casos, o resultado é o mesmo:

Beleza! Perfeito. Agora só precisamos que o filtro funcione.

Para referência, este é o código que temos até agora:

<!DOCTYPE HTML> <html lang='pt'>
<head>
<meta charset='utf-8'>
<title>Um simples exemplo de filtro de busca.</title>
</head>
<body>
<h1>Um simples exemplo de filtro de busca.</h1>
<input type='text' id='search-filter' placeholder='Filtre sua busca por aqui.'>
<div id='options'></div>
<script>
;(() => {
const $ =
selector =>
document.querySelector(selector)
const filterInput = $('#search-filter')
const filteredOptions = $('#options')
const elementList =
itemList =>
itemList
.map(item => {
const li = document.createElement('li')
li.textContent = item
return li
})
.reduce((ul, item) => {
ul.appendChild(item)
return ul
}, document.createElement('ul'))

const optionList = ['foo', 'bar', 'baz']

const optionsArea = $('#options')

optionsArea.appendChild(elementList(optionList))

})()
</script>
</body>
</html>



Fazendo a lista reagir à entrada de dados


Agora que a lista já aparece a partir de um objeto, precisamos fazer com que ela seja filtrada pelo <input> que criamos mais cedo.

Para isso, vamos precisar de algumas ferramentas do JavaScript e do DOM: do primeiro, a API String, e especificamente seus métodos includes e indexOf; do segundo, a parte mais básica da API de eventos, Element.addEventListener.

Acontece que toda vez que você pratica uma ação de entrada de dados em um elemento de formulário, o evento input é emitido. Usando ele, podemos extrair o novo valor a cada atualização. Vamos experimentar:
Experimente adicionar o código acima à página . Cada vez que um valor for inserido na caixinha, um alerta nativo do navegador aparecerá na tela e esperará pelo seu OK. Além de irritante, exemplifica o que queremos fazer — manipular o valor inserido na caixa.
Agora vamos usar a função que definimos mais cedo, elementList, para renderizar a lista cada vez que o evento input for ouvido. Copie a linha usada para criar a lista para dentro do monitor de evento. Não se esqueça, também, de remover a lista anterior, ou você vai acabar com dezenas de listas:
Aqui, a elementList e o optionList estão acessíveis pois foram declarados anteriormente. O próximo passo é, simplesmente, passar algo diferente como parâmetro para elementList — uma versão filtrada de optionList.

Criando o filtro de busca

Para isso, vamos novamente recorrer aos iteradores — neste caso, filter e sort.

Por enquanto, vamos fazer um filtro básico: exibir os elementos da lista que contêm a string inserida no campo de filtro.

O resultado


Boom! O básico da missão já foi cumprido — e em menos de 50 linhas! Por fim, vamos colocar uma lista com itens que signifiquem algo:

Após reorganizar alguns detalhes do código, finalizamos a primeira parte do tutorial com o seguinte código:

<!DOCTYPE HTML> <html lang='pt'>
<head>
<meta charset='utf-8'>
<title>Um simples exemplo de filtro de busca.</title>
</head>
<body>
<h1>Um simples exemplo de filtro de busca.</h1>
<input type='text' id='search-filter' placeholder='Filtre sua busca por aqui.'>
<div id='options'></div>
<script>
;(() => {
const theFellowship = [
'Frodo Baggins',
'Samwise Gamgee',
'Pippin Took',
'Merry Brandybuck',
'Aragorn',
'Legolas',
'Gimli',
'Boromir',
'Gandalf the Grey'
]
const $ =
selector =>
document.querySelector(selector)
const elementList =
itemList =>
itemList
.map(item => {
const li = document.createElement('li')
li.textContent = item
return li
})
.reduce((ul, item) => {
ul.appendChild(item)
return ul
}, document.createElement('ul'))

const optionsArea = $('#options')
optionsArea.appendChild(elementList(theFellowship))

const filterInput = $('#search-filter')
filterInput.addEventListener('input', event => {
const query = event.target.value
const filteredItems =
theFellowship
.filter(item => item.includes(query))

optionsArea.firstChild && optionsArea.removeChild(optionsArea.firstChild)
optionsArea.appendChild(elementList(filteredItems))
})
})()
</script>
</body>
</html>


Obrigado por ter lido até aqui!

Aprenda javaScript : use a cabeça

Comments

Popular posts from this blog

Verificar sequencia de caracteres iguais em uma string c#

Neste post vamos ver como explorar uma string e verificar se existe uma seqüencia de caracteres iguais dentro da string. Utilizaremos a tabela ASCII para verificar letras e números, se você precisar verificar mais caracteres basta consultar a tabela e colocar o número referente na condição. Neste exemplo vou verificar se existe na string uma seqüencia quatro(4) números iguais e o mesmo para uma seqüencia de três(3) letras, pois acho que em números de documentos ou nomes isto não pode acontecer. Tabela de decimal ASCII public static bool ValidaTexto(this string txt) { bool flag = true; if (txt.Length < 3) { flag = false; } else { int contLetras = 0; int contNumeros = 0; int tmp = (int)txt.ToCharArray()[0]; foreach (char c in txt.ToCharArray()) { if (((int)c < 127 && (int)c >= 65)) { if (tmp == (int)c) { contLetras++; } if (contLetras >= 3) { break; } tmp = (int)c; } } //inicia novamente o armazenador tmp =...

CRIANDO APLICATIVOS DO ZERO E FÁCIL!

O QUE VOCÊ VAI APRENDER? SÃO MAIS DE 10 HORAS DE CONTEÚDO PURO E MATERIAIS EXTRAS: CRIAR APLICATIVOS   Mesmo sendo um total iniciante vamos explicar para você como criar aplicativos profissionais. Utilizaremos a linguagem HTML, CSS, Javascript em união com Apache Cordova. MODELO DE NEGÓCIO  Vamos ensinar como ganhar dinheiro* fazendo aplicativos. Apresentamos os 4 pilares básicos para a monetização de um aplicativos e como fazer para ser bem-sucedido. ATENDIMENTO AO CLIENTE - Vamos dar atenção também a parte humana do processo. Como técnicas de atendimento e precificação, para você convencer seus futuros clientes a fechar negócio com você. Veja os módulos do curso aqui POR QUE FAZER ESTE CURSO? CERTIFICAÇÃO PROFISSIONAL A conclusão deste curso oferece certificação profissional válida em todo território nacional. Podendo portanto ser colocado no seu curriculum, com duração total de 10 horas. GARANTIA Se por algum motivo você achar que est...