Change form multiple partials - javascript

I'm following this railscast on nested forms to create a similar application.
In Ryan's application he has a structure like:
Surveys ---> 2. Questions ---> 3. Answers
So a user can create a survey and add/delete questions, with corresponding answers that they can add/delete.
I'm trying to do it a little differently. I have:
Surveys ---> 2. Questions ---> 3. Answers Or Comment
For my application, after a user has created a question, the user can create an answer (just like Ryan's app). But there is a select box next to each answer, with the default being "Answer" and the other being "Comment". I'm trying to get basically the same functionality of Ryan's app, but with two tables: comments table and answers table.
My code mimics the railscast, except for my answer_fields partial looks like:
<fieldset>
<%= f.label :content, "Answer" %>
<%= f.select :switch_answer_table options_for_select(["Answer", "Comment"]), class: "change-type" %>
<%= f.text_field :content %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "remove" %>
</fieldset>
and I have a comment_fields partial that looks like:
<fieldset>
<%= f.label :content, "Comment" %>
<%= f.select :switch_answer_table options_for_select(["Comment", "Answer"]), class: "change-type" %>
<%= f.text_field :comment_content %>
... (leaving out some stuff) ...
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "remove" %>
</fieldset>
My question.rb model looks like:
class Question < ActiveRecord::Base
belongs_to :survey
has_many :answers
has_many :comments
accepts_nested_attributes_for :answers, allow_destroy: true
accepts_nested_attributes_for :comments, allow_destroy: true
end
I've been working on this, and I'm stuck. My jQuery only looks like this so far:
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()
$('form').on 'change', '.change-type', (event) ->
# Confused on how to change partials
Basically, I need a way to onChange, the partial for the selected answer/comment.

alejandro babio is correct. render both partials. give one of them a style of display:none and then toggle the class in your coffescipt onchange handler. In the case that a user starts to type an answer and then switches to a comment you'll also want to clear the fields associated with the newly hidden fields otherwise on submit you could have both comment and answer submitted.

First at all, you must render both partials (answer_fields and comments_fields) with the form.
At the comments_partial replace <fieldset> with <fieldset style="display:none">
Change visibility of partials with coffeescript:
$('form').on 'change', '.change-type', (event) ->
$('.change-type').each ->
$(#).closest('fieldset').each ->
if $(#).is(':visible')
$(#).find('input').val('') # reset all filels to empty values
$(#).toggle()
I thing that do the trick.
I clear only input fields, but there must be all of them to prevent save de hidden element (like says hraynaud)

I haven't tested this, but perhaps you want to use dependent-fields-rails. Depending on the state of the select box you can then show either the comment box or answer box.
The example given on the github page (uses simple form, but you can apply the same to your form_for):
= simple_form_for(#filing) do
= f.input :registration_office, collection: ['habm', 'dpma']
.js-dependent-fields[data-select-id='filing_registration_office' data-option-value='habm']
= f.input :language, collection: ['english', 'german']
Then I guess you use fields_for to satisfy your has_many relationship for example:
f.select :switch_answer_table, collection: ['answer', 'comment']
.js-dependent-fields[data-select-id='question_switch_answer_table' data-option-value='answer']
f.fields_for :answers do |abuilder|
...
.js-dependent-fields[data-select-id='question_switch_answer_table' data-option-value='comment']
f.fields_for :comments do |cbuilder|
...
Again, not tested and a best guess but it hopefully nudges you into the right direction.

The partial fields are generated as a data attribute by the link_to_add_fields helper method. The js then gets the data object and renders it. Consider the following (this is untested but should be a good guide):
The easiest way would be to have seperate 'Add Answer' and 'Add Comment' links with seperate helper methods, or
Allow the user to select Answer or Comment before clicking the link to add and have each different partial as a different data attribute. e.g.
_form.html.erb
<select id="response_type_select_field">
<option value="comment">Comment</option>
<option value="answer">Answer</option>
</select>
<%= link_to_add_reply_fields "Add Reply", f, :answers, :comments %>
surveys_helper.rb
module SurveysHelper
def link_to_add_reply_fields(name, f, association_1, association_2)
object_1 = f.object.send(association_1).klass.new
object_1_id = object_1.object_id
object_1_fields = f.fields_for(association_1, object_1, child_index: object_1_id) do |builder|
render(association_1.to_s.singularize + "_fields", f: builder)
end
object_2 = f.object.send(association_2).klass.new
object_2_id = object_2.object_id
object_2_fields = f.fields_for(association_2, object_2, child_index: object_2_id) do |builder|
render(association_2.to_s.singularize + "_fields", f: builder)
end
link_to(name, '#', class: "add_reply_fields", data: {id_1: object_1_id, id_2: object_2_id, object_1_fields: object_1_fields.gsub("\n", ""), object_2_fields: object_2_fields.gsub("\n", "")})
end
end
Coffee script:
jQuery ->
$('form').on 'click', '.add_reply_fields', (event) ->
time = new Date().getTime()
reply_type = $("#response_type_select_field option:selected").val()
if reply_type is 'answer'
data_attribute = 'object_1_fields'
regexp = new RegExp($(this).data('id_1'), 'g')
else if reply_type is 'comment'
data_attribute = 'object_2_fields'
regexp = new RegExp($(this).data('id_2'), 'g')
$(this).before($(this).data(data_attribute).replace(regexp, time))
event.preventDefault()

Related

FIlter collection_select by value in coffeescript

Well I have this coffeeScript:
jQuery ->
$('#cmbPab').change ->
ubis = $('#cmbUbi').html()
pabellon = $(this).val()
options = $('#cmbUbi').filter("FilterByValue")
I want it to filter options by value, I already saw railcats stuff but he filters by a different way and I need to do it by value cause value is my id
Here is the cod of both collection_select where cmbPab is my main combo:
<div class="form-group">
<%= f.label :Pabellon %>
<%= f.collection_select :idubicacion, Mtopabellon.all,
:codpabellon, :nombre, {prompt: 'Seleccione un pabellón'},
:class=>'form-control', :id => 'cmbPab'%>
</div>
<div class="form-group">
<%= f.label :Ubicacion %>
<%= f.collection_select :idubicacion, CrUbicacion.all, :id,
:nombre, {prompt: 'Seleccione una ubicación'}, :class=>'form-
control', :id => 'cmbUbi' %>
</div>
I believe you're after something along these lines:
$('#cmbPab').change ->
displayFilterFn = (i, el) -> $(el).val() == 'something?' // do your filtering
$ubis = $('#cmbUbi')
$ubis.children().hide()
$ubis.children().filter(displayFilterFn).show()
Without knowing the specifics of how you want to filter your options, other than that it is based on the value, I can't provide you a filtering function implementation (displayFilterFn above). The basic idea is to first hide all options, and then show the ones that match your filter.
For more info on the .filter(), see the jQuery API Documentation.
Here is the answer guys! I think this is the best way to do it, maybe not the easier but it works!
https://kernelgarden.wordpress.com/2014/02/26/dynamic-select-boxes-in-rails-4/

Custom country/state dropdowns with rails

I been diggins some days on gems for country and state/province selection. Some are great (like country-state-select) but not for my needs.
The almost mandatory country_select gem is good but lacks states. Nevertheless is based on this very cool gem called countries. Gem countries really puts together a lot of good info, lets say Mozambique and its subdivisions, and so on, for over 300 countries, and includes i18n.
Having countries gem in the app, all is needed is some javascript to interact with it. Calls are made with this:
country = ISO3166::Country.new('US')
Here is the form:
<%= simple_form_for(#order) do |f| %>
<div><%= f.input :country %></div>
<div><%= f.input :state %></div>
<%= f.button :submit, t('.submit') %>
<% end %>
<script type="text/javascript">
states_drop_down = $("#order_state");
$("#order_country").change(function() {
// How to make this work?
<% unless params[:country].nil? %>
states_drop_down.clearAttributes();
<% ISO3166::Country.new(params[:country]).states.each do |state| %>
states_drop_down.setAttribute(<%= state[:name] %>, <%= state[:alpha2] %>); // How to log to check if new attributes are present?
<% end %>
states_drop_down.reload(); // How to reload the state simple_form input?
<% end %>
});
The goal is the known one, to populate state selector with correct country every time the country dropdown changes. Any help? Thanks.
I found a solution, even though is not using gem countries anymore. Data is seeded to database and pulled from there. Found the data here.
Then all is needed is few steps:
// 1. new action at controller, in my case was orders. This receives the calls and renders the views.
def update_states
country = Country.find(params[:nation_id])
#states = country.states
respond_to do |format|
format.js
end
end
// 2. new get at routes to find the new action
get "orders/update_states", as: "update_states"
// 3. simple_form with collections. Note uses nation to avoid the simple_form country selector error.
<%= simple_form_for(#order) do |f| %>
<div><%= f.input :nation, collection: #countries %></div>
<div><%= f.input :state, collection: #states %></div>
<%= f.button :submit, t('.submit') %>
<% end %>
// 4. new doc at app/views/states/_state.html.erb to render inside the dropdown selector.
<option value="<%= state.id %>"><%= state.name %></option>
// 5. new lines at app/assets/javascripts/orders.js.coffee to listen to nation or country selector.
$ ->
$(document).on 'change', '#order_nation', (evt) ->
$.ajax 'update_states',
type: 'GET'
dataType: 'script'
data: {
nation_id: $("#order_nation option:selected").val()
}
error: (jqXHR, textStatus, errorThrown) ->
console.log("AJAX Error: #{textStatus}")
success: (data, textStatus, jqXHR) ->
console.log("State selection is working!")
// 6. and app/views/orders/update_cities.js.cofee, the piece that close the circle. This actually renders the info and views.
$("#order_state").empty()
.append("<%= escape_javascript(render(:partial => #states)) %>")
Must thanks Kernel Garden, I found the javascript I was looking here.

Activate Javascript function after clicking on Rails form_for select field

I have a form with 3 input fields, strokes, putts, and GIR:
<%= form_for(#played_hole) do |f| %>
<%= f.hidden_field :hole_par, value: #hole.par %>
<%= f.label :strokes, "How many strokes?" %>
<%= f.select(:strokes, options_for_select(generate_strokes_array(#hole.par), selected: #hole.par)) %>
<%= f.label :putts, "How many putts?" %>
<%= f.select :putts, options_for_select(generate_putts_array(5), selected: 1), onclick: calculateGIR() %>
<%= f.label :GIR, "Green in Regulation?" %>
<%= f.select(:GIR, [['Yes', 1], ['No', 0]]) %>
<%= f.submit "Next Hole", class: "btn btn-large btn-success" %>
<% end %>
My goal is to automatically select the 3rd field (:GIR) once the user clicks on the second input, putts. For example, if the hole is a par 4 and they selected 5 in the :strokes field and 2 in the :putts field, then I would know that they didn't get a Green In Regulation. So, I'd like to automatically switch the :GIR select to 'No'.
I'm currently having trouble just getting an onclick() event registered. I've added onclick: calculateGIR() to the :putts select field but I'm getting this error in my PlayedHole#new view: undefined method calculateGIR' for #<#<Class:0xb5a684c4>:0xb591eac8>
I've added this to the javascript/played_holes.js file: function calculateGIR() { alert("calculating GIR"); } but obviously it's not being picked up.
Basically, I could use some help calling javascript (or jQuery) functions within a view. And then once I get that setup I'd like to figure out how to pull the selected value from :strokes and :putts to calculate the GIR.
For those curious, a GIR means you were on the green in 2 (or less) strokes than the par for the hole. i.e. Par 3 - 1 stroke, Par 4 - 2 strokes, Par 5 - 3 (or less) strokes.
And I'm running Rails 3.2, in case you're curious.
Thanks!
You need to put calculateGIR() in quotes:
<%= f.select :putts, options_for_select(generate_putts_array(5), selected: 1), onclick: "calculateGIR()" %>
The onclick HTML attribute should be a a string of JavaScript code that will be executed on click. Instead, you are trying to call a Ruby method, calculateGIR.
Alternative to my comment in your question:
Try: <%= f.select :putts, options_for_select(generate_putts_array(5),
selected: 1), onclick: 'calculateGIR()' %>, put calculateGIR() in
quotes.
It's better not to place javascript into your HTML as you've shown. Unobstrusive way to do this would be to assign an id to the select element and apply calculateGIR logic in the change event.
Something like following is better:
In your view:
<%= f.select :putts, options_for_select(generate_putts_array(5), selected: 1), {}, id: 'putts' %>
In your javascript/played_holes.js
$(document).ready(function() {
$('#putts').on('change', function() {
// Place definition of calculateGIR() here.
});
});

Escape_javascript problem

I have a set of dependent dropdown menus on my user/edit page. The user selects the parent dropdown, which then limits the selections available in the child dropdown. The user can add multiple additional sets of parent/child dropdowns. Product is the parent. Variety is the child.
This all works great unless a variety is entered into the database that includes double quotes. When that happens, it breaks the javascript and the trigger to render the partial doesn't work at all.
Single quotes do not cause this problem, nor do any other characters that I've tried. So this problem is specific to double quotes. I thought that escape_javascript was the answer here, but I've tried placing it in many different places in the code and nothing has worked. It's very possible that I just don't know exactly where the helper and it's parenthesis are supposed to go.
The api documentation is terrible. It says escape_javascript(). That's not helpful for me. Similarly, there isn't much clear guidance online. I've searched for hours.
Here are the relevant parts of my code:
Users#edit.html.erb
<%= render :partial => 'season', :collection => #user.seasons %>
Users#_season.html.erb
<% fields_for prefix, season do |season_form| -%>
<%= error_messages_for :season, :object => season %>
Product: <%= season_form.collection_select :product_id, Product.find(:all, :order =>'name'), :id, :name, {:prompt => "Select Product"}, {:onchange => "collectionSelected(this);"} %>
<% varieties = season.product ? season.product.varieties : Variety.all %>
<%= season_form.select :variety_id, options_from_collection_for_select(varieties, :id, :name, season.variety_id), :prompt => "This is optional" %>
Javascripts#dynamic_varieties.js.erb
var varieties = new Array();
<% for variety in #varieties -%>
varieties.push (new Array (<%=h variety.product_id %>, "<%=h variety.name %>", <%=h variety.id %>));
<% end -%>
function collectionSelected(e) {
product_id = e.getValue();
options = e.next(1).options;
options.length = 1;
varieties.each(function(variety) {
if (variety[0] == product_id) {
options[options.length] = new Option(variety[1], variety[2]);
}
});
}
Assuming you're just doing some escaping wrong (and that rails does it correctly), you could try something like:
var varieties = <%= #varieties.map { |v| [v.product_id, h(v.name), v.id] }.to_json %>

Dynamically Generated Select Menus Not Holding State in Edit View

I'm following Railscast 88 to create a dynamic dependent dropdown menu. http://railscasts.com/episodes/88-dynamic-select-menus
I'm rendering these dropdowns inside a partial that I'm using in a multi-model form. The form I'm using follows the Advanced Rails Recipes process by Ryan Bates. Because I'm rendering the dropdown inside a partial, I had to depart from strictly following the Railscast code. On the Railscast link provided above, comments 30-31 and 60-62 address these issues and provide an approach that I used.
For new records, everything is working great. I select a parent object from the dropdown, and the Javascript dynamically limits the child options to only those items that are associated with the parent I selected. I'm able to save my selections and everything works great.
The problem is that when I go back to the edit page, and I click on the child selection dropdown, the constraints tying it to the parent object are no longer in place. I'm now able to select any child, whether or not it's connected to the parent. This is a major user experience issue because the list of child objects is just too long and complicated. I need the child options to always depend on the parent that is selected.
Here's my code:
Controller#javascripts
def dynamic_varieties
#varieties = Variety.find(:all)
respond_to do |format|
format.js
end
end
Views#javascripts #dynamic_varieties.js.erb
var varieties = new Array();
<% for variety in #varieties -%>
varieties.push(new Array(<%= variety.product_id %>, '<%=h variety.name %>', <%= variety.id %>));
<% end -%>
function collectionSelected(e) {
product_id = e.getValue();
options = e.next(1).options;
options.length = 1;
varieties.each(function(variety) {
if (variety[0] == product_id) {
options[options.length] = new Option(variety[1], variety[2]);
}
});
}
Views#users #edit.html.erb
<% javascript 'dynamic_varieties' %>
<%= render :partial => 'form' %>
View#users #_form.html.erb
<%= add_season_link "+ Add another product" %>
<%= render :partial => 'season', :collection => #user.seasons %>
view#users #_season.html.erb
<div class="season">
<% new_or_existing = season.new_record? ? 'new' : 'existing' %>
<% prefix = "user[#{new_or_existing}_season_attributes][]" %>
<% fields_for prefix, season do |season_form| -%>
<%= error_messages_for :season, :object => season %>
<div class="each">
<p class="drop">
<label for = "user_product_id">Product:</label> <%= season_form.collection_select :product_id, Product.find(:all), :id, :name, {:prompt => "Select Product"}, {:onchange => "collectionSelected(this);"} %>
<label for="user_variety_id">Variety:</label>
<%= season_form.collection_select :variety_id, Variety.find(:all), :id, :name, :prompt => "Select Variety" %>
</p>
<p class="removeMarket">
<%= link_to_function "- Remove Product", "if(confirm('Are you sure you want to delete this product?')) $(this).up('.season').remove()" %>
</p>
</div>
<% end -%>
Here's your culprit:
<%= season_form.collection_select :variety_id, Variety.find(:all),
:id, :name, :prompt => "Select Variety" %>
Works perfectly on a new record because it's showing everything, and gets overwritten when the select changes on the other select box.
You need to do something like this:
<% varieties = season.product ? season.product.varieties : Variety.all %>
<%= season_form.select :variety_id,
options_from_collection_for_select(varieties, :id,
:name, season.variety_id), :prompt => "Select Variety" %>
Which will use only the Varieties linked to season.product. If season.product doesn't exist it lists all of them. It will also automatically select the right one if the existing record had a variety_id.
It also wouldn't hurt to change.
<%= season_form.collection_select :product_id, Product.find(:all),
:id, :name, {:prompt => "Select Product"},
{:onchange => "collectionSelected(this);"} %>
to
<%= season_form.select :product_id,
options_from_collection_for_select(Product.find(:all),
:id, :name, season.product), {:prompt => "Select Product"},
{:onchange => "collectionSelected(this);"} %>
Which will select the proper product on page load. This second part is essentially the Rails way of doing what BYK's first suggestion was. However, given the nature of the onchange method given to the select box, this line on its own would not solve the problem. It would just enhance the user experience by highlighting the product associated with the season.
I think you have two options:
Give one of the products(or simply the first element of the product list) a "selected" attribute which will force the browser to select that one always.
Trigger the "collectionSelected" function on "dom ready" or "window.onload" with giving the product list selectbox as its parameter.
And a note: never, ever trust JavaScript to force the user to send proper data to the server.

Categories