Passing variable value form js function to rails view (dynamic select)
I use Ryan's method to add and remove nested model fields dynamically through JavaScript using jQuery.
There are 4 models: client, c_person; project, p_person. And one-to-many associations: client has many c_people and project has many p_people (these are people from client's side but they belongs to project).
So, I have following problem:
When I use helper method "link_to_add_fields" in my view, I need to pass another parameter (current_client_id) that depends on what client is currently selected in select box.
<!-- new.html.erb -->
<p>
<%= f.label :client_id %></br>
<%= f.collection_select :client_id, Client.order("title"), :id, :title %>
</p>
<p>
<%= f.fields_for :p_people do |builder| %>
<%= render "p_person_fields", :f => builder %>
<% end %>
</p>
<p&开发者_运维技巧gt;<%= link_to_add_fields "Add Person", f, :p_people, current_client_id %></p>
I need that to dynamically change the collection for @people variable in helper method,
# application_helper.rb
def link_to_add_fields(name, f, association, current_client_id)
new_object = f.object.class.reflect_on_association(association).klass.new
@people = CPerson.where(:client_id => current_client_id).order(:name) unless current_client_id.blank?
fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
render(association.to_s.singularize + "_fields", :f => builder)
end
link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
end
to finally use it in partial:
<!-- _p_person_fields.html.erb -->
<div class="fields">
<p>
<%= f.collection_select :content, @people, :name, :name %>
<%= f.hidden_field :_destroy %>
<%= link_to_function "Remove", "remove_fields(this)" %>
</p>
</div>
Here is simple js function to get current_client_id value, but I don't know how to pass it to my view or helper method.
// application.js
function current_client_id() {
var current_client_id = $("select#client_id :selected").val();
}
I appreciate your help! Maybe there is a better solution?
One thing I don't like about this is that you're using logic in a view helper that really belongs in your controller.
Maybe a better approach would be to make an AJAX call to a controller with the current_client_id. That controller can then get the @people object and send back a js.erb template where you can append a partial to your view. Or it could send back a JSON object and the handler to your AJAX call will create new elements to add to the DOM.
I've found the better way to assign people to the project – use multiple select option. This allows not to use nested model fields.
1.Use has_and_belongs_to_many
association between two models: c_person and project.
2.Create join table:
class CreateCPeopleProjects < ActiveRecord::Migration
def self.up
create_table :c_people_projects, :id => false do |t|
t.references :project
t.references :c_person
end
end
def self.down
drop_table :c_people_projects
end
end
3.Modify new.html.erb and the partial.
<!-- new.html.erb -->
<p>
<%= f.label :client_id, "Client" %><br />
<%= f.collection_select :client_id, @clients, :id, :title %>
</p>
<div id="people">
<%= render "c_people" %>
</div>
<!-- _c_people.html.erb -->
<p>
<%= fields_for :project do |f| %>
<% if @update_people.blank? %>
<%= f.collection_select :c_person_ids, @people, :id, :name, {:selected => @project.c_person_ids}, {:multiple => true, :name => "project[c_person_ids][]"} %>
<% else %>
<%= f.collection_select :c_person_ids, @update_people, :id, :name, {}, {:multiple => true, :name => "project[c_person_ids][]"} %>
<% end %>
<% end %>
</p>
4.Add js function to get current client id value, send it to projects_controller and replace partial with new collection.
jQuery(function($) {
$(function() {
$("#project_client_id").change(function() {
var client_id = $("select#project_client_id :selected").val();
if (client_id == "") {client_id = "0";}
$.get("/projects/update_people/" + client_id, function(data){
$("#people").html(data);
})
return false;
});
});
})
5.Add method to projects_controller to dynamically change the collection for people. And one line to update action (for updating nil objects).
def update
@project = Project.find(params[:id])
params[:project][:c_person_ids] ||= []
respond_to do |format|
if @project.update_attributes(params[:project])
format.html { redirect_to(@project, :notice => 'Project was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @project.errors, :status => :unprocessable_entity }
end
end
end
def update_people
@update_people = CPerson.where(:client_id => params[:id]).order(:name) unless params[:id].blank?
render :partial => "c_people"
end
6.Don't forget about the routes: match "/projects/update_people/:id" => "projects#update_people"
If you are using Rails 3, then you should remove "h(" in application_helper.rb :
link_to_function(name, h("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
to
link_to_function(name, "add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")")
精彩评论