Subscribe to access all episodes. View plans →

#141: Exzeitable

Published February 7, 2022

Elixir 1.12

Phoenix 1.6

Phoenix LiveView 0.17

Exzeitable 0.5.2

Follow along with the episode starter on GitHub


Here we have an Elixir Phoenix application. And inside it we have an Album schema module that has fields for an “artist”, “summary”, “title”, and “year” that the album was released. We have some of these album records in the database, but when we open the browser we don’t have any displaying in the UI. Let’s fix that.

Normally we might use something like phx.gen.html to quickly create a page to list of our albums. But in the episode let’s use the Exzeitable package.

Exzeitable makes it easy to add dynamically updating datatables with Phoenix LiveView. And has some great features like full text search, sorting, and periodic data refreshes. This is a great option for when you want to add something like an admin UI, but don’t want to spend a lot of time creating one. Let’s get started. We’ll go to Hex and grab the exzeitable config. Then let’s open our mix.exs and add exzeitable to our list of dependencies.

mix.exs

...
defp deps do
  ...
  {:exzeitable, "~> 0.5.2"},
  ...
end
...

And we’ll download exzeitable with mix deps.get.

$ mix deps.get
...
New:
  exzeitable 0.5.2

Because exzeitable search uses the pg_trgm extension for Postgres, let’s generate a migration to add it.

$ mix ecto.gen.migration add_pg_trgm
* creating priv/repo/migrations/{timestamp}_add_pg_trgm.exs

Then let’s open the migration that was generated for us and I’ll copy the migration code from the docs to create the extension and paste it into the migration file.

priv/repo/migrations/{timestamp}_add_pg_trgm.exs

defmodule Teacher.Repo.Migrations.AddPgTrgm do
  use Ecto.Migration

  def up do
    execute("CREATE EXTENSION pg_trgm")
  end

  def down do
    execute("DROP EXTENSION pg_trgm")
  end
end

Then we’ll go back to the command line and run the migration.

$ mix ecto.migrate
...

Alright, now let’s create a new module that we’ll use to configure our exzeitable table. Since this will act as a kind of admin interface for our albums, let’s create a new directory in “teacher_web” named “admin”. Then inside it we’ll create a module named “album_table.ex”. Let’s define our module and then we’ll use Exzeitable. If we open the docs, there are different options we can use to customize our table.

There are a few required options, our “repo”, the “routes” module, the “path” for our resource, the “fields” we want to include in our table, and an Ecto.Query struct. Then there are some optional options we can provide, like the number of entries per page, how often we want to refresh the data in our table, and any assigns.

Let’s go back to our AlbumTable module and add our “repo”. For our “routes” let’s include the Routes module and then let’s add alias for it above. The path for our album resource will be album_path Then we’ll include our query and to use this we’ll need to import the Ecto.Query.from function. Then let’s also alias the Album module so we can call it without the prefix. Now let’s add some fields for our table to display. We’ll add the artist with the label of “Artist”, the title with the label of “Title”, and then the summary with the label of “Summary”. Since summaries are a bit longer let’s have them hidden by default, which we can do by setting hidden to true. Check the docs for a full list options you can use for your fields.

Because exzeitable adds pagination, let’s specify that we want the pagination to show up above our table and that we want there to be 10 records per page. Now that we’ve configured the options for our table, let’s add a render callback to display our table.

lib/teacher_web/admin/album_table.ex

defmodule TeacherWeb.Admin.AlbumTable do

  import Ecto.Query, only: [from: 2]

  alias TeacherWeb.Router.Helpers, as: Routes
  alias Teacher.Recordings.Album

  use Exzeitable,
  repo: Teacher.Repo,
  routes: Routes,
  path: :album_path,
  query: from(a in Album),
  fields: [
    artist: [label: "Artist"],
    title: [label: "Title"],
    summary: [label: "Summary", hidden: true]
  ],
  pagination: [:top],
  per_page: 10

  def render(assigns) do
    ~H"<%= build_table(assigns) %>"
  end

end

Then let’s open the template we want to display our table on and we can render our table by calling the AlbumTable.live_table passing in the conn.

Template path: lib/teacher_web/templates/album/index.html.eex

...
<%= TeacherWeb.Admin.AlbumTable.live_table(@conn) %>

ExZeitable comes with almost no styling to make is as CSS framework agnostic as possible, but this does mean you’ll probably want to open up your app’s stylesheet and paste in a few styles of your own. With that let’s start up the server.

$ mix phx.server
...

And when we go back to our albums page our exzeitable table is displayed! We can navigate to different pages, search for a specific album, and sort our table by different columns. Because album summaries are longer, we chose to hide them to start, but it’s easy to display any hidden fields.

We’ll click the “Show Field Buttons” link and then “Show Summary” link that appears. Our album summaries are displayed. Let’s go ahead and hide them again.

exzeitable gives us an “actions” column here with the “Show”, “Edit”, and “Delete” actions for an album. Let’s customize this, but instead of updating our AlbumTable module like we did before, we can go back to our index.html.eex template where we render the table and pass in the action_buttons option, specifying that we only want to display the “edit” action.

Template path: lib/teacher_web/templates/album/index.html.eex

...
<%= TeacherWeb.Admin.AlbumTable.live_table(@conn, action_buttons: [:edit]) %>

This makes it easy to customize the same table for different pages or even users. If we go back to the browser our table is updated with only the edit action. Now let’s make one other change to our table and instead of hiding album summaries, let’s show a shortened version. To do that we can add the virtual: true option to the field. This will allow us to add a summary function, which we can define it will take the socket which we’ll ignore since we wont need it, and then the album struct. Inside the function we’ll return the first 50 characters of the album summary and then append an ellipsis.

Another thing worth mentioning is that the search functionality only works with string fields. If we include our year field, which is an integer. We’ll want to include the search: false option.

lib/teacher_web/admin/album_table.ex

defmodule TeacherWeb.Admin.AlbumTable do

  import Ecto.Query, only: [from: 2]

  alias TeacherWeb.Router.Helpers, as: Routes
  alias Teacher.Recordings.Album

  use Exzeitable,
  repo: Teacher.Repo,
  routes: Routes,
  path: :album_path,
  query: from(a in Album),
  fields: [
    artist: [label: "Artist"],
    title: [label: "Title"],
    year: [label: "Year", search: false],
    summary: [virtual: true]
  ],
  pagination: [:top],
  per_page: 10

  def summary(_socket, album) do
    String.slice(album.summary, 0..50) <> "..."
  end

  def render(assigns) do
    ~H"<%= build_table(assigns) %>"
  end

end

With that let’s go back to the browser. And we see our truncated album summaries are being displayed along with the year and our other album fields. Exzeitable is a great option for when you need to quickly add a datatable to your application.

© 2024 HEXMONSTER LLC