Rails AJAX returns error - javascript

I am trying to understand Rails built in AJAX helper -> remote: true.
I have a link in a view (views/show) that calls a controller via AJAX:
<%= link_to 'My Profile', edit_user_path(current_user), class: 'user-profile', remote: true %>
Which hits the appropriate controller (controllers/users_controller.rb) with the appropriate fields (tested with byebug):
def edit
#user = User.find(params[:id])
respond_to do |format|
format.html { render plain: 'profile' }
format.js { render plain: 'profile' }
format.json { render plain: 'test' }
end
end
And I have my JS (javascripts/users.js) perform an action on ajax success or failure:
$(document).ready(function() {
$('.user-profile').on('ajax:success', function(event) { console.log(event) });
$('.user-profile').on('ajax:error', function(event) { console.log(event) });
});
However, no matter what I try, the ajax:error function always executes and I cannot figure out why. Any ideas?

Related

Display Rails error messages inside a Bootstrap modal when using Selectize and Ajax

I'm trying to build an app that allows users to share quotes by artists about other artists. For instance, a quote by Bob Dylan about John Lennon. As such, my Artist model is set up in a way that allows an artist to be both the Speaker and Topic on a Quote, and each Quote belongs_to each Artist as the Speaker or Topic.
I'm having trouble getting a Rails error message to display inside a Bootstrap modal when using Selectize to trigger the modal. I got the modal working by following this demo.
The modal is used to create a new Artist from the quotes/new form, but I can't get the error messages for the Artist model to display in the Bootstrap modal or on the quotes/new page. When I try to create something that triggers an error message (such as validates_uniqueness) in the modal, it just closes the modal and doesn't display the error message. Everything else is working as expected.
What am I missing to connect the Ajax request to the view?
Here's the relevant section of my form:
<%= f.label :speaker, 'Who said it?' %>
<%= f.collection_select :speaker_id, #speakers, :id, :name,
{prompt: 'Select an artist'}, {class: 'form-control selectize-speaker'} %>
Full source for quotes/form.html.erb
Here's the relevant code in my controller:
class ArtistsController < ApplicationController
def create
#artist = current_user.artists.build(artist_params)
authorize #artist
respond_to do |format|
if #artist.save
if request.referer.include?("artists")
flash[:success] = "Artist was successfully created."
format.html { redirect_to #artist }
else
format.json { render json: #artist }
end
else
format.html { render :new }
format.json { render json: #artist.errors.full_messages }
end
end
end
end
Full source for artists_controller.rb
Relevant javascript code:
$(document).on('turbolinks:load', function() {
var selectizeCallback = null;
// Selectize Speaker
$('.speaker-modal').on('hide.bs.modal', function(e) {
if (selectizeCallback != null) {
selectizeCallback();
selecitzeCallback = null;
}
$('#new_speaker').trigger('reset');
});
$('#new_speaker').on('submit', function(e) {
e.preventDefault();
$.ajax({
method: 'POST',
url: $(this).attr('action'),
data: $(this).serialize(),
success: function(response) {
selectizeCallback({value: response.id, text: response.name});
selectizeCallback = null;
$('.speaker-modal').modal('toggle');
}
});
});
$('.selectize-speaker').selectize({
create: function(input, callback) {
selectizeCallback = callback;
$('.speaker-modal').modal();
$('#speaker_name').val(input);
}
}); // end selectize speaker
}); // end document on
Full source for quotes.js.
And my error message partial, shared/_error_messages.html.erb:
<% if object.errors.any? %>
<div id='error_explanation'>
<div class='alert alert-danger'>
<button type='button' class='close' data-dismiss='alert'>×</button>
<p><strong>The form contains
<%= pluralize(object.errors.count, 'error') %>.</strong></p>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
</div>
<% end %>
Additional source files:
models/quote.rb
models/artist.rb
controllers/quotes_controller.rb
Both a successful and unsuccessful save are returning a 200 response, which means that your success callback will be called:
success: function(response) {
selectizeCallback({value: response.id, text: response.name});
selectizeCallback = null;
$('.speaker-modal').modal('toggle');
}
This always toggles the modal, therefore closing it.
If you ensure that the response is a 4xx on validation error, then you can define an error callback which populates your errors list and does not close the modal.
So instead of:
format.json { render json: #artist.errors.full_messages }
Use something like:
format.json { render json: #artist.errors.full_messages, status: :bad_request }
Then, pass an error callback to your AJAX call:
error: function(response) {
// somehow populate your errors list
// display the errors list
}
This won't work right now, though, because the errors container won't exist: you only render it under this condition:
object.errors.any?
Which, on initial load, will always evaluate false. What you can instead do is always render the errors container, default it to some hidden class if there aren't any errors, and in your error callback, remove the hidden class after it's populated.

Respond to format.html with Javascript action

I'd like to execute some javascript on the respond_to action for format.html.
window.top.location.href='#{ #recurring_charge.confirmation_url }'
I need to execute javascript to open the window out of the iframe, otherwise I cannot render the page due to the following error message 'X-Frame-Options'to 'deny'.
How can I do this?
def confirm_billing
current_shop.with_shopify!
#recurring_charge = RecurringCharge.new(recurring_charge_params)
if #recurring_charge.save
respond_to do |format|
format.html { render :js => "window.top.location.href='#{ #recurring_charge.confirmation_url }'" }
format.json { render json: { success: true, redirect_url: #recurring_charge.confirmation_url }, status: 201 }
end
else
respond_to do |format|
format.html { redirect_to root_url, notice: 'Something went wrong' }
format.json { render json: { success: false, errors: #recurring_charge.errors.full_messages }, status: 201 }
end
end
end
I was able to get it to work using render :text, can't believe it actually worked!
format.html { render :text => "<script>window.top.location.href='#{ #recurring_charge.confirmation_url }';</script>" }

Full Calendar: ActiveRecord::RecordNotFound (Couldn't find Event with 'id'=update)

I am trying to include FullCalendar into my rails app. But I am having difficulty with saving after drag-and-drop.
I have 2 models - Event and Workout. Under event, there is workout.
I am able to get the full calendar up and running and save events, but when I drag and drop, then refresh, the events would revert to their original time.
In my console, it says:
Couldn't find Event with 'id'=update.
When the events are created and saved through a form, they are properly displayed and saved on calendar. It's just when I try to drag-and-drop.
Here is my Event controller.
class EventsController < ApplicationController
before_action :set_event, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
def index
#events = current_user.events
end
def show
#event = Event.find(params[:id])
end
def new
#event = current_user.events.new
end
def edit
#event = current_user.events.find(params[:id])
end
def create
#event = current_user.events.new(event_params)
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: 'Event was successfully created.' }
format.json { render :show, status: :created, location: #event }
else
format.html { render :new }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #event.update(event_params)
format.html { redirect_to #event, notice: 'Event was successfully updated.' }
format.json { render :show, status: :ok, location: #event }
else
format.html { render :edit }
format.json { render json: #event.errors, status: :unprocessable_entity }
end
end
end
def destroy
#event.destroy
respond_to do |format|
format.html { redirect_to events_url, notice: 'Event was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_event
#event = Event.find(params[:id])
end
def event_params
params.require(:event).permit(:title, :description, :start_time, :end_time, :workouts_attributes => [:id, :name, :category, :_destroy])
end
end
Below is application.js file that controls the calendar.
$(document).on('turbolinks:load', function() {
$('#calendar').fullCalendar({
editable: true,
events: '/events.json',
eventDrop: function(event, delta) {
$.ajax({
type: "PUT",
url: '/events/update',
dataType: 'json',
data: {id:event.id, start:event.start_time, end: event.end_time}
});
},
});
});
When I try to drag and drop, I get a message below:
ActiveRecord::RecordNotFound (Couldn't find Event with 'id'=update):
app/controllers/events_controller.rb:74:in `set_event'
When I check my console, all the events have IDs. But I'm guessing maybe when the event is being drag-and-dropped, the new event doesn't have an ID?
Rails app is assuming update as :id when you are hitting this URL /events/update
$ rake routes
event PUT /events/:id(.:format) events#update
change the url in ajax request to
$.ajax({
type: 'PUT',
url: '/events/' + event.id,
dataType: 'json',
data: { event: { start_time:event.start_time, end_time: event.end_time } }
});

How update record for Ajax deletion in rails?

Vehicles_controller.rb
class VehiclesController < ApplicationController
before_action :set_vehicle, only: [:show, :edit, :update, :destroy]
load_and_authorize_resource
# skip_before_action :verify_authenticity_token
# GET /vehicles
# GET /vehicles.json
def index
#q = Vehicle.search(params[:q])
#vehicles = #q.result(:distinct => true).order_by([:updated_at, :desc]).page(params[:page]).per(5)
#vehicle = Vehicle.new
end
# GET /vehicles/1
# GET /vehicles/1.json
def show
end
# GET /vehicles/new
def new
end
# GET /vehicles/1/edit
def edit
end
# POST /vehicles
# POST /vehicles.json
def create
params[:vehicle][:name] = params[:vehicle][:name].upcase if !params[:vehicle][:name].nil?
#vehicle = Vehicle.new(vehicle_params)
#vehicles = Vehicle.all.order_by([:updated_at, :desc]).page(params[:page]).per(5)
respond_to do |format|
if #vehicle.save
format.html { redirect_to :back, notice: 'Vehicle was successfully created.' }
format.json { render action: 'index', status: :created, location: #vehicle }
format.js
else
format.js
format.html { render action: 'new' }
format.json { render json: #vehicle.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /vehicles/1
# PATCH/PUT /vehicles/1.json
def update
params[:vehicle][:name] = params[:vehicle][:name].upcase if !params[:vehicle][:name].nil?
respond_to do |format|
if #vehicle.update(vehicle_params)
format.html { redirect_to #vehicle, notice: 'Vehicle was successfully updated.' }
format.json {render json: #vehicle, status: :ok}
else
format.html { render action: 'edit' }
format.json { render json: #vehicle.errors, status: :unprocessable_entity }
end
end
end
# DELETE /vehicles/1
# DELETE /vehicles/1.json
def destroy
#vehicle.destroy
respond_to do |format|
format.html { redirect_to vehicles_url, notice: "#{#vehicle.name} deleted successfully" }
format.json { head :no_content }
format.js { render :layout => false}
end
end
def vehicle_search
#q = Vehicle.search(params[:q])
#vehicles = #q.result(:distinct => true).order_by([:updated_at, :desc]).page(params[:page]).per(5)
respond_to do |format|
format.html
format.js
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_vehicle
#vehicle = Vehicle.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def vehicle_params
params.require(:vehicle).permit(:name, :created_at, :updated_at)
end
end
index.html.erb
<% #vehicles.each do |vehicle| %>
<tr>
<td><%= best_in_place vehicle,:name, class: "v_name", id: vehicle.id%></td>
<td><%= link_to '<i class="fa fa-trash-o"></i>'.html_safe, vehicle, method: :delete, remote: true, data: { confirm: "Are you sure to delete <b>\"#{vehicle.name}\"?</b>", commit: "OK" }, title: "Delete Vehicle", class: "btn btn-danger delete_vehicle" %>
</td>
</tr>
<%end%>
vehicle.rb
class Vehicle
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
validates :name, presence: true, uniqueness: true, :format => {:with => /[1-9a-zA-Z][0-9a-zA-Z ]{3,}/}
has_many :pre_processings
has_many :batch_counts
end
destroy.js.erb
$('.delete_vehicle').bind('ajax:success', function() {
$(this).closest('tr').fadeOut();
});
$(".alert").show();
$(".alert").html("Vehicle \"<b><%=#vehicle.name %></b>\" deleted successfully.")
$(".alert").fadeOut(5000);
Here i am using destroy.js.erb to delete vehicle name. It works fine.
Here i am using Inline Edit for Best_in_place. After update the Vehicle name, The ajax alert shows previous vehicle name. Not update vehicle name. So how to show updated vehicle name alert.
$(".alert").html("Vehicle \"<b><%=#vehicle.name %></b>\" deleted successfully.")
The above alert i will show current vehicle name to delete. But if i update using inline edit after i want to delete i'll shows previous record.
Example:
Vehicle Name: MH P5 2312 , I want to delete it, The alert shows are you delete "MH P5 2312 " Vehicle.
After inline edit i will change Vehicle Name: AP 16 1234, So i want to delete Vehicle Name: AP 16 1234, but the ajax alert shows, Vehicle Name: MH P5 2312 delete vehicle name.
Advance Thanks.
This is because you need to update the alert message on ajax request success
in your destroy.js.erb
you should ditch the .erb and fetch the car name from an html element instead
your js code should be as the following:
$('.delete_vehicle').bind('ajax:success', function() {
var vehicleName = $('vehicle_link').data('vehicle_name');
// here I assume that you have a link for initiating the delete process and you can add an attribute to this anchor tag element called data-vehicle_name and set its value to the car name.
$(this).closest('tr').fadeOut();
$(".alert").show();
$(".alert").html("Vehicle \"<b>" + vehicleName + "</b>\" deleted successfully.");
$(".alert").fadeOut(5000);
});
Firstly, if you're going to use the ajax:success callback (which you don't in this instance), you'll need to bind the alert functions to it:
$('.delete_vehicle').on('ajax:success', function() {
$(this).closest('tr').fadeOut();
$(".alert").show().html("Vehicle \"<b><%=#vehicle.name %></b>\" deleted successfully.").fadeOut(5000);
});
Secondly, if you're not getting an updated #vehicle.name because - I think - your Rails instance will have already rendered it. I'm not exactly sure why, but the key will be to pass your data through the ajax request if you were still going to use it:
#app/controllers/vehicles_controller.rb
class VehiclesController < ApplicationController
def destroy
...
format.js { render :layout => false, json: #vehicle }
end
end
#app/assets/javascripts/application.js
$('.delete_vehicle').on('ajax:success', function(event, data, status, xhr) {
vehicle = JSON.parse(data);
$(this).closest('tr').fadeOut();
$(".alert").show().html("Vehicle \"<b>" + + "</b>" deleted successfully.").fadeOut(5000);
});
--
Bottom line is that js.erb is meant to give you actionable Javascript functionality from the controller action. I think your main problem is you're using ajax within this, likely causing a conflict.
Above is how I'd use Ajax (IE put the code in the javascript assets folder); if you wanted to use destroy.js.erb still, you could use the following:
#app/views/vehicles/destroy.js.erb
$("<%= #vehicle.id %>").closest('tr').fadeOut();
$(".alert").show();
$(".alert").html("Vehicle \"<b><%=#vehicle.name %></b>\" deleted successfully.")
$(".alert").fadeOut(5000);

respond_to not working with format.js if format.html is present

When using respond_to, my AJAX call only invokes the javascript response if the HTML response isn't present.
I have the following javascript for submitting an edit-in-place action:
$(".eip").editable("/surveys", {
name : 'survey[name]',
select : true,
event : 'edit',
submit : 'Save',
cancel : 'cancel',
cssclass : "editable",
onblur : 'ignore',
onedit : function() { $(".eip_desc").hide(); },
onreset : function() { $(".eip_desc").show(); },
onsubmit : function() { $(".eip_desc").show(); }
});
Then I have the following Survey controller method:
class SurveysController < ApplicationController
def create
#survey = Survey.new(params[:survey])
if #survey.save
respond_to do |format|
format.html { redirect_to surveys_path, :notice => "Survey created!" }
format.js
end
else
render :action => :new
end
end
end
If I remove the format.html line, then format.js responds as it should. But if I leave format.html in there, then the whole page renders when I submit via that editable javascript bit.
I'm running Rails 3.0.3 for this app.
from docs
(String) method: Method to use when
submitting edited content. Default is
POST. You most likely want to use POST
or PUT. PUT method is compatible with
Rails.
try to pass a method: "put"

Categories