I use the RESTadpater to persist data. When a validation error occurs, I want to return a 422 response and then log the errors and show an indication next to each incorrect field.
My REST response status code is as follows:
Status Code:422 Unprocessable Entity
My REST response body is as follows:
{
"message": "Validation failed",
"errors": [
{
"name": "duplicate"
}
]
}
In my controller, the becameInvalid fires correctly.
App.AuthorsNewController = Ember.ObjectController.extend({
startEditing: function () {
//Create a new record on a local transaction
this.transaction = this.get('store').transaction();
this.set('model', this.transaction.createRecord(App.Author, {}));
},
save: function (author) {
//Local commit - author record goes in Flight state
author.get('transaction').commit();
//If response is success: didCreate fires
//Transition to edit of the new record
author.one('didCreate', this, function () {
this.transitionToRoute('author.edit', author);
});
//If response is 422 (validation problem at server side): becameError fires
author.one('becameInvalid', this, function () {
console.log "Validation problem"
});
}
...
2 QUESTIONS:
I want to log below the 'console.log "Validation problem"', the complete list of errors returned by the server. How can I do that ?
In my hbs template, I want to indicate an error next to the relevant field. How can I do this ?
I am not sure that the data returned via REST adapter is correct. So problem might be at the REST side or at the Ember side ...
Solution:
In controller save function:
author.one('becameInvalid', this, function () {
console.log "Validation problem"
this.set('errors', this.get('content.errors'));
});
In hbs template:
{{view Ember.TextField valueBinding='name'}}
{{#if errors.name}}{{errors.name}}{{/if}}
Here is how I do it, may not be the best practice but it works for me:
instead of using commit(), I use save(), and if you wonder what's the difference, here is the link. I haven't tried your approach of using transaction, but basically I create the record using record = App.Model.createRecord(...), and here is the code of my apiAddShop function inside the AddShopController:
apiAddShop: function() {
//console.log("add shop");
newShop = App.Shop.createRecord({name:this.get('name'), currentUserRole:"owner"});
//this.get('store').commit(); // Use record.save() instead, then() is not defined for commit()
var self = this;
newShop.save().then(function(response){
self.transitionToRoute('shops');
}, function(response){
// if there is error:
// server should respond with JSON that has a root "errors"
// and with status code: 422
// otherwise the response could not be parsed.
var errors = response.errors;
for(var attr in errors){
if (self.hasOwnProperty(attr)) {
self.set(attr+"Error", true);
self.set(attr+"Message", Ember.String.classify(attr)+" "+errors[attr]);
}
console.log(attr + ': ' + errors[attr]);
}
console.log(response.errors.name[0]);
});
},
the above code assume there is a attrError(boolean) and attrMessage(string) for each of the attributes in your form. Then in your template, you could bind the class of your field to these error attributes, such as <div {{bindAttr class=":control-group nameError:error:"}}>, and the error message could be easily display next to the form field such as: <span {{bindAttr class=":help-inline nameError::hidden"}} id="new_shop_error">{{nameMessage}}</span> Here is my example handlebar gist (have to use gist here, since SO is escaping my html inputs).
Related
I have a component in my application.It have a form with text fields.It will have a submit button.When submit is pressed it will send a post request to the server.I also handled a keyboard event in components js file.When enter is pressed it will send a post request to the server.When the enter key is pressed two times continuously it is making two post request to the server with first request success and second request failed.
I want to make my app in such away even if the user presses the enter key two times continuously it should send only one post request to the server.Can any one help me solve this issue.Thanks in advance.
components js file:
export default Component.extend({
keyDown:function(event){
let self = this;
if(event.keyCode === 13){
self.send('submitform');
return false;
}
actions: {
submitform(){
//logic to handle the post request to the server
}
}
Try usig Ember.run.debounce,
export default Ember.Component.extend({
keyDown: function(event) {
let self = this;
if (event.keyCode === 13) {
// self.send('submitform');
Ember.run.debounce(self,self.get('submitform'),400);
return false;
}
},
submitform(){
//handle submit form logic
}
});
You can play with twiddle here
You will want to disable submitForm() until your POST request is complete. You can do this by setting a property submitting on the component and turning it on before the POST and off once the promise is resolved. Perhaps try something like:
export default Ember.Component.extend({
submitting: false,
keyDown: function(event) {
if (event.keyCode === 13) {
this.submitform();
}
},
submitform() {
// Run only if not currently submitting
if (!this.get('submitting')) {
// What to do when submit succeeds
const success = () => {
this.set('submitting', false);
}
// What to do when submit fails
const fail = () => {
this.set('submitting', false);
}
// Do the POST request
this.set('submitting', true);
this.get('someModel').save().then(success).catch(fail);
};
}
});
And, unrelated, this allows you to do fun things with your template such as disabling and styling the submit button for as long as the POST promise is not resolved yet:
<button {{action 'submitForm'}} disabled={{submitting}} class="{{if submitting 'loading'}}">
{{#if submitting}}
Submitting ...
{{else}}
Submit
{{/if}}
</button>
Oh and lastly, no need to use let self = this; anymore. Use ES6 arrow functions () => { ... } instead so you can keep using this inside.
I would like to display an error message when the server responses with record not found.
The model in the route handler:
model: function(userLoginToken) {
var userLoginToken= this.store.createRecord('userLoginToken');
return userLoginToken;
},
The action:
actions: {
sendOTP: function(userLoginToken) {
var thisObject = this;
var model=this.currentModel;
this.store.findRecord('user-login-token', userLoginToken.get('mobileNumber')).then(function(response) {
//thisObject.get('controller').set('model', response);
},
function(error) {
//thisObject.get('controller').set('model', error);
//alert("model======== "+model.get('errors'));
});
},
The template is not displaying any error message.
The template:
{{#each model.errors.messages as |message|}}
<div class="errors">
{{message}}
</div>
{{/each}}
Unfortunately, the error message doesn't appear.
Ember depends on an DS.error object, in order to get errors from your models the response has to fulfill the requirements. In order to get Ember to recognize an valid error, in Ember 2.x the error code MUST be 422 and has to follow jsonapi http://jsonapi.org/format/#errors-processing
If you want to catch the errors from the backend response you have to use the catch method:
this.store.findRecord('user-login-token', userLoginToken.get('mobileNumber'))
.then(success => {
// Do whatever you need when the response success
})
.catch(failure => {
// Do whatever you need when the response fails
})
},
For catching the errors automatically as you are doing in your template, your backend needs to response in the right way. I would suggest you to read the answer for this SO question.
Is there proper way to handle custom error when saving a model? To give an example, lets say I have a model with just two properties "name" and "value". And when I do :
var myModel = this.get('store').createRecord('myModel', {"name": "someName", "value": "someValue"});
myModel.save().then(function() {
//if success
//server responded with {"myModel:{"id":1,"name":"someName","value":"someValue"}"}
},function() {
//if failure
//server responded with {"error":"some custom error message"}
//BUT HOW TO CATCH THIS AND POSSIBLY REMOVE THE MODEL FROM THE STORE
});
One way to work around this is to make extra ajax call to check if the name is unique and then do the save. I am just wondering what is the best/elegant approach here.
Thanks,
Dee
EDIT : I thought it might help a bit to give more context on the server side of the things in groovy. So here it is:
In my controller I have :
def create() {
try {
newRow = someService.create(params)
render someService.list(newRow) as JSON//returns data in format needed by ember-data
}
catch (ValidationException ex) {
def errors = ["errors":[]]
ex.errors.allErrors.each{
if(it.arguments[0] == "fieldName" && it.code=="constrantViolated"){
errors.errors.push(["field":it.arguments[0],"message":"some custom message"])
}
}
//I am using 422 here because of post in http://stackoverflow.com/questions/7996569/can-we-create-custom-http-status-codes
render(status: 422, contentType: 'JSON', text: (errors as JSON))
}
}
Then in my ember controller:
var myModel = self.get('store').createRecord('myModel ', myModelDataInJSON);
myModel .save().then(function () {
//if success
},
function (response) {
myModel .deleteRecord();
var errors = $.parseJSON(response.responseText);
for (var key in errors.errors) {
//do something
}
});
deleteRecord will delete the record.
myModel.save().then(function(response) {
//if success
//server responded with {"myModel:{"id":1,"name":"someName","value":"someValue"}"}
},function(response) {
//if failure
//server responded with {"error":"some custom error message"}
//BUT HOW TO CATCH THIS AND POSSIBLY REMOVE THE MODEL FROM THE STORE
if(response.error=='no good'){
myModel.deleteRecord();
}
});
You can handle errors at model by adding properties into your model:
becameError: ->
# handle error case here
alert 'there was an error!'
becameInvalid: (errors) ->
# record was invalid
alert "Record was invalid because: #{errors}"
Check: How should errors be handled when using the Ember.js Data RESTAdapter?
Why wouldn't the answer be to just use the: DS.ERRORS CLASS?
From EmberJS docs:
For Example, if you had an User model that looked like this:
App.User = DS.Model.extend({
username: attr('string'),
email: attr('string')
});
And you attempted to save a record that did not validate on the backend.
var user = store.createRecord('user', {
username: 'tomster',
email: 'invalidEmail'
});
user.save();
Your backend data store might return a response that looks like this. This response will be used to populate the error object.
{
"errors": {
"username": ["This username is already taken!"],
"email": ["Doesn't look like a valid email."]
}
}
Errors can be displayed to the user by accessing their property name or using the messages property to get an array of all errors.
{{#each errors.messages}}
<div class="error">
{{message}}
</div>
{{/each}}
Is this question only focused on validation into the model? versus persistence/saving it, hence data is clean already before it hits a data store... Seems like you would still want error management at the adapter data store level, too.
That all said, why wouldn't you just use template or normal based JS validation at the UI control level?
I've seen this question.
Until nothing is done for ember-data failures handling what can i write to patch it, so that i can have a message if my request doesn't have a 200OK response?
I'm looking at ember-data source code, for example, here's the createRecord function
createRecord: function(store, type, record) {
var root = this.rootForType(type);
var adapter = this;
var data = {};
data[root] = this.serialize(record, { includeId: true });
return this.ajax(this.buildURL(root), "POST", {
data: data
}).then(function(json){
Ember.run(adapter, 'didCreateRecord', store, type, record, json);
}, function(xhr) {
adapter.didError(store, type, record, xhr);
throw xhr;
});
}
I expected something like:
success: //do something
error: //do something else
But there's nothing like this. It's only a then after the ajax request.
What should I do? rewrite completely all the methods i need?
As of this commit ember data now uses promises (specifically the rsvp implementation), which replaces the success: and error: callbacks for a .then() style:
promise.then(function(value) {
// success
}, function(value) {
// failure
});
This is very similar to the previous callback style, but follows the promises style.
The first function is called on success and the second function is called on a failure. Looking at the code you posted shows that didError() is called on ajax failure, which is implemented in the adapter.
didError() (source) calls store.recordWasInvalid() if the status was 422, otherwise it calls store.recordWasError() (source).
store.recordWasInvalid() and store.recordWasError() transition the record into the isError or isValid = false state and triggers the becameError() and becameInvalid() events on the record. You can check these states or subscribe to these events to show your error message.
ember-data.js: https://github.com/emberjs/data/tree/0396411e39df96c8506de3182c81414c1d0eb981
In short, when there is an error, I want to display error messages in the view, and then the user can 1) cancel, which will rollback the transaction 2) correct the input errors and successfully commit the transaction, passing the validations on the server.
Below is a code snippet from the source. It doesn't include an error callback.
updateRecord: function(store, type, record) {
var id = get(record, 'id');
var root = this.rootForType(type);
var data = {};
data[root] = this.toJSON(record);
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
this.didUpdateRecord(store, type, record, json);
}
});
},
Overall, what is the flow of receiving an error from the server and updating the view? It seems that an error callback should put the model in an isError state, and then the view can display the appropriate messages. Also, the transaction should stay dirty. That way, the transaction can use rollback.
It seems that using store.recordWasInvalid is going in the right direction, though.
This weekend I was trying to figure the same thing out. Going off what Luke said, I took a closer look at the ember-data source for the latest commit (Dec 11).
TLDR; to handle ember-data update/create errors, simply define becameError() and becameInvalid(errors) on your DS.Model instance. The cascade triggered by the RESTadapter's AJAX error callback will eventually call these functions you define.
Example:
App.Post = DS.Model.extend
title: DS.attr "string"
body: DS.attr "string"
becameError: ->
# handle error case here
alert 'there was an error!'
becameInvalid: (errors) ->
# record was invalid
alert "Record was invalid because: #{errors}"
Here's the full walk through the source:
In the REST adapter, the AJAX callback error function is given here:
this.ajax(this.buildURL(root, id), "PUT", {
data: data,
context: this,
success: function(json) {
Ember.run(this, function(){
this.didUpdateRecord(store, type, record, json);
});
},
error: function(xhr) {
this.didError(store, type, record, xhr);
}
});
didError is defined here and it in turn calls the store's recordWasInvalid or recordWasError depending on the response:
didError: function(store, type, record, xhr) {
if (xhr.status === 422) {
var data = JSON.parse(xhr.responseText);
store.recordWasInvalid(record, data['errors']);
} else {
store.recordWasError(record);
}
},
In turn, store.recordWasInvalid and store.recordWasError (defined here) call the record (a DS.Model)'s handlers. In the invalid case, it passes along error messages from the adapter as an argument.
recordWasInvalid: function(record, errors) {
record.adapterDidInvalidate(errors);
},
recordWasError: function(record) {
record.adapterDidError();
},
DS.Model.adapterDidInvalidate and adapterDidError (defined here) simply send('becameInvalid', errors) or send('becameError') which finally leads us to the handlers here:
didLoad: Ember.K,
didUpdate: Ember.K,
didCreate: Ember.K,
didDelete: Ember.K,
becameInvalid: Ember.K,
becameError: Ember.K,
(Ember.K is just a dummy function for returning this. See here)
So, the conclusion is, you simply need to define functions for becameInvalid and becameError on your model to handle these cases.
Hope this helps someone else; the docs certainly don't reflect this right now.
DS.RESTAdapter just got a bit more error handling in this commit but we are still not yet at a point where we have a great recommendation for error handling.
If you are ambitious/crazy enough to put apps in production today with ember-data (as I have been!), it is best to make sure that the likelihood of failures in your API is extremely low. i.e. validate your data client-side.
Hopefully, we can update this question with a much better answer in the coming months.
I just ran into such a situation, not sure if this is already explained anywhere.
I am using:
Em.VERSION : 1.0.0
DS.VERSION : "1.0.0-beta.6"
Ember Validations (dockyard) : Version: 1.0.0.beta.1
Ember I18n
The model was initially mixedin with Validation mixin.
App.Order = DS.Model.extend(Ember.Validations.Mixin, {
.....
someAttribute : DS.attr('string'),
/* Client side input validation with ember-validations */
validations : {
someAttribute : {
presence : {
message : Ember.I18n.t('translations.someAttributeInputError')
}
}
}
});
In the template, corresponding handlebars is added. (note that ember validations will automatically add errors to model.errors.<attribute> in case of input validations, I will be using same trade-off in server validations as well)
<p>{{t 'translations.myString'}}<br>
{{view Ember.TextField valueBinding="attributeName"}}
{{#if model.errors.attributeName.length}}<small class="error">{{model.errors.attributeName}}</small>{{/if}}
</p
Now, we will be saving the Order
App.get('order').save().then(function () {
//move to next state?
}, function(xhr){
var errors = xhr.responseJSON.errors;
for(var error in errors){ //this loop is for I18n
errors[error] = Ember.I18n.t(errors[error]);
}
controller.get('model').set('errors', errors); //this will overwrite current errors if any
});
Now if there is some validation error thrown from server, the returned packet being used is
{"errors":{"attributeName1":"translations.attributeNameEror",
"another":"translations.anotherError"}}
status : 422
It is important to use status 422
So this way, your attribute(s) can be validated client side and again on server side.
Disclaimer : I am not sure if this is the best way!
Since there's currently no good solution in stock Ember-Data, I made my own solution by adding an apiErrors property to DS.Model and then in my RestAdapter subclass (I already needed my own) I added error callbacks to the Ajax calls for createRecord and updateRecord that save the errors and put the model in the "invalid" state, which is supposed to mean client-side or server-side validations failed.
Here's the code snippets:
This can go in application.js or some other top-level file:
DS.Model.reopen({
// Added for better error handling on create/update
apiErrors: null
});
This goes in the error callbacks for createRecord and updateRecord in a RestAdapter subclass:
error: function(xhr, textStatus, err) {
console.log(xhr.responseText);
errors = null;
try {
errors = JSON.parse(xhr.responseText).errors;
} catch(e){} //ignore parse error
if(errors) {
record.set('apiErrors',errors);
}
record.send('becameInvalid');
}