Redirect to after successful ajax form - ruby-on-rails

I've got a form with remote => true.
And right now my controller looks like:
# POST /items
# POST /items.json
def create
#item = #store.items.build(params[:item])
respond_to do |format|
if #item.save
format.html { redirect_to edit_admin_item_path(#item), :flash => {:success => "#{#item.name} was successfully created."} }
format.js { render :js => "window.location.href = ('#{edit_admin_item_path(#item)}');"}
format.json { render json: #item, status: :created, location: #item }
else
format.html { render action: "new" }
format.js { render :partial => 'fail_create.js.erb', :locals => { :ajax_errors => #item.errors.full_messages } }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
Which works but feels very clumsy. It also doesn't allow me to use a flash notice, which is sad time indeed.
Ideally I feel like I should be able to simply use "format.js { redirect_to...} or check against the request headers and redirect_to. Sheesh!
I'm not sure what the best solution is. Any advice would be super awesome, thanks in advance!
-- PS -- I know this has been asked somewhat before but to no avail: How to redirect after a successful AJAX form submission. There seems to many questions similar floating around, but no real solutions.

I think it might be impossible. The response to a Ajax request is processed by XMLHttpRequest. If a 3xx response is returned, XMLHttpRequest will follow the redirect itself, if the URL is of same origin. No matter how you set the headers, the browser cannot be aware of that. So the only way could be changing window.location with some Javascript.

I use a combination of Rails responders to generate my response messages and some content in my <action>.js file.
The content of — say update.js would look something like this:
// Checks if the article slug has changed.
// If it has the entire page should be reloaded at that new location.
<%= reload_if_slug_changed #article, params[:id] %>
// Displays the flash notices
// See ApplicationHelper#js_flash_response
<%= js_flash_response %>
Where the different methods are defined in some helper (in my case my ApplicationHelper). The content of the different methods are as follows:
def js_flash_response
if flash.now[:notice].present?
js = "$('#notice').html('#{flash.now[:notice]}').change();"
elsif flash.now[:alert].present?
js = "$('#alert').html('#{flash.now[:alert]}').change();"
end
end
def reload_if_slug_changed object, expected_value
"window.location.href = '#{url_for [:edit, object]}';" if object.slug != expected_value
end
The content of the flash messages are generated automatically by Rails responders and displayed with the now scope that deletes the from the flash hash, ensuring that if the user reloads (after the flash has been displayed) they will not reappear.
I don't believe that you should ever make a form pointing to a restful create action a remote one, because you would always expect critical redirect, so in my case I only need to redirect if the url slug has changed.
I hope that this helps. It's not a solution, but simply the way that I handled some of the same problems.
Best regards.

Under your scenario, here's how I would inject javascript into the page from a controller action. After you've completed the logic section of your action insert something like this:
render :update do |page|
page << "javascript_here"
end
This should allow you to insert you window.location or create a javascript flash method and call it when your create method executes correctly.
If you're looking to DRY up your controller actions, I would recommend looking into this Railscast about make_resourceful. Make_resourceful automagically performs each core activity for each action. It also allows you to tap into the hooks that they've created such as before :create, after :create, response_for :create, and after :create_fails. By using this gem, you can run code based on the success or failure of your methods and have finer grained control over them.
In addition to this, you should be able to initialize a create.js.erb and create_fails.js.erb in your view file, include a format.js without anything passed to it in your controller, and Rails will automagically run that file that contains javascript depending on if the controller action executed successfully.

Related

Ruby on Rails 6. Using Ajax after redirection

Say, I have users list on the '/users' page and 2 actions for the 'user' entity: 'index' (with using of Ajax) and 'destroy'.
def index
...
respond_to do |format|
format.html
format.js
end
end
def destroy
...
redirect_to users_url
end
I want to destroy a user (right from the '/users' page) and use Ajax of the 'index' action after that ('index.js.erb' file) in order to render only a part of the opened '/users' page.
Is it possible to do that?
My current solution right now is to use Ajax for 'destroy' action (a separate 'destroy.js.erb' file) and duplicate needed changes for 'index' page there. But, first of all, it's a code duplication, and second, in this case my pagination links are broken (I use 'Kaminari' gem and looks like it works fine only with 'get' requests, at least by default).
There is a 'view' part of updating with Ajax, if necessary:
<div id="users_table">
<table class="table table-hover table-borderless">
...
<tbody>
<%= render #users %>
</tbody>
</table>
<div><%= paginate #users, remote: true %></div>
</div>
If you want the destroy action to render the index.js.erb:
def destroy
...
respond_to do |format|
format.js { render action: :index}
format.html { redirect_to users_url}
end
end
But, to render index.js you will need to, in your destroy action, rebuild the #users object and ensure you're rebuilding it for the correct page. So, when you call the destroy action you'll need to pass the ID(s) of the user(s) you want to destroy, as well as the page you are on.
Your destroy.js.erb should (on successful destruction) remove the destroyed element from the index by deleting a part of the HTML. I don’t expect that the code to do that duplicates the code you have in the index view.
Post your current destroy.js.erb as well as the relevant part of index.html.erb for more help though.
You can also use redirect within a respond_to so your HTML call will redirect while the Ajax uses destroy.js.erb
respond_to do |format|
format.js
format.html { redirect_to users_url}
You could also hack your way to your answer by calling render :index for the js response. But, if you were to try to render the index view here you’ll definitely get duplication of code, along with an extra DB call and probably some broken pagination. So, I’d recommend that you take the approach I first suggested (use destroy.js.erb to remove that user from the HTML)
Finally, more generally, when you’re trying to avoid duplication of view code; a partial might be the answer

Rails redirect if validation fails

In a Rails 3.2 app, I have a validation for an attachment type.
Attachment model:
class Attachment < ActiveRecord::Base
validates_presence_of :name
validates_attachment_presence :attach, :message => "No file selected"
validate :check_type
def check_type
if self.costproject_id != nil
if self.attach_content_type != 'application/pdf'
self.errors.add(:pdf, " ONLY")
return false
end
end
end
But, the return false sends me to this URL:
http://localhost:3000/attachments
I want it to go back to the previous input screen:
http://localhost:3000/attachments/new?costproject_id=2
How do I accomplish that?
Thanks!!
UPDATE1
Perhaps the redirect has to take place in the controller?
format.html { render action: "new" }
Attachment controller:
# POST /attachments
# POST /attachments.json
def create
#attachment = Attachment.new(params[:attachment])
respond_to do |format|
if #attachment.save
format.html { redirect_to session.delete(:return_to), notice: 'Attachment was successfully created.' }
format.json { render json: #attachment, status: :created, location: #attachment }
else
format.html { render action: "new" }
format.json { render json: #attachment.errors, status: :unprocessable_entity }
end
end
end
I changed this line:
format.html { render action: "new" }
To:
format.html { redirect_to request.referer }
And now it goes back to where I want. But, I've lost the errors - they don't display.
To help you understand what's going on here. When you go to /attachments/new you are rendering a form. When you press submit, you are sending a POST request to /attachments, which invokes the create action.
You're create action appears to be solid and idomatic. However when you render action: "new" in the case of an error, it's not a full redirect, it's rendering the form in the context of the current action.
Normally this is fine, because idomatic rails would have you building a single, very similar, model object in both new and create, and the form for helper would render that object. However your new action is creating all kinds of objects based on a large assortment of query parameters, which I'm guessing is why you are seeing behavior you don't like.
I expect your final solution will involve bringing all those parameters into Attachment in some way, if they don't need to be saved to the database, you can make attr_accessors on Attachment
# Model
class Attachment < ActiveRecord::Base
attr_accessor :worequest_id, :workorder_id # etc
end
# View
<%= form_for #attachment do |f| %>
<%= f.hidden :worequest_id %>
<% end %>
Approaching it this way, your post request params will look like
{
attachment:
{
worequest_id: 1,
# etc
}
}
And you would also need to rework your query params to nest the inidividual ids inside of an attachment
/attachments/new?[attachment][worequest_id]=1
This way you could build attachment from params in both actions:
Attachment.new(params[:attachment])
And now your current create action should more or less work as expected, because now it's idomatic rails.
You still aren't going to get the new action with the same query params, but since you are taking those params and filling them in hidden fields on the form, they won't be lost when you try and fail to create. In any case, unless you do something to persist the values between requests, the POST to /attachments is going to wipe out the ery params.
Try this.
Replace
return false
With
redirect_to request.referrer || root_url
Note: root_url here is a catchall. Also this is Rails 4, I do not know if it also applies to Rails 3. Worth a try, though.
Debug ideas
First confirm a simple redirect_to root_url (or whatever name you use for your root) works in your controller
redirect_to root_url
Then, once redirect_to confirmed working, focus on getting the REST interface "request." information. There's a Rails 3 discussion here which may help you.
How to get request referer path?

Rails AJAX - don't redirect or render

Using rails and .js.erb to make an AJAX request (and append values to a div), how do you prevent rendering a new layout? In other words, stay on the same page without going anywhere and just append the fresh data from the server in a div. No reloading the same page, no redirecting.
At the moment my controller looks like this
def update_shipping
#order = Order.find(params[:id])
#order.shipping_option_id = params[:shipping_options]
#order.save!
respond_to do |format|
format.js
format.html
end
end
and my form like zisss:
<%= form_tag update_shipping_order_path(#order), method: :put, remote: true do %>
<%= select_tag 'shipping_options', #options_for_select, onchange: 'this.form.submit()' %>
<% end %>
and my routes look like a so:
resources :orders do
member do
put :update_shipping
end
end
But I get a 'Template is Missing' error
Please help!!
You need to add a update_shipping.js.erb file under app/views/your_controller/ directory. Note the name of the javascript file should be same as the action. Since you have a remote:true in your form so rails will try to render a javascript template in your case update_shipping.js.erb.
Now in your update_shipping.js.erb file write some basic javascript to update the page elements like
#update_shipping.js.erb
$('.some-div').html(<%=j #model.some_value' %>)
Try this:-
respond_to do |format|
format.js { render :nothing => true }
format.html
end
If you don't want to render a layout, you can use !request.xhr? like so:
respond_to do |format|
format.html { layout: !request.xhr? }
format.js
end
If you're looking to get your ajax-powered JS to fire, you just need to call your .js.erb file the same as your view:
#app/views/controller/update_shipping.js.erb
alert("This JS is returned & fired after the Ajax request");
You'll be best doing this in your routes.rb too:
resources :orders do
put :update_shipping
end
A little late, I came across this searching for the same issue. It must of slipped out of my mind at some point while working with action cable, but what is needed is a http response code of no_content. Http response codes tell the browser how to act when a request is returned. Here is a link to a list of them, and their symbols in rails. More on 204 no content
Here is how it would look:
def update_shipping
#order = Order.find(params[:id])
#order.shipping_option_id = params[:shipping_options]
#order.save!
head :no_content #or head 204
end
edit: what solved the solution for me was a link provided by William Denniss in this stack overflow question

How to render erb template to string inside action?

I need a string of html (something like "<html><body>Hello World</body></html>") for faxing purpose.
I wrote it into a seprate erb file: views/orders/_fax.html.erb ,
and try to render the erb in action: html_data = render(:partial => 'fax').
Here is part of the controller that raises the issue:
respond_to do |format|
if #order.save
html_data = render(:partial => 'fax')
response = fax_machine.send_fax(html_data)
......
format.html { redirect_to #order, notice: 'Order was successfully created.' }
format.json { render json: #order, status: :created, location: #order }
else
format.html { render action: "new" }
format.json { render json: #order.errors, status: :unprocessable_entity }
end
end
It gave me an AbstractController::DoubleRenderError as below:
AbstractController::DoubleRenderError in OrdersController#create
Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like "redirect_to(...) and return".
How to solve this problem?
If you only need the rendered HTML, and don't need any functionality from the controller, you might try using ERB directly within a helper class, eg.:
module FaxHelper
def to_fax
html = File.open(path_to_template).read
template = ERB.new(html)
template.result
end
end
The ERB docs explain this in more detail.
EDIT
To get the instance variables from the controller, pass the binding into the result call, eg:
# controller
to_fax(binding)
# helper class
def to_fax(controller_binding)
html = File.open(path_to_template).read
template = ERB.new(html)
template.result(controller_binding)
end
Note: I've never done this, but it seems workable :)
Use the #render_to_string method
it works the same way as the typical render method but useful when you need to add some templated HTML to a json response
http://apidock.com/rails/ActionController/Base/render_to_string
If you don't want to escape html, just call .html_safe on it:
"<html><body>Hello World</body></html>".html_safe
Re your error, please post your OrdersController - looks like you are calling render or redirect more than once in the create action.
(Btw, just in case you are trying it - you can't render a partial in a controller - you can only render partials in views)
Edit: yeah your problem is you trying to render a partial in the controller action. You could use an after_create callback to set up and send the fax - though again you won't want to use a partial (as they are for views). http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Edit: for your fax problem,you could create a normal Ruby Class, see this excellent bit of advice from Yehuda: https://stackoverflow.com/a/1071510/468009
The reason is you cannot render or redirect inside the same action more than once at a given time.
But in your code, you have both render and redirect. I think in your controller you can use simply only the render, assuming you don't need any json output.
Try this
def create
#order.save
render(:partial => 'fax')
end
I haven't tested this, but I guess you get the idea :), and think about a way to handle errors as well (in case order didn't save).

How to redirect after a successful AJAX form submission (Rails 3.2)

Suppose you have an edit form with :remote => true. Your controller method looks like
def update
#article = Article.find(params[:id])
respond_to do |format|
if #article.update_attributes(params[:article])
format.html { redirect_to #article}
format.js { render :js => "window.location.replace('#{article_path(#article)}');"}
else
format.html { render :action => "edit" }
# Render update.js.erb which replaces the body of the form
format.js {}
end
end
end
What's the best way to do the redirect on successful article update in Rails 3.2.1? The raw JS solution seems a little sleazy, but I do like the fact that it's obvious that it's performing the same function as the format.html version.
I would prefer a solution that does not use RJS (if that even works in Rails 3.2?)
How about adding the below line of code on the view file itself
#some_file.js.erb
`window.location = redirect_path
As you mentioned in the question you do not prefer RJS, but I think it's better to follow this pattern better than writing the js code in the controller.
Does your ajax interact with a model (.php,.asp?). My preferred method in this instance is to create a success/fail criteria within the model after submission and redirect directly from there. Not sure if that makes sense in this application though?

Resources