How to get a good response from saving a model - backbone.js

With backbone.js I'm saving a model. A PUT is send to the server and the response is returned. The first time I do it, it returns success, the following times an error is return, because after the first time the response is added to the model.
Save function in Backbone.js:
saveCountry: function() {
this.model.save({},{
success: function(model, response) {
console.log('success! ' + response);
},
error: function(model, response) {
console.log('error! ' + response);
}
});
this.render();
return false;
},
PHP returns a JSON-string:
{"response": "Model saved!"}
Following PUT's get an error as response, because 'response' is added to the model:
Unknown column 'response' in 'field list'
Why is the response added to the model and how do I prevent it?

From Backbone's documentation on model save:
Set a hash of model attributes, and sync the model to the server. If
the server returns an attributes hash that differs, the model's state
will be set again.
http://documentcloud.github.com/backbone/docs/backbone.html#section-41
What to do to make it work: don't return {"response": "Model saved!"} from the server. Just return a success (status 200) with no content.
If the save did not work, return a JSON with the errors, and Backbone will trigger an error event on your model, with the JSON you provided (see http://documentcloud.github.com/backbone/docs/backbone.html#section-41 and http://documentcloud.github.com/backbone/docs/backbone.html#section-145).

Just to resurrect an ancient thread...
It isn't always possible/desirable to change the response you get back from the server.
Another solution is to override parse in the model to take care of this. For your situation, where the response is inappropriate for ALL of your models, you could do it in a superclass.
MyModel = Backbone.Model.extend({
parse: function(data) {
delete data["success"];
return data;
}
});
Address = MyModel.extend({
saveCountry: function() {
this.model.save({},{
success: function(model, response) {
console.log('success! ' + response);
},
error: function(model, response) {
console.log('error! ' + response);
}
});
this.render();
return false;
},
...
});

Related

Backbone.js save always triggers error even on success

I've read several of the other posts about this problem and none of the solutions seem to be working for me. I have the following code in my View:
this.model.set({
username: $('#user-username').val(),
role: $('#user-role').val(),
description: $('#user-description').val()
});
this.model.save({ user_id: this.model.get('user_id')}, {
success: function(user, response) {
console.log('success:', response);
$('.flash-message').text("Success").show();
},
error: function(user, response) {
console.log('error:', response);
$('.flash-message').text(response.error).show();
}
});
and this on my server controller (nodejs running express 3):
UserController.prototype.updateAction = function(req, res) {
if (req.route.method != "put") {
res.send({status: "error", error: "update must be put action and must include values"});
return false;
}
var query = {'user_id': req.params.id};
var user = req.body;
var userRepository = this.userRepository
// delete _id to avoid errors
delete user._id;
userRepository.update(query, user, {}, function(err, updated) {
if ((err) || (!updated)) {
res.send({"status": "error", "error": err});
return false;
}
// send updated user back
util.log('updated user ' + user.user_id);
res.setHeader('Content-Type', 'application/json');
res.status(200);
res.send(JSON.stringify({"status": "success", "updated": updated}));
});
}
On save, my model is saved correctly in the server and I have verified the server response with this. So, as far as I can tell the server is returning status 200, valid JSON, with a valid JSON response header. And yet my backbone model.save function always triggers the error callback. Can anyone please tell me why and how to resolve this?
I am able to get this to work if set the dataType to text like so:
this.model.save({ user_id: this.model.get('user_id')}, {
dataType: "text",
success: function(user, response) {
console.log('success:', response);
$('.flash-message').text("Success").show();
},
error: function(user, response) {
console.log('error:', response);
$('.flash-message').text(response.error).show();
}
});
but doing so does not allow me to get the response back from the server. Instead I get this in the response var:
success: {
"_id": "5133b02062e15ed1d2000001",
}
Backbone expects to get back the model that it sent in its PUT or POST request body.
Instead of:
res.send(JSON.stringify({"status": "success", "updated": updated}));
Try this in your server's response:
res.json(user);
There may be a possibility that your call may have got in state 200 connection established which backbone detects as error, Backbone throws success only when the call is 200OK.
What's your server code? You need to make sure you're sending json back to backbone like so:
//In your express POST route
user.save(function(err) {
if(err){
console.log(err);
return res.json(401);
} else {
console.log('user: ' +user.username + ' saved');
return res.json(200);
}
Then in your backbone view you can check for the response and do what you need:
//some function in your view
this.model.save(this.formValues, {
success: function(model, response, options) {
if (response == 200) {
console.log('success :' + response);
//Do stuff
} else {
console.log('error: '+response);
//etc.
Also note that as per the backbone model documentation:
"save accepts success and error callbacks in the options hash, which will be passed the arguments (model, response, options)"

model.destroy() returning errors with sinatra backend

I'm having an issue getting my model.destroy method to work properly in backbone. This is my function
deleteEvent: function(){
var self = this;
var check = confirm("Are you sure you want to remove record " + this.model.get("ticket_id"));
if (check == true){
this.model.id = this.model.get('session_id');
this.model.destroy({
wait: true,
success: function(model, response, options){
console.log(options);
console.log(response);
self.$el.remove();
},
error: function(model, xhr, response){
console.log("ERROR:");
console.log(model);
console.log(xhr);
console.log(response);
}
});
}
else return;
},
The model looks like this:
vkapp.EventRecordModel = Backbone.Model.extend({
urlRoot: '/user_event',
idAttribute:"_id",
defaults: {
"ticket_id": '',
"start": '',
"end": ''
},
validate: function(attrib){ //This is only called when setting values for the model, not on instantiation
if (attrib.ticket_id == null)
alert("no ticket number");
if (attrib.start == undefined)
alert("no start time");
if (attrib.end == null)
alert("no end time");
if (attrib.start > attrib.end)
alert("start can't be before the end time.");
}
});
And this is what the route looks like in my sinatra.
delete '/user_event/:session_id' do
user_event = ProjectTimer.get(:session_id => params[:session_id])
user_event.destroy
end
I am not sure why I am getting an error return.
If you follow the advice from this answer
delete '/user_event/:session_id' do
user_event = ProjectTimer.get(:session_id => params[:session_id])
user_event.destroy
halt 204
rescue => e
# do something with the exception, maybe log it
halt 500
# or set status to 500 and re-raise
end
See Halting in the Sinatra docs for more.
I did get this working properly however by setting the dataType to "text." I found that Backbone.sync expects JSON of the object deleted to be returned in order to be successful. So, if we change the dataType to Text it over-rides that JSON expectancy. This is my final code
deleteEvent: function(){
var self = this;
var check = confirm("Are you sure you want to remove record " + this.model.get("ticket_id"));
if (check == true){
this.model.id = this.model.get('session_id');
this.model.destroy({
dataType: "text",
wait: true,
success: function(model, response, options){
self.$el.remove();
},
error: function(model, xhr, response){
console.log("ERROR:");
console.log(model);
console.log(xhr);
console.log(response);
}
});
}
else return;
},

parseJSON(response) is null

I have a Backbone model that is making a successful server request. The callback is using backbone's trigger method to trigger an event in the associated view, and its passing the parsed json response from the server request as the second parameter of the trigger method, as described in the backbone documents. In the view, the event triggers the render method, but the response object is null in the view. It's throwing this error
Uncaught TypeError: Cannot read property 'word' of null
Can anyone see what I might be doing wrong?
The server request from the model
new: function() {
var _this = this;
console.log("new function of model");
$.ajax({
url: "/gamestart",
type: "GET",
success: function(response) {
console.log(response);
var json = $.parseJSON(response);
_this.set({lost: false});
_this.set({win: false});
_this.trigger("gameStartedEvent", json);
}
})
},
the event in the initializer method of the view which triggers the render method to render the response
this.model.on("gameStartedEvent", this.render, this);
the render method where the response is null
render: function(response) {
$("#hint").show();
console.log(response);
var html = this.template({characters: response.word});
this.el.hide();
this.el.html(html).show();
},
Note, if it matters, the view is being instantiated with the model
var word_view = new WordView({model: game})
Update
Actually the error's happening here. The response is returning successfully but i'm parsing it incorrectly. The variable json is null when I check the console. Can you see what I'm doing wrong?
var json = $.parseJSON(response);
console.log(json)
Response
Object {word: Array[6]}
word: Array[6]
__proto__: Object
There was actually no need to call parseJSON on the response object. I could get the word property directly off the response object, so I just passed the response object as the second argument to trigger, and called response.word in the view.
$.ajax({
url: "/gamestart",
type: "GET",
success: function(response) {
console.log(response.word);
var json = $.parseJSON(response); ####unnecessary to parseJSON here
console.log(json);
_this.set({lost: false});
_this.set({win: false});
_this.trigger("gameStartedEvent", response);
}
})
},

Show json returned message in a div after each ajax response , extjs4 grid

I have a gridpanel , a Model, an autosync Store with an ajax proxy with read and update functions and a RowEditing plugin in extjs4 .
Consider the following json:
{ "success": true,"Message":"Grid Data Successfully updated.", "users": {"uname":"jdoe","fname":"John","lname":"Doe","SSN":125874,"mail":"jdoe#example.org"},{"uname":"jsmith","fname":"Jack","lname":"Smith","SSN":987456,"mail":"smith#example.com"}}
I want to know if there is a way to render the value of "Message" to an HTML div tag (my_div for example) after receiving each response?
You can use the DOM Helper, see the sencha api : http://docs.sencha.com/ext-js/4-0/#!/api/Ext.DomHelper
Ext.onReady(function(){
Ext.DomHelper.insertHtml('beforeBegin', Ext.getDom('test'), "Prepend this string");
});​
Above code will get the HTML element with ID test and it will insert the string Prepend this string beforeBegin of the content of this div.
See the fiddle to play around: http://jsfiddle.net/PddU4/Prepend this string
EDIT 2012-02-16:
You need to listen to your proxies success and failure: (you could also implement a listener when loading your store or on update)
listeners: {
success: function( response, options ){
console.log(response);
},
failure: function( response, options ){
console.log(response);
},
}
EDIT BASED ON YOUR COMMENT:
First make sure you configured properly your successProperty and messageProperty in your reader. Then implement the listener where you want it to be, update, remove, add, exception etc. :
(configure the listener within your proxy object)
listeners : {
update : function(thisStore, record, operation ) {
console.log('Update happened');
console.log(record);
console.log(operation);
},
save : function() {
console.log('Save happened');
},
exception : function(dataproxy, type, action, options,response, arg) {
console.log('Error happened');
console.log(response);
doJSON(result.responseText);
},
remove : function() {
console.log("Record removed");
}
}
When you console.log(response), you will see the response object. This would be your actual JSON so you need to parse it (as in doJSON() method):
function doJSON(stringData) {
try {
var jsonData = Ext.util.JSON.decode(stringData);
Ext.MessageBox.alert('Success', 'Your server msg:<br />jsonData.date = ' + jsonData.message);
}
catch (err) {
Ext.MessageBox.alert('ERROR', 'Could not decode ' + stringData);
}
}
Please check out this AJAX tutorial: http://www.sencha.com/learn/legacy/Manual:Core:Ext.Ajax

How do I trigger the success callback on a model.save()?

this.model.save({
success: function(model, response){
console.log('success');
},
error: function(){
console.log('error');
}
})
The model is correctly posted to the server which handles the save, but the success callback is not fired. Do I need to send something back from the server ?
The first argument of save is the attributes to save on the model:
this.model.save( {att1 : "value"}, {success :handler1, error: handler2});
For some unknown reason, none of the above method worked for me. The api only was not hit in my case.
But later while searching on this, I bumped into this link, where some one had tried null instead of {} as the first parameter.
this.model.save(null, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
});
so, this worked for me. Hope this helps you too.
Your server must return a JSON object. If the response is not a JSON object, the callbacks will not fire.
If for success your server doesn't return a JSON object, perform a save with dataType:"text" option, like this:
this.model.save([],{
dataType:"text",
success:function() {},
error:function() {}
});
With this option it will not be waiting for a JSON in response, but a text, and thus the callback will be launched.
You may use underscore lib as follows as backbone already depends upon this. Remember first argument of save must either have attributes or you may just pass {} in case you want to save model itself.
this.model.save({}, _.bind(function(model, response){
//Do whatever you want e.g.
this.collection.add(model)
}, this))
so im a little confused - do i still need to pass in all attributes in order for me to call a save event? what if my model is large.. i dont wish to set every property manually
im calling model.save and attempting to do the following:
this.model.save(
{
success: function (model, response) {
console.log('model saved');
}
});
ok just to answer my own question incase anyone finds this post, i did the following which works:
this.model.save({ id: this.model.get('id') },
{
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
});
EDIT: I couldn't reply to you for some reason, but I can edit
but you don't have to set id: this.model.get('id') you can just pass a blank object because a blank attribute just won't extend attributes, does nothing:
this.model.save({}, {
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
});
The following is the code that i am using for backbone model save.
this.model.save(model,{
success:function(model){
console.log("Saved Successfully");
},
error:function(model){
console.log("Error");
}
});
Cheers
Roy M J
For those that want to save a model, without updating the attributes, you can do the following:
model.once("sync", function(model, response, options){
//
});
model.once("error", function(model, response, options){
//
});
model.save();
In you initialize function, bind the sync method to a method you define (onSaveSuccess)
initialize: function (options) {
this.model.on('sync', _.bind(this.onSaveSuccess, this));
},
onSaveSuccess: function() {
console.log('saved');
this.render();
},
This way, any time you run this.model.save(), it will run the onSaveSuccess function as a callback if your sync is successful

Resources