Rails/AJAX: Partial not displaying? - ruby-on-rails

Background (changed after correspondance with #TomLord):
I am building a simple site with only a index page and no subpages. The index page has a "search" field that takes input (id) and sends a GET request to a RESTful API running in the background. The response from the API is JSON {"market price": "4306057.0", "retail price": "4995000.0"}.
I want to display this result on the index page, together with the search field. However, when I press the button the result is not displayed anywhere.
Code:
index.html.erb
<section id="search">
<div class="container">
<%= form_tag({controller: "model_request", action: "result"}, method: "get", remote: true) do %>
<%= label_tag(:q, "Search for") %>
<%= text_field_tag(:q, "913384637") %>
<%= submit_tag("Get the price!") %>
<% end %>
</div>
</section>
<section id="display_result">
<div class="container">
</div>
</section>
And the modelrequest_controller.rb looks like this:
class ModelrequestController < ApplicationController
def result
id = params['q'].capitalize
response = RestClient::Request.execute(
method: :get,
url: "http://0.0.0.0:80/?id=#{id}")
#result = JSON.parse response
respond_to do |format|
format.js {render layout: false}
end
end
end
My routes.rb
Rails.application.routes.draw do
root 'index#index'
match "/modelrequest", to: "modelrequest#result", via: 'get'
end
The javascript for results looks like this:
result.js.erb
$("#display_result").html("<%= escape_javascript(render partial: 'modelrequest/result', locals: { result: #result } ) %>");
And the simple _result.html.erb for displaying the partial is
<div id="display_result">
<%= #result %>
</div>
Output:
Started GET "/model_request?utf8=%E2%9C%93&q=913384637&commit=Get%20the%20price!" for 127.0.0.1 at 2018-12-19 20:55:33 +0100
Processing by ModelRequestController#result as JS
Parameters: {"utf8"=>"✓", "q"=>"913384637", "commit"=>"Get the price!"}
Rendering model_request/result.js.erb
Rendered model_request/_result.html.erb (0.8ms)
Rendered model_request/result.js.erb (6.8ms)
Completed 200 OK in 383ms (Views: 11.6ms | ActiveRecord: 0.0ms)

In a "normal" rails site, a search form works as follows -
You define a form in the view. This will, by default, POST data to an endpoint - but you can also do this via a GET request. In your case, you originally chose:
form_tag("/search", method: "get")
This will send the form data to GET /search, synchronously, and therefore perform a full page reload.
This is a perfectly valid thing to do. However, you wanted to remain on the index page - therefore the request needs to be asynchronous:
form_tag("/search", method: "get", remote: true)
...But now, that endpoint needs to do something different. Instead of rendering a new page, it needs to partially update the current page.
The standard approach for this in a rails application is to do something along the lines of:
# In the controller action, e.g. SearchController
def show
#results = # ...
respond_to do |format|
format.js {render layout: false}
end
end
# In the view, e.g. `search/show.js.erb`
$("#display_result").html("<%= escape_javascript(render partial: 'results', locals: { results: #results } ) %>");
# In an HTML partial, e.g. `search/_results.html.erb`
<% results.each do %>
...
The key idea is that on your main page (index.html.erb?), you must have a container to display the results. Then, rather than rendering a new page, you are merely rendering an HTML update inside that container.
This is, of course, not the only way of doing it. Modern websites will often instead fetch JSON data for a search result and then determine how to display that via JavaScript.
However, the above approach is the most "minimal" change from your current design - without needing to add additional design patterns, you can leave most of your existing haml/erb templates as-is, and just add a little code to render the results as a partial.

Related

ActionView::Template::Error (undefined local variable or method...or no error

I wanted to use create action with ajax on 'index' page. I found some similiar problems here on SO, tried to use it, but no help for me so far. HTML works fine, only JS is a problem.
walls_controller:
def items
#item = Item.new
#items = current_user.items
find_items
#ads = #items_ads
end
def create
#items = current_user.items
find_items
##items_ads via find_items method
#ads = #items_ads
#item = current_user.items.build item_params
current_user.save
respond_to do |format|
format.html { redirect_to :back }
format.js
end
end
items.html.erb:
<div id="items1">
<div class="row">
<div class="col-md-3">
<h3>Wall of user <%= current_user.name %></h3>
<div>
<%= render 'item_form', item: #item %>
</div>
Currently you are looking for these items:
<div>
<%= render 'items_list', items: #items %>
</div>
</div>
<div>
<%= render 'ads/ads_list', ads: #ads %>
</div>
</div>
</div>
_item_form.html.erb:
<%= form_for(item, url: wall_items_path, remote: true) do |f| %>
...
First I had error:
ActionView::Template::Error (undefined local variable or method `items' for #<#<Class:...
then I changed create.js.erb from
$("#items1").html("<%= render 'items_list', locals: {items: #items} %>");
$("#items1").html("<%= render 'ads_list', locals: {ads: #ads} %> ");
to
$("#items1").html("<%= render 'items_list', items: #items %>");
$("#items1").html("<%= render 'ads/ads_list', ads: #ads %>");
and now it doesn't show me any error, but no visible change when trying JS on browser. Thanks for help.
Rendering Partials
You can render a partial with local data in two ways:
Option 1
The shortcut version
<%= render "my_partial", local_var_1: some_var, local_var_2: another_var %>
The shortcut version takes the partial name as the first argument and a hash of local variables as the second parameter.
Option 2
The long form version
This form takes only a single argument in the form of a Hash with all the options.
Don't mix-and-match forms
Doing the following will yield unexpected results
<%= render "my_partial", locals: { local_var_1: some_var, local_var_2: another_var } %>
Technically here you are using the shortcut version (Option 1), rendering a partial named "my_partial" with a single local variable called locals.
You would expect to be able to use local_var_1 and local_var_2 within the partial, however you actually only get a single local variable called locals.
Rendering Partials in an SJR template
escape_javascript GOTCHA
In a server-generated JavaScript template (SJR), you must remember to use escape_javascript whenever you are rendering content which contains HTML.
$("#my-selector").html("<%= escape_javascript render('my_partial') %>");
Regardless of how you choose to render the partial (i.e. either Option 1 or Option 2 above), you must remember to wrap it in an escape_javascript (or its alias j) call.
Helpful Resources
Working With JavaScript in Rails
When to Use escape_javascript in an SJR Template

How to replace a div tag in my view using Ajax

I want to replace a div tag on my Images index page using Ajax. Right now, I have the following:
_sub.html.erb
<% #group.images.each do |image| %>
<%= image_tag image.pic.url(:medium) %>
<%= button_to '+1', image_upvote_path(image), remote: true, method: :post %>
<% end %>
index.html.erb
<div id="next_group">
<%= render 'sub' %>
</div>
upvote.js.erb
$("#next_group").empty();
$("#next_group").append("<%= j render 'sub' %>");
In my images_controller
def index
#group = Group.offset(rand(Group.count)).first
end
def upvote
#image = Image.find(params[:image_id])
#image.votes.create
respond_to do |format|
format.html { redirect_to groups_path }
format.js
end
end
And in my routes, I have
image_upvote POST /images/:image_id/upvote(.:format) images#upvote
My understanding of what is going on:
On my index page I have a div container that renders the sub partial that I want users to see. Inside the sub partial, I have a button_to helper that has the remote: true attribute(?) included as well as the path/action that clicking that button will initiate. The action is the images#upvote. So in the images controller, I define what I want to happen when the button is clicked (an upvote is created), but I also say that I want it to respond with Ajax, which happens because I've declared remote: true.
Here's where I start getting a little confused. Since Ajax is being used, does rails automatically look for the upvote.js.erb file since it's the upvote action that is occurring?
My problem right now is that the vote is being created just fine, but I don't think the javascript in upvote.js.erb is being executed. The page will stay on the current #group that is being displayed, without rendering a new _sub partial. So I guess I'm not sure if there is a problem with the javascript, or maybe something with the way I have the controller, views, and routes set up.
maybe button not belongs to any form, try using link_to or add remote options to the path

trying to render a partial from click on rails 4, getting 500 (Internal Server Error)

I've looked up how to render a partial in Rails 4 and I'm having some problems. I want a form to render when I click a link, but after following some directions I am getting a 500 internal server error.
here is my link code with a remote: true
<%= link_to "Lostings", new_losting_path, remote: true, id: "lostings_span" %>
here is my controller with the appropriate route/path
def new # dont need this later
#losting = Losting.new
respond_to do |format|
format.json
end
end
and here is my new.js.erb file
$("#asset_nav").prepend('<%= escape_javascript(render "new") %>');
and lastly here is the form partial i am trying to show
<div class="container">
<h3>Lost</h3>
<%= form_for #losting, remote: true, html: {multipart: true} do |f| %>
<%= f.label :pet_name, "Pet name:" %>
<%= f.text_field :pet_name %>
<%= f.submit %>
<% end %>
</div>
when I click the link I get this error.
GET http://localhost:3000/lostings/new 500 (Internal Server Error)
What is wrong and what do i need to do in order to correctly render the partial?
Ajax
From what I can see, the main problem you have is you're handling the ajax call incorrectly
The way you'd be best doing it is to render the pure HTML, and then capture it in your JS:
#app/controllers/lostings_controller.rb
Class LostingsController < ApplicationController
layout Proc.new { |controller| controller.request.xhr? false : "application" }
def new
#losting = Losting.new #-> will load the /views/new.html.erb template
end
end
This will enable you to append the pure HTML with your JS (with the Rails ajax UJS callbacks):
#app/assets/javascripts/application.js
$("#lostings_span").on("ajax:success", function(data, status, xhr){
$("#asset_nav").prepend(data);
});
This will give you the ability to append the returned data from your ajax request. The problem is if you wanted to append the direct html on your frontend

Kaminari index from both data-remote and html messing with links

I have an index view for a model(nested ) that never gets called from the model, but is rendered from a couple different models. One view can render the index with either JS or html. With JS is it in a #related div in the show view and a data-remote link. Another option uses the same controller action to render it only has html.
All was fine until I added Kaminari paging. In the full page view, there was no #related div so paging didn't work. I broke out the guts into a partial and added a #related div and rendered the partial. From js I just rendered the partial from JS. That worked fine in the full page view, but in the show page it would render the partial, but the links didn't work, looks like it renders the entire show page. Clinking on another tab and then going back to the Progressions tab loads the partial and all works fine. It is just on the initial load of the show page that I can't get the page links to work.
I can load another tab that does not use paging first and solve my problem, but this was kind of the main information that you go to this page for.
Any ideas?
EDIT Request for code
The controller action method. The index method in this controller also sets #progressions
def progressions
authorize! :read, Progression
#stage = Stage.find(params[:id])
#progressions = #stage.progressions_by_score.page(params[:page]).per(20)
if params[:status] && !params[:status].blank? && #progressions
#progressions = #progressions.where(status: params[:status]).page(params[:page]).per(20)
end
respond_to do |format|
format.js
format.html {render :template => "progressions/index"}
end
end
The progressions.js.erb file in the stages view
$("#related").html("<%= escape_javascript(render('progressions/index'))%>");
The relations div in the show view. This is pretty much my scaffold template where I display or link to related information. The <div id="related"> is where any date-remote links will load the data. On initial load it loads the _index partial.
<div class="relations">
<span class="navspan">
<%= link_to 'Progressions: Status ->', progressions_stage_path(#stage), :'data-remote' => true,
:onclick => '$("#progression_status").val("")' %>
<span class="huh">
<%= hidden_field_tag :stage_id, params[:id]%>
<%= select_tag :progression_status, options_for_select(Progression.statuses["all"], params[:status]),
:prompt => 'All', :class => 'f-left' %>
</span>
<%= link_to 'Assessors', assessors_stage_path(#stage), :'data-remote' => true %>
<%= link_to 'Events', events_stage_path(#stage), :'data-remote' => true %>
<%= link_to 'Select', select_stage_path(#stage) if can? :select_candidates, #stage %>
<%= link_to 'Ad Mgmt', edit_ad_stage_path(#stage) if can? :manage_ad, #stage %>
</span>
<div class="f-clear"></div>
<div id="related">
<%= render "progressions/index"%>
</div>
</div>
The index.html.haml file
#related
= render "progressions/index"
The _index.html.haml file is just an index table listing the progressions but it does contain:
= paginate #progressions, :remote => true
Shortly after posting some code, I went back in my memory and used javascript to load the related div. I've tried to stay away from javascript, but in this case I added this to the end of the page after adding :id => "status_id" to the progressions link:
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$("#status_link").trigger("click");
})
</script>
While this fixes my problem, I still don't understand why the initial html response does not respond to the page links in the related div. I'll leave that as something else to learn. I'll put it in coffescript after I figure out how to have fire only on initial load of the show page.

Ajax adding and updating content on a page

Implementing Ajax requests in my rails 3 app has been one of the most frustrating things I've ever done. After days wasted trying to follow people's instructions online, the only thing that seems to work for me is ajax delete.
Users of my site have a profile page, which consists of some info at the top, then they have several different resumes, each of which is contained in a tab on the page. Within each resume are educations, which are what I'd like to be able to add and update dynamically. That is, User has_many resumes, and each resume has_many educations. I render each resume in a div with id equal to it's id. This is the div that I would like to have reload with ajax on form submission. Here's a little code.
user/show:
...
<%= render partial: 'shared/resume' %>
...
shared/resume:
<% if #resumes.any? %>
<div class="tab-content" id="tabResumes">
<%= render partial: 'shared/resume_item', collection: #resumes %>
</div>
<%= will_paginate #resumes %>
<% end %>
shared/resume_item:
<div class="tab-pane" id="<%= resume_item.id %>">
...
# here I render a few different partials which display the different elements of the resume, such as educations.
# I'd like to reload this area on form submission.
...
</div>
educations_controller:
def create
#resume = Resume.find(params[:resume_id])
#education = #resume.educations.build(params[:education])
respond_to do |format|
if #education.save
format.html { redirect_to(#student, :notice => 'Education created.') }
format.js
else
format.html {render :action => "new" }
format.js
end
end
end
views/educations/create.js.erb
$('#<%=#resume.id%>').html("<%= escape_javascript()%>"); //not sure what to call here, nothing I've tried has given me any success.
I'd also like to make updating refresh the same div, but I wanted to get create working first. Anybody have any advice for how to make this work? The ajax submit appears to be going through, as I am getting
Rendered educations/create.js.erb
Completed 200 OK in 52ms
in the console.
Try having your create.js.erb along the lines of:
$('#<%= #resume.id %>').replaceWith("<%= escape_javascript render(:file => 'shared/resume_item.html.erb') %>");
Alternatively you can also use the jquery .appendTo method instead of replaceWith, but, in this case I think replaceWith works better.

Resources