Displaying an error message without reloading the page in Rails - ruby-on-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!

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

AJAX not rendering as it should

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.

Add a "Thank you for your order" page that is not directly visitable

When the user purchases an item we want to display a thank you page.
Then again we want this page not to be directly visitable.
In other words, if the page was to be /orders/thankyou, the user could directly navigate to it which would be rather ugly and fail aas it would have nothing to show.
how can we show this page only when in context and forbid direct navigation to it?
You can create a partial form and append it to the DOM after the purchase event is fired. In the partial view, be sure to add a redirect action if it was accessed without the purchase event firing.
For just displaying short text (and not also e.g. the order data) you could just use a flash notice. For example:
redirect_to #page, notice: "Thank you for your order!"
Ajax
Sounds like you'll be best using ajax:
#app/views/cart/index.html.erb
<%= form_tag cart_order_path, remote: true do |f| %>
... (cart form)
<%= f.submit %>
<% end %>
This (obviously very simple) form will send a "remote" (Ajax) form submission to your controller. The reason why this is important is because you will then handle the response directly in that page you just sent the request from:
#app/assets/javascripts/application.js
$(document).on("ajax:success", "#your_form_id", function(status, data, xhr) {
$("your_page_div").html(data);
});
The trick here will be to render your thank you view without a layout -
#app/controllers/cart_controller.rb
class CartController < ApplicationController
respond_to :js, only: :create
def create
... business logic here
render "thank_you", layout: false
end
end
This will render the view you want without any of the supporting "layout" HTML - giving you the chance to append that to your current view. This means that if you wanted to show the "Thank You" view without letting the user browse to it directly - that's what you'll do
How It Works
Ajax (Asynchronous Javascript and XML) is a javascript technology which basically allows you to send "pseudo requests" to your Rails backend:
Basically the same as a standard HTTP request, except handled with Javascript, Ajax gives you the ability to create the appearance of "no refresh" functionality in your app. It does this by sending requests on your behalf, through Javascript.
Ajax is typically used for small pieces of functionality on web interfaces - whereby you'll have the ability to send simple requests to the server, gaining a comparable response that you can then work into the DOM.
This is the functionality I have been proposing - whereby you'll be able to send a request to your controller (albeit using the Rails UJS engine), to which you'll then receive a response. This response can then be worked into your page, thus providing you with the ability to show the "Thank You" page without the user refreshing.
You can use any solution from the following:
Using ActionDispatch::Flash:
flash[:notice] = "Thank you for your order!"
redirect_to #page
or
redirect_to #page, notice: "Thank you for your order!"
Using alert, in show.js.haml file (assuming you use action show in orders_controller.rb):
alert("Thank you for your order!");
and then add respond_to :js, only: :show, and format.js in action show for orders_controller.rb

Respond to remote form_for with html or json?

I've currently got several partials that contain forms for different AR models. I'm using these to build a dashboard-style pages so I can do an ajax post to CRUD controllers (rather than posting to a bunch of different actions within a dashboard controller).
Since I'm using AR to validate the data, I'd like to be able to just render the correct partial with the correct object in the CRUD controller and use that as my response. Something like this:
if #note.save?
flash[:notice] = "Note successfully saved!"
render '_note_form', :layout => false
else
flash[:notice] = "Something went wrong. Note was not saved!"
flash[:error] = #note.errors.full_messages.to_sentence
render '_note_form', :layout => false
end
Then I'm adding a js function to handle that response by replacing the form with the response like so:
$(function() {
$('form#new_note_form').bind('ajax:success', function(evt, data, status, xhr){
$("#new_note_form").replaceWith(xhr.responseText);
});
});
This gives me validation the same type of validation that I would see if I did a standard post, but only refreshes the partial rather than the entire page.
I can't help but to feel like there is a better way to handle this. Should I just be passing the error/success messages via json and handling them appropriately on the front-end? Is it bad practice to just replace the entire form?

Calling Rails update Method via Inline Edit

I was putting together a quick inline editing feature in my first Rails app and just as I was getting it working it occurred to me that I may be violating RESTful principles. The edit updated an image name. To do so, it submits, via PUT to Image#update and passes the new modified name as image[name].
The database gets updated properly, but I need that value back so that my markup can reflect the name change. To do that, I was calling /images/:id.json, but that got me wondering whether a PUT request can "validly" (in that RESTful sort of way) return a value like this.
Thoughts?
Update: For whatever it's worth, I'm using jQuery and the jEditable plugin to do the inline editing. Here's my jEditable code:
$(document).ready( function() {
$('h2').editable(
'/images/' + $('#image-id').val() + '.json',
{
method: 'PUT',
name: 'image[name]',
submitdata: { authenticity_token: $('#auth-token').val() },
submit: 'Save',
cancel: 'Cancel'
}
);
})
And my Image#update method as it exists right now:
def update
#image = Image.find( params[:id] )
if #image.update_attributes( params[:image] )
flash[:notice] = "Successfully updated image."
respond_to do |format|
format.html { redirect_to #image }
format.json { render :json => #image.to_json }
end
else
render :action => 'edit'
end
end
If your concern is just that your update method with JSON provide a response body and not just a 200 OK (Rails's head :ok) then I don't think you need to be worried. The default response is 200 OK so the only difference between what you're doing and what Rails does by default (in its scaffolds) is that you're also including a response body. As far as I can tell proper REST etiquette only requires that you return a 200 OK and doesn't care about the response body, which is in line with what you're doing.
Beyond that all your code looks excellent.

Resources