I am trying to get some Javascript working in my Rails app.
I want to have my index page allow me to edit individual items on the index page, and then reload the index page upon edit.
My index.html.erb page looks like:
<div id="index">
<%= render 'index' %>
</div>
In my index.js.erb I have:
$('#index').html("<%=j render 'index' %>");
and in my holders_controller:
def edit
holder = Holder.find(params[:id])
end
def update
#holder = Holder.find(params[:id])
if #holder.update_attributes(params[:holder])
format.html { redirect_to holders_path } #, flash[:success] = "holder updated")
## ^---Line 28 in error
format.js
else
render 'edit'
end
end
When I load the index page it is fine. As soon as click the edit button and it submits the form, I get the following:
But if I go back and refresh the index page, the edits are saved. What am I doing wrong?
You forgot to write responds_to block:
def update
#holder = Holder.find(params[:id])
if #holder.update_attributes(params[:holder])
respond_to do |format|
format.html { redirect_to holders_path } #, flash[:success] = "holder updated")
format.js
end
else
render 'edit'
end
end
But I am suspicious about your index.html.erb, I don't think that will really work the way you think.
Related
I’m using Rails 4.2.3. I want to submit a form in a modal dialog, so I have set up my form like so
<%= form_for #my_object, :remote => true do |f| %>
but if the user submits the form successfully, I would like to reload the page that invoked the modal dialog with a notice of “Saved Successfully.” I can’t figure out what I need to put in my “format.js” to make this happen. This is what I have in my controller so far …
def create
#my_object = MyObject.new(my_object_params)
#current_user = User.find(session["user_id"])
#my_object.user = #current_user
respond_to do |format|
if #my_object.save
format.html { redirect_to controller: "users", action: "index", notice: 'Saved successfully.' }
format.js { render action: ‘../users/index’, notice: ‘Saved Successfully’, location: #my_object }
else
format.html { render action: "index" }
format.js { render json: #my_object.errors, status: :unprocessable_entity }
end
end
end
Right now, a successful submission results in a 500 error complaining about missing partials when I try and execute the above. Pretty sure what I have is wrong anyway.
You can do the following:
#app/controllers/redirect.rb
...
format.js { render js: "window.location='#{url.to_s}'" }
...
If you like keeping things separated, just put format.js in your controller and do the javascript redirect in your view (redirect.js.erb)
In both cases, just set flash[:notice] to whatever you need before redirecting.
redirect_to events_path, format: 'js'
For this you will need to have events/index.js.erb in your file structure.
If you are redirecting anyway, you might as well avoid the remote/AJAX call, and just redirect from the create action.
<%= form_for #my_object do |f| %>
and
def create
#my_object = MyObject.new(my_object_params)
...
redirect_to some_path
end
If you have want to redirect it after successfully create/updated and just use .html method. Otherwise just use JS option like in this LINK.
def create
#my_object = MyObject.new(my_object_params.merge(user: User.find(session["user_id"])))
respond_to do |format|
if #my_object.save
format.html { redirect_to controller: "users", action: "index", notice: 'Saved successfully.' }
else
....
end
end
end
That will help you, from your controller
render :js => "window.location = '/jobs/index'"
The respond_to block in a create controller in my Rails app is not redirecting on a successful save... I'm sure this is a simple solution, but I am inexperienced with Rails and this is the first time that I am encountering this problem.
The form is set so that :remote => true, and the controller is as follows...
def create
#store = current_user.stores.new(store_params)
respond_to do |format|
if #store.save
format.html { redirect_to root_path }
else
format.html { flash[:alert] = "Save failed! #{#store.errors.full_messages.join(";")}"
render "new" }
format.js {}
end
end
end
And while I'm on the subject, the code from the else portion of the conditional doesn't run either, except for format.js {}, which does run the code in my create.js.erb file (an alert, for the time being).
I'm working with Rails 4.2.5. Can someone help me to understand why the redirect and the alert are not working? Thank you!
EDITING TO SHOW SOLUTION
Based on Rich's answer, here's the solution that I came up with:
Controller:
def create
#store = current_user.stores.new(store_params)
flash.now[:alert] = "Save failed! #{#store.errors.full_messages.join(";")}" unless #store.save
respond_to do |format|
format.js
end
if #store.save
flash[:notice] = "New store created"
end
end
create.js.erb
<% if flash.now[:alert] %>
$("#alert_holder").empty();
$("#alert_holder").append("<%= j flash.now[:alert] %>");
<% else %>
window.location.href = "<%= root_url %>";
<% end %>
Note that I needed to add quotes around the redirect url.
On form success, the page redirects to root. On failure, the error message flashes but the form is not refreshed - any answers the user has entered remain.
remote: true is an ajax request.
Ajax is javascript, and as such will invoke the format.js method:
def create
#store = current_user.stores.new store_params
respond_to do |format|
if #store.save
format.js
format.html { redirect_to root_path }
else
format.js
format.html { flash[:alert] = "Save failed! #{#store.errors.full_messages.join(";")}"
render "new" }
end
end
end
The format.js method will call the /app/views/[:controller]/[:action].js.erb file, which will fire any of the JS you have inside it.
If you don't want to have the js format handling the response, you'll have to do away with respond_to and just have what you'd like to return (redirect_to won't work).
Ajax
There are several stipulations you need to appreciate with this:
Ajax cannot "redirect" (on its own)
Ajax will be treated as JS in your Rails controller
You have to "hack" the flash to get it working through JS
If you don't have experience with Ajax, the simple explanation is that it's a "pseudo-request"; it sends an HTTP request without having to reload the browser.
The pattern for Ajax is simple: Ajax request > server > Ajax response
You cannot "redirect" via Ajax unless you parse the response with javascript. As the Ajax acronym (Asynchronous Javascript And XML) suggests, the response is expected to be XML (IE no functionality).
--
To answer your question, you'll need to use flash.now for the "flash" message, and handle the response with your .js.erb file:
def create
#store = current_user.stores.new store_params
flash.now[:alert] = "Save failed! #{#store.errors.full_messages.join(";")}" unless #store.save
respond_to do |format|
format.js
format.html
end
end
This will allow you to call...
#app/views/stores/create.js.erb
<% if flash.now[:alert] %> alert("<%=j flash.now[:alert] %>"); <% end %>
window.location.href = <%= root_url %>;
Ref
Your new code can be improved a little :
def create
#store = current_user.stores.new store_params
if #store.save
flash[:notice] = "New store created"
else
flash.now[:alert] = "Save failed! #{#store.errors.full_messages.join(";")}"
end
respond_to do |format|
format.js
end
end
If you wanted to DRY up your code even more, you'll want to look at the responders gem:
#app/controllers/stores_controller.rb
class StoresController < ApplicationController
respond_to :js, only: :create
def create
#store = ...
respond_with #store if #store.save
end
end
If you have remote: true in your form, the format that is detected by the controller will be format.js, which is not present in your successful #store.save section.
2 options:
Default to normal form submit (by removing remote: true)
Load another js.erb file by adding format.js just like in the else clause then do the error handling there via some javascript.
I have a review page for schools that i link to like this;
<%= school_reviews_path(school_id: #school.id) %>
Generating a link like this;
http://address.com/school_reviews?school_id=1
However, under the reviews is a form that create a new review posting it create action of schools_reviews controller
However, after creation of the review i want to rerender the page with the accompanying parameters i.e school_id=1
I have tried several methods like this but it aint working instead it redirects to http://address.com/school_reviews without the params meanig we do not fetch the right information.
def create
#review = SchoolReview.new(params[:review])
respond_to do |format|
if #review.save
format.html { redirect_to school_reviews_path(:school_id => #review.school_id) }
format.json { head :no_content }
else
format.html { redirect_to school_reviews_path(:school_id => #review.school_id) }
format.json { head :no_content }
end
end
end
Any ideas, will be greatful.
The problem here is that your form, most likely, isn't setting the school id anywhere, so let's make your code be more like what it should be, first setup routes like this:
resources :schools do
resources :school_reviews
end
Your controller name doesn't change, but the implementation will change to be as follows:
class SchoolReviewsController < ApplicationController
before_filter :load_school
def create
#review = #school.school_reviews.build(params[:review])
respond_to do |format|
if #review.save
format.html { redirect_to school_school_reviews_path(#school) }
format.json { head :no_content }
else
format.html { redirect_to school_school_reviews_path(#school) }
format.json { head :no_content }
end
end
end
protected
def load_school
#school = School.find(params[:school_id])
end
end
You will also have to change the form_for call at your view to something like:
<%= form_for( [#school, #review] ) do |f| %>
Your form content here.
<% end %>
Your link will change as well to:
<%= school_school_reviews_path(#school) %>
You might also want to change the SchoolReview model just to Review to remove the school_school_ from the URLs.
And, as usual, read the documentation on routes to understand what's going on.
On the update action of the Video controller, I have written -->
def update
if current_user.video.update_attributes(video_params)
flash[:success] = "Video App Updated!"
redirect_to root_url
else
render :edit
end
end
However, the render :edit part seems to be throwing out an error. It says :
First argument in form cannot contain nil or be empty
Extracted source (around line #6):
<div class="row">
<div class="span6 offset3">
<%= form_for(#video) do |f| %> # line 6
I'm assuming I'm not quite understanding what the render is doing. This is what my edit action looks like -->
def edit
#video = current_user.video
end
What do you guys think? Still a noob, much appreciated :)
You don't set #video variable on update action, so it's nil. You should have:
def update
#video = current_user.video
if current_user.video.update_attributes(video_params)
flash[:success] = "Video App Updated!"
redirect_to root_url
else
render :edit
end
end
You should remember that rendering other action's template in controller doesn't run the code of that action. So, if you have
render :edit
the partial edit will be rendered, but controller code for this action (setting #video) won't run.
The line render :edit will show the edit.html.erb view, but the edit action is not executed. The edit.html.erb is expecting a #video variable that is not set in the update action, that's why you have this error. There is 2 solutions:
Set the #video variable in the update_action
def update
#video = current_user.video
if #video.update_attributes(video_params)
flash[:success] = "Video App Updated!"
redirect_to root_url
else
render :edit
end
end
or
Redirect to the edit action when update fails
def update
if current_user.video.update_attributes(video_params)
flash[:success] = "Video App Updated!"
redirect_to root_url
else
flash[:alert] = current_user.video.errors.full_messages.join('\n')
redirect_to :action => :edit, :id => current_user.video.id
end
end
If the edit was complex, the second solution would be better because it avoids duplication. In your case, the first solution is good also.
The difference between render and redirect_to is explained in Guide Layouts and Rendering in Rails 2.3.2
I have the same type of condition and i have done it like this. and its working in my case
def update
#video = current_user.video
respond_to do |format|
if #video.update_attributes(video_params)
format.html { redirect_to root_url }
else
format.html { render :edit }
end
end
end
I've got an problem with my update action for a nested resource.
In my app, my orders have many invoices.
Creating a new invoice, I correctly end up with the following url:
/orders/11/invoices/new
And when I edit the invoice, again, it's all correct:
/orders/11/invoices/3/edit
This works fine when the save is a success, however if the validation fails, it routes back to:
/invoices/3
I have the following in my invoices controller:
def update
# #order = Order.find(params[:order_id])
# #invoice = #order.invoices.find(params[:id])
#invoice = Invoice.find(params[:id])
respond_to do |format|
if #invoice.update_attributes(params[:invoice])
format.html { redirect_to(order_invoice_path(#invoice.order, #invoice), :notice => 'Invoice was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #invoice.errors, :status => :unprocessable_entity }
end
end
end
def edit
#invoice = Invoice.find(params[:id])
3.times { #invoice.invoice_items.build }
end
I'm assuming I need to edit the #invoice.errors part but I don't know what to change it to?
Any help appreciated. Jx
When updating failed, you use "render" (comparing with the "redirect_to" in the succeeding path), this brings you to invoice editing path by default. You can use "redirect_to" here to keep the URI path you want, but need remembering to preserve the models' states so your users don't need to fill the entire form all over again.
A detail instruction can be found here: How to make a render :edit call show the /edit in the address bar
Yan
in your form you should add your order, like this:
<%= form_for [#order, #invoice] ... do |f| %>
...
<% end %>
And then uncomment this two lines
# #order = Order.find(params[:order_id])
# #invoice = #order.invoices.find(params[:id])
so your form will send its request to POST /orders/XX/invoices/XX