Render show view in a partial - Ruby on Rails - ruby-on-rails

So I have a sidebar in my rails application. Basically, I have a grid full of images. When someone clicks on an image in the grid, I basically want the show view to be displayed in the sidebar. I'm clueless on how to do this.
I've tried copying and pasting the show view's html and erb into my _sidebar.html.erb partial. But I get variable errors. My partial is located in my layout folder. But I can move it if needed.
Here is an image of the application for a better idea of what I'm talking about.

Here's how to do it:
1) Include the sidebar partial in your layout:
#app/views/layouts/application.html.erb
<%= render "shared/sidebar" %>
This calls the _sidebar.html.erb partial in /app/views/shared:
2) Populate the "default" sidebar partial:
#app/views/shared/_sidebar.html.erb
...
The important thing to note here is that partials should never have #instance variables inside.
Partials have locals, which you can pass with the following command:
<%= render "shared/sidebar", locals: { variable: "value" } %>
Partials are meant to run on any part of your web application, and since #instance variables are present for a single "instance" of an object (IE #post won't be available in /users), you can ensure partials are always populated by passing local variables to them.
This is why you received errors when copying your post#show code to your sidebar partial -- the instance variables present for the show action will not be present in other parts of the app.
3) Use JS to pull image objects
When someone clicks on an image in the grid, I basically want the show view to be displayed in the sidebar.
You need to be more specific - do you really want the "show" action to appear, or do you want parts of its functionality?
Either way, you'll be best using JS & ajax to pull the data:
#app/controllers/images_controller.rb
class ImagesController < ApplicationController
layout Proc.new { |controller| !controller.request.xhr? }
end
#app/assets/javascripts/application.js
$(document).on("click", "img", function(e) {
e.preventDefault();
$.get($(this).attr("href"), function(data){
$(".sidebar .image").html(data);
});
});
This will take the rendered images#show action (without layout), and append it to your sidebar partial. This is what you asked for - I can change it if required.

Lets say that your model is called "Image".
In your "images/show.html.erb":
# This is a handy shortcut that will call the 'images/_image' partial.
# Those two lines are exactly the same.
<%= render #image %>
<%= render "images/image", image: #image %>
In your "_sidebar.html.erb":
<div id="sidebar-img"></div>
In your "images/_image.html.erb":
# Code displaying your image, don't use any global variable.
# 'image' is the name of the local variable
In your grid:
# remote: true allows you to do an AJAX call
<%= link_to image, remote: true do %>
# Something like that, I don't know your code
<%= image_tag image.url %>
<% end %>
Create "images/show.js.erb", when clicking on a 'remote :true' link it will call this file.
# set #image to Image.find(params[:id]) in your controller
$("#sidebar-img").html("<%= render #image %>");

Related

How to pass object to partial using controller?

I've been trying to pass my Product object to my rendered partial but no matter what I try it doesn't work. The home page has a quick view button that pops a modal (the partial) and I want to pass the correct product to it.
Route
get 'shop-product-ajax-page', to: "pages#shop_product_ajax_page"
Home Page (shortened to only the link for brevity)
<% #products.each do |product| %>
<%= link_to "Quick View", shop_product_ajax_page_path, :data => {:lightbox => 'ajax'} %>
<% end %>
Controller Action
def shop_product_ajax_page
render :partial => 'pages/shop_product_ajax_page', :layout => false
end
Right now, the button works and displays the HTML in the modal. I want to be able to populate the correct product information for whatever Quick View product is selected.
The problem is that the link is making a completely separate AJAX request, it's hitting the server separately, so the Ruby context you expect (variables etc) isn't available in that new request.
Two choices:
Don't make an AJAX request but render the lightbox as part of the page. You could hide it using display: none or similar, then use Javascript to display it when the link is clicked.
Make the request the way you currently are, but pass in the same parameters that your current controller action is using to get #products and in shop_product_ajax_page do the same thing to hit the database and get the products.
The second choice might be easier without messing with JS. It would be something like:
def shop_product_ajax_page
#products = get_products_from_params(params)
render :partial => 'pages/shop_product_ajax_page', :layout => false
end
private
def get_products_from_params(params)
Product.find(params["product_ids"]) # or whatever you're currently doing
end

Ajax calls - send_data and render a partial - two render methods in a single action

