← Back to Upcase

Dependent country city state


(shashank shandilya v) #1

Hi, I am trying to implement Country, state, a city with cascading behavior. Is there a better way to implement it from rails way. Any help would be appreciated. It’s crucial my upcoming project.
I have tried city-state gem I cant get the cascading behavior up and running from the model. Any example app will do a great help. Thanks in advance @pbmarcano


(Pete Marcano) #2

Hi there @shashankshandilyav,

Without any context, I’m not entirely sure what your end goal is, so I can’t give you the best way to implement it a cascading dropdown behavior you’re looking for, but I gave it a quick shot anyways.

This solution isn’t perfect, but it works and I think it’s a good starting point.

I was unfamiliar with the city-state gem so I looked it up and gave it a try.

Turns out, the city-state gem is specifically designed to return lists of countries, states, and cities. It doesn’t have any functionality revolving around creating the UI elements to give dynamic dropdowns, so you’ll have to implement that yourself.

I decided to do a simple project with a simple implementation, importing nothing but the Rails 5.2.alpha defaults and the city-state gem. My project is designed to track famous people, and their hometown!

My end result looks like this:

The source code can be found on in this github repo.

Getting the states from the country selected

The form for Famous People on my example project started out with just “Name” and “Hometown” as text fields.

Clearly, we wanted to be able to populate the “Hometown” field with a city from the city-state gem. In order to do that, I needed to create two additional dropdown fields, one for Countries, and one for States.

Setting up the form

The Country’s dropdown field was a simple basic select_tag with the CS.countries method in the options_for_select.

The State’s dropdown field was also a basic select_tag but intentionally and explicitly left empty, because I want to use javascript to fill it later.

# ...
<div class="field">
  <!-- Country -->
  <%= label_tag :country %>
  <%= select_tag :country, options_for_select(CS.countries.map { |c| [c[1], c[0]] } ) %>
</div>

<div class="field">
  <!-- State -->
  <%= label_tag :state %>
  <%= select_tag :state, options_for_select([]) %>
</div>

<div class="field">
  <!-- City -->
  <%= form.label :hometown %>
  <%= form.select :hometown, options_for_select([]) %>
</div>
#...

You may notice that I had used map to divide the results so that the value would be the country’s name “United States”, and the id in the select box would be it’s key, “US”.

You also may notice that the “hometown” field uses form.select instead of select_tag. Since I want the form ignore city & state on form submission, those are just standard tags.

Using JavaScript & ajax to grab the country’s id & submit a query to the server

Once the selects their country, we need to populate the next select box with the states of that country.

To get there, we need to grab the value from the selected option, and use AJAX to submit a GET request to the server to query which states are in the country.

I added a simple JS file to the asset pipeline, but here is the important part (for now) at a glance.

// ...
// Grab the Country select box
var country = document.getElementById("country");

// When a user chooses a country, send a GET 
// request to the server with the country's id as a parameter
country.addEventListener("change", function(){
  Rails.ajax({
    url: "/states?country=" + country.value,
    type: "GET"
  })
})
// ...

Now that we have this country data leaving the client’s web browser and heading towards our server. We need to route the traffic to a controller.

# routes.rb
resources :states, only: :index

# app/controllers/states_controller.rb
def index
   @states = CS.states(params[:country])
end
Returning the index.js.erb

Now that our controller is receiving the country we selected and querying the appropriate states we need to return javascript to the client to populate these results as options in the states select box.

// app/views/states/index.js.erb
// Grab the state select box.
var state = document.getElementById("state");

// Clear any populated options in the select box
while (state.firstChild) state.removeChild(state.firstChild);

// Add a placeholder
var placeholder = document.createElement("option");
placeholder.text = "Choose a state";
placeholder.value = "";
state.appendChild(placeholder);

// Use erb templating to add the states
<% @states.each do |s| %>
  state.options[state.options.length] = new Option('<%= s[1] %>', '<%= s[0] %>')
<% end %>

By the end of this, you should have the states populated based on the country submitted.

I repeated and altered the steps above when selecting the state to populate cities, and you should be able to replicate that by looking at the code in Github.

Let me know if this is helpful / what you are looking for.

Cheers,
Pete


(shashank shandilya v) #3

@pbmarcano you are really amazing I will give it a try. I was looking for a workaround and this is perfect. sorry for not being much clear on the goal. I want the user to select the city or nearest city he is living in. Hence the cascading behavior is crucial. I want to query the users located in his area based on several criteria. I want to match them. THANKS A MILLION


(Pete Marcano) #4

You’re welcome :slight_smile:

I had to wrap my head around this exact problem for some client work a good while ago and I remember many hours of banging my head against my desk. Hopefully this helps you out and saves your head!


(shashank shandilya v) #5

@pbmarcano I tried replicating the solution am out of luck, it’s not working to me can you please suggest the place I am going wrong.
I have rails 5.1.5 with ruby 2.5.0.
Here is my repo
thanks in advance,