I'm attempting to make a two column layout. The column on the left is navigation, and the column on the right is content.
Is there a way to display show.html.erb, edit.html.erb, and new.html.erb at different times in the right div, when the corresponding navigation is selected, without re-loading the whole page?
I know I can use a partial for the left div, and render new pages, but I want to avoid having a separate page load for each view.
Item Controller:
def index
#item = item.find(:all, :order => "id DESC")
end
def new
#item = item.new
end
def create
#item = item.new(params[:item])
if #item.save
redirect_to root_path
else
render "new"
end
end
def edit
#item = item.find(params[:id])
end
def update
#item = item.find(params[:id])
if #item.update_attributes(params[:item])
redirect_to root_path
else
render "index"
end
end
HTML:
<div id="left" ">
<p id=link_to "Current_Item", item_path %></p>
<p id=Link_to "Add_Item", new_item_path %></p>
<p id=Link_to "Edit_Item", edit_item_path %></p>
</div>
<div id="right">
</div>
Routes File:
resources :items
Is "but I'm trying to avoid having a separate page load" means that you want to load your views by ajax instead of a complete new page? If so then your links should be a remote_link and your views should update the div id='right' with the content of corresponding view.
Your links should use :remote=>true option. See the details.
And your view should response to update the content within the <div id="right">.
Another link here with some more insights.
I would recommend modifying app/views/layouts/application.html.erb then put
<div id="left">
<p><%= link_to "Current_Item", item_path %></p>
<p><%= Link_to "Add_Item", new_item_path %></p>
<p><%= Link_to "Edit_Item", edit_item_path %></p>
</div>
<div id="right">
<%= yield %>
</div>
into it. Then the show.html.erb, edit.html.erb, and new.html.erb views should be automatically rendered for each appropriate action that gets called into the right section of your page.
Also I recommend Rails tutorial which is a great tutorial on rails. Then go to Codeschool to learn even more.
Related
I have a navigation bar included in application.html.erb. Because for some pages, such as the signup page, I need to place additional code inside the navigation bar, I have excluded those pages for showing the navigation bar through application.html.erb and instead included it in their respective view pages. See code below.
A problem arises when invalid data is entered in the signup form. The controller method then renders new. However, application.html.erb then doesn't seem to recognize that the current_page is still signup_path, therefore not applying the exception for not showing the navigation bar on that page. As a result when it renders new, the navigation bar is shown twice: once by order of application.html.erb and once by order of the view page itself.
Why, when rendering new on an invalid form entry, does it not see that it's still on signup_path? How should I adjust my code so that it does not show the navigation bar twice in that situation? Is there perhaps a way of including <%= yield special code if any %> in application.html.erb and <% special_code %> <% end special_code %> in the view page that passes this special code to application.html.erb?
In application.html.erb I have:
<% unless current_page?(signup_path) %>
<nav class="banner">
<%= render partial: "shared/header" %>
</nav>
<% end %>
In the view of my signup page:
<nav class="banner">
<%= render partial: "shared/header" %>
Additional code that needs to be within 'nav' for this page
</nav>
Controller method:
def create
#user = User.new(user_params)
if #stakeholder.save
flash[:success] = "A confirmation email has been sent to you."
redirect_to root_url
else
render 'new' ###This is where it goes wrong!
end
end
You can use content_for and yields to create a default in your layout which views can override.
# layouts/application.html.erb:
<% if content_for?(:banner) %>
<%= yield(:banner) %>
<% else %>
<div id="banner">
<h1>This is the default...</h1>
</div>
<% end %>
/users/signup.html.erb:
<%- content_for :banner, flush: true do -%>
<!-- move along, nothing to see here -->
<%- end -%>
The advantage here is that you don't end up turning your layouts into a birds nest of conditionals. You can easily just inject whatever you want into the layout from views.
The cons are that you have to use a stupid hack with a HTML comment to override the block to display nothing since content_for? trims the block. content_for does not play nice with fragment catching either.
addded
I didn't touch on this before unless current_page?(signup_path) does not work as you expect since render 'new' does not magically move you to the new action. In fact the current_path is /users since the form POST's to that url.
It just tells rails to find a template named 'new' and render it.
A corrected version would be:
<% unless controller_name == 'users' && ['new', 'create'].include?( action_name) %>
<nav class="banner">
<%= render partial: "shared/header" %>
</nav>
<% end %>
You are completely right. This is where it goes wrong
render 'new' ###This is where it goes wrong!
Here's what happens
user requests a new action, which renders the new template
user submits the form, thus requesting the create action in your controller
inside your create action you render your new template instead of create when validation fails
So basically user is no longer on the new page, but on the create page with a view rendered from new.
The easiest solution would be to change expectation for the header to both new and create actions, since you redirect on success, so you won't use it otherwise.
Hi I am quite new to Rails and am just setting up comments for my Shop_Profile model. I am using the acts_as_commentable gem to allow for polymorphic comments. I am allowing comments on the profile Show page so am displaying the list of comments and the new comment form on the same page.
My Show Action in the ShopProfilesController looks like this:
def show
#comments = #shop_profile.comments
#comment = #shop_profile.comments.new
end
And I am rendering the comment form and comments in the show view with:
<% if user_signed_in? %>
<%= render 'comments/form' %>
<% end %>
<%= render #comments %>
My Create action on my comment controller is:
def create
#comment = #user.comments.build(comment_params)
#commentable = Comment.find_commentable(params[:comment][:commentable_type], params[:comment][:commentable_id])
if #comment.save
redirect_to #commentable
end
end
and my _comment partial is:
<p>
<strong>Title:</strong>
<%= comment.title %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.comment %>
</p>
<p>
<small>By:</small>
<%= comment.user.username %>
</p>
The new #comment for the form keeps getting included in the #comments and is thus causing an error "undefined method `username' for nil:NilClass" because the new #commentn has no user_id.
How can I display my #comments without including this new #comment for the form_for?
Thanks for any help
Unfortunately (in this case) new/build adds the built object to the association's collection. So you'll need to declare your intent that you only want items stored in the database for the #comments collection.
You have two options I know of off the top of my head:
def show
#comment = #shop_profile.comments.new
#comments = #shop_profile.comments(true)
end
This forces the #comments to be loaded cleanly, so it will only contain the original list. Unfortunately you're hitting the database twice for the same list, that's silly.
Even better, I think, for this would be to do:
def show
#comments = #shop_profile.comments.to_a
#comment = #shop_profile.comments.new
end
So now you detatch the #comments collection from the active record association by making it an array, so the new call later on won't modify anything you still are holding on to.
You're creating an additional comment in your collection, and that new comment doesn't have an associated user yet, and isn't saved in the database yet.
If you want the new comment to be skipped entirely, you can do this:
<%= render #comments.reject{|c| c == #comment } %>
If you want the new comment to show up, but skip the "By" section, you can do this:
<% if comment != #comment %>
<p>
<small>By:</small>
<%= comment.user.username %>
</p>
<% end %>
I have a model "Thing" which has_many "Comments". I want the list of #thing.comments to refresh with ajax when the "post a comment" button is pressed.
This code works to list the text of the first comment:
view
<script type="text/javascript">
$("#post").click(function () {
$.get( "<%= postcomment_thing_path(:id => #thing.id) %>", function( data ) {
$('#comments_h2').html(data);
});
});
</script>
controller:
def postcomment
#thing = Thing.find(params[:id])
render text: #thing.comments.first.text.to_s
end
But when I try to print the whole comment block, it only prints one single "#".
controller:
def postcomment
#thing = Thing.find(params[:id])
render text:
#thing.comments.each do |comment|
comment.text.to_s
end
end
How can I print the text for all the comments?
You don't need to load all comments when a new one is posted, you load the old ones with rails and display them, then with ajax you append/prepend a new one to the list of old ones.
http://api.jquery.com/prepend/
http://api.jquery.com/append/
I assume your comments are in a div:
<div class="comments">
<% #thing.comments.each do |comment| %>
<div class="comment_<%= comment.id %>">
<%= comment.content %>
</div>
<% end %>
</div>
you form where you add a comment should have remote: true:
<%= form_for #comment, remote: true do |f| %>
inputs here
<% end %>
when you add a comment, I assume it's action create that's being called, so you'll need a create.js.erb file in app/views/comments/ folder and a _comment.html.erb in the same location.
create.js.erb will contain your js code that will append/prepend the comment to the div.comments element:
<% if #comment.valid? %> // #comment should be loaded in controller create action.
$('div.comments').append("<%= j(render(#comment)) %>");
$("#new_comment")[0].reset();
<% else %>
alert("Can't add comment");
<% end %>
You should add a "remote: true" to your "post comment" button. From there, you can have your controller respond_to |format| and have the data updated in realtime.
More information about working with ajax in Rails found here
Well, while its not a good idea to be displaying all the comments again as suggested by #rmagnum2002. You should be appending or prepending the current comment based on your requirements. However, to get the current code working you must make some changes
def postcomment
#thing = Thing.find(params[:id])
comment_text = ""
#thing.comments.each do |comment|
comment_text += comment.text.to_s
end
# you could use map and join them by a delimiter also
render text: comment_text
end
and that should render what you expect.
Hope that helps
I am currently trying to optimize rendering a page in ruby on rails.
I have a question, is there a way to make a page, only render another page when a link (href) associated to it is clicked.
some codes in index.html.erb:
.....
.....
<h4>
<%= item.name %>
<br/>Details
</h4>
<div id="<%=item.id%>_item_modal" class="modal fullscreen">
<div class="item-modal">
<%=render item%>
</div>
</div>
.....
.....
In code above, I am using modal. So that when the link is clicked, _item.html.erb will be shown appear in a smaller page. Similar like a popup.
When index.html.erb is loaded, then all codes inside _item.html.erb will also be loaded automatically and all queries inside will be executed because of
<%render item%>
code.
What I want is, I don't want index.html.erb also render all item inside _item.html.erb page unless I click "Details" button.
<br/>Details
I think by doing this think, I can save some amount of time when a user requests for item page, because the system doesn't need to retrieve all information which are not yet required.
Could you guys help me?
Please let me know if you need another information.
You have to use ajax in order to achieve this. You can create a hidden div with a specific id, say "modal".
Revert your h4 to this:
<h4>
<%= item.name %>
<br/>
= link_to 'Details', item_path(item), data: {remote: true}, item_details: true
</h4>
Then in your item controller, on the show action:
def show
# load the item
if params[:item_details]
respond_to {|format| format.js}
else
# do what you already do here
end
end
And in your item views folder, create a file show.js(.coffee):
$('#modal').html('<%= j(render partial: "item", object: #item) %>');
$('#modal').modal('show');
This is just the idea, you have to make it compatible with your code.
If you want to go the ajax route:
<h4>
<%= item.name %>
<br/>
<%= link_to "Details", item_path(item), remote: true %>
</h4>
Then in your items_controller:
def show
...
respond_to do |format|
format.js
end
end
Then create the view "views/items/show.js"
$(body).append(<%= escape_javascript(render partial: "items/item_modal") %>);
...code to show modal...
...hook to remove modal when it is dismissed...
And the view "views/items/_item_modal.html.erb"
<div id="<%=item.id%>_item_modal" class="modal fullscreen">
<div class="item-modal">
<%=render item%>
</div>
</div>
New to AJAX and search. I feel like I'm an inch away on this one, but I'm not sure what to fix. Also, my controller looks really hacky to me right now.
At any rate, I'm trying to create a search that allows users to search through blog posts on my page using AJAX. Here are the (relevant parts of the) parts:
posts_controller.rb
def show
#posts = Post.all.reverse
#post = Post.find(params[:id])
#link_num = 10
respond_to do |format|
format.html # show.html.erb
format.json { redirect_to #post }
end
end
def search
#link_num = 10
#posts = Post.all.reverse
#post = Post.find(params[:id])
#The including function returns the search results
#search = Post.first.including(params[:term])
render 'show'
end
What strikes me as "hacky" here is that I repeat all the variable assignments (there are others I didn't show cause they're not relevant). Shouldn't an AJAX call ideally not have to redefine/reload all these variables? Also, I have to pass :id to my search action through a hidden field. This feels weird/wrong to me.
show.html.erb
<h1 class="squeeze">Recent Posts</h1>
<%= form_tag("/search", method: "get", class: "search") do %>
<%= text_field_tag(:term, '', placeholder: "Search posts:") %>
<%= hidden_field_tag(:id, #post.id) %>
<%= submit_tag("Search", class: "btn search_button", remote: true) %>
<% end %>
<% if !#search%>
<ul>
<% #posts.first(#link_num).each do |p| %>
<li>(<%= p.created_at.strftime("%b %d, %Y") %>)</span></li>
<% end %>
<% if #posts.length > #link_num %>
<div class="link_disclaimer">
<h4>---</h4>
<h5><%= "Only showing #{#link_num} most recent posts." %></h5>
<h5>Search to narrow results.</h5>
</div>
<% end %>
</ul>
<% elsif #search.empty? %>
<h3>Term not found!</h3>
<% else %>
<ul>
<% #search.first(#link_num).each do |p| %>
<li>(<%= p.created_at.strftime("%b %d, %Y") %>)</span></li>
<% end %>
<% if #search.length > #link_num %>
<div class="link_disclaimer">
<h4>---</h4>
<h5><%= "Only showing first #{#link_num} relevant hits." %></h5>
<h5>Narrow search for more specific results.</h5>
</div>
<% end %>
</ul>
<% end %>
routes.rb
match '/search', to: 'posts#search'
Currently, the search itself works fine, with three major problems:
The aforementioned messiness of my Controller.
The fact that the whole page reloads. Isn't that the opposite of what AJAX is supposed to do? How can I get it to reload just the list of links?
The URL. It's super messy (i.e "/search?utf8=✓&term=limits&id=11&commit=Search"). I'd ideally have it remain untouched by the search, but if that's not possible, maybe just something like "/search?=". How's that done?
Based on the comment here is basic logic to make the function work(Sorry for no real code as that is too time consuming)
In controller you make a method say "search". The method need an argument which is the phrase to search. It gets the result from db and respond to JSON only with the result.
In template you have a search form.
In JS, you listen the event of user clicking the submit button, catch the characters they write, and handle it to ajax function.
Write Ajax code, preferred using jQuery's ajax(). Basically use ajax() to send the typed characters to controller method in #1 as argument. Then ajax() will get the response(the search result) from server.
Update the result in DOM. You can either add a new div to show search result, or replace current main content div to show result.