Rails 3.1: Calling a template via JavaScript - ruby-on-rails

I was just going through this
old rails cast episode, and one thing it mentions is the (now obsolete, apparently) link_to_function code. One interesting snippet it mentions is
link_to_function "Add a Task" do |page|
page.insert_html :bottom, :tasks, :partial => 'task', :object => Task.new
end
In short, clicking the "Add a Task" link appends the "task" partial to the page without ajax.
I'm familiar with how to do this in Rails 3/3.1 via AJAX and/or manual javascript, but how do you pull in a template partial on the fly without touching ajax?

It just simply stores the html content within the javascript code, which it's rendered while the page is rendered. So when you click "Add a task" the partial is already there escaped in the javascript code of the add task action. Just take a look to the content of the page with firebug.
Also the docs shows that the instert_html looks like this for prototype:
def insert_html(position, id, *options_for_render)
content = javascript_object_for(render(*options_for_render))
record "Element.insert(\"#{id}\", { #{position.to_s.downcase}: #{content} });"
end
So you can see that the content is actually the task partial, and it's inserted into javascript code.
Also i have to mention that this is an obtrusive way to use javascript.
Take a look at the docs.

I was going through the same railscast, and I figured out the reason why this is not working is because this method uses Prototype, which by default is unavailable with the Rails 3.1 .
You could get this working by installing prototype gem from:
https://github.com/rails/prototype-rails

Related

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.

How to do user content from CMS in Rails

I'm trying to build a CMS in Rails from scratch, and for showing the user generated pages I'm having trouble deciding exactly how to do it.
The way I have it right now, I have a controller named 'content' with a single action called 'show'. In routes.rb I have a rule that passes any name after the name of the website to the content controller, show action with parameter name.
For example, www.mysite.com/about_us would route to
:controller => 'content', :action => 'show', :page => 'about_us'
Inside the content controller, I do a find on the Pages model to locate the named page:
#markup = Page.find_by_name(params[:page])
And then in the show.html.erb view I use the raw helper to display the content:
<%= raw #markup.text %>
Does this method violate anything about the way I should do be doing things in Rails? Or is this an OK solution?
I ended up using the sanitize helper, by default it removes <script> tags which is essentially what you need to prevent XSS, as far as I understand. For those who have found this via a search, the only code that changes from what I described above is that in the view you change to:
<%= sanitize #markup.text %>

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

rails ajax update only once

I have a remote_form_for that I use to let a user know if their form was submitted successfully. In this method I have :success=>'updateMain()
I would like to add an ajax update request to updateMain. The problem is that I can't find a way to make a single update request in rails (I know this is available in prototype).
The closest thing I've found is periodically_call_remote but this is not what I'm looking for as it continues to poll the server (and I only need it to happen once)
Another issue is that the ajax code needs to go into the updateMain js method, so I can't use periodically_call_remote as it automatically wraps the its js code with <script></script>.
I could write this manually with prototype but it would be nice to do it with rails. Any ideas?
In your controller method that your remote_form_for is submitting to, why don't you just use render :update?
def ajax_method
# If the form was valid
render :update do |page|
page.replace_html 'id_of_div_to_update', :partial => 'successful_form_submission'
end
end
The page will be updated each time you submit the ajax form.

Attaching onClick event to Rails's link_to_function

How do I attach an onclick event to a link_to_function such that clicking on the event refreshes an element on the page (using partials)?
When the user clicks the generated link, I'd like to refresh the partial containing the code so that i gets updated.
def add_step_link(form_builder)
logger.info 'ADD_STEP_LINK'
link_to_function 'add a step' do |page|
form_builder.fields_for :steps, Step.new, :child_index => 'NEW_RECORD' do |f|
logger.info 'inserted js'
html = render(:partial => 'step', :locals => { :step_form => f, :i=>#i+=1})
page << "$('steps').insert({ bottom: '#{escape_javascript(html)}'.replace(/NEW_RECORD/g, new Date().getTime()) });"
end
end
end
I have a partial in a larger form that contains just:
<%= add_step_link(technique_form) %>
I am attempting keeping track of the number of steps in a technique. In the form I am creating, users can add new steps to a set of instructions. Right now, I have default fields for steps 1-7. Adding one step, gets you step 8. The problem is that subsequent steps are numbered '8' also.
I am extending the "Multiple child models in a dynamic form" tutorial in http://railsforum.com/viewtopic.php?id=28447 for my own purposes.
Ok, I'm a little confused about what you are doing, but I'm going to try and make some suggestions.
Rather than use link_to_function, use link_to_remote. Link to function calls javascript, but what you actually want to do is call back to a controller that then runs some rjs to either replace the full partial or, more likely, append the new partial (containing the step) to the end of your current steps.
This is similar to all those examples you will have seen where people append a comment to the end of their blog comments (expect using link_to_remote rather than remote_form_for) see 3/4 of the way through http://media.rubyonrails.org/video/rails_blog_2.mov
You can see the docs for link_to_remote here: http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper.html
I suggest using link_to_remote as RichH suggests. The trick for you it seems is keeping track of #i. I'd either put it as a URL parameter in link_to_remote, or in the session object. I'd suggest the session—it seems easier.

Resources