Escape_javascript problem - javascript

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 %>

Related

select user and then dropdown of his challenges

A user has_many challenges.
When a user is selected...
<%= f.select :user_id, options_for_select(#challengers.collect { |challenger| [challenger.full_name] }) %>
... how can we show another dropdown with a list of his challenges?
<%= f.select :challenge_id, options_for_select(#challenger_challenges.collect { |challenged| [challenged.full_challenge]}) %>
In other words, how can we make "#challenger_challenges = the selected user's challenges"?
As it stand I get an error undefined method 'collect' for nil:NilClass since #challenger_challenges is nil.
OPTION 1
In challenges_controller I could do this:
#challengers = User.all
#challenger = User.find(params[:challenger_selected]) if (params[:challenger_selected]).present?
#challenger_challenges = #challenger.challenges
And then I would just need a way to refresh the page once a user is selected so that the user ID is passed in the params as :challenger_selected
OPTION 2
Achieve the aim of this question without the need of a page refresh. *Preferable
UPDATE
Based upon the comments below I realize I need to elaborate.
A user has_many challenges.
A user can create a duel.
In a duel there are two duelers.
The creator of the duel selects his own :challenge_id and then he selects the other dueler as well as one of his :challenge_id and then sets the #duel.consequence the dueler will have to do if he fails his challenge. The other dueler will get a duel request notification and then has the choice to accept or decline the conditions of the duel.
challenges.show.html.erb
<%= render 'duels/form' %>
duels/_form.html.erb
<%= simple_form_for(#duel) do |f| %>
<%= f.fields_for :duelers do |dueler| %>
<%= f.hidden_field :challenge_id, :value => #challenge.id %>
<%= #challenge.full_challenge %>
<% end %>
<%= f.fields_for :duelers do |dueler| %>
<%= render 'duels/dueler_fields', :f => dueler %>
<% end %>
<%= button_tag(type: 'submit', class: "btn", id: "challenge-create-save") do %>
Request Duel
<% end %>
<% end %>
duels/_dueler_fields.html.erb
<%= f.select :user_id, options_for_select(#challengers.collect { |challenger| [challenger.id] }) %>
# Trying to make this responsive to the user that is selected above
<%= render 'challenges/select', :f => f %>
<script>
$('#duel_duelers_attributes_1_user_id').change(function () {
var challenger_id = $(this).find(":selected").val();
var address = "<%= select_path %>".concat(challenger_id);
$.get(address, function(data) {
$("#duel_duelers_attributes_1_challenge_id").html(data);
});
});
</script>
routes
get 'challenges/select/:id' => 'challenges#select', as: 'select'
challenges/_select.html.erb
<%= f.select :challenge_id, options_for_select(#challenger_challenges.collect { |challenged| [challenged.full_challenge]}) %>
challenges_controller
def select
if (params[:challenger_id]).present?
#challenger = User.find(params[:challenger_id])
else
#challenger = User.find(1)
end
#challenger_challenges = #challenger.challenges
end
Credit for this should go to #Fallenhero - I am just explaining it in more detail.
You need to be able to identify the select tag.
<%= f.select ..., :html => {:id => :first} %>
You also need somewhere to put the second one.
<div id="second"></div>
Using jQuery:
$('#first').change(function () {
var challenger_id = $(this).find(":selected").val();
var address = "<%= [prints address to new select tag] %>".concat(challenger_id);
$.get(address, function(data) {
$("#second").html(data);
});
});
The address Ruby prints out should look something like challenges/select/ depending on how you want to design it. The / at the end is important.

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.

Change form multiple partials

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()

Rails jQuery autocomplete and dynamic nested fields

I have an application in which I'm using the gems rails3-jquery-autocomplete and nested_form to suggest existing users to be tagged as contributors to a project entry.
The form view:
<%= f.fields_for :contributors do |contributor|%>
<%= contributor.autocomplete_field :name, autocomplete_user_profile_last_name_projects_path, :update_elements => { :user_id => "#user_id" } %><br>
<%= contributor.hidden_field :user_id %>
<%= contributor.radio_button :contributor_type, 'Colleague' %>
<%= contributor.radio_button :contributor_type, 'Supervisor' %>
<%= contributor.radio_button :contributor_type, 'Client' %>
<%= contributor.link_to_remove "Remove this contributor" %>
<% end %>
<%= f.link_to_add "Add a contributor", :contributors %><br>
I have also overwritten the get_autocomplete_function(parameters) function in order to allow more than one column in my autocomplete, namely the columns first_name and last_name
def get_autocomplete_items(parameters)
items = UserProfile.select("DISTINCT CONCAT_WS(' ', first_name, last_name ) AS full_name, first_name, last_name, id, user_id").where(["CONCAT_WS(' ', first_name, last_name) LIKE ?", "%#{parameters[:term]}%"])
end
I also have the following line in my Projects controller:
autocomplete :user_profile, :last_name, :full => true, :extra_data => [:first_name, :last_name, :user_id ], :display_value => :fullname
With the `fullname' function simply being:
def fullname
"#{first_name} #{last_name}"
end
What I want to do is to get the value of user_id from the autocomplete items and put its value inside the hidden field, but when I check the server the it outputs something like:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"DYQ7iJeqdwMQrprSGn0WNHXY5iXDZLA5pUd3OlYJ2so=", "project"=>{"creator"=>"1", "title"=>"lesdodis", "contributors_attributes"=>{"0"=>{"name"=>"Test User the 1st", "user_id"=>"", "contributor_type"=>"Client", "_destroy"=>"false", "id"=>"21"}, "1"=>{"name"=>"Test User the 2nd", "user_id"=>"", "contributor_type"=>"Supervisor", "_destroy"=>"false", "id"=>"22"}, "1393677266696"=>{"name"=>"", "user_id"=>"", "_destroy"=>"1"}, "1393677267518"=>{"name"=>"", "user_id"=>"", "_destroy"=>"1"}}, "description"=>"asdad", "tag_list"=>"sdads", "link"=>"asdasd", "gallery_attributes"=>{"screenshots_attributes"=>{"0"=>{"description"=>"", "_destroy"=>"false", "id"=>"10"}}, "id"=>"16"}}, "commit"=>"Update", "id"=>"16"}
where the user_id field inside contributors is left blank. I have already tried manually assigning ids to the fields by using contributor.object.id to assign a unique number to the field name with limited success, as the new dynamically added fields do not have object ids. I am using this form for both creating and updating a project entry, so I would like to ask for help in how to make this work regardless of editing the field or adding a new one.
Update:
After a while of searching I finally got it to work by adding this code
<% field_name = "#{contributor.object_name}[user_id]".gsub(/(\])?\[/, "_").chop %>
<%= contributor.autocomplete_field :name, autocomplete_user_profile_last_name_projects_path, :update_elements => { :user_id => "##{field_name}" } %><br>
I hope this helps anyone who might encounter the same problem.
I've been wrestling with a similar problem as well. In your case, my feeling is that the nested_form helpers are generating unique identifiers which are defeating the "#user_id" selector passed to :update_elements.
You might be able to workaround it by hooking into the event streams for each gem and then manually updating the hidden :user_id for the specific contributor in the form. For the dynamically added contributors, possibly something like:
$(document).on('nested:fieldAdded', function(nf_event) {
var ac_input = nf_event.field.find('.ui-autocomplete-input');
ac_input.bind('railsAutocomplete.select', function(ac_event, data) {
$(this).next('input[type=hidden]').val(data.item.id);
});
});
For your contributors generated through existing associations (ie. editing), you can bind to their autocomplete inputs on $(document).ready(). This might at least get you pointed in the right direction.
After a while of searching I finally got it to work by adding this code
<% field_name = "#{contributor.object_name}[user_id]".gsub(/(\])?\[/, "_").chop %>
<%= contributor.autocomplete_field :name, autocomplete_user_profile_last_name_projects_path, :update_elements => { :user_id => "##{field_name}" } %>
I hope this helps anyone who might encounter the same problem.

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