Dynamically insert ruby generated HTML - ruby-on-rails

I have a form where a user can elect to create more input areas (to provide more information). I have a link the user will click on and then the extra form inputs will be created. I'd like to use the rails form helpers so that I don't have to write the html myself. I've tried inserting the form helpers directly into coffeescript and saving the outputted html to a data tag on the link, but I can't get the coffeescript to execute the ruby code and I'm having escaping issues with the data attribute.
Here's the form:
= simple_form_for([#site, #zone]) do |f|
= f.error_notification
.form-inputs
= f.input :site_id
= label_tag "X"
= text_field_tag 'x_coords[]'
= label_tag "Y"
= text_field_tag 'y_coords[]'
= label_tag "X"
= text_field_tag 'x_coords[]'
= label_tag "Y"
= text_field_tag 'y_coords[]'
= label_tag "X"
= text_field_tag 'x_coords[]'
= label_tag "Y"
= text_field_tag 'y_coords[]'
= link_to "Add Point", "#", id: "add_point", data: { fields: label_tags }
.form-actions
= f.button :submit
When a user clicks the "Add Point" link, I'd like to add another block of:
= label_tag "X"
= text_field_tag 'x_coords[]'
= label_tag "Y"
= text_field_tag 'y_coords[]'
label_tags is in application_helper.rb:
def label_tags
label_tag "Z"
end
The problem is the output for the "Add Point" link is:
Z" id="add_point">Add Point
and the quotation marks are causing the link to come out with the text: "Z" id="add_point">Add Point"
I got the data attribute idea from this screencast

You cannot execute Ruby code from Javascript. When the page is requested all embedded ruby is evaluated and the results are what you get. The issue that I can see from you paste is that your label block is in the right data attribute but it's not escaped.
What you'll need to do is escape the quotes on the generated HTML going into that field and then unescape them via Javascript. You could use html_escape here like: data: { fields: h(label_tags) } (h is an alias for html_escape or you could do this yourself, manually.
def escape(str)
str.gsub(/</, "<").gsub(/>/, ">").gsub(/"/, """)
end
# later in the view for the form
data: { fields: escape(label_tags) }
And then your CoffeeScript would click handler would like:
function unescape(str) {
return str.replace(/((?:<)|(?:>)|(?:"))/g, function($1) {
switch($1) {
case ">":
return ">";
case "<":
return "<";
case """:
return '"';
}
});
}
$("a").on("click", function() {
var html = unescape(this.data("fields"));
$(".the-place-to-put-it").html(html);
});
I do not doubt a better solution exists and as of the posting of this answer I have not tested this to work (in theory it should). Ideally, you should just generate the elements with jQuery in Javascript and not depend on this method for doing this - yes, it's duplicate code duplicated between ruby and Coffee.

Simple solution for me was to replace the double quotes in my generated HTML with single quotes. In code:
= link_to "Add Point", "#", id: "add_point", data: { fields: label_tags.gsub("\"", "'") }
Also, had to use capture and concat in the helper method:
module ApplicationHelper
def label_tags
capture do
concat label_tag "X"
concat text_field_tag 'x_coords[]'
concat label_tag "Y"
concat text_field_tag 'y_coords[]'
end
end
end

Related

Using multi-part form with Haml and partials

I have a form divided into two parts (the second part is displayed depending on user's first choice). Note that the second part is in a partial. The two parts must be sent to the same controller.
Using Haml, how can I get all the parameters sent to the controller, using only one submit button ?
ps : I'm using form_tag here
Main file
= form_tag({url: users_search_path}, id:'form1') do
label
= radio_button_tag 'user_type', 'Requestor', false, id: "radio-requestor"
= label_tag 'radio-requestor', "Demandeur"
label
= radio_button_tag 'user_type', 'Contributor', false, id: "radio-contributor"
= label_tag 'radio-contributor', 'Contributeur
.contributor_form
= render partial: 'contributor_search_form'
.requestor_form.hidden
= render partial: 'requestor_search_form'
----------------------------------------------
2nd part (partial contributor_search_form)
= form_tag({url: users_search_path}, id:"form2") do
label
= check_box_tag 'prof', 'prof', false, id: 'prof'
= label_tag 'prof', 'Prof'
label
= check_box_tag 'ticket', 'ticket', false, id: 'ticket'
= label_tag 'ticket', "Ticket"
= submit_tag "Afficher les résultats"
Don't use 2 form_tag if you want to submit them all in 1 click.
If you want the view change base on user interaction without server, you have to do via Javascript
As I guess, you want the param receiving in the controller looks like
{
user_type: "Requestor",
prof: true,
ticket: false
}
so you can write your partial without form_tag as
# contributor_search_form
label
= check_box_tag 'prof', 'prof', false, id: 'prof'
= label_tag 'prof', 'Prof'
label
= check_box_tag 'ticket', 'ticket', false, id: 'ticket'
= label_tag 'ticket', "Ticket"
= submit_tag "Afficher les résultats"
And your main file with partial view being rendered
# Main file
= form_tag({url: users_search_path}, id:'form1') do
label
= radio_button_tag 'user_type', 'Requestor', false, id: "radio-requestor"
= label_tag 'radio-requestor', "Demandeur"
label
= radio_button_tag 'user_type', 'Contributor', true, id: "radio-contributor"
= label_tag 'radio-contributor', 'Contributeur
.contributor_form
= render partial: 'contributor_search_form'
.requestor_form.hidden
= render partial: 'requestor_search_form'
Notice that the partial form has been rendered inside the main form
Now write some Javascript with jQuery
When user select Requestor, show .requestor_form and vice versa for Contributor
Only submit fields within the displayed zone, then you have to disable fields when hidden.
Here is an example
function toggleFormPartial (target, isShow) {
if (isShow) {
target.removeClass("hidden")
// Remove `disabled` attribute for fields within `target`
} else {
target.addClass("hidden")
// Add `disabled` attribute for fields within `target`
}
}
$("#radio-requestor").change(function (e) {
toggleFormPartial($(".requestor_form"), e.val())
})
$("#radio-contributor").change(function (e) {
toggleFormPartial($(".contributor_form"), e.val())
})

Select2 rails form repopulation

I created a rails (v5) form with multiple select and collection_select elements.
Then I use Select2-rails (v4.0.3) to allow a nice selection looking like tags.
The search-options are pulled by ajax.
It works fine until one presses the submit-button with missing required fields.
Valid field-content has now been deleted from the field.
Let me give some example-code:
controller:
...
def form
if params[:form_request].nil?
#form_request = FormRequest.new
else
#form_request = FormRequest.new(params[:form_request])
end
end
def request_form
#form_request = FormRequest.new(params[:form_request])
if #form_request.valid?
render :summary
else
render :form
end
end
...
form:
...
<%= bootstrap_form_for(#form_request, url: '/form/request_form') do |f| %>
<%= f.select :field, [], {label: 'Field label'} %>
<%= f.submit "Submit form" %>
<% end %>
:field is for sure a writable field in the model (and data is set fine)
coffee-script:
Query ->
$("#form_request_from").select2({
ajax: {
url: func =(params) ->
filter = params.term
return "/data.json?filter=" + filter;
,
dataType: 'json',
processResults: processData
},
theme: 'bootstrap',
placeholder: 'Enter data here'
});
processData = (data) ->
mapdata = $.map(data, func =(obj) ->
obj.id = obj.id;
obj.text = obj.name;
return obj;
);
return { results: mapdata };
I am thinking of a lot of possibilities, but at the end I am not sure where the field-data comes from. It is inside the object, but it isn't written to the resulting HTML in any way.
And even if the id would be written as a selected option,
the select2 script would need to know how to transform that into the string to show the real data.
Any idea how to achieve that the data is still written into a field after a failing validation?
After trying out some things I found out how to do it.
At first I just changed the empty array to be the :field variable,too.
This doesn't work too well because it only remembers the ID of the value that has been entered before and like this the SELECT2-script could not find the value to that key and nothing is shown.
Then I created a new variable inside the controller in which I place the array with name and id:
field_object = ObjectsModel.find(#form_request.field.to_i)
#form_field = []
if !field_object.nil?
#form_field = [[field_object.name, field_object.id]]
end
And in the view I now use this field to show the available options:
<%= bootstrap_form_for(#form_request, url: '/form/request_form') do |f| %>
<%= f.select :field, #form_field, {label: 'Field label'} %>
<%= f.submit "Submit form" %>
<% end %>
This works perfectly fine for me without the need to touch the SELECT2-script.
The possible values are still fetched by AJAX but already filled out fields will persist upon redirect to another action.

add checkbox content to text field

New to rails, also not great in english... I need some advice please.
I have a form with a non-exhaustive list of drugs. The user can select drugs with check-boxes.
There is a check-box called "Other" which makes a text field appear (with a JS script).
The content of this text field is saved in my database under the column :taken_drugs
Here is the code I got until now :
= form_for #quizz, :url => { :action => "create" } do |f|
h4 Which drugs did you take ?
h5 (You can answer more than one)
.field class="list-group"
div class="list-group-item"
= check_box("LSD", "yes")
= label_tag 'LSD'
div class="list-group-item"
= check_box("Psilocybine", "yes")
= label_tag 'Psilocybine (mushrooms)'
div class="list-group-item"
= check_box("DMT", "yes")
= label_tag 'DMT (ayahuasca)'
div class="list-group-item"
= check_box("other", "other", {}, "yes", "no")
= label_tag 'other'
div id="disappear_consomme" style="display:none"
p Which other drugs did you take ?
= f.text_field :taken_drugs
=f.submit
How can I have a result where I save to :taken_drugs the drugs that are checked AND the drugs that are added to the text field ?
Examples of what I need :
if the user check "LSD" and "DMT" I have "LSD DMT"
if the user check "LSD" and write "CANNABIS VALIUM" I have "LSD CANNABIS VALIUM"
You have two options, you can either catch this data in the front end with javascript or you can catch it in the back end with Ruby before it is posted to your database.
The js way:
var text = "";
for (var i = 0; i < form.elements.length; i++ ) {
if (form.elements[i].type == 'checkbox') {
if (form.elements[i].checked == true) {
text += form.elements[i].value + ' ';
}
}
}
Stick that in a function. Call it from your submit button with an onClick or onSubmit event handler.
The ruby way would be handled in your rails controller create method and parsing through whatever you are passing up in your params hash.

How to pass parameters to autocomplete_source jquery autocomplete?

I'm using Rails 4.0.2 with jquery-rails (3.1.0) and jquery-ui-rails (4.1.1) gems. I'm added autocomplete in order to do a specific search based on what user typed and other fields at form.
The form:
<%= text_field_tag :field , some_value, data: { autocomplete_source: select_path( { :id => #order.Id , :type => #order.type } ) } %>
Form.js:
$('#field').autocomplete
minLength: 0
source: $('#field').attr('data-autocomplete-source')
select: ( event, ui ) ->
$('#pedido_venda_CodTransp').val(ui.item.value)
$('#transportadora_escolhido').val(ui.item.label)
this.form.submit()
false
...
The controller:
def select
# retrieve parameters
id_cliente = params[:id]
retira_entrega = params[:type]
term = params[:term]
# do the query, etc...
end
When I run the code, everything is OK. The controller receives all parameters and run the query flawlessly.
The parameter type, however, is based on a SELECT control and, in order to change it, I put the following code in the SELECT control.
<%= f.select :type, options_for_select( [['RETIRA','R'],['ENTREGA','E']] , #pedido.RetiraEntrega ) ,{}, { :onchange => "change_type();" } %>
JS Code function:
function change_type()
{
var e = document.getElementById("type");
var option = e.options[ e.selectedIndex ].value;
var field = document.getElementById("field");
var origem = "type=";
source = field.attributes["data-autocomplete-source"].value;
// pesquisa a string retira_entrega=
index = source.search(origem);
field.setAttribute("data-autocomplete-source", source.substring(0,index+origem.length) + String(option));
}
The JS function is called, the last line is run, the attribute is set (I put an alert at the end retrieving the attribute).
The problem is that the controller never receives the changed value (it always receives the value when the form is created).
So, the question is: how can I change a parameter passed on to autocomplete in order to use it in rails controller?
Not sure if this is what your looking for but I was struggling with the same issue because I had two input fields that I wanted different lists loaded to in the autocomplete widget. So what I did was pass an extra param to the auto complete source like this:
<!-- /_form.html.erb -->
<%= f.text_field :auto1, :size => "100", class: "form-control", data: { autocomplete_source: root_path(:fieldType => "numerouno")} %>
<%= f.text_field :auto2, :size => "100", class: "form-control", data: { autocomplete_source: root_path(:fieldType => "numerodos")} %>
Then in my controller I used that extra param to determine which list I needed to show:
if param[:fieldType] == "numerouno"
format.json { render :json => #unoList}
elsif param[:fieldType] == "numerodos"
format.json { render :json => #dosList }
else
flash[:danger] = "Error loading list for autocomplete!"
end
param[:term] still goes through too!

Rails: using partials for inputs is correct?

I'm building a Rails app. I've done all the logic (db, controllers, models, etc). Now its time to make it nice.
In order to centralize the view of the app I was thinking in creating partials for the common stuff. For example one partial called common/_text_input.html.erb that will contain
<div class="field">
<%= f.label id %><br />
<%= f.text_field id %>
</div>
This will be called from inside a form using
<%= render partial: "common/text_input", locals: { f: f, id: :name } %>
Is this approach correct? Is there any other option in rails to do this?
If this is the correct way to do this, how can I acchieve this for a form tag, for example (where content is inserted inside it)?
Thanks
1 - There is another option to do this, Helpers and content_tag:
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
Usage:
= form_for #resource do |f|
= text_input(f, :first_name)
= text_input(f, :last_name, input: { style: 'color: red;' }, label: { class: :another_class })
2 - It is correct to do with partials, but it is not as flexible as the Helpers are (see the options hash and the possibility to use another method in specific cases). To handle the form_tag (i.e. no form_builder), you can implement a new method:
# usage
= form_tag root_path, method: :get do
= text_input(nil, :search, input: { value: params[:search] }, label: { content: "Search for something!" })
# helper methods
def text_input(form_builder, attribute, options = {})
options = { div: { class: :field }, label: { class: attribute } }.merge(options) # default options
return text_input_tag(attribute, options) if form_builder.blank?
content_tag :div, options[:div] do
f.label(attribute, options[:label]) + content_tag(:br) + f.text_field(attribute, options[:input])
end
end
def text_input_tag(attribute, options = {})
value = options[:input].try(:delete, :value)
label_content = options[:label].try(:delete, :content)
content_tag :div, options[:div] do
label_tag(attribute, label_content, options[:label]) + content_tag(:br) + text_field_tag(attribute, value, options[:input])
end
end

Resources