Partial not updated on first click - ruby-on-rails

I am having problems with a remotely executed action and a partial that doesn't update the first time I click the link.
Inside the view (a partial named books) I am creating a link:
link_to "⊗", read_book_path(book), :remote => true
The read_book_path is defined in routes.rb
There is also a conditional that displays a different text when that book is read.
Inside my controller, I have defined a new action:
def read
#books = Book.all
#book = Book.find(params[:id])
#book.read = !#book.read
#book.save
respond_to do |format|
format.html { redirect_to(books_url) }
format.js {render :layout => false, :locals => { :book => #book } }
end
end
This means I need a file read.js.erb, this file's content is:
$("#books").empty().html("<%= escape_javascript( render(:partial => "books") ) %>");
When I click the link, I can see in the terminal window that the database field is updated but the partial is not. Clicking the same link again updates the partial.
Changing the link to :remote => false also works but the page reloads (as expected).
I have tried to debug it with Safari and the Developer tools and I can see the server's response when clicking the link for the first time.
Something is wrong there, the HTML generated by <%= escape_javascript( render(:partial => "books") ) %> contains the wrong HTML with the old content of the partial. Only the second or third click shows the updated HTML.
I have integrated jquery-ujs - is that the reason the partial doesn't update the first time or am I missing something else?
This really gave me a headache, can you help me?
Edit:
If that helps: I created a listener in application.js to ajax:before and ajax:complete. The first one shows a little spinner, the second one hides it.
When I click the link, the spinner shows but it doesn't hide.

It looks like you have an ordering problem that's causing the trouble. You're capturing a complete set of books into the #books variable and then modifying a separate copy of a single book. This change will not be propagated back.
# Load and modify the one book by flipping the flag
#book = Book.find(params[:id])
#book.read = !#book.read
#book.save
# Load all books
#books = Book.all
As a note this is an extremely inefficient way of doing things, so I hope you're not working on a large amount of data. You might find it's easier to do this by simply toggling the one field with a simple UPDATE query:
Book.update_all({ :read => true }, { :id => params[:id] })
I'm not sure why you're calling $(...).empty().html(...) instead of simply $(...).html(...) since the html() method should replace the HTML wholesale with no need to clear it in advance.
One thing that might help is using .js.rjs where the equivalent would be:
page[:books].replace_html(:partial => 'books')
With simple JavaScript, RJS allows you to eliminate a lot of the drudgery. You can use JS in RJS as well for cases where there is no equivalent:
page << '$("#books").empty()'
page[:books].replace_html(:partial => 'books')
To make this more Rails friendly, you could call your partial _book which would make the local variables redundant. Each partial has a default variable with a name matching the template name:
render(:partial => 'book', :collection => #books)

Related

Add section to form by rendering a partial when link is clicked

UPDATE 3:
For anyone who reads this, this is why it wasn't working as expected in update 2 below: Passing a local variable to a partial that is rendered after the view has already loaded
If anyone knows how to solve that issue, let me know please.
UPDATE 2:
I updated the javascript with the quotation marks and it partially works...in the sense that the javascript is now functional and it will cause a string of text to appear on the page when I click the link as long as I have the partial only contain a string of text. However, when the partial includes the form fields code, something goes wrong.
If I just paste the following render code directly into the form in the new.html.erb view, it produces a new form section properly.
<%= render "add_round", f: f %>
However, when I try to include similar code in comps_helper.rb and then reference it from the link_to, it does not work:
In comps_helper.rb:
def addRound(f)
render "add_round", f: f
end
In new.html.erb:
<%= link_to "render it!", addRoundLink_path, remote: true %>
<div id="some_id"></div>
And I changed addRoundLink.js.erb to:
$("#some_id").html("<%=j addRound(f) %>"); #Is this the correct change to have made here?
Clicking the link_to link does nothing in that case.
Any thoughts?
UPDATED CODE:
Thanks for the reply. I've made the following changes and it still does not appear to be working. The link appears at the bottom of the form but when clicked does not change anything. What am I missing?
routes.rb:
resources :comps
match '/new_competition', :to => "comps#new"
get "/addRoundLink" => "comps#addRoundLink", :as => :addRoundLink
Note: I included the other 2 lines related to "comps" just in case those would cause an issue.
comps_controller.rb:
def addRoundLink
respond_to do |format|
format.js
end
end
comps_helper.rb:
def addRound
render "add_round"
end
addRoundLink.js.erb:
$("#some_id").html(<%=j addRound %>);
comps/new.html.erb:
<%= link_to "render it!", addRoundLink_path, remote: true %>
<div id="some_id"></div>
Thanks.
ORIGINAL QUESTION
First off, I'm new to rails. I've read and tried many solutions to similar questions but nothing has worked so far.
I created a form with rails form_for and fields_for. The form creates a new competition (comp). The competition has many rounds. The top half of the form (the form_for section) accepts the details about the competition as inputs and the bottom half of the form accepts details about each round (the fields_for section). The form works perfectly in this basic format.
I took all the code that is in the fields_for section and put it into a partial. My plan was to then create a "add new round" link to the bottom of the form that would simply display the partial above the link each time the link is pressed. This would add a new section to the form for a new round and allow the user to input as many rounds as they'd like. This is the part that I am struggling to make work.
I added this code to my comps_helper:
def addNewRound
render "add_round"
end
This renders the file /views/comps/_add_round.html.erb.
My question is: how do I get this to render in the form when a link is clicked. As far as I can get with the research I have done is:
<%= link_to "Add new round", { }, :remote => true %>
I don't exactly know what is supposed to go in the {} that will execute the addNewRound method. And I don't know what, if anything, I need to add to my comps_controller file.
Thanks so much for the help.
You have to create an action in your controller
app/controllers/some_controller.rb
def hello
respond_to do |format|
format.js
end
end
and define a route to this action.
routes.rb
get "/hello" => "some#hello", :as => :hello
then create a link to this action like that:
<%= link_to "render it!", hello_path, remote: true %>
<div id="some_id"></div>
When you click this link it will find its way to your action and respond with js(javascript) because we told action to respond with only js.
At the end render the partial to anywhere you want in your view(*in this example to the some_id div*)
app/views/some/hello.js.erb
$("#some_id").html("<%=j addNewRound %>");
WARNING: Creating dynamic forms is a pain. You will face a lot of problems (like setting different ids for new form elements etc...). I highly recommend you to use ryan bates nested_form gem

Rails: What does it actually mean to "render a template"

I've become a bit confused about the idea of "rendering" a "template" due to the way an author speaks about it in a book I'm reading.
My original understanding of "rendering a template" was that it meant that Rails is providing the content that is viewed on the screen/presented to the viewer (in the way that a partial is rendered) but the book I'm reading seems to be using the concept of "rendering a template" to also mean something else. Let me explain in context
This book (rails 3 in action) sets up a page layout using the conventional layouts/application.html.erb file, and then it "yields" to different view pages, such as views/tickets/show.html.erb which adds more content to the screen. that's all straightforward..
Within this view views/tickets/show.html.erb, there is a rendering of a partial (which is also a straightforward concept).
<div id='tags'><%= render #ticket.tags %></div>
Now within this partial there is, using ajax, a call to a "remove" method in the "tags_controller.rb" which is designed to allow authorized users to remove a "tag" from a "ticket" in our mock project management application.
<% if can?(:tag, #ticket.project) || current_user.admin? %>
<%= link_to "x", remove_ticket_tag_path(#ticket, tag),
:remote => true,
:method => :delete,
:html => { :id => "delete-#{tag.name.parameterize}" } %>
<% end %>
Now here is the "remove" action in the tags controller (which disassociates the tag from the ticket in the database)...
def remove
#ticket = Ticket.find(params[:ticket_id])
if can?(:tag, #ticket.project) || current_user.admin?
#tag = Tag.find(params[:id])
#ticket.tags -= [#tag]
#ticket.save
end
end
end
At the end of this remove action, the author originally included render :nothing => true , but then he revised the action because, as he says, "you’re going to get it to render a template." Here's where I get confused
The template that he gets this action to render is "remove.js.erb", which only has one line of jquery inside it, whose purpose is to remove the "tag" from the page (i.e. the tag that the user sees on the screen) now that it has been disassociated from the ticket in the database.
$('#tag-<%= #tag.name.parameterize %>').remove();
When I read "rendering a template" I expect the application to be inserting content into the page, but the template rendered by the "remove" action in the controller only calls a jquery function that removes one element from the page.
If a "template" is "rendered", I'm expecting another template to be removed (in order to make room for the new template), or I'm expecting content to be "rendered" in the way that a partial is rendered. Can you clarify what is actually happening when a "template" is "rendered" in the situation with the jquery in this question? Is it actually putting a new page in front of the user (I expected some sort of physical page to be rendered)
You're nearly there! Rendering a template is indeed always about producing content, but for a slightly wider description of content. It could be a chunk of html, for example an ajax call to get new items might produce some html describing the new items, but it doesn't have to be.
A template might produce javascript as it does in your second example. Personally I am trying to avoid this and instead pass JSON back to the client and let the client side js perform the required work.
Another type of rendering you might perform is to produce some JSON. APIs will often do this, but you might also do this on a normal page. For example rather than rendering some javascript to delete tag x you might render the json
{ to_delete: "tag-123"}
and then have your jQuery success callback use that payload to know which element to remove from the DOM, by having this in your application.js file
$('a.delete_tag').live('ajax:success', function(data){
var selector = '#' + data.to_delete;
$(selector).remove()
}
(Assuming that your delete links had the class 'delete_tag')
Rendering JSON like this isn't really a template at all, since you'd usually do this via
render :json => {:to_delete => "tag-#{#tag.name.parameterize}"}
although I suppose you could use an erb template for this (I can't imagine why though).
My understanding is that js.erb is "rendered" by executing the javascript functions within it. Very often something like the below is done:
jQuery(document).ready(function() {
jQuery('#element').html('<%= escape_javascript(render pages/content) %>');
});
There's a really succinct overview of rendering at http://guides.rubyonrails.org/layouts_and_rendering.html that may help as it also goes into the details of the ActionController::Base#render method and what happens behind the scenes when you use render :nothing (for example). Render but can be used for files or inline code as well -- not just 'templates' in the traditional sense.

Basic Ruby on Rails AJAX Error

I'm working through Agile Web Development with Rails, Edition 4 with some tweaks (mostly just naming variations), and I've arrived at Iteration F2. In this iteration, you modify the index button with :remote => true, you add format.js to the respond_to section of the controller, and you generate a js.rjs file to execute the AJAX render. Or at least that's my interpretation of it. The goal of these steps is to have a cart (in this case, a team) in the sidebar update using AJAX when adding new line items (in this case, members)
In my case, I'm trying to add members to a team. Her's some code snippets I've added:
index.html.erb:
<%= button_to 'Add to Team', members_path(:player_id => player),
:remote => true %>
members_controller:
def create
#team = current_team
player = Player.find(params[:player_id])
#member = #team.add_player(player.id)
respond_to do |format|
if #member.save
format.html { redirect_to(nba_url) }
format.js
format.xml { render :xml => #member,
:status => :created, :location => #member }
else
format.html { render :action => "new" }
format.xml { render :xml => #member.errors,
:status => :unprocessable_entity }
end
end
end
create.js.rjs:
page.replace_html('team', render(#team))
The page is able to render, and I'm still able to click the button to add members to the team. However, the AJAX isn't working. When I reload, I can still see that the members have been added in the sidebar. All of the other team functionality remains, as I'm able to empty the team and add whichever members I wish. When I check the server log, I find the following error:
Error:
ActionView::Template::Error (undefined local variable or method `page' for #<
#<Class:0x413e1b8>:0x413cb20>):
1: page.replace_html('team', render(#team))
app/views/members/create.js.rjs:1:in `block in _app_views_members_create_js_rj
s___908569197_34199712_807066544'
app/views/members/create.js.rjs:1:in `_app_views_members_create_js_rjs___90856
9197_34199712_807066544'
app/controllers/members_controller.rb:47:in `create'
Based on this it seems like it has found the create.js.rjs but is having trouble interpreting it. I'm not sure what the weird symbols are in front of page.
Edit: I've also found that if I view the source code before and after clicking the button, the button is indeed refreshing the code and adding the desired items. The problem seems to be exclusively in trying to refresh the partial.
Any help is appreciated. Thanks!
It seems your rjs file has some invalid bits at the start. Maybe try to re-create the file?
What did you expect to do with render(#team) ?
I've taken a look at the action view's method "render" and didn't found how you were expecting it to function. Maybe there is another functionality that you are aware and I don't.
You can also use erb and not rjs, using it just like a view
Along the lines of Bert Goethal's answer, is your editor saving your text file as UTF-8 with BOM?
A BOM will add two unicode encoded characters to the beginning of the file, and that might be where those are coming from...

Rails 3: Simple AJAXy Page updates?

I can't believe I've been looking four hours for this simple task, but I have.
In Rails 2.3, I could replace one section of a page with this simple code:
render :update do |page|
page.replace_html "div_id", :partial => "new_content",...
end
In Rails 3, Ryan Bates has me writing entire new javascript functions, switching from Prototype (rails default) to jQuery, and otherwise not enjoying life. The other tutes are no more straightforward.
What am I missing? How do we replace a <div> these days?
Thanks, guys. The official answer seems to be that, yes, the team felt simple is the enemy of good and made it more complicated.
The first key is to create a .js.erb file NAMED for the method CALLING the ajax update. So if the index method handles the update, put the raw javascript in index.js.erb. This goes in the views folder.
Second, the code that worked in index.js.erb was
m = $('list_users');
m.innerHTML = "<%= escape_javascript(render :partial => "reload_users") %>";
Then to make the call, add in the respond_to block of the controller method, add:
format.js
Finally, the calling view has:
<%= link_to "Update User List", #reload_users_path, :remote => true %>
By the way, supposedly all the old pages using page.replace will work if you install a plugin. The plugin download page suggests that it broke in the last releases of Rails 3 and has not been fixed. Also, various bloggers will come to your home and birch-switch you if you use it.
The whole RJS stuff makes the javascript inline and makes the dom very obtrusive. Also, by avoiding inline javascript you could open up other possible ways of optimizing you javascript by compressing and caching those files in browser. Thats the reason why RJS is getting out of scope from rails 3. A little bit of getting around with jQuery or Prototype for a day should get you on gears with these kind of small stuff and will help the project on long run.
Do you still have jQuery in there? I'd recommend it over Prototype any day...
If it's still there you can just use the following in your Javascript:
$.get("<%= url_for path/to/partial %>",
function(response) {
$("#div_id").html(response);
});
This gets the partial via AJAX and just dumps it into the div with id div_id.
Hope this helps!
I'm not even sure you need to make an AJAX call to load that partial. I believe that in a js.erb file, a call to render(:partial => object_or_path) will just return a string with all the html, which you can wrap in a jQuery object and append. Example:
$('#div_id').html($('<%= render :partial => #object %>'))
As far as I know, along the same line as the answer above, you can do something like this in your template:
<%= link_to "Update User List", #reload_users_path, :remote => true %>
And in controller, do this:
respond_to do |format|
format.js {
render :text => "alert('reloaded')"
}
end
This way you can have controller "execute" client side JS much the same as as render :update used to do. This is equivalent to doing the following in Rails 2:
render :update do |page|
page << "alert('reloaded')"
end
Is there any reason why this approach is not advisable?
Try this:
page.call "$('#div_id').html", render(:partial => 'new_content')

how can I cache a partial retrieved through link_to_remote in rails?

I use link_to_remote to pull the dynamic contents of a partial onto the page. It's basically a long row from a database of "todo's" for the day.
So as the person goes through and checks them off, I want to show the updated list with a line through those that are finished.
Currently, I end up needing to click on the link_to_remote again after an item is ticked off, but would like it to redirect back to the "cached" page of to-do's but with the one item lined through.
How do I do that?
Here is the main view:
<% #campaigns.each do |campaign| %>
<!--link_to_remote(name, options = {}, html_options = nil)-->
<tr><td> <%= link_to_remote(campaign.name, :update => "campaign_todo",
:url => {:action => "campaign_todo",
:id => campaign.id
}
) %> </td></tr>
<% end %>
</table>
<div id="campaign_todo">
</div>
I'd like when the New/Create actions are done to go back to the page that redirected it there.
When someone wants to "do" a task, it takes them to the new action. here is the controller:
def create
#contact_call = ContactCall.new(params[:contact_call])
if #contact_call.save
flash[:notice] = "Successfully created contact call."
redirect_to contact_path(#contact_call.contact_id)
else
render :action => 'new'
end
end
I switched to redirect_to :back, which takes me back to the main view shown above...but WITHOUT the PARTIAL. It means I need to reload the partial all over again, which is a time-consuming database call.
1) Is it possible to go back to a view that has a partial called through AJAX and still have those results show up?
2) Can that result, however, be marked via CSS to indicate that it has been "checked off"?
I would render the to-do list item response from javascript ERB files. So when you make the link_to_remote call, instead of redirecting back to the page, instead render javascript.
You'd have the form in /app/views/contact_call/_form.html.erb
/app/views/contact_call/create.js.rjs
page.replace :contact_form, :partial => "contact_call/form", :locals => { :user => #user }
page.visual_effect :highlight, :contact_form
Your controller would then render the javascript, which would in turn replace html on your page with the latest version (and highlight it). And your page would load the partial with strike-through on completed items.
you have to create an ajax call when you mark one item as 'done', in this action you'll need to
update your list item to add the 'line-through' to text-decoration
create a method like 'after_save', to expire your cache
you can read about the CSS 'line-through' here:
http://www.icelab.eu/en/blog/css-4/xhtml-and-css-strike-alternatives-86.htm
and the documentation for expire_page and expire_action is here:
http://api.rubyonrails.org/classes/ActionController/Caching/Sweeping.html
I think you may be able to use action caching on your index action, or whatever the name of the action is that renders your main page.
You can also do page caching and fragment caching, which would work with partials. For more information on Rails caching strategies, see the rails guide:
http://guides.rails.info/caching_with_rails.html

Resources