We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
Screencast
34. Dynamic Select Fields
In this video, I want to go through how to do a dynamic select. By dynamic select, I mean having two select fields, where the first one narrows down the results for the second one. This is useful when the list in the second field would be too large to show directly. We have a list of cities in Colorado, each with a set of fake breweries. The goal is to pick a city, then pick one of its breweries, and display some information about it. This is a fairly common use case. I already have a list of breweries seeded in this app, so that’s where the data comes from. Let’s go ahead and start. I’m adding a function called ListCities to the existing breweries context module. That’s the natural place for it, since this concerns the breweries database. The idea is simple. We select all unique cities from the breweries table, and order them alphabetically by city name. That will be enough to list them out later in the select field. With the city query in place, the first thing I need to do is load the cities in the mount function. This way, cities are always loaded when the live view mounts. Inside the mount function, I also need to set up the form. I’ll create a form struct with a city field, and set its initial value to nil. Next up is the changes in the template. I’ll wrap everything in a form. Inside that form, I will display the select field. I’m using the core components input that we got when we generated the application. The options for that select field come from the cities we just loaded in the mount function. Finally, we need to handle the update event. We specified a PHX change as an attribute on the form, and that is what we are going to listen to. For now, it won’t do anything. We’ll add that functionality in the next step. Let’s test this in Browser and make sure to start the server. As you can see, everything looks correct. The select shows all the cities in alphabetical order, and I can use the select or dropdown to pick any city from the list. On to the next step. The goal now is to retrieve all breweries for a selected city. We start by adding a new query to list all breweries for a specific city. We filter where the city matches what we pass in, order by name, and return all results. If the city is null, we return an empty list. Note that we’re using a naive approach here. We’re just using plain strings for cities. If someone misspells a city, we could end up with duplicate or missing results. In a real setup, you’d store cities in their own table, and match on a city ID instead. That would be a much more reliable approach. Let’s add the second dropdown field where we can select breweries for a selected city. The first thing I need to do is update the mount function with a few new assigns. I’ll assign breweries, defaulting to an empty list, and selected brewery, defaulting to nil. Then, I’ll update the form to include brewery ID as an initial value in the form parameters. In the template, we add the new select field. The field is the brewery ID, and the options come from the list of breweries. One thing to note – the dropdown field is disabled if the list is empty. That means that we will ignore this field when submitting the form. Now we update the event handler, which previously didn’t do anything. It still takes the city, and now we can optionally look for the brewery ID in the params. We start by querying the city. Then, we use the city to eventually fetch the list of breweries. If a brewery ID is present, we also find and select the matching brewery. We also need to assign our new values to the socket to make sure it’s updated. That also updates the second dropdown list in the form with an eventual list of breweries for a selected city. You might have noticed that we called a function Find Brewery here. That is supposed to be a private helper function that I will just add here in the bottom of the module. The purpose is to find the brewery based in the brewery ID from the list of breweries that exists in the city. There is no need to make a separate database query for this, since we already have it loaded. Up in the template, if a brewery is selected, we render a brewery card with its details. For simplicity, I will just paste the code. It’s time to switch over to the browser and refresh and see what we have. As you can see, both dropdown fields are visible in the form. So, when I choose a city, the brewery list updates to show only the breweries in that city. When I pick Boulder as a city, I get the list of fake breweries in Boulder. And when I pick one of them, in this case Flat Iron Brew, the card is updated under the form. When I change the city, both the brewery dropdown and the card reset to defaults. And there we have it. I think this feature is complete as it is. There are some things we could improve later on, but I’ll leave this for now. As I mentioned in the intro, this is a very common pattern in web applications, and it’s a good tool to have in your toolbox.