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.
Related
Im new at Ruby on Rails language and I really need that someone explain me some 'topics' if possible.
I've created an app and I Scaffolded it and it created in the controller lots of code but I have doubts.
One of them is:
This app is 'empty' so far. It only has a 'New Book' in the first page.
//books\index.html.erb
||| <%= link_to 'New Book', new_book_path %> |||
new_book_path redirects me to books_controller
def new
#book = Book.new
respond_to do |format| //-----> What means this 'format'?
format.html # new.html.erb // What really mean two options for 'format'?
format.json { render json: #book } // What means render json: #book
end
# new.html.erb -> has this code inside
New author
/*<%= render 'form' %>
<%= link_to 'Back', authors_path %>*/
Can someone explain me what's hapenning here?
I know that these are really silly questions but I'm not getting it.
Thanks in advance.
The best two ways to understand Rails in-depth are reading its code (https://github.com/rails/rails) and reading Documentation (http://api.rubyonrails.org and http://guides.rubyonrails.org).
So you'll find enough information to cover this topic here: http://api.rubyonrails.org/classes/ActionController/MimeResponds.html or here: http://guides.rubyonrails.org/action_controller_overview.html.
But if you want short answer... listen to the story :)
The entire respond_to do ... end block is responsible for defining rules on how your app should response on different 'formats'. Rails supports a lot of different formats, i.e :html, :json, :xml (you even can define your own formats). Beside mime types it has variants: :desktop, :tablet, :phone. Obviously that with mime types you describe how you want to answer on different types of request and with variants you specify different options for various user agents.
:format variable passed into block has type ActionController::MimeResponds::Collector. They didn't call it so for nothing. It collects all different response types you specify inside block and then using headers section from http request picks an appropriate variant from that options.
Hope it was useful. But again, better check Documentation.
Rails uses MVC pattern in as its foundation ([http://en.wikipedia.org/wiki/Model–view–controller]). So what we've seen before was a Controller. And you can treat new.html.erb as a View for :new action for that controller.
The file itself is an html file flavored with ERB (NOT the same as Epic Rap Battles of History, but [http://en.wikipedia.org/wiki/ERuby]) template engine. ERB is able to inject chunks of ruby code into your pages. <% %> enclosing tag is used just for evaluation and <%= %> for injection of the result of evaluation. So in your case with <%= render 'form' %> you inject result of #render method call into your html and with :link_to helper you create link.
IN CONCLUSION: I recommend you to start with https://www.railstutorial.org. That's an excellent tutorial for starters. You'll find answers for most of your questions and even develop your own little Twitter! (at least 2nd edition is about Twitter).
respond_to is a Rails controller method (explanation here: http://api.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_to), which gets a block as argument. In short, block is a part of code ran within method it has been passed to.
For a block you declare variable called 'format'. Because this in just variable name so you may declare it i.e. 'f' or whatever you want.
Within the block of respond_to method, you may declare how your controller action responds for given MIME type. So, for HTML you may leave it empty, however if you want your controller to respond to JSON (MIME: application/json and you define it in the request header from the client side), then you have to tell your controller that's the response has to be in json format.
I have a calendar page that shows a person's routine for an entire month. At the top, I have button for the previous and next months. When those are clicked, I make an AJAX call to the change_date action which updates the dates and finds the new routines. When the change_date.js.erb is called, do I have to write the code to change the content for everyday's actions again? This seems to be violating DRY.
Is there any way I can reuse the partials I used to populate the page on initial load to repopulate the page with the new values?
Of course, there is. Simply use render as you did in your html templates. Usually a setup that supports ajax and regular calls looks something like this (of course names are wildly guessed):
# controller
def change_date
#dates = Date.where(:date => params[:date])
respond_to do |format|
format.html
format.js
end
end
# change_date.html.erb
<ul id="calendar">
<%= render #dates %>
</ul>
# change_date.js.erb
$("#calendar").html("<%= escape_javascript(render #dates) %>");
# _date.html.erb
<li><%= date.date %></li>
So you see, you can simply use the same partial on both scenarios.
And just another suggestion: Depending on your setup it may be more restful to not have another method in the controller, but to use the show method which operates depending on a query param in this case...
I would load the person's routine via ajax when the page is first loaded as well.
I would encapsulate it in a js function and call that function when the page is first loaded and everytime the month is changed.
I have a rails action called index that renders the content for my page along with the layout. When I go to the /index action with a browser it works like expected. I want to be able to also render this action by calling it with Ajax, I am doing this using the following:
<%= link_to "Back", orders_path, :id => 'back_btn', :remote => true %>
<%= javascript_tag do %>
jQuery("#back_btn").bind("ajax:complete", function(et, e){
jQuery("#mybox").html(e.responseText);
});
<% end %>
When the action is called this way I would like it to render and pass the index action back, excluding the layout. How can I do this?
You should be able to add a format.js action to your controller action like so:
respond_to do |format|
format.js
format.html
format.json { render json: #foos }
Ideally, you would want to create a index.js.erb file that would build the contents of the page:
$('#foos_list').update("<%= escape_javascript(render(#foos)) %>");
If you're going to update the contents of a div, to basically update a whole page inside of a layout, then you're going to want to change it up a little bit. Inside of the format.js, you can do this:
format.js { render 'foos/index', :layout => false }
But if you're trying to go with an ajaxified front-end, may I recommend a framework for doing this, like Spine? It will go a long way in helping you build your site.
Also, using a framework like this will force you to separate your application per #Zepplock's second suggestion.
You can just detect if the request is an XML HTTP Request, then render a blank layout like so:
render layout: 'blank' if request.xhr?
You'll need to create a blank layout in app/views/layouts/blank.html.erb like this:
<%= yield %>
You need a way to let server know that there's a difference in request type. It can be done in several different ways:
Append a key value to the URL (for example layout=off) and change your controller logic to render data with no view. This is kind of a hack.
Make your controller return data via XML or JSON (controller will know what content type is being requested) then format it accordingly and present in browser. This is more preferred way since you have a clear separation between content types and is better suited for MVC architecture.
Create an API that will serve data. This will lead to separate auth logic, more code on client side, additional APi controller(s) on server etc. Most likely an overkill for your case
I'm familiar with using Ajax templates to update particular parts of a page, but how do you render with layout when doing so? For example, given a layout:
#foo
= yield :foo
a simple view "show.html.haml":
= render #bar
and a partial:
- content_for :foo
= bar.to_html
... the HTML result would render within the layout and I'd see my bar content, but say I want to use Ajax to update only the #foo div. I create "show.js.erb":
$("#foo").html("<% escape_javascript(render(#bar)) %>");
But the result is nothing, as my _bar partial is rendered but outside of the layout, thus my :foo content is never yielded to. How do I get the JS template to render inside that layout?
I've found two answers, but I wonder if there's a better way still. These answers are for Rails 3, by the way.
1. Use a separate JS layout.
This is probably the "more correct" way, but unfortunately didn't work for my situation as I'll explain shortly.
What I wasn't fully aware of, is that in a JS request, the lookup formats are set to JS and HTML. Meaning that the controller will render the HTML template if the JS template does not exist.
But it will not look to the HTML layout in the same fashion, meaning the HTML template will be rendered, but the content_for block is never yielded to, leading to an empty response.
So to make the simple example above work out-of-the-box. You'd delete "show.js.erb" and add a JS layout, (e.g. "bars.js.erb") in the lookup path, which would look like this:
$("#foo").html("<% escape_javascript(yield(:foo)) %>");
In this way, the HTML template is rendered, but in the JS layout, and the HTML of #foo is swapped out for the new content of the response.
2. Render the HTML content in the JS response block.
However, #1 this was not an ideal answer for me. My app uses many nested layouts, most of which are very similar. To make the above example work I'd have to create a lot of JS layouts, all of which more or less copies of the original HTML layouts. A waste of time, and not at all DRY. So I came up with this solution.
It feels less ideal than #1, and please tell me if there's a more appropriate way. But this is what I came up with:
# in bars_controller.rb
def show
# ...
respond_to do |format|
format.js do
lookup_context.update_details(:formats => [:html]) do
#content = render_to_string
end
render
end
end
end
In this way I temporarily set the mimetype for the template lookup to be HTML, render the content to a variable, then render the JS template:
// show.js.erb
$("#foo").html("<%= escape_javascript(#content) %>");
There is one further complication to this. In my nested layout setup, in the HTML response, the layout calls the rendering of its parent to continue to build the body, leading to the complete page. In my case, I want it to simply return the body content. So while I don't need JS layouts for this solution, I do need to slightly change my layout, like this:
-# my_layout.html.haml
-# (given a parent layout that yields to :body)
- content_for :body do
= yield(:foo)
- if request.xhr?
= yield(:body)
- else
= render :file => "layouts/my_parent_layout"
In this way the parent is not called on a JS request, simply resulting in the body (up to this point in the nested layout stack).
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.