I am trying to create a form that takes values from a table of input boxes. The user can enter the values in this table manually, or upload an excel spreadsheet. All of this works fine, but I want the user to be able to import a spreadsheet, and have the data table updated without reloading the page. The primary model to store this information is a DataTable object.
views/data_tables/index.html.erb looks like this:
<div id="tabs" class="tabs-bottom">
<ul>
<li>Data Input</li>
<li>Data Display</li>
</ul>
<div class="tabs-spacer"></div>
<div id="tabs-1" class="tab_frame">
<%= render #data_table %>
</div>
<div id="tabs-2" class="tab_frame">
<%= render "data_display" %>
</div>
</div>
I am using a tabbed view from the Jquery-UI.
The partial, views/data_tables/_data_table.html.erb, looks something like this:
<%= nested_form_for #data_table, :remote => true do |f| %>
<div id="table_pane">
<table id="table_input" class="table_input">
<tbody>
<%= f.fields_for :financial_rows, :wrapper => false do |dr| %>
<tr class="fields">
<td>
<%= dr.link_to_remove "x", {:class => "btn btn-danger"} %>
</td>
<td class="month">
<%= dr.text_field :month, :value => (dr.object.month.to_i) %>
</td>
<td class="year">
<%= dr.text_field :year, :value => (dr.object.year.to_i) %>
</td>
<td class="currency">
<% # . . . %>
<% #Multiple fields omitted for brevity %>
<% # . . . %>
</td>
</tr>
<% end %>
</tbody>
</table>
<%= f.link_to_add "+", :financial_rows, :data => { :target => "#table_input" }, :class => "btn btn-success" %></br>
</div>
<%= f.submit :value => "Render Data" %>
<% end %>
<%= form_tag import_data_tables_path, multipart: true, remote: true do %>
<%= file_field_tag :file %>
<%= submit_tag "Import Spreadsheet" %>
<% end %>
The import form at the end is what I am having trouble with. In my controller, controllers/data_tables_controller.rb, my import method looks like this:
def import
#data_table = DataTable.new
if !params[:file].blank?
imported_file = params[:file]
spreadsheet = read_file(imported_file)
header = spreadsheet.row(2)
(3..spreadsheet.last_row).each do |i|
data = Hash[[header, spreadsheet.row(i)].transpose]
f_row = FinancialRow.new
f_row.attributes = data.to_hash.slice(*FinancialRow.accessible_attributes)
#data_table.financial_rows << f_row
end
respond_to do |format|
format.html { redirect_to #data_table}
format.js
format.json { render json: #data_table, location: #data_table}
end
#render('index')
else
flash[:error] = "Select a file for import"
#data_table.financial_rows.build
#render('index')
end
end
When I import a file without trying to use Ajax, the page reloads with the text fields of the data table fully populated, but when I try to use Ajax, the page just reloads without any updates to the data table. I have written a javascript file which theoretically should replace the div the datatable is in.
views/data_tables/import.js.erb contains:
$("#tabs-1").html("<%= escape_javascript(render(:partial => #data_table)).html_safe %>");
I've scoured stackoverflow for hours, reading posts on how to replace a rendered partial using Ajax, but I haven't found anything that has worked for me. I'm sure it is something simple I am overlooking, but I'm having a lot of trouble wrapping my head around how Ajax is implemented with Rails, and how and why one should use either format.html, format.js, or format.json.
I am using rails 3.2.12. Some noteworthy gems I am using is the 'nested_form' gem to handle the data_table form, and 'roo' to handle importing the spreadsheet.
Related
I have a AtpRank model containing the first 100 Atp tennis players.
My goal is to create in the view a table listing all tennis players and their attributes, along with a button for each player useful for the user to choose a list of tennis players. The home.html.erb code is below:
<% #atp_ranks.each do |tennis_player| %>
<tr id="tennist-<%= tennis_player.ranking %>">
<td class="atpranking"> <%= tennis_player.ranking %> </td>
<td class="atpname"> <%= tennis_player.name %> </td>
<td class="atppoints"> <%= tennis_player.points %> </td>
<% unless Time.now.month == 12 %>
<td>
<div id="atpenlist_form">
<% if current_user.atpenlisted?(tennis_player) %>
<%= form_for(current_user.atp_selections.find_by(atp_rank_id: tennis_player.id),
html: { method: :delete }, remote: true) do |f| %>
<%= f.submit "Dump", class: "btn btn-warning btn-sm" %>
<% end %>
<% else %>
<%= form_for(current_user.atp_selections.build, remote: true) do |f| %>
<div><%= hidden_field_tag :atp_id, tennis_player.id %></div>
<%= f.submit "Choose", class: "btn btn-primary btn-sm" %>
<% end %>
<% end %>
</div>
</td>
<% end %>
</tr>
<% end %>
As you can see, the form uses Ajax having set remote: true in the form_for helper.
Requests are handled by the atp_selections controller. Below is an extract of the create action of this controller:
current_user.atpenlist(tennist)
respond_to do |format|
format.html { redirect_to root_url }
format.js
end
The destroy action uses the atpdiscard method instead of the atpenlist method.
In app/views/atp_selections I created the create.js.erb and destroy.js.erb files.
Below is the app/views/atp_selections/create.js.erb file:
$("#atpenlist_form").html("<%= escape_javascript(render('users/atpdiscard')) %>");
$("#atp_count").html('<%= current_user.atp_ranks.count %>');
Each of the app/view/users/_atpenlist.html.erb and app/view/users/_atpdiscard.html.erb partials contain the respective form (the same exact part of the code above starting with form_for).
I have to say that in the original code for the home page I did not explicitly included the entire code for the forms, but I just rendered the partials. This did not work: rails warned me that it could not find the variable or method tennis_player used in the iteration, for some reason to me unknown. So I had to renounce to render the partials and decided to include the entire code.
The issue is now that Ajax does not work: I have to refresh the page to see the results of submitting the form. I checked my code and could not find errors or explanation for this.
tennis_player is a local variable for you home.html.erb .
So you need to pass it in js.erb like
$("#atpenlist_form").html("<%= escape_javascript(render('users/atpdiscard'), locals: {tennis_player: your_tennis_player_object}) %>");
Set the tennis player object in your controller & use that in above js.erb
I am using the Zurb Foundation CSS framework on a RefineryCMS site and I have a gallery page with a number of thumbnail images and I would like the the image to be a link that opens the reveal modal with a larger image and description inside it, basically my show page.
I have used the fancy_box plugin before which was really simple but cant figure this one out.
Index page
<ul class="large-block-grid-4" id="galleries">
<% #galleries.each do |g| %>
<li>
<%= link_to refinery.galleries_gallery_path(g), :remote => "true", :data => { :"reveal-id" => "modal", :"reveal-ajax" => "true" } do %>
<%= image_fu g.image, "250x250" %>
<% end %>
</li>
<% end %>
</ul>
show.js.erb
$("#modal").html('<%= escape_javascript(render "modal", image: #image)%>')
$('#modal').foundation('reveal', 'open');
_modal.html.erb
<div id="modal" class="reveal-modal" data-reveal>
<h2><%=raw #gallery.title %></h2>
<%= image_fu #gallery.image, "600x600" %>
<%=raw #gallery.description %>
<a class="close-reveal-modal">×</a>
</div>
Controller
def show
#gallery = Gallery.find(params[:id])
respond_to do |format|
format.js
end
end
you can use image tag inside link tag
<%= link_to image_tag(g.image, "250x250"),"#" %>
I would like to create a "load more" ajax pagination, with Kaminari.
I'm using this code :
class BienvenueController < ApplicationController
def index
#articles = Admin::Article.page(1).per(2)
respond_to do |format|
format.html
format.js
end
end
end
# Bienvenue#index
<div class="container" style="min-width:<%= #width %>px">
<%= render "shared/articles" %>
<%= link_to_next_page #articles, 'Load More', :remote => true, :id=>"load_more_link" %>
# Shared/articles
<% #articles.each do |a| %>
<article class="<%= a.rubrique.color %>">
<div class="sharing">
<%= image_tag "facebook-32.png" %>
</div>
<p class="color<%= a.rubrique.color %>"><i>Le <%= I18n.localize(a.created_at, :format => :long) %> par David Perrotin</i></p>
<h1><%= a.titre %></h1>
<div class="excerpt">
<%= a.chapo.html_safe %>
</div>
<div class="image">
<%= image_tag a.mainphoto.url(:medium), :width=>"100%" %>
</div>
<div class="contenu">
<%= a.contenu.html_safe %>
</div>
<div class="readmore">
<%= link_to "Continuer la lecture", article_path(a) %>
</div>
</article>
<% end %>
# index.js.erb
$('.container').append("<%= escape_javascript(render 'shared/articles')%>");
$('#load_more_link').replaceWith("<%= escape_javascript(link_to_next_page(#articles, 'Load More', :remote => true, :id=>'load_more_link'))%>");
But the problem is that when I click on "Load More", it always shows the two same articles, the partial is never refreshed with two more articles, like I would like.
I just ran into an issue with this that might help others. Depending on your version of jQuery, don't use replaceWith on the #load_more_link in index.js.erb.
There is a regression (http://bugs.jquery.com/ticket/13401) that an empty replaceWith does nothing, so on the very last page of your set, the link_to_next will be empty, making the line: $('#load_more_link').replaceWith(''); and thus will not replace the last "more" button, so you'll continually load the last page of your data set.
Fixed by updating jQuery version or use empty().html('...') instead of replaceWith.
I'm implementing show/hide feature for users comments.
Discussed here: https://stackoverflow.com/a/10174194/439688
My aim was to:
1. Limit the default shown comments to 2.
2. Have a span with text that states the number of total comments for that particular micropost and when clicked by a user have it expand and show all comments for that micropost. I would be using Jquery/Ajax to hide, show, prepend etc.
The first change was to limit the amount of comments shown to the user and I achieved this by creating a method in my helper called "comments" and here I pass in the id of the micropost the comment belongs to.
def get_comments(micropost_id)
Comment.limit(2).order("created_at DESC").where(:micropost_id => micropost_id)
end
Now the each loop that loops through each comment will only show the 2 most recent comments.
<<% #microposts.each do |m| %>
<% if m.poster_id.nil? %>
<div class="postHolder">
<nav class="micropostOptions">
<ul class="postMenu">
<li class="deletePost"><%= link_to content_tag(:span, "Delete post"), m, :method => :delete, :confirm => "Are you sure?", :title => m.content, :class => "message_delete", :remote => true %>
</li>
<li class="disableCommenting"><%= link_to content_tag(:span, "Pause commenting"), "2" %></li>
<li class="blockCommenter"><%= link_to content_tag(:span, "Block commenter"), "3" %></li>
<li class="openInNewWindow"><%= link_to content_tag(:span, "Open in new window"), "4" %></li>
<li class="reportAbuse"><%= link_to content_tag(:span, "Report abuse"), "5" %></li>
</ul>
</nav>
<%= link_to image_tag(default_photo_for_current_user, :class => "poster_photo"), current_users_username %>
<div class="post_content">
<div class="post_container">
<div class="mainUserNameFontStyle"><%= link_to current_users_username.capitalize, current_users_username %> - <div class="post_time"> <%= time_ago_in_words(m.created_at) %> ago.</div>
</div>
<%= simple_format h(m.content) %> </div>
<div class="commentsCount">
<%= content_tag :span, pluralize(m.comments.count, 'comment'), :class => "view_all_comments" if m.comments.any? %>
</div>
<% if m.comments.any? %>
<% comments(m.id).each do |comment| %>
<div class="comment_container">
<%= link_to image_tag(default_photo_for_commenter(comment), :class => "commenter_photo"), commenter(comment.user_id).username %>
<div class="commenter_content"> <div class="userNameFontStyle"><%= link_to commenter(comment.user_id).username.capitalize, commenter(comment.user_id).username %> - <%= simple_format h(comment.content) %> </div>
</div><div class="comment_post_time"> <%= time_ago_in_words(comment.created_at) %> ago. </div>
</div>
<% end %>
<% end %>
<% if logged_in? %>
<%= form_for #comment, :remote => true do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :micropost_id, :value => m.id %>
<%= f.text_area :content, :placeholder => 'Post a comment...', :class => "comment_box", :rows => 0, :columns => 0 %>
<div class="commentButtons">
<%= f.submit 'Post it', :class => "commentButton", :disable_with => "Post it" %>
<div class="cancelButton"> Cancel </div>
</div>
<% end %>
<% end %>
</div>
</div>
From here this is where it gets confusing for me. I got slightly further using link_to but then decided I'd prefer not to have the url to the comments count show in the browser status bar. This is why I switched to using span.. but now it's not quite easy to do what I wish to do as I can't use the link_to/remote => true now.
How do I make it so when a user clicks the comment count span an ajax call is made pointing to:
def load_comments
#load_comments = Comment.where(:micropost_id => params[:id])
respond_to do |format|
format.js { render :load_comments }
end
end
I thought about putting a click function in users.js but how would I pass the params of the micropost that is in the each loop in the code above into users.js? I don't think it's possible.
All my comment posting is done via ajax but because I used forms for these it was so much easier for me to just add remote => true and create some js templates and do something on success of ajax post.
Not sure if I'm even going about this the right way. I'd appreciate some help/advice from more experienced rails programmers.
Kind regards
Rails partial
#Display all the comments based on local passed to this partial
# Initially pass limit as 2(or whatever you want). then on click of span pass limit as nil. then you can check if limit is nil you can query the model without limit specifier.
<% #comments = Comment.custom_find(#your_params) %>
<% #comments.each do |comment| %>
<%= comment.title %>
<% end %>
javascript/jquery
function load_all_comments(id)
{
new Ajax.Updater('show_comments',
'<%=url_for(:controller => "your_controller", :action => "your_action")%>', {
parameters: {'id':id },
method: 'get',
onSuccess: function(request){
div_comments = document.getElementById("partial_comments_list");
div_comments.innerHTML = request.responseText;
}
});
} // you can call this js function on span click. use jquery if you want.
Controller:
Then inside your_action of your_controller, dont forget to render the partial
render :partial => "show_comments", :layout => false
Edit:
you can even pass locals to your partial
render :partial => "show_comments", :locals => {:post => #post}
Using this every time your partial view will get updated, on the basis of locals you pass.
of course this is just an example not a complete code/solution.
There may be better ways. but this worked fine for me.
Another option is to just output all of the comments and hide the ones you don't want to show first. <div class="hidden_comments" style="display:none;"> a comment </div>
Then just have some javascript to show them when the span is clicked?
<script type="text/javascript">
$("#span_id").click(function() {
$('.hidden_comments').show();
});
</script>
This works great if you do not don't have a ton of comments.
If you really want to do it your way, I have done it before but it gets messy.
Put this in your application.js
$('.comment_span').live('click', function () {
$.get(this.data_url, null, update_row, 'json');
return false;
});
Your span would look like this:
<span class="comment_span" data_url="http://website.com/resource/more_comments">
show all comments
</span>
This example returns the data as json, so I used the update_row function to update replace the comments data.
function update_row(data, status) {
$("#comments-table").append(data.html);
};
Here is what my controller looked like:
def more_comments
#comments = Comments.all
if #comments
respond_to do |format|
format.js {
render :json => {
:html => render_to_string(:partial => "comments"),
}.to_json
}
end
end
end
You should do this via your index action.
Pass a param to it to determine if you want to show all comments or just the current set (I'd use will_paginate to handle this.
Haven't looked too deep into your code as I'm on my phone right now, but something like this:
class CommentsController < ApplicationController
def index
If params[:show_all] == "true"
#comments = Comment.all
else
#comments = Comment.where(foo: bar).paginate(per_page: 2, page: params[:page])
end
end
Then you have it respond to JavaScript and send the page param with your Ajax request
On my site, the user can watch his profile. In his profile he can have a look at his data (i.e. signature).
Now I want my users being able to edit this data while watching it.
So I coded the following in my view:
<div id="profile-signature">
<p>
<b>Signature:</b>
<%=h #user.signature %>
</p>
<%= form_remote_tag(:update => "signature",:url => { :action => :update_signature }) %>
<%= text_area(:signature,:class=>"form-textarea") %>
<%= submit_tag "Save Signature" %>
</div>
in my user controller, I created a new action update_signature
def update_signature
puts 'in function!'
#user = current_user
puts #user.login
puts params[:signature]
#user.signature = params[:signature]
#user.save
puts 'saved'
end
Now, submitting the form, puts params[:signature] will output: classform-textareasfsffsfs
where sfsffsfs is the text I entered.
Reloading and my page and output the signature on page (<%=h #user.signature %>), I get:
"--- !map:HashWithIndifferentAccess classform-textarea: sfsffsfs "
Why do I get this strange string instead of just sfsffsfs (in this case)?
What to do, to update the data (<%=h #user.signature %>) automatic without page reload?
Use text_area_tag for getting the text_area field values . With reloading the page there is a mismatch in the div id it should be signature not profile-signature .
<div id="profile-signature">
<p>
<b>Signature:</b>
<%=h #user.signature %>
</p>
<%= form_remote_tag(:update => "signature",:url => { :action => :update_signature }) %>
<%= text_area(:signature,:class=>"form-textarea") %>
<%= submit_tag "Save Signature" %>
</div>
Make the following changes
<div id="signature">
<p>
<b>Signature:</b>
<%=h #user.signature %>
</p>
<%= form_remote_tag(:update => "signature",:url => { :action => :update_signature }) %>
<%= text_area_tag(:signature,:class=>"form-textarea") %>
<%= submit_tag "Save Signature" %>
</div>
Hope this helps !
It looks like your text_area call isn't quite right, looking at the docs it should be like this:
text_area(object_name, method, options = {})
so your css class is being set as the method, instead you should use text_area_tag:
<%= text_area_tag(:signature, #user.signature, :class=>"form-textarea") %>
Then the correct value (the text in the text area) should be submitted as the params you're expecting.