Environment Ruby 2.0.0, Rails 4.0.3, Windows 8.1, jQuery
EDIT: Just FYI, I was discussing the issue the other day and I was told that the common method to solve this problem would be just to pass the record ID. Certainly, I would recommend that solution in a general case. In this case, the record is being created and has not yet been stored, so it has no ID and cannot have one until all required fields are completed.
I need to pass the object instance from the view through jQuery to the controller so that the controller use it to render a partial using dependent selects. This process was generally working even though I was just passing a string that named the object. But, now I have to implement strong parameters to permit updates and that requires the actual instance and not just the string name of the instance.
In jQuery, I use the following to obtain the instance but it is obviously wrong because it only gets me the string name of the instance. I assume it needs to be serialized perhaps? But, I can only get the string name which cannot be serialized.
var car = $('select#car_year_id').attr("car");
The basic question is, how do I retrieve the actual instance of car within jQuery? Alternatively, I guess, the question would be that, given the string name of an instance within Ruby on Rails, how do I address the actual instance? Either one would probably suffice. Of course, other alternatives will be welcomed. Thanks.
The form is:
<div class="span8">
<%= simple_form_for #car,
defaults: {label: false},
html: {class: 'form-vertical'},
wrapper: :vertical_form,
wrapper_mappings: {
check_boxes: :vertical_radio_and_checkboxes,
radio_buttons: :vertical_radio_and_checkboxes,
file: :vertical_file_input,
boolean: :vertical_boolean
} do |f| %>
<%= f.input(:stock_number, {input_html: {form: 'new_car', car: #car}, autocomplete: :off, placeholder: 'Stock number?'}) %>
<%= f.input(:year_id, {input_html: {form: 'new_car', car: #car}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
<%= render partial: "makes", locals: {form: 'new_car', car: #car} %>
<%= render partial: "models", locals: {form: 'new_car', car: #car} %>
<input type="submit" form="new_car" value="Create Car" class="btn btn-default btn btn-primary">
<% end %>
</div>
The "makes" partial is:
<%= simple_form_for car,
defaults: {label: false},
remote: true do |f| %>
<% makes ||= "" %>
<% if !makes.blank? %>
<%= f.input :make_id, {input_html: {form: form, car: car}, collection: makes.collect { |s| [s.make, s.id] }, prompt: "Make?"} %>
<% else %>
<%= f.input :make_id, {input_html: {form: form, car: car}, collection: [], prompt: "Make?"} %>
<% end %>
<% end %>
The jQuery is:
$(document).ready(function () {
// when the #year field changes
$("#car_year_id").change(function () {
// make a GET call and replace the content
var year = $('select#car_year_id :selected').val();
if (year == "") year = "invalid";
var form = $('select#car_year_id').attr("form");
if (form == "") form = "invalid";
var car = $('select#car_year_id').attr("car");
if (car == "") car = "invalid";
$.post('/cars/make_list/',
{
form: form,
year: year,
car: car
},
function (data) {
$("#car_make_id").html(data);
});
return false;
});
});
The controller action is:
def make_list
makes = params[:year].blank? ? "" : Make.where(year_id: params[:year]).order(:make)
render partial: "makes", locals: { car: params[:car], form: params[:form], makes: makes }
end
I found the answer! So excited!
There is a new HTML construct that allows you to using an arbitrary attribute to an HTML element as long as the name is preceded by "data-". For example:
<%= f.input(:year_id, {input_html: {form: 'new_car', data-car: #car}}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
This is problematic in Rails, because Rails doesn't like hyphens in symbols. However, there is an optional helper using the data: symbol to pass a hash as in:
<%= f.input(:year_id, {input_html: {form: 'new_car', data: { car: #car}}, collection: Year.all.collect { |c| [c.year, c.id] }, prompt: "Year?"}) %>
See: Best way to use html5 data attributes with rails content_tag helper?
Then, within JavaScript, you can use the dataset property to retrieve a DOMStringMap object as follows:
var element = document.getElementById('car_year_id');
var car = element.dataset.car;
See: HTML5 Custom Data Attributes (data-*)
This returns car as a hash object, which is really just what I needed!
Overall reference that helped a lot: Rails 3 Remote Links and Forms: A Definitive Guide
Just for completeness, I used to following code to convert the hash into an object back in the controller:
car_hash = params[:car].gsub!(/":/, '" => ')
null = nil
#car = Car.new(eval(car_hash))
Related
I have a simple has_many and belongs_to relationship in my rails app. I'm using simple_form and want to dynamically change the dropdown options based on the value chosen by the user.
Models
class Processor < ApplicationRecord
has_many :processor_bank_accounts
end
class ProcessorBankAccount < ApplicationRecord
belongs_to :processor
end
Form inputs
<%= simple_form_for [#customer, #transaction] do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :status, :collection => ["payment request"], include_blank: false %>
<%= f.input :processor, collection: #processors ,label_method: :name,value_method: :id,label: "Processor" , include_blank: false %>
<%= f.input :processor_bank_account, collection: #bank_accounts , label_method: :bank_name, value_method: :id, label: "Processor Bank Account" , include_blank: true %>
<%= f.input :tcurrency, collection: #currencies, include_blank: false, label: 'currency' %>
<%= f.input :amount, as: :decimal, label: 'amount' %>
</div>
<div class="form-actions text-center">
<%= f.button :submit, "Add transaction", class: "form-button"%>
</div>
<% end %>
So essentially, I need the processor_bank_account dropdown to populate based on the processor chosen by the user. In the console, this would just be: ProcessorBankAccount.where(processor: processor).
Need to load options using JS and think I need to use JSON but not sure where to go from here
One way to do this would be to use jQuery to make an AJAX call to a controller action and then let Rails handle the rest through an erb template.
So on your page, with the form, invoke the action via AJAX using something like:
<script>
$(document).ready(function() {
$('#processor_id').on('change', function() {
$.ajax({
url: '/transactions/get_processor_bank_accounts',
type: 'GET',
data: {
processor_id: this.value
},
dataType: 'script',
error: function() {
alert('An error occurred retrieving bank accounts for the selected processor.');
}
});
});
});
</script>
NB, #processor_id is the id for your dropdown control.
Next, instantiate the bank accounts within your action in your controller:
def get_processor_bank_accounts
#processor_bank_accounts = ProcessorBankAccount.where(processor_id: params[:processor_id])
end
And finally simply create a view that will be responsible for repopulating your dropdown:
$select_list = $('#processor_id');
$select_list.empty();
<% #processor_bank_accounts.each do |pba| %>
$select_list.append($('<option value="<%= pba.id %>"><%= pba.name %></option>'));
<% end %>
I came up with the following solution:
1) Add a new method to my processors controller to render the inputs for the second (dynamic) dropdown in JSON format:
def processor_bank_accounts
render json: #processor.processor_bank_accounts.each do |bap|
{ id: bap.id, name: bap.name }
end
end
2) Assign this new function to a new route in config/routes:
get 'api/bankaccounts', to: 'processors#processor_bank_accounts', as: 'bankaccounts'
3) Create a JS function to access the route with the id of the processor selected in the first dropdown and populate the second dropdown with items from the JSON array:
// select first dropdown
const processor = document.getElementById("transaction_processor");
// select second dropdown
const bapSelect = document.getElementById("transaction_processor_bank_account");
function update_baps(processor_id) {
const url = `INSERTWEBURLHERE/api/bankaccounts?id=${processor_id}`;
fetch(url)
.then(response => response.json())
.then((data) => {
bapSelect.innerHTML = ""; // clear second dropdown
data.forEach((bap) => { // go through all the BAPs
const elem = `<option value="${bap.id}">${bap.bank_name}</option>`; // create option elements to insert into the second dropdown, bank_name is the chosen label method in the form
bapSelect.insertAdjacentHTML("beforeend", elem); // insert options into the dropdown
});
});
}
4) Trigger the JS when the first dropdown field is changed:
processor.addEventListener('change', function () {
update_baps(parseInt(processor.value));
});
You should add an id to the selects so you can identify them form the script.
$('select#processor').on('change', function() {
var processor_id = this.value;
var processor_bank_account = $('select#processor_bank_account')
$.ajax({
type: "GET",
url: <%= your_path %> ,
data: { processor_id: processor_id },
success: function(data, textStatus, jqXHR){
processor_bank_account.empty();
var option = new Option(data.bank_name, data.id, false, false);
processor_bank_account.append(option);
},
error: function(jqXHR, textStatus, errorThrown){...}
})
});
I have successfully integrated Jquery-Autocomplete into my rails app, currently users can search more than 5000+ airports and so far the integration works exactly as desired, however...
Any Airport has multiple columns, currently i am returning the airport name but would like to instead show
**
airport_name - airport_city - airport_iata - airport_icao
**
I have so far used Ryan Bates Railscast to get me started
Episode #102
But amended and tweaked thanks to other resources since his video. Sorry i can't reference them right now but will update my question once i've found links to their instructions.
Either way Autocomplete does work as designed and i have managed to populate a hidden field but i would really like to display more than just the Airport name when searching. I will continue to only save the airport ID.
Any help is appreciated, here is my code.
_form.html.erb
<div class="field">
<%= f.label :departure_airport %><br>
<%= text_field_tag nil, nil, :id => 'claim_departure_airport_name', data: {autocomplete_source: Airport.order(:name).map { |t| { :label => t.name, :value => t.id } } }, class: "form-control" %>
</div>
<%= f.hidden_field :d_airport_id, id: 'd_airport_id' %>
<%= f.hidden_field :a_airport_id, id: 'a_airport_id' %>
claims.coffee
jQuery ->
$('#claim_departure_airport_name').autocomplete
source: $('#claim_departure_airport_name').data('autocomplete-source')
select: (event, ui) ->
# necessary to prevent autocomplete from filling in
# with the value instead of the label
event.preventDefault()
$(this).val ui.item.label
$('#d_airport_id').val ui.item.value
$('#claim_arrival_airport_name').autocomplete
source: $('#claim_arrival_airport_name').data('autocomplete-source')
select: (event, ui) ->
# necessary to prevent autocomplete from filling in
# with the value instead of the label
event.preventDefault()
$(this).val ui.item.label
$('#a_airport_id').val ui.item.value
As you can see i am directly reaching the model data without the need for a dedicated controller although i realise this would be much more intelligent than my current solution as i wish to roll this out in other areas of the platform.
I don't fully understand everything that is happening in the jquery code in my coffee file this was obtained from another source although i forget who to give credit. As far as i know it's taking the ID of the airport and populating the hidden field?
If you can spot how to show other airport database columns to the user that would be great.
Thanks
edit
Also wish to restrict autocomplete from loading the data source into html due to seriously long page load times. A screen shot of what i mean is below.
screenshot of data loading
You can modify the autocomplete source to
<%= text_field_tag nil, nil, id: 'claim_departure_airport_name', data: { autocomplete_source: Airport.select(:id, :name, :city, :iata, :icao).order(:name).map { |t| { label: "#{t.name}-#{t.city}-#{t.iata}-#{t.icao}", value: t.id } } }, class: "form-control" %>
To refactor this a bit, you can move the active record query into a helper
def airport_autocomplete_data
Airport.select(:id, :name, :city, :iata, :icao).order(:name).map { |t| { label: "#{t.name}-#{t.city}-#{t.iata}-#{t.icao}", value: t.id } }
end
and your text field becomes
<%= text_field_tag nil, nil, id: 'claim_departure_airport_name', data: { autocomplete_source: airport_autocomplete_data }, class: "form-control" %>
The main point is here data: {autocomplete_source: Airport.order(:name).map { |t| { :label => t.name, :value => t.id } } }
It builds an array of objects, like [{'label': 'BKK', value: 1}, {'label': 'HAM', value: 2}]
So you either need to add something to the label key, or maybe add a different key and use it later in the select callback.
If you are using rails4-autocomlete Gem. You can easily override from controller.
def autocomplete_profile
term = params[:term]
profiles = Profile.where(
'LOWER(profiles.first_name) LIKE ? OR LOWER(profiles.last_name) LIKE ?',
"%#{term}%", "%#{term}%"
).order(:id).all
render :json => profiles.map { |profile| {:id => profile.id, :label => profile.id, :value => profile.id} }
end
I have migrated a :completed boolean, I then added it to my form like so (simple_form)
<%= f.input :completed, as: :boolean %>
This allows me to update completed, not completed in the edit view.
How can I edit the :completed method in my index view? I want to be able to update complete or not complete on the fly.
EDIT:
My Form
<%= f.input :completed, as: :boolean, input_html: { class: 'completed', id: #todo.id } %>
My View (label has to be after input as per materializecss)
<%= check_box_tag todo.id, 'completed', todo.completed?, class: 'completed' %>
<label class="strikethrough">
COMPLETE
</label>
The JS
// This could be other events as well, so add/change/remove the event trigger as needed
$(document).on('click', '.completed', function(){
updateCompleted($(this));
});
function updateCompleted($el) {
var id = $el.attr('id'),
val = $el.is(':checked');
$.PATCH({
url: '/todos/'+id,
data: { todo: { completed: val } },
success: function(){ whatever },
error: function() { whatever }
});
}
My Controller
params.require(:todo).permit(:title, :item, :image, :completed)
You are going to require some AJAX here. In my experience, doing this is a bit outside of the "rails way", but totally possible.
First, make sure you have the ID of the object somewhere in the dom:
<%= f.input :completed, as: :boolean, input_html: { class: 'completed', id: myobject.id %>
I used the simple_form syntax since that's what it appears you are using for your form builder. If not, comment back and I'll adjust the tag.
Secondly, add a jquery event listener:
// This could be other events as well, so add/change/remove the event trigger as needed
$(document).on('click', '.completed', function(){
updateCompleted($(this));
});
function updateCompleted($el) {
var id = $el.attr('id'),
val = $el.is(':checked');
$.PATCH({
url: '/your_endpoint/'+id,
data: { your_object_name: { completed: val } },
success: function(){ whatever },
error: function() { whatever }
});
}
And in your controller:
private
def your_object_params
params.require(:your_object).permit(..., :completed)
end
That's basically it.
Basically you will want to make a post to the update action from the index action. I would use JavaScript but you can also use a hard refresh if you want to stick with rails. Here is a good intro if you are not familiar with using JS in your rails app. http://railscasts.com/episodes/324-passing-data-to-javascript.
I recently tried to implement a cascading dropdown into my application with this tutorial on petermac.com: http://www.petermac.com/rails-3-jquery-and-multi-select-dependencies/
The tutorial basically talks about doing a cascading dropdown, where every dropdown box is an a separate partial and gets loaded with an jQuery onChange event when the parent select is changed.
Now I got this to work without a problem. But actually my select boxes have quite complicate relationships between them.
So, the form I belongs to a Model called AuditFunction and as the name says is for auditing. Every audit has a source and a target, which can be compared via several commands. The source as well as the target are selected via 3 select boxes. The first box selects the type of database the field is in. The second box selects the table and then the third box selects the field. As the field box can contain thousands of options I tried to implement the cascading dropdown to make it easier for the user to select the field.
To give you an overview, this is what my actions look like:
# new.html.erb
<%= simple_form_for #audit_function do |f| %>
<%= f.input :database_1, :as => :select, :collection => #databases, :include_blank => true %>
<%= render :partial => 'databases_1' %>
<%= render :partial => 'fields_1' %>
<%= f.input :database_2, :as => :select, :collection => #databases, :include_blank => true %>
<%= render :partial => 'databases_2' %>
<%= render :partial => 'fields_2' %>
<% end %>
The javascript for this looks like this:
# jQuery
<script type='text/javascript' charset='utf-8'>
jQuery(function($) {
// when the #country field changes
$("#audit_function_database_1").change(function() {
var database_1 = $('select#audit_function_database_1 :selected').val();
if(database_1 == "") database_1="0";
jQuery.get('/audit_functions/update_database_1_id_select/' + database_1, function(data){
$("#database_1_id").html(data);
})
return false;
});
})
</script>
<script type='text/javascript' charset='utf-8'>
jQuery(function($) {
// when the #country field changes
$("#audit_function_database_2").change(function() {
var database_2 = $('select#audit_function_database_2 :selected').val();
if(database_2 == "") database_2="0";
jQuery.get('/audit_functions/update_database_2_id_select/' + database_2, function(data){
$("#database_2_id").html(data);
})
return false;
});
})
Now I'm only going to show you the partials for database_1_id and field_1_id, but they look the same as database and field 2.
# _databases_1.html.erb
<script type="text/javascript">
jQuery(function($) {
$("#audit_function_database_1_id").change(function() {
var database_1_id = $('select#audit_function_database_1_id :selected').val();
if(database_1_id == "") database_1_id="0";
jQuery.get("/audit_functions/update_field_1_id_select/" + ("<%= params[:id] %>_" + database_1_id), function(data){
$("#field_1_id").html(data);
})
return false;
});
})
</script>
<%= simple_form_for "audit_function" do |f| %>
<% if params[:id] %>
<% if params[:id] == "imp" %>
<%= f.input :database_1_id, collection: AdOriTbl.all.order(ori_filename: :asc).collect{ |a| [a.ori_filename,a.id]} %>
<% elsif params[:id] == "ori" %>
<%= f.input :database_1_id, collection: AdOriTbl.all.order(otb_filename: :asc).collect{ |a| [a.otb_filename,a.id]} %>
<% elsif params[:id] == "mod" %>
<%= f.input :database_1_id, collection: AdQryMod.all.order(qry_mod_text: :asc).collect{ |a| [a.qry_mod_text,a.id]} %>
<% end %>
<% end %>
<% end %>
And now the file containing the target field.
# _fields_1.html.erb
<%= simple_form_for "audit_function" do |f| %>
<% if params[:id] %>
<% if params[:id].gsub(/_{1}\d{1,}\z/, "") == " mod " %>
<%= f.input :field_1_id, collection: AdQryFld.where(ad_qry_mod_id: params[:id].gsub(/\A\w{1,}_{1}/, "").to_i).order(order_id: :asc).collect{ |f| [f.qry_field_text,f.id]} %>
<% else %>
<%= f.input :field_1_id, collection: AdOriFld.where(ad_ori_tbl_id: params[:id].gsub(/\A\w{1,}_{1}/, "").to_i).order(id: :asc).collect{ |f| [f.otb_colhdg,f.id]} %>
<% end %>
<% end %>
<% end %>
The controller then contains all the actions triggered in the javascripts:
# audit_function_conroller.rb
def new
authorize! :new, :audit_functions
#audit_function = AuditFunction.new
#functions = [[I18n.t("text sum"),"sum"],[I18n.t("text quantity"),"quantity"],[I18n.t("text largest_value"),"largest_value"],[I18n.t("text smallest_value"),"smallest_value"]]
#databases = [[I18n.t("text original_database"),"imp"],[I18n.t("text archive_database"),"ori"],[I18n.t("text query_database"),"mod"]]
end
def update_database_1_id_select
if params[:id] == "mod"
type = "mod"
elsif params[:id] == "ori"
type = "ori"
elsif params[:id] == "imp"
type = "imp"
end
render partial: "databases_1", id: type
end
def update_field_1_id_select
type = params[:id]
render partial: "fields_1", id: type
end
Now, as messy as all of this looks, the good thing is that it gets the job done. And to clarify my MVC, these are the relations:
AdOriTbl has_many AdOriFlds
AdOriFld belongs_to AdOriTbl
AdQryMod has_many AdQryFlds
AdQryFld belongs_to AdQryMod
I hope the names don't bother you too much when reading this.
Now lets get back to the problem:
As I said this code works for creating a new object and everything is selected fine. But when I try to edit an object only the field with the database type (database_1 and database_2) are filled. The select boxes for the ID's of the databases are not rendered, while the boxes for the fields are. But all four ID fields are empty.
Now I already tried to fill the boxes by hand with a jQuery that basically looks similar to the ones I already have, but instead of getting triggered onChange, I trigger it when my audit_function has a database_id and render the select box and fill it with the value according value of database_id. This works as well.
The problem is that I can't do this with the field_id, because in the partial of database_1_id where the jQuery for the fields get triggered, I don't have the #audit_function object at hand and also it seems to interfere with the other javascripts.
Besides that I'd also like to think that there is a better way to do this, then my way. But I already tried other tutorials and ways and they either don't work when you don't have your straight-forward Country-State-City relationships or they don't work when editing.
So, any help would be really appreciated. Thanks!
I took the following tutorial as template to rewrite my cascading dropdown:
http://homeonrails.blogspot.de/2012/01/rails-31-linked-dropdown-cascading.html
So, now I throw all the different models into one array and filter it by appending names to the class, to differentiate not only by ID, but also by name. Also the jQuery Plugin chainedTo makes the code much more readable.
So, the controller looks now like this:
#types_for_dropdown = [[I18n.t("text archive_database"),"ori"],[I18n.t("text query_database"),"mod"]]
#tables_for_dropdown = []
#ad_qry_mods = AdQryMod.all
#ad_qry_mods.each do |i|
#tables_for_dropdown = #tables_for_dropdown << [i.qry_mod_text,"mod#{i.id}",{:class => "mod"}]
end
#ad_ori_tbls = AdOriTbl.all
#ad_ori_tbls.each do |i|
#tables_for_dropdown = #tables_for_dropdown << [i.otb_filename,"ori#{i.id}",{:class => "ori"}]
end
#fields_for_dropdown = []
#ad_qry_flds = AdQryFld.all
#ad_qry_flds.each do |i|
#fields_for_dropdown = #fields_for_dropdown << [i.qry_fieldname,i.id,{:class => "mod#{i.ad_qry_mod_id}"}]
end
#ad_ori_flds = AdOriFld.all
#ad_ori_flds.each do |i|
#fields_for_dropdown = #fields_for_dropdown << [i.otb_fieldname,i.id,{:class => "ori#{i.ad_ori_tbl_id}"}]
end
And the form looks like this:
<%= content_for :head do %>
<script>
$(document).ready(function(){
$('select#audit_function_database_1_id').chainedTo('select#audit_function_database_1');
$('select#audit_function_field_1_id').chainedTo('select#audit_function_database_1_id');
$('select#audit_function_database_2_id').chainedTo('select#audit_function_database_2');
$('select#audit_function_field_2_id').chainedTo('select#audit_function_database_2_id');
});
</script>
<% end %>
<div class="grid-6-12">
<%= f.input :database_1, label: I18n.t("field_label audit_function database_1"), hint: I18n.t("field_hint audit_function database_1"), as: :select, collection: #types_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :database_2, label: I18n.t("field_label audit_function database_2"), hint: I18n.t("field_hint audit_function database_2"), as: :select, collection: #types_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :database_1_id, label: I18n.t("field_label audit_function database_1_id"), hint: I18n.t("field_hint audit_function database_1_id"), as: :select, collection: #tables_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :database_2_id, label: I18n.t("field_label audit_function database_2_id"), hint: I18n.t("field_hint audit_function database_2_id"), as: :select, collection: #tables_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :field_1_id, label: I18n.t("field_label audit_function field_1_id"), hint: I18n.t("field_hint audit_function field_1_id"), as: :select, collection: #fields_for_dropdown, include_blank: true %>
</div>
<div class="grid-6-12">
<%= f.input :field_2_id, label: I18n.t("field_label audit_function field_2_id"), hint: I18n.t("field_hint audit_function field_2_id"), as: :select, collection: #fields_for_dropdown, include_blank: true %>
</div>
This is really a nice solution and I can recommend it to everyone!
I am in the early stages of creating an app, and am just putting some basic code in place. Here is the current code...
app/views/cards/front.html.erb
<%= form_for(front_of_card_path) do |f| %>
<%= f.fields_for :competency_templates do |builder| %>
<%= render 'add_fields', f: builder %>
<% end %>
<%= link_to_add_fields "Add New Tag", f, :skill %>
<% end %>
routes
controller :cards do
get '/front', action: 'front', as: 'front_of_card'
post '/save', action: 'create', as: 'save_card'
get '/my_contact_info', action: 'back', as: 'back_of_card'
put '/save', action: 'update', as: 'save_card'
get '/my_card', action: 'show', as: 'card'
end
controller
def create
#skill= Skill.new(params[:skill])
#tag = Tag.new(params[:tag])
#tag.save
#skill.tag_id = #tag.id
#skill.save
redirect_to front_of_card_path, notice: 'Skill was successfully created.'
#get user/session
#save skills & tags
end
cards.js.coffee
jQuery ->
$('form').on 'click', '.remove_fields', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_fields', (event) ->
time = new Date().getTime()
regexp = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regexp, time))
event.preventDefault()
app_helper
module ApplicationHelper
def link_to_add_fields(name, f, association)
new_object = f.object.send(association).klass.new
id = new_object.object_id
fields = f.fields_for(association, new_object, child_index: id) do |builder|
render(association.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_fields", data: {id: id, fields: fields.gsub("\n", "")})
end
end
So right now this code gives me two text fields. One for the a tag name and another for a tag weight, and the controller inserts everything in the DB. I would like use some javascript to dynamically add as many of these tag/weight fields as I like. Everything I've found seems to focus on nested attributes. Any ideas appreciated.
Update
Added more code to flesh this out. The issue I am having is the 3rd variable I am passing in on this line...
<%= link_to_add_fields "Add New Tag", f, :skill %>
It does not like ':skill', but I am not sure what I should be passing here.
So here is what I came up with...here are my two models...
class Skill < ActiveRecord::Base
belongs_to :tag
attr_accessible :tag_id, :weight
end
class Tag < ActiveRecord::Base
has_many :skills
attr_accessible :name
end
I'm calling a partial from app/views/skills/_form.html.erb and using a js tag to add new fields. Also note that I am re-rendering the partial, then hiding it in the last div tag.
<div id="skillSet">
<%= render partial: "skills_form" %>
</div>
Add New Tag
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<div class="hide" id="new_skills_form">
<%= render partial: "skills_form", locals: {skill: false} %>
</div>
The partial is pretty simple. All I am doing here is storing the values in an array...
<div class="skillsForm">
<%= label_tag 'tag' %>
<%= text_field_tag 'tags[]' %>
<%= label_tag 'weight' %>
<%= text_field_tag 'weights[]' %>
</div>
...here is the javascript...real straight forward, just say when #addNewTag is clicked, appeand #new_skills_form to #skillSet
$(document).ready(function(){
$("#addNewTag").click(function(){
$("#skillSet").append($("#new_skills_form").html());
});
});
...and finally the controller action decontructs the arrays, and saves them...
def create
#skill = Skill.new(params[:skill])
tags = params[:tags]
weights = params[:weights]
tags.each_with_index do |tag, index|
tag = Tag.create :name => tag
Skill.create :tag_id => tag.id, :weight => weights[index]
end
end