How to pre-populate dynamically added form inputs in Rails - javascript

I have an edit.html.erb form for #profile instances. Normally when I load the form, the inputs inside the form are pre-populated with the corresponding attribute of the instance.
e.g. <%= f.text_field :name %> would pre-populate its value from #profile.name.
I would like to add additional fields on runtime using jQuery that allows users to input additional data with additional dynamically added inputs.
i.e. #profile.subjects is a hash containing the subjects and their descriptions (for example it could be {"math" => "I teach calculus", "science" => "I have a physics degree"} )
I retrieve a list of subjects from #profile.subjects, and insert textarea elements into the form for each subject using append() in jQuery.
So if #profile.subjects contains "math" and "science", I would do the following in jQuery:
var subject = *string containing subject*;
parent.append("<textarea id='profile_" + subject + "_desc' name='profile[" + subject + "_desc]'></textarea");
This would imitate creating the fields <%= f.text_area :math_desc %> and <%= f.text_area :science_desc %> to my knowledge.
However, when I pass in the attributes math_desc and science_desc to the instance variable #profile in my controller, the inputs do not pre-populate with their values unlike the static form inputs.
I can access #profile.math_desc and #profile.science_desc in the view, but I would like the inputs to have these values upon the view loading.
I do not know how I would add ruby variables to the append() argument with a variable as the attribute name.
e.g. append("<textarea><%= #profile.send('math_desc') %></textarea>") works, but append("<textarea><%= #profile.send('" + subject + "_desc') %></textarea>") does not.
EDIT 1:
Here is how I assigned additional attributes to my instance:
# NOTE: #profile.subjects contains a string representation of {"Math" => "I teach calculus", "Science" => "I have a physics degree", ...}
def edit
#tutor = current_tutor
#profile = #tutor.profile
# Call method to parse subjects JSON
subject_parsing(#profile)
end
private
# Decode JSON subject field into a hash
def subject_parsing(profile)
unless profile.subjects.blank?
# Decode subjects into a hash
parsed_subjects = ActiveSupport::JSON.decode(profile.subjects)
# Extract subject names from hash into a string separated by commas
profile.subject_names = parsed_subjects.keys.join(', ')
parsed_subjects.each do |subj, desc|
subj = subj.downcase
profile.class_eval do
attr_accessor "#{subj}_desc"
end
profile.send("#{subj}_desc=", desc)
end
end
end

So my workaround was to use the gon gem to send a Rails hash to Javascript then calling the subject descriptions according to the subject_names array.
Added this to the controller (see EDIT 1 code)
# Decode JSON subject field into a hash
def subject_parsing(tutor_profile)
unless tutor_profile.subjects.blank?
# Decode subjects into a hash TODO: change this because it is very slow
gon.subjects = ActiveSupport::JSON.decode(tutor_profile.subjects)
# Extract subject names from hash into a string separated by commas
tutor_profile.subject_names = gon.subjects.keys.join(', ')
end
end
Then ended up with this in the Javascript:
var subjectNamesInput = $('#tutor_profile_subject_names');
// Initialize subject description input boxes
$.each(subjectNamesInput.tagsinput('items'), function(i, subject){
updateSubjectInputs(subject, 'add');
});
function updateSubjectInputs(subject, action){
var parent = $('#subject-description-inputs');
var subject = escape(subject);
var text = "";
if (gon.subjects[subject] != undefined)
text = gon.subjects[subject];
if (action == 'add'){
parent.append("<textarea id='tutor_profile_" + subject.toLowerCase() + "_description' name='tutor_profile[" +
subject.toLowerCase() + "_description]' class='form-control form-text-area' rows='5' placeholder='Add some detail about your experience with " + subject +
" (optional)'>" + text + "</textarea>");
}
else if (action == 'remove'){
$('#tutor_profile_' + subject.toLowerCase() + '_description').remove();
}
}
This was in my view:
<%= f.text_field :subject_names, class: 'form-control tagsinput typeahead', placeholder: 'Subjects you teach' %>
<div id="subject-description-inputs">
</div>

Related

Rails - Javascript - Add param to params before form submit

I have a form for multiple pupils, with checkboxes next to each pupil to collect their IDs. I'm trying to have two buttons that each lead to different forms, and I've tried submitted params with the button click but to no avail:
simple_form_for - multiple submit buttons with different params
Is there any way to write some JS that adds a parameter to the params before submitting the form? My form is as follows:
<%= simple_form_for :pupils, url: edit_multiple_school_group_pupils_path(school, group) do |f| %>
...
...
<%= f.button :submit, 'Change Levels', name: 'editing', value: 'levels' %>
<%= f.button :submit, 'Move to A Class', name: 'editing', value: 'classes' %>
<% end %>
Each of the buttons have their own ID in the raw HTML so I'm hoping theres a way to pluck that before the form gets submitted and add it to the params.
I'm extremely green with JS so wouldn't even know where to start. Thanks in advance.
I'm not 100% sure what is needed for the rails portion, but it sounds like you want to serialize your form.
This is fairly easily accomplished. Here is an example of what a serialize to JSON function might look like
function toJSONString( form ) {
var obj = {};
var elements = form.querySelectorAll( selectors );
for( var i = 0; i < elements.length; ++i ) {
var element = elements[i];
var name = element.name;
var value = element.value;
if( name ) {
obj[ name ] = value;
}
}
return JSON.stringify( obj );
}
The selectors can be pretty much whatever you want within your form. Inputs, textareas, buttons etc.
Hope this helps!

How to reject Rails update method and trigger JavaScript alert box?

I've update method inside my controller:
def update
if #daily_order.update(daily_order_params.merge({default_order:false}))
respond_to :js
else
render :edit
end
end
I've pax as one of the params inside the daily_order_params and what I'm trying to do now is, I want to show a JS alert when user is trying to update a daily order with zero (0) pax value.
I try to use byebug inside that update method to check params[:pax] but it returns nothing. It's blank.
But the newly updated pax value was saved successfully inside the database.
I try to use before_update inside my model, but I didn't know how to move forward.
before_update :reject_zero_pax_order_update
def reject_zero_pax_order_update
# i got stucked here
end
update.js.erb
console.log("record updated");
What should I do to:
Reject the update if the entered pax is 0
Trigger a JS alert box if the entered pax is 0
Maintain the old pax inside the database
The :pax attribute is inside the :daily_order hash not inside the params hash.
Your params looks something like this:
{ daily_order: { pax: 0 }}
hence you should access it with params[:daily_order][:pax]
Your action should look something like this :
unless params[:daily_order][:pax] == 0
#daily_order.update(daily_params.merge{default_order: false} )
respond_to :js
else
render :edit
end
#update.js.erb
alert('hey, the pax cant be 0 ');
I am assuming, you want the update to take place only when the pax is present else show the alert.
For displaying alert box for pax value can't be equal to 0, you need to add id or class on both update button and pax field. Also you have to add hidden field for getting id of current daily order which is to be updated.
# Add hidden field:
<%= hidden_field_tag "daily_order_id", #daily_order.id %>
# Add id on pax field:
<%= f.text_field :pax, id: 'daily_order_pax' %>
# Add id on update button:
<%= f.submit 'Update', id: 'update_daily_order' %>
Then add following code in js file:-
$(document).on('click', '#update_daily_order', function(event) {
event.preventdefault();
var id = $('#daily_order_id').val();
var pax_value = $('#daily_order_pax').val();
if (pax_value == 0) {
alert("pax value can't be equal to 0");
}else {
$.ajax({
type: 'PUT',
url: '/daily_orders/' + id
data: $('#form_id').serialize();
});
}
});
Above ajax will call only when pax value is not equal to 0

How to add random button to form

I want to provide users a 'random' option so they can select a previous created date idea to use from the database (inside the letsgos table). There’s a “Let’s Go...” section that users can fill out a form and propose a date they would like to go on. There are going to be users who won’t be able to come up with a date idea on their own. So for those users who cannot create their own date I want to provide a ‘random’ button that with each click will insert a date (that’s from the database) into the form. The dates in the database from the letsgos table have content and tag assign to them. When a user clicks on random it should populate the form with the content and tag (each random click should show new data from the database). I don't have any javascript experience so I am not sure if I am doing it the right way.
/views/letsgos/_form.html.erb:
<%= form_for(#letsgo) do |f| %>
<div class="field">
<%= f.text_area :content, placeholder: "Propose new date..." %>
</div>
<%= f.select :tag, options_for_select( [["Select One", ""], "Eat/Drink", "Listen/Watch", "Play", "Explore", "Other"]) %>
Click here for a Random letsgo
<%= f.submit "Post" %>
<% end %>
/views/layouts/application.html.erb
<head>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
$(document).ready(function() {
$('.ajax').click(function() {
$.get(this.href, function(response) {
console.log(response);
$('body').html(response);
});
});
});
<script>
</head>
letsgo controller:
def create
#letsgo = current_user.letsgos.build(letsgo_params)
if #letsgo.save
flash[:success] = "Date posted!"
redirect_to root_url
else
flash[:error] = "Date was not posted!"
redirect_to root_url
end
end
def destroy
#letsgo.destroy
redirect_to root_url
end
def random
#letsgo = Letsgo.random.first
if request.xhr?
end
end
private
def letsgo_params
params.require(:letsgo).permit(:content, :tag)
end
def correct_user
#letsgo = current_user.letsgos.find_by(id: params[:id])
redirect_to root_url if #letsgo.nil?
end
Caching columns migration:
rails g migration add_ids_count
def self.up
add_column :letsgos, :ids_count, :integer, :default => 0
Letsgo.reset_column_information
Letsgo.all.each do |l|
l.update_attribute :id_count, l.id.length
end
end
def self.down
remove_column :letsgos, :id_count
end
end
A creative solution to this would be to set up a caching column to store an array of IDs of Letsgo's, if you're worried about the performance on Antarr Byrd's suggestion. Basically, this would cache the Letsgo.pluck(:id) in a single column in the DB. (Maybe do this in a worker in a post-save and/or post-delete hook on Letsgos.) I'd recommend doing this in a buffer of some sort, or maybe as an hourly task.
You could then either pull this in as a JavaScript array (letsgos_ids_array in the example) and create a Math.random() value based on the length of that array and send it to the .find(). Of course you could also just output the array's length directly.
var item_index = Math.floor(Math.random() * letsgos_ids_array_length);
$.get("/letsgos/random", {
"ind" : item_index
}, function(data){
/* do something with the data */
});
Then, this index can be used to pull out the actual ID value from the array from the db.
letsgoarray = Letsgosarray.first # this is the single-column "cached" array of IDs
item_id = letsgosarray[params[:id_index]]
#random_letsgo = Letsgos.find(item_id)
format.json do {
render json: #random_letsgo
}
Array access is fast, and so is single db column querying.
Here you have some good read about random rows:
http://jan.kneschke.de/projects/mysql/order-by-rand/
I've never done this but you can probably do
def random
Letsgos.find(Letsgo.pluck(:id).sample)
end

How can I organize this logic

Help me please!!
I want to create editable data table. In top of the table will be a button "Add" that adds row to the table with javascript. And in each row should be butons(links) "Save" and "Delete".
How can i send post request to server?
SORRY FOR MY dullness..
In application.js I have a function like this
function AddElementsToPage(elements) {
/*elements - is array with name elements whose will be add to page*/
this.add_row_to_data_table = function(obj,attributes,selector_table){
last_row_of_table = selector_table.find('tbody tr').last();
var new_row = "<tr><th scope='row'></th>";
if ($.isEmptyObject(last_row_of_table.html()))
{
$.each(attributes, function(index, item){
input_tag = "<input id=\""+obj+"_"+item+"\" name=\""+obj+"["+item+"]\" size=\"10\" type=\"text\" />";
new_row = new_row + "<td>"+input_tag+"</td>";
})
alert(new_row);
selector_table.append(new_row+"</tr>");
}else
{
alert("not empty");
}
}
my controllers action new
def new
#table_row = SchOfWorkInformation.new
respond_to do |format|
format.js do
render :action => 'new'
end
end
end
My new.js file
var obj = new AddElementsToPage();
obj.add_row_to_data_table("sch_of_working",["date","hour"], $('.data_table'))
My index.haml
= link_to content_tag('span', "Add" , :class=>"add"), new_sch_of_work_information_path, :remote => true
%table{:border=>"1", :class=>"data_table", :style=>"width:450px"}
%thead
%th{:style=>"width:5%;"} №
%th{:style=>"width:10%;"} Date
%th{:style=>"width:10%;text-align:center;"} schedule_code
%th{:style=>"width:2%;",:class=>"transperent_right_border"}
%th{:style=>"width:2%;"}
%tbody
%tr
%th{:scope=>"row"}
%td=#sch.date
%td=#sch.schedule_code
%td{:style=>"text-align:center"}= link_tag "save", sch_of_work_informations, #how can send post request in here
%td{:style=>"text-align:center"}
I think this is the kind of answer you would be looking at. Follow the tutorial from railscasts http://railscasts.com/episodes/196-nested-model-form-part-1 nested form and it should help you. Basically you would have an add row button and it would add in javascript the html row, you could even have a remove the row button. Once this is done just press the save button of the form to allow all change to be done.

Rails Updating a DIV (with reload from model) after a javascript call

OK, I have a page which shows the customer how many Widgets he has. Here's the view (haml):
#available
= "Available widgets: #{#customer.widgets.unused.count()}"
("unused" is a scope in the model showing the available widgets).
When Customer redeems Widgets with a form with ":remote => true", some javascript places a nice DIV on the page with animation and the model is updated by the controller.
Here's the controller:
def redeem
#customer = Customer.find(params[:customer_id])
number = params[:amount].to_i
unless #customer.widgets.unused.empty?
number.times do
#customer = Customer.find(params[:customer_id])
widget = #customer.widgets.unused.first # Grab first unused pass
widget.status = "Redeemed"
widget.save!
end
else
#pay = "true"
# customer.widgets.new
end
# redirect_to #customer
end
And here's the javascript (js.erb):
var number = <%= params[:amount] %>;
<% if #pay.eql? "true" %>
$("#widget-pay").modal('toggle');
<% else %>
while (number > 0) {
var item = $('<div class="widget-show">...</div>');
$('#allwidgets').isotope('insert', item);
number --;
}
<% end %>
My problem is I now want to update the "#available" DIV with the new Widget count. How do I do this?
At worst I could reload the page so the data is pulled from the model again, at best just update the DIV. Neither which I seem to be able to do from the javascript.
You can do something like this:
render :js => "$('#available').append(widget)"
widget.save!

Categories

Resources