Dynamic Select Fields in Rails using Hotwire

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hey guys this episode we're going to be talking about how to build dynamic select fields using hot wire we've talked about this in the past using some gems and stimulus to make a json request to the server with ajax and dynamically update the other select field but we can do this even easier using hotwire and the rails request.js library that's brand new so let's dive into building this so we're going to use a scaffold to create an address model with a country and a state and this is going to be the two fields that we'll have in our form that we want to fill out we'll fill out the country first and that's going to give us the available states so let's go and run bundle add the city state gem which is a gem that has countries cities states that are easily loadable for us there's just some example data that we can use very easily but you can use other gems other libraries build your own whatever you need for your application and then we're going to run railsdb migrate to create that addresses table and we can load up the addresses slash new route and we'll see what we have our form here with our country and state we need to change these to select fields though so let's go and go to our form and we'll change this text field for each one of these to be a select instead and that's going to have two different arguments that we have to add one is going to be the options for the field the next is going to be options for those such as prompt select a country and lastly is the options that go in the html on that field that's generated so we're going to do basically the same thing for this and we'll just leave this empty for the state field and voila now we have these fields for us with select so we can go and change these to use the bootstrap form select field instead and that is going to give us the proper styling and everything and we can go change this from an empty array to cs.countries.invert and that's going to give us all of the countries as a hash with the key is the name of the country like united states and the value of it is us so if we refresh now we will see we have all of the countries and if we choose albania we can inspect the html and we will see that the selected item for albania whereas that has the value of al so we're going to be able to use that value in order to grab all of the states or provinces for the given country on the server side so this is good we're ready to go we can set up some javascript to listen for when this field changes and have stimulus run a request.js request to the server to update the states so let's start wiring up our stimulus controller we're going to have a div around this called controller is say country and that is going to wrap around these two fields and then we want this first country field to say data action when this changes we'll call the country controller we'll call say a change method on it so we're going to need to add a controller to our app javascripts controllers folder i've got hello controller in here let's just rename that to the country controller to match and then we can get rid of this connect and add a change instead and this will just say console.log hello and we'll just test to make sure that that is wired up correctly and we'll grab the event for it because we're going to need that to grab the value that you selected so let's go back here and fix this we're going to need the two curly braces to close that element that we added later and there we go so now if we open up the javascript console we should see hello is printed out anytime we change one of these and it is that's great and now we can go and um add the request to this uh event so first off to build our request we actually need to grab the event target dot selected options grab the first one and call value so what is that doing well that is saying when you click this and you change to andorra we're going to look at that andorra option element and grab the value from it so we get ad if we go and grab australia we get a u we can go down to kosovo and we get xk and we get all of the country codes as two letters so that's perfect so this is really the country that we need and in order to make our ajax request we're going to be using the brand new rails request.js library i've worked on that talked about it in previous episodes so if you haven't seen those go right ahead and watch them we're going to run yarn ad at railsrequest.js to make sure it's in our package json and we can import the get from at railsrequest.js this is going to make it easy for us to make get requests to our server so the first argument is some route so we can say addresses states and then pass in the country that we want and in order to interpolate in javascript we can use backticks instead of double quotes or single quotes and that'll let us build a url that we can request to the server now we do need to pass an option in here and that is response kind and we say we want a turbo stream response so rails will see okay we need to check and see if there's a turbo stream format that the controller responds to and we can render that out so this is great and will get us to where we need to go on the javascript side but our server side is going to need that route so we'll go to our routes and we'll add collection do get states and we can go into our addresses controller and add very quickly in here a states action that we will respond with so the state's action needs to look at the params to grab that country and then ask the city city-state gem for all of the states or provinces in that country so to do that we're going to grab at states and we'll say cs dot get params country and that will get us all of the states for that country that we gave it and then we're going to invert that as well same reason is we want the name to be the key and the value to be the id of the state like the the two letter ny for new york for example then we can respond to do format format dot turbo stream and that is all we need in our controller so if we go into our views we can go into addresses and add states.turbostream.erb and if we do that then we can add our turbostream response so we'll say dot update and we need to give it some target to update on the page and then we give it a block of html to actually go and update on the page well this part is very easy we can say options for select just like we're used to doing in our forms we just pull out that piece into this small partial and we can use a turbo stream to update that so very simply this is going to be the at states that we want to render and that's going to generate all of the options tags for us now the question becomes how do we go and grab the correct target on the page because turbostream wants us to either find an id or a class in order to update those now the downside to using a class is that it will update all of those matching ones on the page so if you had the country select on the page twice then this would actually update both of them if we gave it a class and that would be bad so we actually want to have some sort of id in here so we want to have a target and we want the target equal to pram's target and we can simply update our javascript over here to also include that so if we have target equals target and country we can say the target is the new select that we're trying to update and its id so let's go and take a look at the page real quick rails is smart enough to generate an id by depaul by default for our selects so we just need to tell stimulus there's this target that's the select element we'll grab the id from that and pass it along so what we'll do is we'll say static targets equals state or we could do like two letters or two words state select and then update our form so if we go over on this side and we say data country target state select close those curly braces then our target will be available in stimulus this dot state select target dot id that will grab the id from the html pass it on to our request and then our turbo stream response can then use that to update that individual one and that will make sure that it only updates the one stimulus state select on the page for that controller so let's try this out and see if it works if we go to andorra we will get all of andorra's provinces or states and we can go to austria and see all of those we can go down to say south africa and we'll see that those get updated as well now what's really cool about this is that our request to the server is actually receiving a turbo stream response but request.js knows that if you get a turbo stream response we want to actually update the page and execute those changes immediately so it does that behind the scenes and we don't even have to process the response at all it just works and does everything for us which is super duper handy so that's one of the benefits of using request.js and hotwire is our javascript becomes way simpler because the server can tell us how to update the page on its own the next step now that the feature is working is to actually take a look at our code and refactor it and improve it in any way we can so the first thing is we can use the values api from stimulus 2 to have a url passed in it is a string and we can then use that url here instead of a hard-coded url so this will be much more flexible and reusable for other select field types that might need to be dynamically loaded so then we need to go to our form and say data country url value equals either the addresses states that we had which was hard coded before or we can use erb for this and say states addresses path and then we'll have it included dynamically in the url if we ever change that it will still be able to generate that for more routes so this is a good improvement but what about our query params they're hard coded with the keys and values maybe you can improve that well the browser provides a api or object type that we can use called url search params and this is actually pretty cool it can actually parse params that you might have foo equals bar in a string or you can give it an object and it will parse that and then it's able to spit out that target equals target and country equals country and it will also url escape all of those for you so it encodes it properly so it will be in the url and be interpreted correctly so this is a useful tool for us and we can just add things to it by saying params.append country is this and the same thing for target and actually let's just go and join these and change this to params.append target voila so that's going to make it much nicer so that our request is just going to simply interpolate the params as a string voila now another thing we could do too is we could have the name of this be dynamic as well so the country is what we want to use for this example but in you know a different one we might not want the name to be country for the parameter so we could say pram name um we could say just param as a string and this will end up being this dot param value and this should be this dot url value i'm sorry um in order to be correct so this dot param value that will be whatever we put in the html so data country param value equals country and that starts to make it to the point where we can rename this to maybe just select and say select target and now this doesn't have to have any knowledge of it being a country select so this is just maybe a dynamic select controller instead so if we say we'll just call it select controller to keep it simple but you could probably call it dynamic select controller to be more clear so here we can go and change all of these to say data controller select data select url value we'll go down here and we'll say select change and we'll also change this country target to select and select target there we go now the one last thing is to fix this typo here we want params for this value not to be confused with the param value from stimulus but once all of those things are added we can now go back to our page refa fresh recompile the javascript and test and see if this still works and it does which is great now there's one last thing that i want to point out here and that is there is a dependency on this field having an id and rails is helpful to automatically generate an id for us but what if there isn't one well if we want to use the stimulus controller on the same page two times we need to be able to have an id for each of those fields so it's separate we talked about that before but if there is no id what do we do we don't have a value and so it will never be able to find it well the solution is we can when we connect the stimulus controller this will be called for every time it's mounted on the page so we can say this dot select target dot id check and see if it's equal to the empty string which is the value you get back if there is none and we can just assign one so we can say select target dot id equals math.random dot to string we'll get like 36 bytes or whatever that is to be assigned so now we can go refresh this and when we see that select this time around we will still get that id because it's already there so created by the form but if we had a field with a nil id for example then it will auto generate one for us so this one has a random character string we can refresh this it's going to be different and if we added two of these to the page we'd have different unique ids for each so that updating the one won't accidentally update the other one as well and we can always guarantee then that there is an id so that is a other useful improvement to have here and now we have a stimulus controller that can work for dynamic selects for any purpose and that is a really useful enhancement to this that we didn't have before so refactoring this was very very useful because now by extracting those options and removing the hard coded stuff this controller can be useful for many different reasons not just a country state select but something else maybe where you're selecting a team and a user inside of it and you're asking rails on the back end to give you the teams and the users for example now a quick teaser before we go in the next episode i actually saw this url search premium stuff and building out this string for the url and realized this would be nice to have as a feature inside request.js itself i would love to be able to say query is country is x and target is y and just have this take care of it for me and then we can get rid of all this stuff and just say here's the url go add these query params to it for us and all we have to do is build an object and it would be much easier and cleaner to do that rather than creating the search params and building the string for the url with those we can have request.js do that for us automatically so that's what i implemented and i'll walk you through making that pull request in the next screencast until then i will talk to you guys later peace
Info
Channel: GoRails
Views: 4,478
Rating: undefined out of 5
Keywords: Ruby, Ruby on Rails, Rails, Javascript, HTML, CSS, Servers, Databases, Screencast, Tutorial, Rails 6, Action Text, Active Support, Action Mailbox, Webpacker, Active Storage, Active Record, rails testing, ruby on rails testing, ruby testing, devise, rails devise
Id: ReZS6wfh3lo
Channel Id: undefined
Length: 19min 45sec (1185 seconds)
Published: Mon Aug 02 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.