-
-
Save andrielfn/f3b2e151811f720c60d24ba821b6cc61 to your computer and use it in GitHub Desktop.
Search and filters with Phoenix LiveView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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