AJAX not rendering as it should - ruby-on-rails

I am at a loss with what’s stopping my code not rendering AJAX where it should be, I have a less serious JS ‘Parse error’ which I can’t work out also.
I have a default prevented rails form_for where upon submit event jQuery finds the element and its attribute values, posts them to the model via appropriate action, model then responds with the new object and is supposed to render the JSON via a jbuilder form.
All is fine when I get the page to render via a redirect, but not by a render ‘create’, content_type: :json, error displayed is a missing template error. I also see from network response that it ‘failed to load response data’. views/reviews/create.json.jbuilder is saved is in the correct place I believe, class and id names are all correct I believe, files and folders are named correctly and in the right place I believe, I can’t see anything wrong? Unsure whether it’s a jbuilder error, a controller syntax error, or a jQuery syntax error. Here is my code:
controllers/reviews_controller.rb:
def create
#restaurant = Restaurant.find(params[:restaurant_id])
#review = #restaurant.reviews.new(params[:review].permit(:thoughts, :rating))
if #restaurant.reviews.find_by user_id: current_user.id
flash[:notice] = "You already reviewed this restaurant!"
redirect_to restaurants_path
else
#review.user = current_user
#review.save
# redirect_to restaurants_path, will do a redirect, but defeats AJAX purpose!
render 'create', content_type: :json # results in a missing template error #'missing templete reviews/create' error
end
end
Assets/restaurants/restaurants.js:
$(document).ready(function(){
$('.new_review').on('submit', function(event){
event.preventDefault();
var reviewList = $(this).siblings('ul');
var currentRestaurant = $(this).parent();
$.post($(this).attr('action'), $(this).serialize(), function(review){
if review # This line results in Uncaught SyntaxError: Unexpected Identifier
var newReview = Mustache.render($('#review_template').html(), review);
reviewList.append(newReview);
currentRestaurant.find('.review_count').text(review.restaurant.review_count)
currentRestaurant.find('.average_rating_number').text(review.restaurant.average_rating);
currentRestaurant.find('.average_rating_stars').text(review.restaurant.average_rating_stars);
}, 'json');
});
});
views/restaurants/index.html.erb (jbuilder template element):
<template id='review_template'>
<li>
<strong>{{ star_rating }}</strong> -*- {{ thoughts }}
</li>
</template>
views/reviews/create.json.jbuilder:
json.thoughts #review.thoughts
json.star_rating star_rating(#review.rating)
json.restaurant do
json.average_rating number_with_precision(#restaurant.average_rating,
precision: 1)
json.average_rating_stars star_rating(#restaurant.average_rating)
json.review_count pluralize(#restaurant.reviews.count, 'reviews')
end
Been on this for hours now trying to solve this one, pfff!! any idea where I’m going wrong folks? Am I doing something really dim somewhere here? Thank you.

As you can see in the error message, rails is looking for a file called reviews/create[extension] or application/create[extension] and extensions allowed are .erb, .builder, .raw, .ruby, .coffee or .jbuilder.
I suggest to change you ajax call to ask for js, not json, create a file called reviews/create.js.erb, and add this kind of code :
<% if #restaurant.reviews.find_by user_id: current_user.id %>
// do the code to show the error message in javascript
<% else %>
reviewList.append("<%= j(render('create')) %>");
currentRestaurant.find('.review_count').text(<%= pluralize(#restaurant.reviews.count, 'reviews') %>)
currentRestaurant.find('.average_rating_number').text(<%= number_with_precision(#restaurant.average_rating, precision: 1) %>);
currentRestaurant.find('.average_rating_stars').text(<%=star_rating(#restaurant.average_rating) %>);
<% end %>
This code should be executed after the success of the creation. You also have to create a file called reviews/_create.html.erb with the html you want to show. Finally, you have to delete some logic in the javascript and in the controller.

Related

How should a form error page be rendered with Turbolinks 5

Using the new Turbolinks 5 in a rails application - what is the best way to render a form with error messages. The documentation says:
Instead of submitting forms normally, submit them with XHR. In response to an XHR submit on the server, return JavaScript that performs a Turbolinks.visit to be evaluated by the browser.
So if my form submits a remote request to update should i be just doing a js form replace or does turbolinks 5 have a better way? Example -
controller:
def update
#success = #team.update_attributes( team_params )
end
update.js
<% if #success %>
Turbolinks.visit('<%= teams_path %>', {action: 'replace'});
<% else %>
$('form').replaceWith('<%= j(render partial: '/teams/form') %>');
<% end %>
Is there a more turbolinks 5 way to handle the failed update?
I'm still tinkering around as well. The only thing I'd be doing differently is put the conditional logic in the controller instead of the view.
somethings_controller.rb
def create
if #something.save
redirect_to #something
else
render 'errors'
end
end
errors.js.erb
$('form').replaceWith('<%= j(render partial: 'form') %>');
Why does the controller-based redirect work? Because in the docs for Turbolinks 5 it says...
The Turbolinks Rails engine performs this optimization automatically
for non-GET XHR requests that redirect with the redirect_to helper.
...which I take to mean they're basically wrapping our redirect in a Turbolinks.visit for us. The nice thing is that it'll degrade gracefully for users without javascript without us having to futz with respond_to logic.
You can see why redirect_to works by checking out the gem: /gems/turbolinks-5.0.0.beta2/lib/turbolinks/redirection.rb
Turbolinks doesn't handle form submissions yet so you need to handle the submission and response explicitly (via remote: true or otherwise). There's a discussion about it here with some other solutions: https://github.com/turbolinks/turbolinks/issues/85

Using Private-pub with channels that have segment keys and coffeescript variant

I'm trying to replicate a push notification system similar to facebook's using private_pub. Ideally I would want to link this to show notifications using a gem such as gritter (other gem suggestions are welcome)
Whenever a certain action from a controller is called, I want to send a notification to all subscribers that are part of a specific id. As long you are logged in, you are subscribed to the channel, achieved by putting the subscribe_to in the layouts.
in the view:
<%= subscribe_to "/messages/#{#group_id}" %>
in the controller
PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')")
this works just fine, however I would like to have something more sophisticated than an alert as a response (such as a gritter notification), so instead:
PrivatePub.publish_to("/messages/#{#group_id}", data: #some_data)
Following the tutorial, they use coffeescript for this. However, I cannot get the simple alert going (probably due to the id in the channel)
In this question, the OP was able to solve this using a js.erb view. But I can't get it to work.
disclaimer: my js and coffeescript knowledge is almost zero.
Any help is appreciated :)
EDIT
Some more info: I've a method in a controller that's part of a public API, and expects POST request. If everything is ok it sends out a JSON success response. Aside from this, the same method sends a notification to all users of a specific group.
I've actually managed to get this working, putting this in the controller:
callback method:
respond_to do |format|
format.js #-> calls callback.js.erb
#format.json { render json: {"success" => true}.to_json }
end
and putting the gritter stuff in my_api_controller/callback.js.erb:
<% publish_to "/messages/#{#group_id}" do %>
<%= add_gritter(
"Nova " + link_to("reserva", reservation_path(#r)) + " de #{#channel} para " +
link_to(#hostel_name, hostel_path(#hostel_id)),
:title => "Nova reserva!",
:sticky => true,
:image => :notice
) %>
<% end %>
note: since the subscription to the channel is done in every view (through the layout), you can receive a notification on any page/view
My problem at the momento is, as you can guess, the JSON response. Since I cant render two responses, only the js.erb is called, but the JSON response is never sent
Although I've not got much experience with this gem, here's something which may help:
JS
Client-side, your JS is basically running an eventlistener on the private_pub object (defined when you include the private_pub / gritter JS on your page), which you can use to perform other actions (call alerts, append data to page, etc)
It seems your back-end is working, it's just the receipt & processing of the data from the server you're struggling with. To remedy this, you can do 2 things: 1) run a standard JS call from application.js or run a js file from your controller action:
Controller
According to the private_pub documentation, you should do this to create a JS file:
#app/controllers/your_controller.rb
def create
#message = "Hello"
respond_to do |format|
format.html { PrivatePub.publish_to("/messages/#{#group_id}", "alert('test')") }
format.js #-> calls create.js.erb
end
end
#app/views/your_controller/create.js.erb
<% publish_to "/messages/new" do %>
$("#chat").append("<%= j render(#messages) %>");
<% end %>
Browser
#app/assets/javascripts/application.js.coffee
PrivatePub.subscribe("/messages/new", (data, channel) ->
alert data.message.content
I was able to accomplish this by directly adding the gritter script in the publish_to method of Privat pub.
In my controller:
PrivatePub.publish_to
"/some/URI/#{entity.id}"
,"jQuery.gritter.add({
image: '#{ActionController::Base.helpers.asset_path('notice.png')}'
, sticky: true
,title:'#{t('some_title')}'
, text: '#{t('some text'}'
});"
render json: {"error"=>{"code"=>20,"msg"=>e.message},"success" => false}.to_json
Basically, I was able to publish to PrivatePub witouth resorting to the html response, wich enabled me to return a JSON response as intended.
I guess, you can handle your problem with the help of gon gem like below:
In view
<%= subscribe_to "/messages/#{#group_id}" %>
In controller
gon.group_id = #group_id
PrivatePub.publish_to("/messages/#{#group_id}", message: #message)
In messages.coffee
if gon.group_id
PrivatePub.subscribe "/messages/#{gon.group_id}", (data, channel) ->
jQuery.gritter.add
image: '/assets/notice.png'
title: 'Notification!'
text: data.message.content
But, gon.group_id can make trouble sometimes so you need to take care of that.
So, I recommend to use js.erb which is easy and we can access to controller's variable easily in js.erb file.
Hope that answer your problem.

Displaying an error message without reloading the page in Rails

I have a page that lets one create records - if the validations aren't satisfied, it redirects to the same page and shows an error message. Here's that snip from the controller:
def create
#signature = Signature.new(signature_params)
if #signature.save
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
end
The trouble is, this is resulting in a full page refresh - so the error message isn't visible because the input form is placed under the fold of the page. I can place it at the top of the page, of course, but is there a way to show the message without reloading the page? Thanks.
OK, so here's what I've settled on:
1) I'm handling validation on the client side with HTML5 "required" attributes - they were created for this explicit purpose and no other gems or plugins are needed. They are supported in all major browsers. Details in this article.
2) I've moved the error messages to the top of the page to handle the case in which a user either is on an old or mobile browser or has JavaScript disabled. Error messages must work with a complete request-response cycle (even if this means re-loading the page) before they work with anything else - this is the unobtrusive JavaScript approach.
3) For the AJAX version, I'm going to be using remote: => true on the form element as explained in the Rails guides. I might be making this open source once I'm done with the callback part of it, and will post a link here.
Obviously, handling errors with flash is the most uniform & DRY way to show the user what's going on, but if you're willing to think outside the box, you'll be able to use Ajax to accomplish a similar job by just handling the errors yourself:
Code Example
#app/controllers/signatures_controller.rb
def create
#signature = Signature.new(signature_params)
if #signature.save
#success = "true"
end
respond_to do |format|
format.js { #errors = error_messages(#signature) }
format.html {
if #success.defined?
redirect_to "/thanks"
else
redirect_to :back, :notice => error_messages(#signature)
end
}
end
end
#app/views/signatures/create.js.erb
<% unless #success.defined? %>
alert(<%=j #errors.inspect() %>)
<% end %>
#app/assets/javascripts/signatures.js
$(document).on("submit", "#signature_form", function() {
$.ajax({
url: "/signatures"
type: "POST"
data: $(this).parent().serialize(); //serialize the form (not the button)
error: function() {
alert("Sorry, there was an error!");
}
});
});
You'd actually be better using JSON for this. If you like the idea, I can refactor it to include JSON for you!

Preventing a page from getting opened

When user clicks on a chart, I get some ID in the Javascript side of its onclick event and pass that as a query param to the page that I want to open and I say
window.open(go_to);
which goto will have that query param in it for exampel like "http://localhost:3000/myapp/people?provider=134"
so now it hits the index action method in the Rails side, in there I also get another variable for logged in user:
def index
cur_user_id = current_user.id
provider_id = params[:provider]
# call the REST service and pass those two params
# and get the appropriate JSON back from the service
if provider_id == cur_user_id
render nothing: true
end
end
The problem I have is with this logic:
if provider_id == cur_user_id
render nothing: true
end
So if logged in user is the same as the provider I don't want to open that page or show anything. Render Nothing is helping with not showing anything but still the window.open part of the code from Javascript is opening the page, blank tho
How can I tell it hey don't even open the new page?
You can use ruby code mixed with javascript:
var provider_id = $('#provider_id').val();
var go_to = '/path/to/somewhere?provider_id=' + provider_id;
if(provider_id == <%= current_user.id %>) {
alert('You cannot go there!');
} else {
window.open(go_to);
}
Remember that the ruby code will be executed first (on server-side), then your HTML/javascript will be generated, and finally be executed on the client-side. So <%= current_user.id %> inside the javascript will just print the value in there, like if it was hard-coded in JS.
It seems that you didn't know about the ruby code inside the javascript parts, to give you an example, try this in one of your view using Javascript:
# this is on the server-side, it will be generated:
<script type="text/javascript">
<%= "alert('hello Eric!');".html_safe %>
</script>
# on the client-side, you will receive this generated HTML content:
<script type="text/javascript">
alert('hello Eric!');
</script>
For HAML:
:javascript
#{"alert('hello Eric')".html_safe}
var hello = '#{"hello World!"}';
alert(hello);

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.

Resources