kevin lopez
kevin lopez

Reputation: 87

Using js.erb in rails

In Rails 5 app with devise, I need to use a new.js.erb file to update select tag in my registrations view and controller. I cant seem to figure out why my new.js.erb file isn't working.

I've tried to use respond_to in controller as below,

registrations-controller.rb

 def new
   super 
   @cities = CS.get(:us,params[:state]) 
   respond_to do |format|
     format.js { render '/new.js.erb' }# layout: false }          
     format.html 
   end
 end

new.html.erb

<%= form_for(resource, as: resource_name, url: registration_path(resource_name), :remote => true) do |f| %>
   <div class="signup-input-container">
     <div class="field">
        <%= f.text_field :firstname, autofocus: true, autocomplete: "firstname", placeholder: "First name", class: "signup-input-container--input" %>
     </div>
     <div class="field">
        <%= f.select :state, options_for_select(CS.states(:us).map { |code, name| [name, code] }),{:prompt => "State"}, {:class => "signup-input-container--input", :id => "state-picker"} %>
     </div>
     <div class="field">
        <%= f.select :city, options_for_select([]),{}, {:class => "signup-input-container--input", :id => "city-picker"} %> 
     </div>
  </div>
<% end %>

new.js.erb

  var city = document.getElementById("city-picker");

  while (city.firstChild) city.removeChild(city.firstChild);

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

 <% @cities.each do |c| %>
   city.options[city.options.length] = new Option('<%= c %>');
 <% end %>

main.js

  var state = document.getElementById("state-picker");
  state.addEventListener("change", function() {
     $.ajax({
        url: "/states?state=" + state.value,
        type: "GET"
     })
  })

I'm expecting this to create select tag options with my array of cities in my controller. Does anyone know how to get this to work?

Upvotes: 1

Views: 2170

Answers (1)

max
max

Reputation: 102423

To solve this you should just setup a separate controller where you can fetch the data from asynchronously and alternatively there are also several free API's which can be used for geographical lookup such as Googles Geocoding API and Geonames.

To setup a separate controller you can do it by:

# /config/routes.rb
get '/states/:state_id/cities', to: 'cities#index'

# /app/controllers/cities_controller.rb
class CitiesController < ApplicationController
  # GET
  def index
    @cities = CS.get(:us, params[:state_id]) 
    respond_to do |f|
      f.json { render json: @cities }
    end
  end
end

I would skip using a .js.erb template altogether and just return JSON data which you can use directly in your JS or with one of the many existing autocomplete solutions. .js.erb only makes sense for extensive HTML templating (like for example rendering an entire form) where you want to reuse your server side templates - it greatly increases the complexity and generally makes a mess of your javascript which is not worth it just to output a list of option tags.

// If you are using jQuery you might as well setup a delegated
// handler that works with turbolinks, 
$(document).on('change', '#state-picker', function(){
  $.getJSON("/states/" + $(this).value() + "/cities", function(data){
    // using a fragment avoids updating the DOM for every iteration.
    var $frag = $('<select>');
    $.each(data, function(city){
      $frag.append('<option>' + data + '</option>');
    });
    $('#city-picker').empty()
                  .append($('frag').children('option'));
  });
});

Upvotes: 1

Related Questions