A utilização de selects dinâmicos é uma excelente forma de filtrar uma listagem muito grande e tornar a navegação de uma aplicação mais amigável.
Neste post iremos construir selects dinâmicos com Phoenix e LiveView. A aplicação de exemplo é uma simples página de listagem de guitarras. O resultado final é ilustrado abaixo:
Uma guitarra pertence a uma marca, como Gibson ou Fender. Cada marca oferece algumas opções diferentes de modelo, como Les Paul e SG no caso da Gibson, ou Stratocaster e Telecaster no caso da Fender.
Diante da página de listagem de guitarras, os usuários podem selecionar a marca no primeiro select. De acordo com a marca selecionada, os modelos pertencentes a marca são populados no segundo select.
O primeiro passo é a criação da rota que associa uma URL a uma LiveView. Neste exemplo, associamos a URL raíz / ao módulo
GuitarLive.Index através da seguinte definição no arquivo router.ex:
scope "/", InstrumentStoreSelectWeb do
pipe_through(:browser)
live("/", GuitarLive.Index, :index)
end
O módulo GuitarLive.Index é responsável pelas definições das callback functions executadas de acordo com o ciclo de vida da LiveView e interações dos usuários com a página. As callback functions são mount/3 e handle_event/3.
A função mount/3 é responsável pelos dados iniciais que serão exibidos na página.
def mount(_params, _session, socket) do
brands = Inventory.list_unique_brands()
{:ok,
socket
|> assign(:guitars, [])
|> assign(:models, [])
|> assign(:selected_brand, nil)
|> assign(:brands, brands)}
end
Nessa função, fazemos uma chamada à função list_unique_brands/0 , parte do módulo Inventory. Esse módulo é o context module, responsável por encapsular os detalhes de acesso ao banco de dados. A função list_unique_brands/0 retorna uma lista de marcas que será usada para popular o primeiro select no template index.html.heex a seguir:
<form phx-change="set-filter">
<select name="brand" id="brand">
<option/>
<%= for brand <- @brands do %><option value={brand}
selected={if @selected_brand == brand, do: "selected"}>
<%= brand %>
</option>
<% end %>
</select>
</form>
</select></form>
Uma das formas mais simples de responder a eventos do DOM em LiveView é através de bindings. O binding phx-change é responsável por “ligar” qualquer evento de mudança no formulário a função handle_event/3, definida no módulo GuitarLive.Index.
De volta a GuitarLive.Index, implementamos a primeira versão dessa função:
def handle_event("set-filter", %{"_target" => ["brand"], "brand" => brand}, socket) do
{:noreply,
socket
|> assign(:models, Inventory.filter_models_by_brand(brand))
|> assign(:selected_brand, brand)}
end
Utilizamos pattern matching para interceptar o evento ("set-filter"), os dados da origem do evento ("_target" => ["brand"]), valor selecionado no primeiro select ("brand" => brand), e o socket referente ao usuário interagindo com a página (socket).
Uma vez selecionada a marca, a função handle_event/3 irá filtrar os modelos disponíveis desta marca através da função filter_models_by_brand/1 do módulo Inventory.Os modelos de guitarra estarão disponíveis no socket através da propriedade models, que pode ser acessada no template através de @models:
<select name="model" id="model">
<option/>
<%= for model <- @models do %>
<option value={model}><%= model %></option>
<% end %>
</select>
No que diz respeito a selects dinâmicos, a feature está concluída! Ao detectar mudanças no primeiro select, a função handle_event/3 será executada pelo Phoenix e irá popular o socket com os dados a serem utilizados pelo segundo select 💥
A última etapa é a listagem das guitarras, filtradas por marca e modelo. Para isso, definimos uma nova clause da função handle_event/3 conforme o código a seguir:
def handle_event("set-filter",
%{"_target" => ["model"], "model" => model}, socket) do
selected_brand = socket.assigns.selected_brand
guitars = Inventory.filter_by_brand_and_model(selected_brand, model)
{:noreply, assign(socket, :guitars, guitars)}
end
A assinatura dessa função é bem parecida com a anterior, mas não é exatamente igual. Note a diferença no segundo argumento:
%{"_target" => ["brand"], "brand" => brand}
vs.
%{"_target" => ["model"], "model" => model}
Ambos os elementos <select> emitem o mesmo evento "set-filter", porém, o primeiro emite valores para brand enquanto o segundo emite valores para model.
Uma vez populada com os valores das guitarras a serem exibidas, a socket permite que o template leia esses valores e monte a tabela:
<tbody id="guitars">
<%= for guitar <- @guitars do %>
<tr id={"guitar-#{guitar.id}"}>
<td><%= guitar.brand %></td>
<td><%= guitar.model %></td>
<td><%= guitar.year %></td>
</tr>
<% end %>
</tbody>
A listagem está concluída!
A utilização de selects dinâmicos é uma excelente forma de filtrar uma listagem muito grande e tornar a navegação mais amigável. Aprendemos como implementar uma forma de select dinâmico com Phoenix e LiveView, através de bindings e callback functions.
Para os interessados em explorar o código fonte da aplicação de exemplo mais a fundo, ele está disponível no link a seguir:
github.com
Espero que este post ajude você em seu próximo projeto. Caso sua empresa precise de ajuda na construção de aplicações Elixir e LiveView, entre em contato!