using the city-state gem in rails - ruby-on-rails

Hey guys im currently working on a devise sign up form, the end goal is a user can select a state from the united states and a select tag below will populate with the correct cities in that state. im using the city-state gem. https://github.com/loureirorg/city-state
ive looked at other examples like this one https://forum.upcase.com/t/dependent-country-city-state/7038 my code is below
routes.rb
resources :states, only: :new
registrations.new.html.erb
<div class="field">
<%= f.select :state, options_for_select(CS.states(:us)), {: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>
main.js
document.addEventListener("turbolinks:load", function(){
var state = document.getElementById("state-picker");
state.addEventListener("change", function() {
Rails.ajax({
url: "/states?country=" + "United States" + "&state=" +
state.value,
type: "GET"
})
})
});
registrations-controller.erb
def new
super
#cities = CS.get(:us, params[:state])
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 %>
im trying to get this to work the way the example link shows. the only difference is that users will only choose states from the US and citys

From our discussion in the comments the issue appears to be that you are requesting the wrong URL for your cities.
You have a route at /states/new but are making the request to /states. Try updating your main.js to:
document.addEventListener("turbolinks:load", function(){
var state = document.getElementById("state-picker");
state.addEventListener("change", function() {
Rails.ajax({
url: "/states/new?state=" + state.value,
type: "GET"
})
})
});
(I removed the country parameter in the URL because you don't seem to be using it, you say you only need this for the US anyway.)
Let me know how you get on with that.

Related

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?
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'));
});
});

Dynamic update with method calling to Amazon API

I am a beginner in Rails, but I have done a lot of searching on this and can't seem to find something to help me since I am having difficulty breaking down the problem. I have built a working method that requests information about a book given the ISBN from Amazon and would now like to use it to autofill information about the book after a user enters in the ISBN into a form. Here is my method (which is in my listing.rb model file):
def self.isbn_lookup(val)
request = Vacuum.new('US')
request.configure(
aws_access_key_id: 'access_key_here',
aws_secret_access_key: 'secret_access_key_here',
associate_tag: 'associate_tag_here'
)
response = request.item_lookup(
query: {
'ItemId' => val,
'SearchIndex' => 'Books',
'IdType' => 'ISBN'
},
persistent: true
)
fr = response.to_h #returns complete hash
author = fr.dig("ItemLookupResponse","Items","Item","ItemAttributes","Author")
title = fr.dig("ItemLookupResponse","Items","Item","ItemAttributes","Title")
manufacturer = fr.dig("ItemLookupResponse","Items","Item","ItemAttributes","Manufacturer")
url = fr.dig("ItemLookupResponse","Items","Item","ItemLinks","ItemLink",6,"URL")
return {title: title, author: author, manufacturer: manufacturer, url: url}
end
Here is my controller for now. I am not sure how to make this generic so that the ISBN number relies on what the user enters (it should take in a value given by the user instead of assuming the #isbn instance variable is always set):
def edit
#isbn = Listing.isbn_lookup(1285741552)
end
Here is my _form.html.erb partial where I want to call this ISBN autofill:
<%= form_for(#listing, :html => {class: "form-horizontal" , role: "form"}, method: :get) do |f| %>
<div class="form-group">
<div class="control-label col-sm-2">
<%= f.label :isbn, "ISBN" %>
</div>
<div class="col-sm-8">
<%= f.text_field :isbn, id: "auto-isbn", class: "form-control" , placeholder: "ISBN (10 or 13 digits)", autofocus: true %>
</div>
</div>
...
<% end %>
Finally, here is my JS for what I think should maybe be the start to the AJAX call:
$(document).ready(function() {
$(document).on('keyup','input#auto-isbn',function() {
$.get(this.action, $(this).serialize(), null, "script");
return false;
});
});
How do I make it so that when users put in an ISBN, my app will call the isbn_lookup method and then return the information gathered?
To begin, I would create a lookup path in your routes.rb file. That would look something like:
resources :listings do
collection do
get :lookup
end
end
Which will give you:
lookup_listings GET /listings/lookup(.:format) listings#lookup
Then create the lookup action in your listings_controller.rb, something like:
class ListingsController < ApplicationController
...
def lookup
#isbn_lookup_result = Listing.isbn_lookup(params[:isbn])
render partial: 'isbn_lookup_result'
end
...
end
Naturally, this requires that you have a _isbn_lookup_result.html.erb file that accesses/uses the values from #isbn_lookup_result.
Then, to call this action from your JS, do something like (full disclosure, I use coffeescript, so my plain JS skills are a little rusty):
$(document).ready(function() {
#TIMEOUT = null
$(document).on('keyup','input#auto-isbn',function() {
clearTimeout(#TIMEOUT)
#TIMEOUT = setTimeout(function(){
var ajaxResponse = $.ajax({
url: "listings/lookup",
type: 'GET',
data: {isbn: $('input#auto-isbn').val()}
});
ajaxResponse.success(function(data){
# do stuff with your data response
# perhaps something like:
$('#isbn-lookup-results-container').html(data)
});
}, 500);
});
});
This bit:
clearTimeout(#TIMEOUT)
#TIMEOUT = setTimeout(function(){
...
}, 500);
creates a 1/2 second delay between when your user stops typing and when the ajax function is called. That way, you're not literally doing a lookup on every keyup, only when the user pauses in their typing.
This bit:
var ajaxResponse = $.ajax({
url: "listings/lookup",
type: 'GET',
data: {isbn: $('input#auto-isbn').val()}
});
is the AJAX call. You can see the new listings/lookup path in use. The data: {isbn: $('input#auto-isbn').val()} bit gives you params[:isbn], which is used in the lookup action.
Then, upon success, you use this bit to do something with your response:
ajaxResponse.success(function(data){
# do stuff with your data response
# perhaps something like:
$('#isbn-lookup-results-container').html(data)
});
In this case, data is the HTML that resulted from the render partial: call, so could load it into a div.

