Tutorial
View on GithubTable sorting with Ecto and LiveView
A very common or even mandatory feature in e-commerce stores is the ability to sort a list of products by attributes. This is easy enough and a good fit for Phoenix LiveView. In this tutorial, I will build on an existing page that displays a list of products and implement sorting on product name and prices.
STEP 1 - Setup dynamic sorting in Ecto
The Ecto documentation have a section on filtering and sorting on dynamic attributes. I want to change how I query all products from:
# lib/shop_test/products.ex
def list_products do
Repo.all(Product)
end
To make it possible to add the params from the controller or in this case the LiveView.
# lib/shop_test/products.ex
def list_products(params \\ %{}) do
from(
p in Product,
order_by: ^filter_order_by(params["order_by"])
)
|> Repo.all()
end
defp filter_order_by("name_desc"), do: [desc: :name]
defp filter_order_by("name_asc"), do: [asc: :name]
defp filter_order_by("price_desc"), do: [desc: :price]
defp filter_order_by("price_asc"), do: [asc: :price]
defp filter_order_by(_), do: []
NOTE that I allow-list the possible combinations so I don’t end up in a situation where a user can attempt to get the system behave in an unwanted way.
STEP 2 - Add the form to the LiveView
I want to have a simple select field where a user can specify the sorting. For this example is name or price in ascending or descending order. Even if its strictly not needed, I have started to use adhoc schemaless changesets that I specify directly in the LiveView.
I also want to setup so that I pass in the params list_products
that I changed above. So, I will add the changeset and add the new code to handle_params
callback function.
# lib/shop_test_web/live/product_live/index.ex
defmodule ShopTestWeb.ProductLive.Index do
use ShopTestWeb, :live_view
import Ecto.Changeset
alias ShopTest.Products
@impl true
def handle_params(params, _url, socket) do
{
:noreply,
socket
|> assign(:order_and_filter_changeset, order_and_filter_changeset(params))
|> assign(:products, Products.list_products(params))
}
end
# OTHER CODE ...
defp order_and_filter_changeset(attrs \\ %{}) do
cast(
{%{}, %{order_by: :string}},
attrs,
[:order_by]
)
end
end
Then I can add the form to the template:
<%= f = form_for @order_and_filter_changeset, "#", phx_change: "order_and_filter", as: "order_and_filter" %>
<% options = ["Order by": "", "Name": "name_asc", "Name (Desc)": "name_desc",
"Price": "price_asc", "Price (Desc)": "price_desc"] %>
<%= select f, :order_by, options, class: "tag-select tag-select-sm" %>
</form>
If you are wondering why I actually use a changeset here is because since I dont trust params coming from the big scary Internet, I can validate before I actually use the input.
To tie this up, I need to handle the on change event in the LiveView. So basically I do this by:
- cast the params in the changeset
- If its valid, use LiveViews push_patch to the same route, but with the added params. This will invoke handle_params again and I will do the new database query.
- If its not valid, do nothing.
# lib/shop_test_web/live/product_live/index.ex
defmodule ShopTestWeb.ProductLive.Index do
# OTHER CODE ..
@impl true
def handle_event("order_and_filter", %{"order_and_filter" => order_and_filter_params}, socket) do
order_and_filter_params
|> order_and_filter_changeset()
|> case do
%{valid?: true} = changeset ->
{
:noreply,
socket
|> push_patch(to: Routes.product_index_path(socket, :index, apply_changes(changeset)))
}
_ ->
{:noreply, socket}
end
end
end
Note I am calling the changeset order_and_filter_changeset
because it will fairly trivial to allow other params like max_price and min_price and have a second form that will have range sliders. But I will leave that for another day
Tag Cloud
Phoenix Bolerplate
Generate a Phoenix Boilerplate and save hours on your next project.
SAAS Starter Kit
Get started and save time and resources by using the SAAS Starter Kit built with Phoenix and LiveView.
Subscribe for $39/mo to geat ahead!
Learn MoreRelated Tutorials

Published 31 Aug - 2020
Fuzzy find with Ecto in Phoenix LiveView
Fuzzy find is both a simple and a complex thing. Even though though it's simple to implement, its hard to get right from a UX perspective. Luckily, ..