Tutorial
Building a datatable in Phoenix LiveView
To display a static table on webpage that contains a lot of data is a pretty bad user experience. There are popular javascript libraries that implements sorting and pagination but in this tutorial, i will implement these datatable features with Phoenix LiveView.
The initial table looks like this. A table with customers that have several columns.
Sorting by column names
The first goal in the tutorial is to sort by columns by the column name. Also, I want to implement code that is reusable across different liveviews.
- Create a new helper module that contains the sorting logic
- Extent list_customers/1 function so it can take a second params argument
- Modify the CustomerLive Index to pass along the params to
The params that I will be working with here will have the shape of:
%{"sort_direction" => "asc", "sort_field" => "name"}
Step 1 - DataTable Module
In the data table module, I first want to add the sort/1 functions. I need to examine both field and direction and return a tuple with sort order and field. That tuble can then be passed in as a second argument in order_by/3 when doing the query.
# lib/tutorial_web/live/data_table.ex
defmodule TutorialWeb.Live.DataTable do
import Phoenix.LiveView.Helpers def sort(%{"sort<i>field" => field, "sort</i>direction" => direction}) when direction in ~w(asc desc) do
{String.to<i>atom(direction), String.to</i>existing_atom(field)}
end def sort(_other) do
{:asc, :id}
end
end
NOTE that I use String.to_existing_atom(field)
since I want to avoid that we dynamically create atoms based on user input.
Next step in the data table module is to add the tablelink/3. That means that in the table-markup, inside the th-tag, I can add <%= table_link(@params, "Name", :name) %>
So, add these functions in the module:
Step 2 - Implement the sorting
To make sorting work, I need to add list_customers/1 that take the params and use the sort/1 function to get the sort sirection and sort field.
# lib/tutorial/customers.ex
import TutorialWeb.Live.DataTable, only: [sort: 1]def list_customers(params) do
from(
c in Customer,
order_by: ^sort(params)
) |> Repo.all()
end
Step 3 - Update the live view
Last step here is to update the live view that is responsible to render the table. I need move the initial loading of customers (listcustomers/0) in the mount-callback and instead move it to handleparams and call it with the params. So the relevant code now looks like this:
NOTE That I used
And now, if I test it in the browser, it should work.
The pagination library that I will use for this tutorial is Scrivener and more specifically ScrivenerEcto. I need to add it to the list of dependencies:
And run the command
To get started, I need to change the
Now, instead if a list, the function returns a Scrivener.Page struct with the entries and some info needed for pagination. The response will look something like this:
Note that the page still works if I refresh it. The Scrivener.Page module has implemented a behaviour that makes it possible to loop over the entries just like it was a list, which is pretty neat.
Next, I need to create the pagination component. The component will loop through the pages from 1 to in this case 11, since it is 11 pages. I could use the
There are some helper methods as well in the code below that helps me figure out what links to show, in regards to
NOTE I use
Now that I test this in the browser the pagination should work as expected.
Note that the pagination preserves any sorting that I already have applied.
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..
live_patch
under the hood in the DataTable module. That means that it wont be a full page-refresh but it will directly call the handle_params/3
in the LiveView. That means that it has a much smaller impact and the UI feels much more responsive.
Now I just need to implement the table_link inside the tags.
Pagination with Scrivener
Repo.all/1
to Scrivener.paginate/2
in the list_customers/1
. And I also set a default pagesize here.
@distance
variable to set the number of page-links that will be visible at the same time.
@distance
and how to behave on the first and last page.
live_patch
here as well for the same reason that I did above. This prevents an entire page reload and just hits the handle_params
.
Result
Related Tutorials
Table sorting with Ecto and LiveView