Subscribe to access all episodes. View plans →

#157: LiveSelect

Published January 16, 2023

Note: the video uses LiveSelect version 0.3.2 but in version 0.3.3 the default_value option has been changed to value

Phoenix 1.6

LiveSelect 0.3.2

Follow along with the episode starter on GitHub


Here we have our demo album application. And if we select an album - we see some details about it. Now when we go to edit the album the genre field is a dropdown with a lot of different choices. And because this dropdown is pretty long it can take a while to find and select the correct genre.

It would be nice to have a more dynamic user experience where instead of looking through this list we could just start typing the genre we want and it would filter the choices for us - making for a better user experience. Since we’re using LiveView there’s a great library we can use to do just this called LiveSelect.

LiveSelect is a LiveView component that implements a dynamic selection field with a dropdown. Let’s use this for our genre field. To get started we’ll go to Hex and grab the live_select config then we’ll open our Mixfile and add it to our list of dependencies.

mix.exs

...

defp deps do
  [

  ...

  {:live_select, "0.3.2"},

  ...

  ]
end

...

Then let’s go to the command line and run mix deps.get.

$ mix deps.get
...
New:
  live_select 0.3.2
* Getting live_select (Hex package)

Our example app is using Tailwind CSS and since LiveSelect supports Tailwind, let’s go ahead and configure it to use the standard tailwind utility classes. We’ll open tailwind.config.js and add the following line to the content section of the config.

assets/tailwind.config.js

...

module.exports = {
  content: [
  ...
  '../deps/live_select/lib/live_select/component.*ex'
  ...
  ]
]

...

LiveSelect also needs JS to work so let’s open our app.js and we’ll want to import live_select and then add the LiveSelect hook to our live socket.

assets/js/app.js

...

import live_select from "live_select"

let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}, hooks: live_select})

...

Now our album form is handled by the form_component.ex so let’s open that and we’ll need to import LiveSelect in order to use the live_select form helper.

lib/teacher_web/live/album_live/form_component.ex

defmodule TeacherWeb.AlbumLive.FormComponent do
  ...

  import LiveSelect

  ...
end

Great now let’s open the corresponding form_compononent.html.heex template and here we can replace the select form helper that we are currently using for our genre with the live_select. If we look at the docs we can see there are several different options that you can use to customize your live_select field.

Let’s update our genre field to use the live_select. We’ll go ahead and set the default_value to be the album’s genre and let’s set the placeholder to be the album’s genre as well. Also, let’s include the update_min_len option with a value of 1. This is the minimum length of text in the text input field that will trigger an update of the dropdown.

Template path: lib/teacher_web/live/album_live/form_component.html.heex

...
<%= label f, :genre %>
<%= live_select f, :genre,
  default_value: @album.genre,
  placeholder: @album.genre,
  update_min_len: 1 %>
<%= error_tag f, :genre %>
...

Now when a user starts to type a genre to select in our new live_select field, it will send a LiveSelect.ChangeMsg message to our LiveView - we need to update that corresponding LiveView to handle this message.

Let’s open the LiveView that will handles this - our show.ex LiveView and we’ll add a handle_info callback….pattern matching on the %LiveSelect.ChangeMsg{}. Then once we have our message, we can grab the text property, which contains the current text entered by the user. We’ll need to take this partial text and use it to filter the list of genres. To do this our application has a function Genre.search. It’s not important here to know how it works, just know that it takes some text and then returns a list of genres that best match that text.

This list of matching genres will be displayed by the LiveSelect dropdown. With this list we can update our LiveSelect by calling LiveSelect.update_options(change_msg, genres). Then we’ll need to return a :noreply tuple. And let’s add an alias for the genre module so we can call it without the prefix.

lib/teacher_web/live/album_live/show.ex

defmodule TeacherWeb.AlbumLive.Show do
  ...
  
  alias Teacher.Recordings.Genre
  
  ...

  @impl true
  def handle_info(%LiveSelect.ChangeMsg{} = change_msg, socket) do
    genres = Genre.search(change_msg.text)
    LiveSelect.update_options(change_msg, genres)
    {:noreply, socket}
  end

  ...
end

With that let’s test out our changes. We’ll go to the command line and start our server.

$ mix phx.server
...

Now when we edit our album and start typing a genre, we can see a dropdown with an updated list of genres appears as we type. And if we select the genre and save the album - it works. This is great, but some of our albums might be in multiple genres - how can we handle that? Fortunately LiveSelect has a tag mode which makes it easy for us to do just that. Let’s go back to the form_component.html.heex and set it to use tags mode. With this enabled our selected genres will be sent to our LiveView as a list. So to make it easier for us to handle this format, let’s update our field to use the album’s genre_list.

If we take a quick look at our Album module, we can see that the genre_list field is an array of strings, which is what we want. Then let’s go back to our template and update the default_value to use the genre_list and then for the placeholder, let’s join any genres present and separate them with a comma. Our join will fail if genre_list is nil, so let’s go ahead and include an empty list to use if it is.

Template path: lib/teacher_web/live/album_live/form_component.html.heex

...
<%= label f, :genre %>
<%= live_select f, :genre_list,
  mode: :tags,
  default_value: @album.genre_list,
  placeholder: Enum.join(@album.genre_list || [], ", "),
  update_min_len: 1 %>
<%= error_tag f, :genre_list %>
...

Because we’re now using the genre_list field, let’s open our album’s show template and update it to display our genre_list using the same formatting we did for the placeholder.

Template path: lib/teacher_web/live/album_live/form_component.html.heex

<li>
  <strong>Genre:</strong>
  <%= Enum.join(@album.genre_list || [], ", ") %>
</li>

With that let’s go back to the browser and if we now try to edit our album’s genre we’re able to select one genre and then another. This is great and the selected genres are automatically displayed above our field!

We can also remove one and select a replacement. And great they are saved and album is updated with multiple genres. LiveSelect made it easy to update our LiveView to use a dynamic selection field with a dropdown.

© 2024 HEXMONSTER LLC