I am setting a model in CustomerRoute's setupController hook as follows:
var self_controller = controller;
var onSuccess = function(customer) {
var reloadSuccess = function(customer_reloaded) {
// customer is reloaded successfully
self_controller.set('model', customer_reloaded);
};
var reloadError = function(err_response_for_reload) {
if(err_response_for_reload.status == 401) {
terminate_session();
}
else {
HTTP_error();
self_controller.transitionTo('all_customers');
}
};
// reload Model to forcefully fetch from server
customer.reload().then(reloadSuccess).catch(reloadError);
};
var onError = function(reason) {
if(reason.status == 401) {
terminate_session();
}
else {
HTTP_error();
self_controller.transitionTo('all_customers');
}
};
var customer_promise = self_controller.store.find('customer', model.id);
customer_promise.then(onSuccess).catch(onError);
But I am unable to catch HTTP errors (401 unauthorized, 500 Internal Server Error, etc.).
ember-data provides a hook ajaxError to handle all error msgs in DS.RestAdapter. If you write it for ApplicationAdapter it will be used by all models based on RestAdapter. Here is how code looks(extracted from ember docs)
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var jsonErrors = Ember.$.parseJSON(jqXHR.responseText)["errors"];
return new DS.InvalidError(jsonErrors);
} else {
return error;
}
}
});
Link for same in ember docs http://emberjs.com/api/data/classes/DS.RESTAdapter.html#method_ajaxError
Related
If I use a transitionTo on a route with a slow model hook, the loading.hbs state never gets triggered (I have loading.hbs files at all of the levels -- cluster, cluster.schedule and cluster.schedule.preview_grid). I tried renaming the one at cluster.schedule preview_grid-loading.hbs with no luck.
On the transitionTo, there is no model or model id passed in, just the route:
viewPreviewGrid: function() {
this.transitionTo('cluster.schedule.preview_grid');
},
I also have a loading action defined as follows:
loading(transition) {
var controller = this.controller;
if (!Ember.isNone(controller)) {
this.controller.reset();
}
transition.promise.finally(function() {
NProgress.done();
});
}
During the transitionTo call the page just stays on the previous route until the promises in the model hook resolve, and then it transitions to the other route. If I refresh the page, the loading state gets triggered just fine. Is this a known behaviour for transitionTo?
This is my model hook:
model: function (/*params*/) {
var socialProfile = this.modelFor('cluster.schedule').get('firstObject');
if (!socialProfile.get('isInstagram')){
throw new Error("Attempted to access preview with non-ig profile: " + socialProfile.get('id'));
}
var accessToken = socialProfile.get('token');
var self = this;
return Ember.RSVP.hash({
igPosts: new Ember.RSVP.Promise(function(resolve) {
self.getUsersRecentMedia(accessToken).then(function(response) {
var igPosts = Ember.A([]);
response.data.forEach(function(data) {
igPosts.pushObject(self.igPostFromResponse(data, socialProfile));
});
resolve(igPosts);
});
}),
posts: new Ember.RSVP.Promise(function(resolve) {
self.store.query('gram', { type: 'preview', social_profile_id: socialProfile.get('id'), limit: self.get('postLimit') }).then(function(grams) {
var filteredGrams = grams.filter(function(gram) {
return (gram.get('scheduledInFuture')) && (gram.belongsTo('socialProfile').id() === socialProfile.get('id')) && (gram.get('active'));
});
resolve(filteredGrams);
});
}),
igUser: new Ember.RSVP.Promise(function(resolve) {
self.getSelf(accessToken).then(function(response) {
resolve(self.igUserFromResponse(response.data, socialProfile));
});
})
});
},
You need to return true at the end of the loading() hook to tell Ember to go ahead and show the default loading route (loading.hbs).
loading(transition) {
var controller = this.controller;
if (!Ember.isNone(controller)) {
this.controller.reset();
}
transition.promise.finally(function() {
NProgress.done();
});
return true;
},
I have a basic Ember app and I am trying to handle validation errors on save (model is using the REST Adapter). In my route I am doing:
task.save().then(
function() {alert("success");},
function() {alert("fail");}
).catch(
function() {alert("catch error");}
);
When the record is valid I get the "success" alert, but when record is invalid, I do not get the "fail" alert OR "catch error". In the console I get:
POST http://localhost:8080/api/tasks 422 (Unprocessable Entity)
Error: The adapter rejected the commit because it was invalid
The response from the api look like this:
{"errors":{"name":["can't be blank"],"parent_task":[]}}
I am using Ember Data 1.13.
You need to extend your adapter to handle the errors, the REST Adapter does NOT do this for you (only the Active Model one)
Something like this:
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function(jqXHR) {
var error = this._super(jqXHR);
if (jqXHR && jqXHR.status === 422) {
var response = Ember.$.parseJSON(jqXHR.responseText),
errors = {};
if (response.errors !== undefined) {
var jsonErrors = response.errors;
Ember.EnumerableUtils.forEach(Ember.keys(jsonErrors), function(key) {
errors[Ember.String.camelize(key)] = jsonErrors[key];
});
}
return new DS.InvalidError(errors);
} else {
return error;
}
}
});
I am trying to catch 404 errors in my ember app, and redirect to /not-found.
I have an errors action on my ApplicationController, and I have an RSVP.on('error') function too but the 404's aren't getting caught. I just get a 404 error thrown to my console from jQuery, but the error is not getting passed to the error handler.
Errors initializer:
import Ember from 'ember';
var initialize = function(container) {
var errorReporting = container.lookup("service:errorReporting");
Ember.RSVP.on('error', function(err) {
Ember.warn("Ember.RSVP error..... Logging error:");
console.log(err);
if (err.name && err.name === 'TransitionAborted') {
Ember.debug("TransitionAborted error. Doesn't look like we should be catching these.");
} else {
container.lookup('route:application').send('error', err);
}
});
window.onerror = function(err) { // window general errors.
Ember.warn("Uncaught error (tripped window.onerror)..... Logging error:");
console.log(err);
errorReporting.report(err);
};
};
export default {
name: 'errors',
initialize: initialize
};
The error action on my applicationRoute is huge (and I can post it), but it doesn't even seem to be getting called.
EDIT 1: Route Code
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
titleToken: function(model) {
return model.get('name');
},
model: function(params) {
return this.store.find('location', params.location_id);
}
});
EDIT 2: ApplicationRoute / Error handler
error: function(err, transition) {
if (!Ember.isNone(transition)) {
transition.abort();
}
let errorHolder = this._getErrorDataFrom(err);
let errorMessage = this._getErrorMessageFrom(errorHolder);
let isFourOhFour = (typeof(err.status) !== 'undefined' && err.status === 404) || errorHolder.reason === 'not_found';
if (isFourOhFour) {
return this.transitionTo('not-found');
}
let requireAuthentication = (errorHolder.reason === 'not_authenticated');
if (requireAuthentication) {
window.localStorage.setItem('toast-on-reload', errorHolder.message);
return this.session.invalidate();
}
let isValidationError = ( errorHolder.reason === "validation_error" ||
( !Ember.isNone(errorHolder.errors) && !Ember.isNone(errorHolder.message) ) );
if (isValidationError) {
this.toast.error(errorMessage);
return;
}
let verificationRequired = (errorHolder.reason === "verification");
if (verificationRequired) {
this.toast.error(errorMessage);
return this.transitionTo('verification');
}
let invalidRequest = (errorHolder.reason === 'unprocessable_entity');
if (invalidRequest) {
this.toast.error(errorMessage);
return;
}
this.errorReporting.report(errorHolder);
this.toast.error(errorMessage);
return this.transitionTo('error');
}
},
_getErrorDataFrom: function(obj) {
if (!Ember.isNone(obj.responseJSON)) {
return obj.responseJSON;
} else if ( !Ember.isNone(obj.success) || !Ember.isNone(obj.errors)) {
return obj;
} else if (!Ember.isNone(obj.jqXHR) && !Ember.isNone(obj.jqXHR.responseJSON)) {
return obj.jqXHR.responseJSON;
} else {
Ember.warn("No error handler available, using default ( {} ). Error:");
console.log(obj);
return {};
}
},
_getErrorMessageFrom: function(errorHolder) {
if ( typeof(errorHolder.errors) === 'object' && !Ember.isNone(errorHolder.errors.message) ) {
return errorHolder.errors.message;
} else if (!Ember.isNone(errorHolder.errors)) {
return errorHolder.errors;
} else if (!Ember.isNone(errorHolder.message)) {
return errorHolder.message;
} else {
return "Sorry, something went wrong.";
}
}
If you want to use the error event, then place its handler inside an actions hash in the application route.
Alternatively, consider the use of an error route. You can define this in pods/application/error, with templates, routes, and controllers just like any other route. See http://guides.emberjs.com/v1.10.0/routing/loading-and-error-substates/#toc_code-error-code-substates. The error code will be passed to that error route as its model.
Finally, in many cases it's most simple and reliable to catch the error from the find.
model: function(params, transition) {
return this.store.find('location', params.location_id) .
catch(err => this.send('ajaxError', err));
}
Then define the ajaxError action on your application route which does the same kinds of things you are doing in your error hook now. However, this will catch only ajax errors, not other sorts of errors that might occur during transitions, and be swallowed (or in your case reported by Ember.RSVP.on('error').
how is possible handle restAdapter errors from store or adapter?
Now I am using this code:
App.ApplicationRoute = Ember.Route.extend({
model: function(){
var self = this;
return this.store.find('item').then(function(data){
return data;
}, function (error){
console.log('error');
return [];
});
},
});
It would be better something more general. Thanks
Until there is some more sophisticated error handling throughout ember data, you could do something like the following to handle network errors in a cross-cutting way:
Extend the RESTAdapter to parse errors from the xhr object
App.ApplicationAdapter = DS.RESTAdapter.extend({
ajaxError: function (jqXHR) {
jqXHR = this._super(jqXHR) || {status : 'unknown'};
var error;
if (jqXHR.status === 404) {
error = 'not_found';
} else if (...) {
...
} else {
error = 'dunno';
}
return error;
}
});
Extend the store to publish error events when bad things happen
App.Store = DS.Store.extend(Ember.Evented, {
recordWasError: function (record, reason) {
this._super.apply(this, arguments);
this.trigger('error', reason);
}
});
Catch the error in your Application Route
App.ApplicationRoute = Ember.Route.extend({
setupController: function () {
this.get('store').on('error', function (error) {
// Do something with the error
console.error(error);
});
},
...
});
I have an Ember.js app backed by a RESTful API. Session control is done through an authentication token: once a user logs in, he appends his authentication token to each request he makes to the server. I do this by adding the authentication to the data in $.ajaxSetup.
$.ajaxSetup({
data: { auth_token: this.get('authToken') }
});
Now, this works fine for GET requests. However, when saving models to the server through a POST or PUT request, the Ember Data RESTAdapter stringifies the data object. In DS.RESTAdapter.ajax it does
....
if (hash.data && type !== 'GET') {
hash.contentType = 'application/json; charset=utf-8';
hash.data = JSON.stringify(hash.data);
}
...
Because of this, the authentication token is not merged into the data. In this jQuery ticket they say that it's something they are never going to support.
What's the most elegant way of solving this? I'd rather not override the Ember's RESTAdapter.ajax function because the code is changing so quickly so my overridden function might not be compatible with the rest of the codebase at the next release.
In the end, I couldn't find another solution besides overriding RESTAdapter.ajax. I ended up adding three parameters: auth[token], auth[school] and auth[name].
DS.RESTAdapter.reopen({
/* Override to add the authToken, school and name */
ajax: function(url, type, hash) {
var adapter = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
hash = hash || {};
hash.url = url;
hash.type = type;
hash.dataType = 'json';
hash.context = adapter;
if (hash.data && type !== 'GET') {
hash.contentType = 'application/json; charset=utf-8';
/* Add the data to the hash before it's stringified. */
if (HstryEd.Session.get('isLoggedIn')) {
hash.data.auth = {};
hash.data.auth.token = HstryEd.Session.get('authToken');
hash.data.auth.school = HstryEd.Session.get('currentUser').get('school');
hash.data.auth.name = HstryEd.Session.get('currentUser').get('name');
}
hash.data = JSON.stringify(hash.data);
}
if (adapter.headers !== undefined) {
var headers = adapter.headers;
hash.beforeSend = function (xhr) {
forEach.call(Ember.keys(headers), function(key) {
xhr.setRequestHeader(key, headers[key]);
});
};
}
hash.success = function(json) {
Ember.run(null, resolve, json);
};
hash.error = function(jqXHR, textStatus, errorThrown) {
if (jqXHR) {
jqXHR.then = null;
}
Ember.run(null, reject, jqXHR);
};
Ember.$.ajax(hash);
});
}
});