Rails render partial with block - ruby-on-rails

I'm trying to re-use an html component that i've written that provides panel styling. Something like:
<div class="v-panel">
<div class="v-panel-tr"></div>
<h3>Some Title</h3>
<div class="v-panel-c">
.. content goes here
</div>
<div class="v-panel-b"><div class="v-panel-br"></div><div class="v-panel-bl"></div></div>
</div>
So I see that render takes a block. I figured then I could do something like this:
# /shared/_panel.html.erb
<div class="v-panel">
<div class="v-panel-tr"></div>
<h3><%= title %></h3>
<div class="v-panel-c">
<%= yield %>
</div>
<div class="v-panel-b"><div class="v-panel-br"></div><div class="v-panel-bl"></div></div>
</div>
And I want to do something like:
#some html view
<%= render :partial => '/shared/panel', :locals =>{:title => "Some Title"} do %>
<p>Here is some content to be rendered inside the panel</p>
<% end %>
Unfortunately this doesn't work with this error:
ActionView::TemplateError (/Users/bradrobertson/Repos/VeloUltralite/source/trunk/app/views/sessions/new.html.erb:1: , unexpected tRPAREN
old_output_buffer = output_buffer;;#output_buffer = ''; __in_erb_template=true ; #output_buffer.concat(( render :partial => '/shared/panel', :locals => {:title => "Welcome"} do ).to_s)
on line #1 of app/views/sessions/new.html.erb:
1: <%= render :partial => '/shared/panel', :locals => {:title => "Welcome"} do -%>
...
So it doesn't like the = obviously with a block, but if I remove it, then it just doesn't output anything.
Does anyone know how to do what I'm trying to achieve here? I'd like to re-use this panel html in many places on my site.

While both of those answers above work (well the example that tony links to anyway) I ended up finding the most succinct answer in that above post (comment by Kornelis Sietsma)
I guess render :layout does exactly what I was looking for:
# Some View
<%= render :layout => '/shared/panel', :locals => {:title => 'some title'} do %>
<p>Here is some content</p>
<% end %>
combined with:
# /shared/_panel.html.erb
<div class="v-panel">
<div class="v-panel-tr"></div>
<h3><%= title -%></h3>
<div class="v-panel-c">
<%= yield %>
</div>
</div>

Here's an alternative based on previous answers.
Create your partial on shared/_modal.html.erb:
<div class="ui modal form">
<i class="close icon"></i>
<div class="header">
<%= heading %>
</div>
<div class="content">
<%= capture(&block) %>
</div>
<div class="actions">
<div class="ui negative button">Cancel</div>
<div class="ui positive button">Ok</div>
</div>
</div>
Define your method on application_helper.rb:
def modal_for(heading, &block)
render(
partial: 'shared/modal',
locals: { heading: heading, block: block }
)
end
Call it from any view:
<%= modal_for('My Title') do |t| %>
<p>Here is some content to be rendered inside the partial</p>
<% end %>

You can use the capture helper, and even inline in the render call :
<%= render 'my_partial',
:locals => { :title => "Some Title" },
:captured => capture { %>
<p>Here is some content to be rendered inside the partial</p>
<% } %>
and in shared/panel:
<h3><%= title %></h3>
<div class="my-outer-wrapper">
<%= captured %>
</div>
which will produce:
<h3>Some Title</h3>
<div class="my-outer-wrapper">
<p>Here is some content to be rendered inside the partial</p>
</div>
See http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html

Based on the accepted answer this is what worked well for me using Rails 4.
We can render a panel as such:
= render_panel('Non Compliance Reports', type: 'primary') do
%p your content goes here!
This requires a helper method and a shared view:
helper method (ui_helper.rb)
def render_panel(heading, options = {}, &block)
options.reverse_merge!(type: 'default')
options[:panel_classes] = ["panel-#{options[:type]}"]
render layout: '/ui/panel', locals: { heading: heading, options: options } do
capture(&block)
end
end
View (/ui/panel.html.haml)
.panel{ class: options[:panel_classes] }
.panel-heading= heading
.panel-body
= yield

I think it will work (just did quick dirty test) if you assign it to a variable first and then output it.
<% foo = render :partial => '/shared/panel', :locals =>{:title => "Some Title"} do %>
<p>Here is some content to be rendered inside the panel</p>
<% end %>
<%= foo %>

Related

Rendering with jquery and locals

Im having trouble rendering with jquery, I have a notification page which shows all notifications, then when i click on 1 for example it will show all the 1's and if i click 2 it will show all the 2's etc etc.
This is where all the notifications are rendered to start with. The locals work fine here.
<% #notifications.each do |x| %>
<div class="testing123">
<%= render 'notification', x: x %>
</div>
<% end %>
Then i click the link to change them
and here is the controller
def index
if params[:type]
#notifications = Notification.where(notification_type: params[:type])
else
#notifications = Notification.all
end
respond_to do |format|
format.html
format.js{
render 'notifications/jserb/select_notifications'
}
end
end
Which then arrives here:
$('.testing123').empty();
$("<%= escape_javascript(render #notifications, :locals => {:x => x}) %>").appendTo(".testing123");
But i keep getting this error
ActionView::Template::Error (undefined local variable or method x' for #<#<Class:0x007fae10882ed8>:0x007fae0f6234b8>):
EDIT-1: here is where i click the link:
<%= link_to "likes", notifications_path, remote: true %>
EDIT-2: here is the notification:
<div class="notification-body flerowspb" id="body<%=x.id%>">
<div class="flerowspb" style="width:80%">
<div class="notif-check flecolcen">
<%= check_box_tag "read_notif[]", value="#{x.id}", checked = false, options = {class: 'checked-id', id: "check-#{x.id}"} %>
</div>
<div class="notif-details ">
<div class="notif-time flerowspb">
<% if !x.viewed %>
<span class="glow" id="glow-<%=x.id%>"> New <%= x.notification_type.capitalize %></span>
<% else %>
<span><span class="text-grey"> New <%= x.notification_type.capitalize %></span></span>
<% end %>
<span class="text-grey font-small"> <%= time_ago_in_words(x.created_at) %> ago</span>
</div>
<div class="notif-body font-medium text-grey">
You have <%= x.status_count %> <%= x.title %> <br>
Click <span class="text-blue"><%= link_to "Here", entry_path(x.entry_id, notification_id: x.id) %> </span> to view it
</div>
</div>
</div>
<div class="notif-image">
<%= image_tag x.entry.image.small_thumb %>
</div>
</div>
EDIT-3: here is the full error that i get:
ActionView::Template::Error (undefined local variable or method `x' for #<#<Class:0x007fae0e6f32b8>:0x007fae11ac1838>):
1: <div class="notification-body flerowspb" id="body<%=x.id%>">
2: <div class="flerowspb" style="width:80%">
3: <div class="notif-check flecolcen">
4: <%= check_box_tag "read_notif[]", value="#{x.id}", checked = false, options = {class: 'checked-id', id: "check-#{x.id}"} %>
Any help will be appreciated
Issue with your code is after click on link it hits the notifications action and where you are getting #notifications as array of notifications, so after this in your js.erb you are rendering appending partial notification.html.erb with local variable x but here at your controller you are getting #notifications variable,
This should be like:
in notifications/_notification.html.erb
<div class="testing123">
<% notifications.each do |x| %>
<%#= render 'notification', x: x %>
#...
<% end %>
</div>
and in notification.js.erb
$('.testing123').empty();
$(".testing123").append("<%= escape_javascript(render 'notifications/notification', notifications: #notifications) %>");

Rails: How can I dynamically load from db into a bootstrap accordion only if it opens

I am using the
Awesome Nested Set gem to create a hierarchy now I want to display this hierarchy.
I have an index page with a typical controller action
klasses_controller.rb
def index
#klasses = Klass.where(depth: 0).order('lft ASC')
end
klasses/index.rb
<div id="accordion" role="tablist" aria-multiselectable="true">
<% #klasses.each do |klass| %>
<%= render :partial => "klass", locals: {klass: klass} %>
<% end %>
</div>
in my index action I am calling a partial
klasses/_klass.html.erb
<div class="klass">
<div class="klass-header" role="tab" id="heading-<%=klass.id%>">
<h5 class="mb-0">
<a data-toggle="collapse"
data-parent="#accordion"
href="#collapse-<%=klass.id%>"
aria-expanded="false"
aria-controls="collapse-<%=klass.id%>">
<%= klass.symbol + " : " + klass.title%>
</a>
</h5>
</div>
<div id="collapse-<%=klass.id%>"
class="collapse"
role="tabpanel"
aria-labelledby="heading-<%=klass.id%>">
<div class="klass-block">
<Here is where I want to render partial for each child>
</div>
</div>
</div>
This is standard accordion and is working as is. Here is the problem I have a very big db and I don't want to render anything unnecessarily. I want to wait until the header is clicked and then asynchronously retrieve the children of the class i clicked and render each using the same partial.
How can I achieve this? Is there a way using a route/controller action combination or should I start writing coffee/java-script? I am not trying to reinvent the wheel so if anyone knows of an example that would be welcome.
This was a tricky problem only because there were a lot of moving parts. It was a real headscratcher but in hindsight it is pretty straight forward.
First I wanted to use a custom controller action so I added a route for my "children" method which is set up to receive an ajax call.
routes.rb
get 'klasses/:id/children', to: 'klasses#children', as: 'children_of'
klasses_controller.rb
def index
#klasses = Klass.where(depth: 0).order('lft ASC')
end
def children
respond_to do |format|
format.js {render 'children', :klass => #klass }
end
end
This respond block is expecting to have a children.js.erb to call for.
children.js.erb
$("#collapse-<%=#klass.id%>").html("<%= escape_javascript(render :partial => 'klasses/children', locals: { :klass => #klass })%>");
This is where we specify the div that we are going to insert the new content into and we call another partial.
_children.html.erb
<div class="child-block">
<% children = #klass.children %>
<% children.each do |child| %>
<%= render :partial => 'klasses/klass', locals: { :klass => child } %>
<% end %>
</div>
Finally I rewrote my _klass.html.erb partial so It has a link in the heading that will both trigger bootstrap's accordion js and call our new controller action.
_klass.html.erb
<div class="klass">
<div class="klass-header" role="tab" id="heading-<%=klass.id%>">
<h5 class="mb-0">
<%= link_to klass.symbol + " : " + klass.title,
children_of_path(klass.id),
remote: true,
:data => { :toggle => "collapse",
:parent => "#accordion",
:target => "#collapse-#{klass.id}"
},
:aria => { :expanded => "false",
:controls => "collapse-#{klass.id}"
}%>
</h5>
</div>
<div id="collapse-<%=klass.id%>"
class="collapse"
role="tabpanel"
aria-labelledby="heading-<%=klass.id%>">
</div>
</div>
That's pretty much it. Add some padding to the child-block and you have yourself a nice indented Hierarchy that dynamically loads content.
I did have to disable cross site Request forgery for my custom action in my controller
protect_from_forgery :except => :children
If anyone knows a way around that let me know.

Rails: 'Load More' button with Ajax & Kaminari

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.

Cleanup erb files by abstracting/moving ruby code

I was wondering if it would be possible to cleanup or reorganize my views so that I have less Ruby code. The HTML often becomes cumbersome to work with because it has so much Ruby code.
I thought about moving all the Ruby stuff into helpers and assign each function (links, tags etc.) to methods.
Example. Problem becomes much worse with a more complicated layout.
<div class="sidebar">
<div id="art_nav">
<%= link_to "Previous", art_path(#previous), :remote => true, :class => "prev" unless #previous.nil? %>
<%= link_to "Next", art_path(#next), :remote => true, :class => "next" unless #next.nil? %>
</div>
</div>
Would become:
<div class="sidebar">
<div id="art_nav">
<%= link_to_previous %>
<%= link_to_next %>
</div>
</div>
Helper:
def link_to_previous
link_to "Previous", art_path(#previous), :remote => true, :class => "prev" unless #previous.nil?
end
def link_to_next
link_to "Next", art_path(#next), :remote => true, :class => "next" unless #next.nil?
end
This seems to work with simple examples.. but I am wondering how I should organize stuff when I have to do loops or similar.
UPDATED: Loop example added
<% arts.each do |art| %>
<h3><%= art_title %></h3>
<p><%= art_description %></p>
<div id="comments_<%= art.id %>">
<%= render :partial => "/comments/index", :locals => {:resource => art} %>
</div>
</div>
<% end %>
What would you do?
Here is a pattern you could use in your code, to clean it up further:
def menu(*options, &block)
params = options[0]
active = (params[:active] if params) || false
first = (params[:first] if params) || false
"
<div class=\"menu\">
<div class=\"menu-left #{active ? "active" : "inactive"} #{first ? "first" : "not-first"}\">
</div>
<div class=\"menu-center\">
<div class=\"menu-content #{active ? "active" : "inactive"}\">
#{capture(&block)}
</div>
</div>
<div class=\"menu-right #{active ? "active" : "inactive"}\">
</div>
</div>
".html_safe
end
With this helper, code gets very simple:
<%= menu(:active => #active_menu == :menu1 ? true : false, :first => true) do %>
<%= link_to "Les produits et les prix", menu1_path %>
<% end %>
At the center of this pattern, there is the call
#{capture(&block)}
Sorry this example is not "generic" at all, I did a copy-and-paste from my own code. But I'm sure you get the idea.

Rendering Partial onclick in Rails

I am trying to render a partial onclick to save on page loading time.
<ul id="pop_twitter">
<% #topic_count.each do |k,v| %>
<% #k = k %>
<!-- make a link that onclick renders a partial below -->
<li><% link_to_remote k, ?????? %></li>
<ul id="twitter_<%= k %>" style="display:none;">
<!-- where the partial should load -->
</ul>
<% end %>
</ul>
I need to load the partial 't_tweets' and it also need the variable 'k' in from the .each loop.
here's a rough start:
link_to_remote("Show", :url => {:action => 'show_tweets', :k => k})
EDIT: i guess since you're only showing a partial, it would make sense to also pass :method => :get, since link_to_remote defaults to :post.
and in your controller
def show_tweets
render :update do |page|
page.insert_html :bottom, 'pop_twitter', :partial => 't_tweets', :locals => params[:k]
page["tweet_#{params[:k]}"].visual_effect :highlight #using scriptaculous
end
and your partial
<div id="<%= "tweet_#{k}" %>">
<%= tweet.body %>
</div>

Resources