I am trying to set up some dynamic Dropdown Select Menus in a Search Form using form_tag. What I would like is similar functionality to the example found at Railcasts #88
Models:
class Count < ActiveRecord::Base
belongs_to :host
end
class Host < ActiveRecord::Base
belongs_to :site
has_many :counts
end
class Site < ActiveRecord::Base
belongs_to :state
has_many :hosts
end
class State < ActiveRecord::Base
has_many :sites
end
View:
<%= form_tag(counts_path, :method => "get", id: "search-form") do %>
<%= select_tag "state_id", options_from_collection_for_select(State.all.order(:name), :id, :name) %>
<%= select_tag "site_id", options_from_collection_for_select(Site.all.order(:name), :id, :name) %>
<% end %>
A State has_many Sites which has_many Hosts which has many Counts. Or conversely, Counts belong_to Host whichs belongs_to Site which belongs to State
So I would like to select a state from the States dropdown that would then "group" the Sites based on the state they associate through the Host.
I have struggled with this nested association and can't seem to figure out how build the grouped_collection_select.
I know I'm overlooking something obvious! Could sure use some pointers...
You can fire jquery-ajax request. Change event in first select box will call action on controller and called method will change the value of second dropdown through ajax call. Simple example:
In your view file:
<%= select_tag 'state_id', options_for_select(State.all.order(:name), :id, :name) %>
<%= select_tag "site_id", options_for_select(Site.all.order(:name), :id, :name) %>
In JS file of that controller:
$(document).on('ready page:load', function () {
$('#state_id').change(function(event){
$("#site_id").attr('disabled', 'disabled')
$.ajax({
type:'post',
url:'/NameOfController/NameOfMethod',
data:{ state_id: $(this).val() },
dataType:"script"
});
event.stopImmediatePropagation();
});
});
In NameOfController.rb
def NameOfMethod
##no need to write anything
end
In NameOfMethod.js.erb
<% if params[:state_id].present? %>
$("#site_id").html("<%= escape_javascript(render(partial: 'site_dropdown'))%>")
<% end %>
in _site_dropdown.html.erb file:
<% if params[:state_id].present? %>
<%= select_tag 'site_id', options_for_select(Site.where("state_id = ?", params[:state_id])) %>
<% else %>
<%= select_tag "site_id", options_for_select(Site.all.order(:name), :id, :name) %>
So it will change site dropdown based on selected state dropdown. You can go upto n number of level for searching. Good luck.
Related
In my application users belongs to a group and this is done in my Usergroup Model.
class Usergroup < ApplicationRecord
belongs_to :user
belongs_to :group
end
class User < ApplicationRecord
has_many :usergroups
has_many :groups, through: :usergroups
end
class Group < ApplicationRecord
has_many :usergroups
has_many :users, through: :usergroups
end
When I want to add a user to the group though I need to know the ID of the group and the ID of the user which is less than ideal. I created a autocomplete field using jQuery to take care of this for me.
<%= form_with(model: usergroup, local: true) do |form| %>
<div class="field">
<%= form.label :user_id %>
<%= form.text_field :user_id, id: 'user', data: { autocomplete_source: User.order(:email).map(&:email) } %>
</div>
<%= form.submit %>
<% end %>
When I attempt to create the new usergroup with the email I selected from this dropdown menu it's submitting because it's requiring there to be a user. How do I pass the complete user object to this field instead of just the email of the user I want to create? Am I approaching this correctly?
This is the route that is being used to get the users.
user GET /users/:id(.:format) users#index
When I have to find a value from a large list I like to use the gem "select2-rails" gem
you can make an Ajax code to get your values like this
class UserController < ApplicationController
respond_to :html, :json
def index
if params[:email].present?
#users = User.where(email: params[:email])
else
#users = User.all
end
respond_with #users
end
end
in your HTML all you need
<div class="field">
<%= form.label :user_id %>
<%= form.hidden_field :user_id, id: 'user_id', class: 'select2' %>
</div>
and in your JS
$(document).ready(function() {
$('.select2').select2({
ajax: {
url: "<%= user_path(format: :json) %>",
dataType: "json",
results: function(data, page) {
return {
results: $.map( data, function(user, i) {
return { id: user.id, text: user.email }
} )
}
}
}
});
});
I hope that this works for you or points you in the right direction
Typically when you have an autocomplete, you want to use a select box for this. One of the advantages of a select box is that you can display one value and the select will actually submit a different one. This is perfect for your situation where you want to have the user select an email but you actually want to receive a user_id server side.
<%= form.collection_select :user_id, User.order(:email), :id, :email %>
You can wrap a select like this with libraries like Selectize or Select2 to provide an autocomplete style dropdown.
I believe the jQuery Autocomplete library would work for this too, but I'm not terribly familiar with it. This looks like what you'd want where each item is an object with a value and a label. https://jqueryui.com/autocomplete/#custom-data
It might look like this with your existing JS that loads the autocomplete_source attribute, but no guarantees this will work.
<%= form.collection_select :user_id, User.order(:email), :id, :email, data: { autocomplete_source: User.order(:email).map{ |u| { value: u.id, label: u.email } } %>
I have a model 'item', two models 'bar' and 'restaurant' and a model 'user'.
The relations between those models are:
User has_many :bars and has_many :restaurants
Item belongs_to :activity, polymorphic: true
Bar has_many :items, as: :activity
Restaurant has_many :items, as: :activity
How my _form view to create a new item should be like?A user can create an item and assign it to a model that can be bar or restaurant, so i would like that user can choose in which activity the item should belongs to.
In my form i have something like <%= f.select :activity, #my_activities.collect { |a| [a.name, a.id] } %> but doesn't work.
For polymorphic associations you use a fields_for block:
<%= form_for(#bar) do |f| %>
Bar<br />
<%= select :id, options_for_select(#bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
New Item<br />
<%= f.fields_for :items do |a| %>
Kind: <%= a.select :kind, options_for_select(Item.kinds.keys.map.with_index {|k,i| [k, i]}) %><br /> <!-- # If you have an enum of kind for item -->
Vendor: <%= a.select :vendor_id, options_for_select(current_user.vendors.map {|i| [i.name, i.id]}) %><br /> <!-- # If you have specific vendors per user -->
Name: <%= a.text_field :name %><br />
<% end %>
<%= f.submit %>
<% end %>
This will go inside your form_for tag. Use fields_for block to nest any polymorphic relation.
You would only use select for an attribute that exists when creating something new. The example you've partly written out looks like you're simply selecting from existing items. That would be a different answer if you're looking for that. You've specifically asked about creating an item. So you won't be using select for creating something by name, you will need a text_field to enter the new name.
You can ignore the two select fields for your solution. They are there to demonstrate select. I don't believe your answer needs a select field.
https://gorails.com/episodes/forum-nested-attributes-and-fields-for
On another note your naming scheme for Item may be confusing. The standard naming would be more like.
class Item < ActiveRecord::Base
belongs_to :itemable, polymorphic: true
end
class Bar < ActiveRecord::Base
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
end
class Restaurant < ActiveRecord::Base
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
end
In this case you would use a fields_for on :items. The polymorphic relationship name activity or itemable is not referred to in the fields_for field. Rather it is the plural for Item so :items.
To answer:
i have a page to add an item, where the user fill informations like
title, description, etc, and then chooses in which 'bar' or
'restaurant' he wants to publish it.
<%= form_for(#item) do |f| %>
<%= f.select :itemable_type, options_for_select([Bar.name, Restaurant.name]) %>
<%= f.select :itemable_id, [1,2,3] %># COMPLICATION, NEED AJAX/JS TO GET AVAILABLE IDs
<%= f.text_field :name %>
<% end %>
Well this is basically what you want to do. But you would need to have an Ajax/JavaScript call to change the available options for :itemable_id to the list of either Bars or Restaurants mapped with [:name, :id]. You could just use a text field to input the number of the ID of the Bar/Restaurant but this is not a user friendly experience.
If I was proficient in JavaScript I could give you a way to do this. One way would be to have duplicate :itemable_id fields and have javascript disable/remove whichever the select field it isn't using.
Alternative solution 1:
You could make a page for each type new_bar_item.html.erb and new_restaurant_item.html.erb and each of those you would simply put a hidden_field for itemable_type to be either Bar.name or Restaurant.name respectively. And then you would already know which collection to give to your select field. Map the collections for the select field to your :name, :id. This removes all the complication for doing this.
A workable solution 2:
A good way I can recommend to do it without JavaScript is to have both Bars and Restaurants listed.
<%= form_tag(#item) do |f| %>
Choose either Bar or Restaurant.<br />
Bar: <%= select_tag 'item[bar_id]', options_for_select(#bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
Restaurant: <%= select_tag 'item[restaurant_id]', options_for_select(#restaurants.map {|i| [i.name, i.id]}, include_blank: true) %><br />
Item name: <%= text_field_tag 'item[name]' %>
<% end %>
Then in your ItemController you will need to write a method to check which field isn't blank and set the polymorphic type with that.
before_action :set_poly_by_params, only: [:create, :update]
private
def set_poly_by_params
if !params["item"]["bar_id"].empty? ^ !params["item"]["restaurant_id"].empty?
if !params["item"]["bar_id"].empty?
params["item"]["itemable_id"] = params["item"].delete("bar_id")
params["item"]["itemable_type"] = Bar.name
else
params["item"]["itemable_id"] = params["item"].delete("restaurant_id")
params["item"]["itemable_type"] = Restaurant.name
end
else
raise "some error about incorrect selection"
end
end
# NOTE: The above code will fail if the form doesn't submit both a bar_id field and a restaurant_id. It expects both, empty or not.
Solution 3 (revised #2)
<%= form_tag(#item) do |f| %>
Location: <%= select_tag 'item[venue]', options_for_select(
#bars.map {|i| [i.name, "b"+i.id.to_s]} +
#restaurants.map {|i| i.name, "r"+i.id.to_s]}
) %><br />
Item name: <%= text_field_tag 'item[name]' %>
<% end %>
We've added a b before the ID for bar or r before the ID for restaurant. Then we simply need to parse the params for it.
before_action :set_poly_by_params, only: [:create, :update]
private
def set_poly_by_params
if params["item"]["venue"]["b"]
params["item"]["itemable_type"] = Bar.name
else
params["item"]["itemable_type"] = Restaurant.name
end
params["item"]["itemable_id"] = params["item"].delete("venue")[1..-1]
end
This meets your requirement of one select field with both Bars and Restaurants in it.
I used part of the solutions posted by 6ft Dan.
in items_controller i created a
before_action :set_activity, only: [:create, :update]
def set_activity
#itemable = params["item"]["itemable"]
if params["item"]["itemable"]["bar"]
#activity = Bar.find(#itemable[3..-1])
elsif params["item"]["itemable"]["res"]
#activity = Restaurant.find(#itemable[3..-1])
end
end
and then in create action i added
#item.update_attribute(:itemable, #activity)
after the
#item = Item.new(item_params)
and in my form i have
<%= f.select :itemable, options_for_select(#bars.map {|i| [i.name, "bar"+i.id.to_s]} + #restaurants.map {|i| [i.name, "res"+i.id.to_s]}) %>
Now item creates and has an itemable attribute linking to the activity which it belongs!
I'm building an open-source spreadsheet application. So far, its a rails application that has 4 models: table, row, column, and item.
Here is what they look like:
Table.rb:
has_many :columns
has_many :rows
accepts_nested_attributes_for :columns
Column.rb
belongs_to :table
has_many :items
Row.rb
belongs_to :table
has_many :items
accepts_nested_attributes_for :items
Item.rb
belongs_to :row
On the page where you add a new row. You should be presented with something like this:
So the columns are already set, so now you are adding new items which when inserted should include the row_id, column_id, and value for each item.
So far my form looks like this:
<%= nested_form_for [#table, #row] do |f| %>
<% #columns.each do |column| %>
<%= column.name %> <br>
<%= f.fields_for :items do |item_form| %>
<%= item_form.text_field :value %>
<% end %>
<% end%>
<%= f.submit %>
<% end %>
I'm using the nested_form for the nested forms. But so far, I can't get a textbox for the item's value to show up. Also, is this the best way to get what I want (like the picture), or is there a cleaner way?
Thanks for all help!
You'll need to build at least 1 item for fields_for to render anything. Try doing this in your new controller action.
def new
...
#row.items.build
end
I'm trying to display the recent scores of a team from a database based on the selection from a collection_select drop down. I know that I need to listen for the change event on the drop down but I don't know how to do the AJAX request or how to display the data in the view.
Ok, I will write a example with category and subcategory models.
The first is relation between models:
class Category
has_many :subcategories
has_many :objects
end
class Subcategory
belongs_to :category
has_many :objects
end
class Object
belongs_to :category
belongs_to :subcategory
end
Now the form on view, for example with simple_form gem (you can do it with form_for):
<%= simple_form_for(#object, :html => {:multipart => true }) do |f| %>
<%= f.input :category, :collection => Category.all :prompt => "Select Category" %>
<%= f.input :subcategory, :label_html => { :class => 'subcategory_label_class' } do %>
<%= f.grouped_collection_select :subcategory_id, Category.order_by(:title), :subcategories, :title, :id, :name, include_blank: true %>
<% end %>
<%= f.button :submit %>
<% end %>
With this we grouped the subcategories with their parent category.
The next step you must add the code to your objects.js.coffee
$('#object_subcategory_id').parent().hide()
subcategories = $('#object_subcategory_id').html()
$('#object_category').change ->
category = $('#object_category :selected').text()
escaped_category = category.replace(/([ #;&,.+*~\':"!^$[\]()=>|\/#])/g, '\\$1')
options = $(subcategories).filter("optgroup[label='#{escaped_category}']").html()
if options
$('#object_subcategory_id').html(options)
$('.subcategory_label_class').show()
$('#object_subcategory_id').parent().show()
else
$('#object_subcategory_id').empty()
$('.subcategory_label_class').hide()
$('#object_subcategory_id').parent().hide()
You can adapt this example to your needs.
I hope it helps.
Regards!
you need to build a separate controller and send the ajax request when your change event is triggered, the controller sends back a js response, that you have to handle in your clients javascript... the following link should give you an example http://blog.bernatfarrero.com/jquery-and-rails-3-mini-tutorial/
I have 2 models, clients and client_prices, I would like to have 2 client prices nested forms sections in the client's show page. 1 nested form would be for non-custom prices and another for custom prices. The non-custom (custom == false) prices would only have the "custom" attribute available to edit. The "Custom Prices" would have all of the attributes available to edit.
I've tried a few different approaches, but I don't know where to put the conditional logic to make this work correctly. I'm using simple_form to generate the forms, but I'm not married to it.
Controllers
class Client < ActiveRecord::Base
attr_accessible :name, :client_prices_attributes
has_many :client_prices
accepts_nested_attributes_for :client_prices, :allow_destroy => true
end
class ClientPrice < ActiveRecord::Base
attr_accessible :client_id, :price, :visit_type, :id, :custom
belongs_to :client
belongs_to :default_price
end
Client Show page
<%= simple_nested_form_for #client do |f| %>
<%= f.fields_for :client_prices do |def_price_form| %>
non-custom prices form here
<%end%>
<%= f.fields_for :client_prices do |def_price_form| %>
custom prices form here
<%end%>
<%end%>
Try the following:
<%= simple_nested_form_for #client do |f| %>
<%= f.fields_for :client_prices do |def_price_form| %>
<%= if def_price_form.object.custom %>
Here you fields for custom form
<% end %>
<% end %>
<% end %>