I've been at this for hours and I can't figure out why angular is not triggering my error call back when my rails back-end raises a proper error. I'm using angular 1.2.0rc1.
According to the documentation:
non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
And I'm using it in my angular controller during a save product operation:
$scope.saveProduct = function(product){
if (product.id) {
Product.update({id: product.id},{product: product}, function(data){
console.log('handle success')
}, function(){
console.log('handle error') //THIS IS NEVER OUTPUT!
});
}
}
Here is the resource definition:
angular.module('sellbriteApp.resources').factory('Product', function ($resource) {
return $resource('/api/products/:id', { id: "#id" },
{
'create': { method: 'POST' },
'index': { method: 'GET', isArray: true },
'show': { method: 'GET', isArray: false },
'update': { method: 'PUT' },
'destroy': { method: 'DELETE' }
}
);
});
Here is my rails controller:
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to [:edit, #product], notice: 'Product was successfully updated.' }
format.json { render 'products/show.json.jbuilder', status: :accepted }
else
format.html { render action: 'edit' }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
Rails returns a 422 status when attempting to save a product with a duplicate sku, and I want to display an error msg on the front end.
I would expect that angular should execute the error handling function provided in the update call, but I can't get that far. Instead in my console I see:
TypeError: Cannot read property 'data' of undefined with an unhelpful stacktrace:
TypeError: Cannot read property 'data' of undefined
at $http.then.value.$resolved (http://localhost:9000/bower_components/angular-resource/angular-resource.js:477:32)
at wrappedCallback (http://localhost:9000/bower_components/angular/angular.js:9042:59)
at wrappedCallback (http://localhost:9000/bower_components/angular/angular.js:9042:59)
at http://localhost:9000/bower_components/angular/angular.js:9128:26
at Object.Scope.$eval (http://localhost:9000/bower_components/angular/angular.js:9953:28)
at Object.Scope.$digest (http://localhost:9000/bower_components/angular/angular.js:9809:23)
at Object.$delegate.__proto__.$digest (<anonymous>:844:31)
at Object.Scope.$apply (http://localhost:9000/bower_components/angular/angular.js:10039:24)
at Object.$delegate.__proto__.$apply (<anonymous>:855:30)
at done (http://localhost:9000/bower_components/angular/angular.js:6542:45)
What am I missing?
UPDATE:
Apparently this http interceptor is related. If I comment this code out, the error function is called. I had copied this snippet from some where else and modified it in order to redirect a user to the sign_up page if they hit a rails api when they are not logged in. It must be interfering, but I'm not sure how I should fix it.
App.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('securityInterceptor');
}]);
App.factory('securityInterceptor', ['$injector', '$location', '$cookieStore', function ($injector,$location,$cookieStore) {
return function(promise) {
var $http = $injector.get('$http');
return promise.then(null, function(response){
if (response.status === 401) {
$cookieStore.remove('_angular_devise_merchant');
toastr.warning('You are logged out');
$location.path('/sign_in');
}
});
};
}]);
You need to reject the promise in your interceptor as well, otherwise its considered as if you've 'handled' the exception.
So:
App.factory('securityInterceptor', ['$injector', '$location', '$cookieStore', '$q', function ($injector,$location,$cookieStore, $q) {
return function(promise) {
var $http = $injector.get('$http');
return promise.then(null, function(response){
if (response.status === 401) {
$cookieStore.remove('_angular_devise_merchant');
toastr.warning('You are logged out');
$location.path('/sign_in');
}
return $q.reject(response);
});
};
}]);
Related
I have a button on my new and edit views that sends a post request to my Letter controller through an Ajax call. If the Ajax call works perfectly in the new view, it throws a 404 error for my edit view.
Route:
post 'letters/ajax_send_test_botletter', to: 'letters#send_test_botletter', as: 'send_test_botletter'
The form is defined like this:
<%= form_for(letter, :html => {class: "directUpload", remote: true}) do |f| %>
The button triggering the Ajax call in the form:
<button class="cta3" id="send_test_letter">Send a test campaign to yourself</button>
Ajax call:
$('#send_test_letter').on('click', function(){
$('form').submit(function() {
var valuesToSubmit = $(this).serialize();
$.ajax({
type: "POST",
url: "/letters/ajax_send_test_botletter",
data: valuesToSubmit,
dataType: "JSON" // you want a difference between normal and ajax-calls, and json is standard
}).success(function(json){
if(json['value'] == "No Recipient") {
$('#send_test_letter').css('display', 'none');
$('#save_test_user').css('display', 'block');
} else {
console.log("Success")
$('#confirmation_test_sent').html('Test successfully sent. Check your Messenger.')
}
$('form').unbind('submit');
});
return false; // prevents normal behaviour
});
});
My send_test_botletter method
def send_test_botletter
#message_content = params[:letter]['messages_attributes']['0']['content']
#button_message = params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_text'] if params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_text'] != ''
#button_url = params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_url'] if params[:letter]['messages_attributes']['0']['buttons_attributes']['0']['button_url'] != ''
#cards = params[:letter]['cards_attributes'] if params[:letter]['cards_attributes'].present? == true
#test_segment = Segment.where(core_bot_id: #core_bot_active.id, name: "test").first
#recipients = BotUser.where(core_bot_id: #core_bot_active.id, source: #test_segment.token)
if #recipients.exists?
send_message_onboarding if #message_content != '' and #button_message.present? == false
send_message_button_onboarding if #message_content != '' and #button_message.present? == true and #button_url.present? == true
send_card_onboarding if #cards
respond_to do |format|
format.json { render json: {"value" => "Success"}}
end
else
respond_to do |format|
format.json { render json: {"value" => "No Recipient"}}
end
end
end
I get the following error in the Chrome console for the edit view:
POST http://localhost:3000/letters/ajax_send_test_botletter 404 (Not
Found)
And in my Rails logs:
ActiveRecord::RecordNotFound (Couldn't find Letter with
'id'=ajax_send_test_botletter):
It seems it calls the Update method instead of the send_test_botletter method...
Any idea what's wrong here?
I found the trick. The problem was the PATCH method in the edit form.
I found a plugin in this discussion in order to modify the serialized data and change the method to "post":
$('#send_test_letter').on('click', function(){
$('form').submit(function() {
var valuesToSubmit = $(this).awesomeFormSerializer({
_method: 'post',
});
$.ajax({
type: "POST",
url: "/letters/ajax_send_test_botletter",
data: valuesToSubmit,
dataType: "JSON" // you want a difference between normal and ajax-calls, and json is standard
}).success(function(json){
if(json['value'] == "No Recipient") {
$('#send_test_letter').css('display', 'none');
$('#save_test_user').css('display', 'block');
} else {
console.log("Success")
$('#confirmation_test_sent').html('Test successfully sent. Check your Messenger.')
}
$('form').unbind('submit');
});
return false; // prevents normal behaviour
});
});
(function ( $ ) {
// Pass an object of key/vals to override
$.fn.awesomeFormSerializer = function(overrides) {
// Get the parameters as an array
var newParams = this.serializeArray();
for(var key in overrides) {
var newVal = overrides[key]
// Find and replace `content` if there
for (index = 0; index < newParams.length; ++index) {
if (newParams[index].name == key) {
newParams[index].value = newVal;
break;
}
}
// Add it if it wasn't there
if (index >= newParams.length) {
newParams.push({
name: key,
value: newVal
});
}
}
// Convert to URL-encoded string
return $.param(newParams);
}
}( jQuery ));
form_for(letter... generates a different url and method depending whether or not the instance is persisted, defaulting to create and post or update and patch as appropriate.
When you hit submit, it's trying to hit this endpoint, before your listener kicks in. And in doing so, breaks the remaining js.
However, you can also provide url and method options to form_for. Try providing a blank url option and the correct method (form_for letter, ..., url: '', method: :post).
Alternatively, you could stop the default behaviour / propagation on form submission:
$('form').submit(function(e) {
e.stopPropagation() // Or could simply be `preventDefault()`, depending on your use case
...
// your AJAX
}
Able to test out these approaches?
Update
Your method is actually nesting a submit listener within the click one. Try the following:
$('#send_test_letter').on('click', function(e){
e.stopPropagation()
var $form = $(this).closest('form')
var valuesToSubmit = $form.serialize();
$.ajax({
type: "POST",
url: "/letters/ajax_send_test_botletter",
data: valuesToSubmit,
dataType: "JSON" // you want a difference between normal and ajax-calls, and json is standard
}).success(function(json){
if(json['value'] == "No Recipient") {
$('#send_test_letter').css('display', 'none');
$('#save_test_user').css('display', 'block');
} else {
console.log("Success")
$('#confirmation_test_sent').html('Test successfully sent. Check your Messenger.')
}
return false; // prevents normal behaviour
});
});
I know there are many questions about that already on stackoverflow but none of them has been useful for me. Here is my ajax code:
function update_schedule($task){
var taskId = $task.data("taskId"),
startHour, endHour,
userId = $('#users').val();
if( !$task.hasClass('daily-task')){
startHour = getStartHour($task),
endHour = startHour + (+$task.data('time'));
}
console.log(startHour, endHour)
$.ajax({
url: '/heimdall/update_scheduled_task',
method: 'POST',
data: { "task_id": taskId, "start_hour": startHour, "end_hour": endHour, "user_id": userId },
success: function (){
console.log("SUCESS!!", data['head'])
},
error: function () {
console.log("FAILURE");
},
async: true
});
}
The controller code:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
end
I want to return the id of found or created task. I don't know how to send/receive this information. I've read about respond to but still I don't know how to use it in this case.
You may do render json: ... to return the required info to ajax in JSON format:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id])
render json: {scheduled_task_id: scheduled_task.id}
end
And in the ajax function's success, use it like:
success: function (data){
var data = JSON.parse(data);
console.log(data["scheduled_task_id"]);
},
First, you need to improve your controller code structure (You can rely on the default scaffold generator methods). Then, you must indicate that your method will respond to json format only, with the answer you want to return, something like this:
def update_scheduled_task
scheduled_task = ScheduledTask.find_or_create_by(task_id: params[:task_id])
if (scheduled_task && scheduled_task.update_attributes(start_hour: params[:start_hour], end_hour: params[:end_hour], task_id: params[:task_id], user_id: params[:user_id]))
render json: { scheduled_task_id: scheduled_task.id }
else
render json: { error: l18n.t("error.messages.request_error") }
end
end
Then, you must modify the success and failure response methods of the jquery ajax request. Here's an example of how it might look:
$.ajax({
url: "/heimdall/update_scheduled_task",
method: "post",
data: { task_id: taskId, start_hour: startHour, end_hour: endHour, user_id: userId },
success: function(result) {
if (result["scheduled_task_id"] != null) {
console.log("Schedule record => " + result["scheduled_task_id"])
} else {
console.log("Error: " + result["error"])
}
},
error: function() {
console.log("Ajax request error");
},
// async: true => By default JQuery set true to async param.
});
Do not forget that you need to add a rule to access this method in the file config/ruotes.rb, something like this:
post update_scheduled_task, :defaults => { :format => 'json' }
I hope this helped you, regards!
jQuery's success and error callbacks rely on the status code returned by the controller. Make sure that you return the right status code when you are unable to create/read/update the object. On success, simply render a JSON with the id property set. I beleive update_attributes returns true on success:
if scheduled_task && scheduled_task.update_attributes(...)
render json: { id: scheduled_task.id }
else
head :unprocessable_entity
end
I'm trying to add a before_action that checks for a user and returns json to the angular app with a status of 401 if there is no user.
the function is simply
def authenticate_user
if !logged_in
render json: {:status => 401}
end
end
However, this never hits my httpInterceptor and renders the json straight to the page. How can I get this response to return with the proper headers so the interceptor can handle a 401?
app.run(function($http, $cookies, $rootScope, $location) {
$http.defaults.headers.common['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers.common['Content-Type'] = 'application/json';
});
app.factory('myHttpInterceptor', ['$cookies', '$location', '$window',
function($cookies, $location, $window) {
return {
'request': function(config) {
return config
},
'response': function(response) {
if ($location.$$path !== '/' && !$cookies.csrftoken){
$location.path('/');
}
return response
}
}
}
]);
Did you add your interceptor to the httpProvider ?
app.config(function($httpProvider) {
$httpProvider.interceptors.push('myHttpInterceptor');
})
See there : https://docs.angularjs.org/api/ng/service/$http
The question is simple: return an error from a controller as JSON response:
//controller
def action1
# .....
return render(json: { status: :unprocessable_entity }) if !is_my_valid?
# .... doing something
render(json: my_hash.to_json)
end
//view
$.ajax({
url: 'some_url',
//...
}).done(function(data) {
// it always gets here even in case of an error
}).fail(function(jqXHR, textStatus) {
alert("error: " + textStatus);
});
So the problem is that on the HTML page, even in case of an error, the callback function in .done() is executed, not the one in .fail(). How do I make it into .fail()?
Send back a 500 HTTP status code:
render(json: { ... }, status: 500)
I'm trying to execute a ajax every 5 seconds an get the json result. But the ajax it's not working I always get:
SyntaxError {stack: (...), message: "Unexpected token <"}
$(document).ready(function(){
setInterval(function(){
var url = "/hangout_second_user";
$.ajax({
url: url,
type: "GET",
data: {"username" : "someusername"},
dataType: 'json',
success: function(){
alert(result);
},
error: function(xhr, textStatus, error) {
console.log("Impossible to connect");
console.log(xhr.statusText);
console.log(textStatus);
console.log(error);
}
});
}, 5000);
});
Here is the action of my controller:
def hangout_second_user
#incoming_kid = Kid.where(username: params[:username]).first
prueba = REDIS.smembers "hangout:#{current_kid.id.to_s}"
if !prueba.empty?
current_kid.set_busy_status!
respond_to do |format|
format.json { render json: "{url:#{prueba[0].to_s}}", success: :ok, error: false }
end
end
end
UPDATE
I changed the render format like this:
format.json { render json: prueba[0].to_json, success: :ok, error: false }
but I still getting and html file and that's why I'm getting an error. Any help please
You should use either the $.ajax() function or the $.getJSON() function, as $.getJSON() will also perform an AJAX call.
So a possible solution would be:
setInterval(function(){
var url = "/hangout_second_user";
$.ajax({
url: url,
data: {"user_id" : "537c63ea20db9040d2000332"},
dataType: 'json',
success: function(result){
$.each(result, function(i, field){
alert(result);
});
},
error: function(xhr, textStatus, error) {
console.log("Impossible to connect");
console.log(xhr.statusText);
console.log(textStatus);
console.log(error);
}
});
}, 5000);
Or alternatively:
setInterval(function(){
var url = "/hangout_second_user";
$.getJSON(url, {"user_id" : "537c63ea20db9040d2000332"}; function(result) {
$.each(result, function(i, field){
alert(result);
});
});
}, 5000);
You cant use "#{current_kid.id.to_s}" in javascript. Try storing its value somewhere in hidden _field and access it in javascript