Filter field based on previous field selection

How can I use simple_form to filter a field, based on a previous fields value?
For instance, I have an Opportunities form, with two fields, Company and Contact.
Company Field:
<div class="form-group">
<%= f.association :company, collection: Company.all.order(:account), prompt: "", :label_method => :account, :value_method => :id %>
</div>
Contact Field:
<div class="form-group">
<%= f.association :contact, collection: Contact.all.order(:first_name), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
Here is what I want to do: If I select a company called "Deviant" from the Company field above, I want the Contact field to only display those contacts associated with the company called "Deviant".
I am trying something like this, but can't get it to work:
<div class="form-group">
<%= f.association :contact, collection: Contact.where("company_id = ?", params[:id]), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
I don't know how to reference the value in the Company field.
How can I do this?
Thanks.
Update
Anyone? Surely this must be possible. This is a key functionality in any form. I would hope I don't need jQuery or something.
I think the best approach is to use ajax requests to update your contacts collection dinamically whenever the company's selected value is changed.
First you'll need an action in your contacts controller:
app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def contacts_list
if params[:company_id]
#contacts = Contact.where(company_id: params[:company_id])
else
#contacts = Contact.all
end
respond_with(#contacts) do |format|
format.json { render :json => #contacts.to_json(:only => [:id, :first_name, :last_name]) }
end
end
end
Add this to your routes:
config/routes.rb
post 'contacts_list' => "contacts#contacts_list", as: :contacts_list
Then use the coffeescript code bellow to populate your contacts' collection:
app/assets/javasctipts/companies.js.coffee
$(document).ready ->
if $("#opportunity_company_id")
populate_contacts()
$("#opportunity_company_id").change ->
populate_contacts()
populate_contacts = ->
$contacts_select = $("select#opportunity_contact_id")
$contacts_select.attr "disabled", "disabled"
company_id = $("select#opportunity_company_id").val()
if company_id is ""
$contacts_select.html "<option value=\"\">(select the company first)</option>"
else
$contacts_select.html "<option value=\"\">(loading contacts...)</option>"
data = {company_id: company_id}
data[window._auth_token_name] = window._auth_token
$.ajax "/contacts_list",
type: "post"
dataType: "json"
data: data
success: (contacts) ->
_html = '<option value="">Select the contact:</option>'
_html += '<option value="'+contact.id+'">'+contact.first_name + ' ' + contact.last_name + '</option>' for contact in contacts
$contacts_select.html _html
$contacts_select.removeAttr "disabled"
error: ->
alert 'Error trying to load contacts.'
Finally, inside your html's head tag:
<% if protect_against_forgery? %>
<script>
window._auth_token_name = "<%= request_forgery_protection_token %>";
window._auth_token = "<%= form_authenticity_token %>";
</script>
<% end %>
Hope it helps...
update:
Add the following line to your ApplicationController (app/controllers/application_controller.rb):
respond_to :html, :xml, :json, :js

Rails show index results from options_from_collection_for_select

I have a Rails app with Clients that have_many Contacts. I would like to modify the contacts/index list with a selection of the Client. So, the user selects the Client and the screen refreshes showing the Contacts for that Client.
I started with this dropdown selection:
<%= select_tag 'Client', options_from_collection_for_select(Client.all, :id, :client_name) %>
But, I'm not sure how to modify the table code that gets the Contacts:
<% #contacts.each do |contact| %>
Do I need to use jquery?
Thanks for the help!
Update1:
Could I add #selected_client to the statement:
<%= select_tag 'Client', options_from_collection_for_select(Client.order(:client_name), :id, :client_name, #selected_client) %>
Then use this:
<% #selected_client.contacts.each do |contact| %>
Here's how I'd do it, note, this isn't tested since I just typed it up in here, but it should give you a good place to start from. Post back with your code if you need more help from here on.
<%= select_tag 'client', options_from_collection_for_select(Client.all, :id, :client_name) %>
<%= select_tag "client_contacts" %>
Then you'll need some js for the ajax:
$(document).ready(function() {
$("#client").on("change", function() {
var the_id = $(this).val();
$.getJSON("/client/contacts", {id: the_id},
function(data){
var options = '';
for (var i = 0; i < data.length; i++){
options += '<option value="' + data[i].id + '">' + data[i].name + '</option>';
}
$("#client_contacts").html(options);
});
});
});
Then you'll need a route for client/contacts:
get 'client/contacts/:id', to: 'client/contacts'
controller example:
def contacts
#client = Client.find(params[:id])
respond_to do |format|
format.json { render: json: {#client.contacts.to_json}}
end
end

Refresh div with gmaps4rails (AJAX)

I'm using Rails 3.0.9 version, and jquery.
I've been using this gem without a database. It is used only for map display, and display KML file on it. For this I used:
<div id='ajax_map'>
<% #kmlurl="http://mysite/file1.kml" %>
<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl.inspect}}]" } ) %>
</div>
All great shows.
I want to do that after you change the links (# kmlurl), and click on the button, the map updated with this new KML file. I use a separate action js.erb with the following code:
$('#ajax_map').html('<%= #kmlurl="http://mysite/file2.kml" %>'+'<br />'+'<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl.inspect}}]" } ) %>');
But he does not update the DIV. "js.erb" rendered normally, without using the method of gmaps () it normally returns # kmlurl. I tested this same code in the ". Html.erb" in the tags , it loads a new file, but, of course, just when the page loads.
How can I solve this problem?
Solved the problem as follows (in js.erb):
$('#ajax_map').html('<%= escape_javascript( gmaps({:last_map => false}) ) %>');
Gmaps.map = new Gmaps4RailsGoogle();
Gmaps.load_map = function() {
Gmaps.map.map_options.maxZoom = 15;
Gmaps.map.initialize();
Gmaps.map.kml = [{ url: '<%= "#{#kmlurl}" %>'}];
Gmaps.map.create_kml();
Gmaps.map.adjustMapToBounds();
Gmaps.map.callback();
};
Gmaps.loadMaps();
First I would refactor things just a bit.
Say that first bit of code were in your index page. I'd move the setting of #kmlurl into the corresponding controller action:
def index
#kmlurl = "http://mysite/file1.kml"
end
Then (assuming index?) your index view would be simply:
<div id="ajax_map">
<%= gmaps( :kml => { :data => "[{ url: #{#kmlurl}}]" } ) %>
</div>
Then to add a link that will update the map:
<%= link_to 'Other Map', '/othermap', :remote=>true %>
Now you'd create a route in routes.rb:
match '/othermap' => 'foo#othermap'
Then in foo_controller.rb:
def othermap
#kmlurl = "http://mysite/file2.kml"
end
Then create othermap.js.erb:
$('#ajax_map').html(
'<%=
escape_javascript(
gmaps( :kml => { :data => "[{ url: #{#kmlurl}}]" } )
)
%>'
)
That's a quick fix, but what I would REALLY do is strive to make your view code as simple as possible, and do all the real work in the controller. Ideally your view would just be:
<div id="ajax_map">
<%= gmaps( :kml => { :data => #mapdata } ) %>
</div>
set up #mapdata as appropriate in your controller. You've got too much stuff that really belongs in a controller embedded in your view code! Your othermap.js.erb should be equally simplified. i.e.
$('#ajax_map').html('<%= escape_javascript(gmaps( :kml => { :data => #mapdata } ))%>')

Resources