Users can download reports via a link, and next to the link is a text flag indicating whether or not they have downloaded the file already - an 'unread' alert. I hava a partial which shows a single item in an index of reports:
<p id="chapter_report_index_item">
<%= link_to "#{report.chapter_report_original_filename}", chapter_report_path(report), remote: true %>
<%unless check_read_status?(report) %>Unread
<%end%>
<p>
In chapter_reports_controller.rb I have -
def show
set_chapter_report
set_read_status_true(#chapter_report)
send_data #chapter_report.chapter_report.read, filename: #chapter_report.chapter_report_original_filename
respond_to do |format|
format.js
end
end
And in show.js, a call to re-render this single item partial -
$("#chapter_report_index_item").html( "<%= j (render(partial: 'index_item', locals: {report: #chapter_report})) %>" );
You'll note the check_read_status method in the view - essentially the idea is that when the partial is displayed, the unread flag is displayed if the user has not clicked on the download link.
My problem is, I'm trying to render two things in the action: the download, and the partial. What's the technique for avoiding this?
Essentially, I want a download button that changes when the file has been downloaded.
Update
I'm trying to solve this though a javascript to a helper (as per comments below). My problem is that send_file and it's ilk are not available as methods in application_helper.rb
Essentially I've got a jQuery call to a partial which just contains a call to the download method -
<%= report.class.name %>
<%= report.chapter_report_original_filename %>
<%= asset_download(report) %>
In the helper -
def asset_download(object)
object.chapter_report_original_filename
#send_file(object.chapter_report.read, filename:object.chapter_report_original_filename)
end
I've put the calls to .class.name etc to see what's getting through. When I uncomment the send_file call, I get the error -
ActionView::Template::Error (undefined method `send_file' for #<#<Class:0x0...
I'm guessing cos the method is available to the ActionController not ActionView.
So the question is - how do I make controller methods available in this context? Or do I find another way to do this?
Update 2 as per the answer below, you can call the send_file method by rendering a partial which contains the call through javascript. You need to make the method available in the helper as per this question.
You could split your action in two seperate actions (the download and the ajax call) and write some frontend (javascript) code that listens for the download click and triggers a second ajax call to the update_read_status action (for example via jquery).

Rails partial from different controller in a Bootstrap tab

I have a Home page with 2 Bootstap tabs. I'm having problems with the 2nd tab. I'm trying to render a list of workorders. For that list I'm using dataTables and ajax.
My issue is that workorders/index7.html.erb works fine as a stand alone page. But, I want to use it in a Bootstrap tab on the Home page.
For all my other Bootstrap tab lists, I'm using partials from within the same controller. But, in this case, I need to use a view from the Workorder controller not the Home controller.
This is the code I'm trying in the Home view:
<div class="tab-pane" id="tab2">
<%= render "workorders/index7" %>
</div>
That code gives me:
Missing partial workorders/index7
WHY? If I change the file name to _index7.html.erb the code won't execute to get the json.
But, because it's a partial, starting with _, the connection to the Workorder controller code for index7 doesn't work. I also tried _index7 in the Workorder controller.
The Workorder controller has this code:
def index7
#workorders = Workorder.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: Workorders3Datatable.new(view_context) }
end
end
That code uses a file called workorders3_datatable.rb to get the data via ajax and format it for dataTables.
QUESTIONS:
1) Do I have to use a partial in the Home tab? Or is there a way to render a file called workorders/index7.html.erb in the tab?
2) Why does index7.html.erb use the controller code for index7, yet the partial _index7.html.erb won't use code if I call it _index7 in the controller?
3) I read this "Render will just render the view, it won't run the action associated with it." - is that true?
Thanks for your help - I know this is confusing.
Reddirt
UPDATE 1
The view runs great as workorders/index7 - but, if I change it to a partial and put it in a tab = workorders/_index7 - the controller code for index7 doesn't seem to execute.
1) Do I have to use a partial in the Home tab? Or is there a way to view a file called workorders/index7.html in the tab?
If you want to follow this approach. From Home controller pass the collection (#workorders = Workorder.all)
Then in the home view pass it to the workorders/index7 as locals
<div class="tab-pane" id="tab2">
<%= render "workorders/index7" locals: {workorders: #workorders} %>
</div>
This will make it available to workorders/index7 do remember to change the variable to a local variable workorders.
You need to include :partial in the view for your Home page:
<%= render :partial => "workorders/index7" %>
The app/views/workorders folder will need a file called _index7.html.erb. To accomplish this from your current setup, you can move the contents of index7.html.erb into this partial, and then use:
<%= render :partial => "index7" %>
in the original index7.html.erb. That way you can render that as a whole page and within your Home page as a partial.

rails navigation and partial refresh

Thanks for your time!
I get some reports data on my hand and I want to present these data on the web. The view of the HTML will be divided into two parts, the left part and the right part. There's a tree view in the left part consisting of the report names. In the right part presents the contents of the report.
What I want to achieve is when I click the report name on the left part, it will call an Action in the Controller, and passed the report name as parameter. The Action will fetch the data from the database and represent the data in the right part. And now I am stuck on how to realize this kind of view.
I've Googled a lot on the Internet and found Frameset, Partials or Ajax may capable of this. Because I've never developed web applications before and also new to Rails. So can anyone give me some advise or suggestion?
Things I've already known :
I've used Frameset to accomplish a view like this. But I found it needs a lot of .html files and all these .html files are static. And many people don't suggest it at all.
Then I've Googled Partials. But it seems Partials don't call the Action. It directly loads the _partials.html.erb to the main view. And besides, how can I control the layout? Using CSS?
I've never used Ajax.
If you want a fluid, seamless transition between one report and another, you should use both AJAX and Partials.
The way that it works is something like:
Make a left column in the html that has some links
Make the right column inside a partial
Assign the links to jQuery listeners to call the AJAX.
I'll put a bit of code here to show how it works:
Controller:
def index
reports = Report.all
if params[:report_id]
reports = Report.find(params[:report_id]
end
respond_to do |format|
format.html
format.js { render :template => "update_reports" }
end
end
update_reports.js.erb (in the same folder as the report views):
$('#report_viewer').html('<%= escape_javascript render :partial => "report_detail" %>');
In your view:
<div style=float:left>
<ul>
<li><%= link_to "Some report", "#", :class => "ajax" %></li>
</ul>
</div>
<div style=float:right id="report_viewer">
<%= render :partial => "report_detail" %>
</div>
<script type='text/javascript'>
$(document).ready(function() {
$(".ajax").click(function(e) {
$(this).ajax("your route to the action");
}
});
</script>
I think it's basically this, now let me explain a few things:
I don't remember if you have to do this, but in my case I created a new custom route to force the call to the action to be a json call instead of a html one. You can do this by adding :format => "js" to your route
You must name all your partials like "_yourname.html.erb". Rails won't recognize partials without the leading underscore.
In the controller, everything that comes after "format.js" is optional, you don't need to specify the template name, and if you don't Rails will look for the file index.js.erb.
The update_reports.js.erb file is basically a callback javascript that executes to update the current page. It finds the div where the partial is, and updates it rendering a new partial with the new report.
In the view, the link to change the report don't need to be a link at all if you're using the jQuery.click listener, but if it is a link, it must have the href as "#", or else the browser will just try to redirect to that location.
There are several ways to hook your link to the ajax function, I just chose the one I like it better, but you also could have a named function and call it in the html tag "onClick='yourFunction()'".
You need jQuery to call ajax like this. If you're sing Rails 3.0 or lower, you should replace the default Prototype with jQuery, because it's much better (IMHO), but I think prototype also have some ajax features.
It may seem complicated, but once you get the idea of it it'll become simple as writing any other action.
In the js callback file you could also add an animation to smooth the transition, like a fading. Look for the jQuery fade function for more info on this.
This is quite an open question so don't take this answer verbatim, but merely as a guide.
app/views/layouts/reports.html.erb
<!DOCTYPE html>
<html itemscope itemtype="http://schema.org/">
<head>
# omitted
</head>
<body>
<div id="body-container">
<div id="left-column">
<ul id="reports-list">
<% for report in #reports %>
<li><%= link_to report.name, report %></li>
<% end %>
</ul>
</div>
<div id="right-column">
<%= yield %>
</div>
</div>
</body>
</html>
app/controllers/reports_controller.rb
class ReportsController < ApplicationController
before_filter { #reports = Report.all }
def index
end
def show
#report = Report.find(params[:id])
end
def edit
#report = Report.find(params[:id])
end
def new
#report = Report.new
end
def update
# omitted
end
def create
# omitted
end
def destroy
#report = Report.find(params[:id])
end
end
routes.rb
YourApp::Application.routes.draw do
resources :reports
root to: "reports#index"
end
This would achieve the effect your after using just rails, of course adding ajax could add a better user experience.

rails access view name inside partial

I have a partial that I am using in different pages. I want to conditionally hide a certain div inside the partial based on the view that is rendering it.
I was thinking about creating a page specific javascript file that would look up the div and and hide it.
But, if there was a way to retrieve the view name / page name inside the partial it would be central to the partial and would not necessitate loading the same javascript file in multiple pages.
Does anybody know a way to do this inside a partial
While #wahaj's answer would work, if you want to do what you need in a central location, you could check the controller_name and action_name variables in the partial to determine the view you're in (e.g. controller_name == "services" and action_name == "show" would let you know you're in the Show view for the Service controller)
you can get the name of the currently-rendering partial from within a Helper method with the following :
controller.view_context.view_renderer.instance_variable_get('#_partial_renderer').instance_values['path']
You could send the style parameter as a local variable to the partial, varying the parameter depending on where you're calling from. Something like:
render :partial => 'xyz', :locals => {:style => 'display:none or display:block'}
and in the partial you could do:
<div style=<%=style%>></div>
simple solution , inside your partial check if you need to show partial or not
_partial_name.html.erb
<%= content_tag :div, :style=> "display : #{show_div? ? 'block' : 'none'}" do%>
html...or other stuff
<%end%>
and then in application helper
app/helper/application_helper.rb
def show_div? #you can set name of your div like 'show_sidebar_div?'
#decide either show this div or not
action_name == 'show'
end

Resources