Skip to content

Instantly share code, notes, and snippets.

@andrielfn
Created July 17, 2019 01:20
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save andrielfn/f3b2e151811f720c60d24ba821b6cc61 to your computer and use it in GitHub Desktop.
Save andrielfn/f3b2e151811f720c60d24ba821b6cc61 to your computer and use it in GitHub Desktop.
Search and filters with Phoenix LiveView
<div class="container mx-auto p-4">
<div class="lg:flex items-start">
<%= form_tag "", [class: "box w-full lg:w-2/6 xl:w-1/4 lg:mr-4 mb-8 md:mb-0", phx_change: "filters-updated"] do %>
<h2 class="box-header">Filtros</h2>
<div class="px-8 py-4 border-b">
<div class="flex items-center border rounded p-2">
<%= icon_tag(@socket, "search", class: "text-2xl mr-1 text-blue-600") %>
<input type="text" name="filters[q]" value="<%= @params["q"] %>" class="w-full outline-none border-blue-500">
</div>
</div>
<div class="px-8 py-4 border-b" data-controller="search-filter-collapse">
<div class="flex items-center justify-between mb-2">
<span class="block text-xs uppercase text-gray-500">Modalidade</span>
<button data-action="search-filter-collapse#toggle">
<%= icon_tag @socket, "minus", class: "text-2xl text-gray-400", data: [target: "search-filter-collapse.minusIcon"] %>
<%= icon_tag @socket, "plus", class: "hidden text-2xl text-gray-400", data: [target: "search-filter-collapse.plusIcon"] %>
</button>
</div>
<div data-target="search-filter-collapse.options">
<div class="grid grid-columns-2 grid-gap-4">
<%= for {id, name} <- @categories do %>
<div class="search-filter-item">
<input type="checkbox" name="filters[categories][]" value="<%= id %>" class="hidden search-filter-item" id="category_<%= id %>" <%= checked(@params["categories"], id) %>>
<label for="category_<%= id %>" class="bg-gray-200 rounded flex flex-col items-center justify-center text-sm text-gray-600 py-4">
<%= icon_tag(@socket, "bike", class: "text-4xl mb-1") %>
<p><%= name %></p>
<div class="check"><%= icon_tag @socket, "check-circle" %></div>
</label>
</div>
<% end %>
</div>
</div>
</div>
<div class="px-8 py-4 border-b">
<div class="flex items-center justify-between mb-4">
<span class="text-sm text-gray-700">Apenas bikes femininas</span>
<div class="form-switch inline-block align-middle">
<input type="checkbox" name="filters[ebike_only]" value="true" id="ebike_only" class="form-switch-checkbox" <%= checked([@params["ebike_only"]], "true") %> />
<label class="form-switch-label" for="ebike_only"></label>
</div>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-700">Apenas bikes elétricas</span>
<div class="form-switch inline-block align-middle">
<input type="checkbox" name="filters[female_only]" value="true" id="female_only" class="form-switch-checkbox" <%= checked([@params["female_only"]], "true") %> />
<label class="form-switch-label" for="female_only"></label>
</div>
</div>
</div>
<div class="px-8 py-4 border-b" data-controller="search-filter-collapse">
<div class="flex items-center justify-between mb-2">
<span class="block text-xs uppercase text-gray-500">Ano</span>
<button data-action="search-filter-collapse#toggle">
<%= icon_tag @socket, "minus", class: "text-2xl text-gray-400", data: [target: "search-filter-collapse.minusIcon"] %>
<%= icon_tag @socket, "plus", class: "hidden text-2xl text-gray-400", data: [target: "search-filter-collapse.plusIcon"] %>
</button>
</div>
<div data-target="search-filter-collapse.options">
<%= for {year, number_of_bikes} <- @years do %>
<label class="flex items-center mb-1">
<input type="checkbox" name="filters[years][]" value="<%= year %>" class="form-checkbox" <%= checked(@params["years"], year) %>>
<span class="ml-2 flex-1 text-sm text-gray-700"><%= year %></span>
<span class="text-gray-500 text-sm"><%= number_of_bikes %></span>
</label>
<% end %>
</div>
</div>
<div class="px-8 py-4 border-b" data-controller="search-filter-collapse">
<div class="flex items-center justify-between mb-2">
<span class="block text-xs uppercase text-gray-500">Marca</span>
<button data-action="search-filter-collapse#toggle">
<%= icon_tag @socket, "minus", class: "text-2xl text-gray-400", data: [target: "search-filter-collapse.minusIcon"] %>
<%= icon_tag @socket, "plus", class: "hidden text-2xl text-gray-400", data: [target: "search-filter-collapse.plusIcon"] %>
</button>
</div>
<div data-target="search-filter-collapse.options">
<%= for {slug, maker, number_of_bikes} <- @makers do %>
<label class="flex items-center mb-1">
<input type="checkbox" name="filters[makers][]" value="<%= slug %>" class="form-checkbox" <%= checked(@params["makers"], slug) %>>
<span class="ml-2 flex-1 text-sm text-gray-700"><%= maker %></span>
<span class="text-gray-500 text-sm"><%= number_of_bikes %></span>
</label>
<% end %>
</div>
</div>
<div class="px-8 py-4 border-b" data-controller="search-filter-collapse">
<div class="flex items-center justify-between mb-2">
<span class="block text-xs uppercase text-gray-500">Tamanho da roda</span>
<button data-action="search-filter-collapse#toggle">
<%= icon_tag @socket, "minus", class: "text-2xl text-gray-400", data: [target: "search-filter-collapse.minusIcon"] %>
<%= icon_tag @socket, "plus", class: "hidden text-2xl text-gray-400", data: [target: "search-filter-collapse.plusIcon"] %>
</button>
</div>
<div data-target="search-filter-collapse.options">
<%= for {wheel_size, number_of_bikes} <- @wheel_sizes do %>
<label class="flex items-center mb-1">
<input type="checkbox" name="filters[wheel_sizes][]" value="<%= wheel_size %>" class="form-checkbox" <%= checked(@params["wheel_sizes"], wheel_size) %>>
<span class="ml-2 flex-1 text-sm text-gray-700"><%= wheel_size %></span>
<span class="text-gray-500 text-sm"><%= number_of_bikes %></span>
</label>
<% end %>
</div>
</div>
<div class="px-8 py-4 border-b" data-controller="search-filter-collapse">
<div class="flex items-center justify-between mb-2">
<span class="block text-xs uppercase text-gray-500">Velocidades</span>
<button data-action="search-filter-collapse#toggle">
<%= icon_tag @socket, "minus", class: "text-2xl text-gray-400", data: [target: "search-filter-collapse.minusIcon"] %>
<%= icon_tag @socket, "plus", class: "hidden text-2xl text-gray-400", data: [target: "search-filter-collapse.plusIcon"] %>
</button>
</div>
<div data-target="search-filter-collapse.options">
<%= for {speeds, number_of_bikes} <- @speeds do %>
<label class="flex items-center mb-1">
<input type="checkbox" name="filters[speeds][]" value="<%= speeds %>" class="form-checkbox" <%= checked(@params["speeds"], speeds) %>>
<span class="ml-2 flex-1 text-sm text-gray-700"><%= speeds %></span>
<span class="text-gray-500 text-sm"><%= number_of_bikes %></span>
</label>
<% end %>
</div>
</div>
<div class="px-8 py-4 border-b" data-controller="search-filter-collapse">
<div class="flex items-center justify-between mb-2">
<span class="block text-xs uppercase text-gray-500">Grupo</span>
<button data-action="search-filter-collapse#toggle">
<%= icon_tag @socket, "minus", class: "text-2xl text-gray-400", data: [target: "search-filter-collapse.minusIcon"] %>
<%= icon_tag @socket, "plus", class: "hidden text-2xl text-gray-400", data: [target: "search-filter-collapse.plusIcon"] %>
</button>
</div>
<div data-target="search-filter-collapse.options">
<%= for {slug, maker, drivetrain, number_of_bikes} <- @drivetrains do %>
<label class="flex items-center mb-1">
<input type="checkbox" name="filters[drivetrains][]" value="<%= slug %>" class="form-checkbox" <%= checked(@params["drivetrains"], slug) %>>
<span class="ml-2 flex-1 text-sm text-gray-700"><%= maker %> <%= drivetrain %></span>
<span class="text-gray-500 text-sm"><%= number_of_bikes %></span>
</label>
<% end %>
</div>
</div>
<% end %>
<div class="w-full lg:w-4/6 xl:w-3/4">
<h2 class="text-lg font-semibold mb-4">Resultado da busca (<%= length(@bikes) %>)</h2>
<div class="grid grid-gap-4 grid-automin-200px">
<%= for bike <- @bikes do %>
<%= link to: Routes.bike_path(@socket, :show, bike), class: "box hover:border-blue-400 p-4" do %>
<div class="mb-2 text-center">
<%= img_tag GoBikesWeb.BikeView.featured_image_url_for(bike, width: 170), alt: "#{bike.maker.name} #{bike.model} #{bike.year}", class: "inline" %>
</div>
<h5 class="font-semibold text-gray-800 mb-2"><%= bike.maker.name %> <%= bike.model %> <%= bike.year %></h5>
<% end %>
<% end %>
</div>
</div>
</div>
</div>
defmodule GoBikesWeb.BikeSearchLive do
use Phoenix.LiveView
alias GoBikesWeb.Router.Helpers, as: Routes
alias GoBikes.Repo
alias GoBikes.Search.Filters
alias GoBikes.Search
alias GoBikes.Bikes.Category
alias GoBikes.Bikes.Bike
def render(assigns) do
GoBikesWeb.BikeSearchView.render("new.html", assigns)
end
def mount(session, socket) do
{:ok, assign(socket, filters(%{"q" => ""}))}
end
# catchall
def handle_params(params, _uri, socket) do
params = decoded_params(params)
{:noreply, assign(socket, filters(params))}
end
def handle_event("filters-updated", %{"filters" => params}, socket) do
{:noreply,
live_redirect(socket, to: Routes.live_path(socket, __MODULE__, encoded_params(params)))}
end
defp filters_for(key, params, fun) do
Bike
|> Filters.apply(Map.delete(params, key))
|> Search.run(params)
|> fun.()
end
defp filters(params) do
%{
params: params,
bikes: bikes(params),
years: filters_for("years", params, &Filters.list_years/1),
makers: filters_for("makers", params, &Filters.list_makers/1),
categories: Filters.list_categories(Category),
wheel_sizes: filters_for("wheel_sizes", params, &Filters.list_wheel_sizes/1),
speeds: filters_for("speeds", params, &Filters.list_speeds/1),
drivetrains: filters_for("drivetrains", params, &Filters.list_drivetrains/1)
}
end
defp bikes(params) do
Bike
|> Filters.apply(params)
|> Search.run(params)
|> Repo.all()
|> Repo.preload([:images, :maker])
end
defp encoded_params(params) do
Enum.reduce(params, %{}, fn {key, value}, map ->
case value do
items when is_list(items) -> Map.put(map, key, Enum.join(items, ","))
_ -> Map.put(map, key, value)
end
end)
end
@listable_filters ["categories", "years", "makers", "wheel_sizes", "speeds", "drivetrains"]
defp decoded_params(params) do
Enum.reduce(params, %{}, fn {key, value}, map ->
case key do
k when k in @listable_filters -> Map.put(map, key, String.split(value, ","))
_ -> Map.put(map, key, value)
end
end)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment