Backbone save model success and error - backbone.js

I have this backbone code, create a view and model, and calls the save method to save data to database:
var form = document.forms[0];
var RetailerModel = Backbone.Model.extend({
urlRoot: ' retailer.php',
defaults: {
name: 'company-name',
address: 'company-address',
phone: 'company-phone',
icon: 'http://localhost/icon.png'
}
});
var RetailerCollection = Backbone.Collection.extend({
});
var RetailerView = Backbone.View.extend({
className: 'retailer',
template: _.template($('#retailer-template').html()),
initialize: function() {
var obj = {
name: form.name.value,
address: form.address.value,
phone: form.phone.value
};
var o = this;
this.model.save(obj, {
success: function(model, response) {
console.log(model);
console.log(response);
o.render();
console.log('success');
},
error: function(model, response) {
console.log(model);
}
});
},
render: function() {
$('#retailer-list').append(this.$el.html(this.template(this.model.toJSON())));
return this;
}
});
var RetailerViews = Backbone.View.extend({
});
$('#submit').click(function(e){
var retailer_model = new RetailerModel();
var retailer_view = new RetailerView({model: retailer_model});
form.reset();
});
And the php code for receiving data is as follow:
<?php
$connect = mysql_connect('127.0.0.1','root','xxxxxx');
if (!$connect) {
die('Could not connect: ' . mysql_error());
}
mysql_select_db("retail", $connect);
if($_SERVER['REQUEST_METHOD'] == 'POST') //POST GET PUT DELETE
{
$data = json_decode(file_get_contents('php://input'), true);
}
$name = $data['name'];
$address = $data['address'];
$phone = $data['phone'];
$icon = $data['icon'];
if(!(mysql_query("INSERT INTO retailer (name, address, phone, icon)VALUES ('".$name."', '".$address."','$phone', '".$icon."')")))
{
echo 200;
}
else{
echo 'record has not been insert to db';
}
mysql_close($connect);
?>
One problem I'm having is that when the error function is called, the model returned from server still has modified attributes. I am wondering why this is happening and how can I make sure that if error happens, model stays unchanged.
Another question is in the php code, when the sql query is successful, if I echo 200, or '200', backbone will call success, but if I echo a string, backbone will call error, I'm wondering what's the logic behind it.

From the backbone docs:
Pass {wait: true} if you'd like to wait for the server before setting
the new attributes on the model.
If you don't want the model to update until after the save is success full pass wait: true as an option.
this.model.save(obj, {
success: function(model, response) {
console.log(model);
console.log(response);
o.render();
console.log('success');
},
error: function(model, response) {
console.log(model);
},
wait: true // Add this
});

The Backbone
save( so are others like fetch, update...)
returns a promise. You can use
save().done(
function( data ) {}, // success
function( err ) {} // fail
)
just like how you handle promises. The done() method is guaranteed to execute after the server has returned stuff.
See the jQuery API docs for AJAX.jqXHR for more information.

Backbone returns a promise.
Here is what I have to get it works.
save({wait: true}).success(function(data){
console.log(data); //returned data from server
}).error(function(error){
console.log(error); //error returned from server
});

Related

Backbone model is plain text file. How to fetch?

I have a model File which contains a plain text file. For example Github Gists have this url structure: https://gist.githubusercontent.com/140bytes/962807/raw/dfb480a5038214d152544bddb412dfe2e8656413/LICENSE.txt.
To do this, should I override fetch/save/etc, or should I override the model's sync?
var File = Backbone.Model.extend({
path: '',
contents: '',
initialize: function(options) {
this.path = options.path || '';
},
fetch: function() {
// Do I override fetch/save/etc?
$.get(this.path).done(function(contents) {this.contents = contents});
},
sync: function (method, model, options, error) {
// Or do I override sync?
}
});
You can just override parse, fetch and url method a little:
var File = Backbone.Model.extend({
url: function(){
return this.get('path')
},
// call original Backbone.Model#fetch with `dataType` equal `text` for $.ajax
fetch: function(options){
options = _.extend(options || {}, {
dataType: 'text'
});
this.constructor.__super__.fetch.call(this, options);
},
// store response in content attribute
parse: function(response){
return {content: response};
}
});
In this case your code will be more idiomatic and you will have all benefits of Backbone native methods (success and error callbacks to fetch, request and sync events, change events etc). You can use it like:
var someFile = new File({
path: 'http:/example.com/someFile.txt'
});
someFile.fetch({
success: function(){
console.log(someFile.get('content'); // => content of someFile.txt
}
});

parse not being called on my model in Backbone

I am working on developing a Backbone + require app. Things are working somewhat, but after updating a model on the server, though the server is returning a 200, the 'error' function in the options hash passed to the model's 'save' is being called.
I think I have identified the problem in that the server returns a JSON object containing 'id', whereas the model has an id attribute labeled 'aid'.
My understanding is that this should be handled in the model's 'parse' function, but I cannot get either the model's ''parse' function to be called. Here is my model:
define([
// These are path alias that we configured in our bootstrap
'jquery', // lib/jquery
'underscore', // lib/underscore
'backbone', // lib/backbone
'util'
], function($, _, Backbone){
// Above we have passed in jQuery, Underscore and Backbone
// They will not be accessible in the global scope
var Address = Backbone.Model.extend({
initialize: function() { console.log("Address initialized"); },
urlRoot: '/address/',
parse: function(response, options) {
console.log("In Address::parse");
for(thing in response) {
console.log("Key:" + thing + ", Val: " + response[thing]);
}
}
});
return {
address: Address
};
});
and here is the relevant part of my view:
events: {
"submit #add-address-form": "addAddress",
},
addAddress: function(ev) {
var that = this;
ev.preventDefault();
var addressDetails = $(ev.currentTarget).serializeObject();
var addr = new A.address();
addr.save(addressDetails, {
success: function(model, response, options) {
that.Backbone.application.router.navigate('', {trigger: true});
},
error: function(model, response, options) {
console.log("Response status: " + response.statusCode());
}
});
return false;
},
When the form presented by the view is submitted 'addAddress' is triggered and the server is updated. My app receives a 200 from the server, and the JSON object
'{id: }', but the parse function in the model is never called.
Any help appreciated;
You have to return a value in your parse function :
parse: function(response, options) {
console.log("In Address::parse");
for(thing in response) {
console.log("Key:" + thing + ", Val: " + response[thing]);
}
return response;
}

Validate on save and save().complete - in backbone

I have problem with validation in my model. It seems that it is impossible to use save().complete(function() {..... in the same time as validation- here is the code:
my model:
App.Models.Task = Backbone.Model.extend({
defaults: {
title:'',
completed: 0
},
validate: function (attrs, options) {
if(attrs.title == '' || attrs.title === undefined) {
return "fill title pls"
}
},
urlRoot: 'tasks'
});
and then in my view i try to save it in add method :
App.Views.TaskAdd = Backbone.View.extend({
tagName: 'div',
template: template('taskTemplateAdd'),
events : {
'click .addTask' : 'add'
},
initialize: function () {
this.model.on('add',this.render, this)
},
add : function () {
var title = $("#addNew input:eq(0)").val();
var completed = $("#addNew input:eq(1)").val();
this.model.set('title', title);
this.model.set('completed', completed);
this.model.save({},
{
success: function (model, response) {
console.log("success");
},
error: function (model, response) {
console.log("error");
}
}).complete(function () {
$("<div>Data sent</div>").dialog();
$('#list').empty();
});
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this
}
});
when validate fires i get error :
Uncaught TypeError: Object false has no method 'complete'
I understand that it tries probably to run complete callback on the return value but how to solve this problem ???
Model.save is documented returning the jqHXR object if successful or false if not.
So, unless your server never fails, you need to handle the case where save returns false. Here's a simple example of the logic you would need:
var valid=this.model.save();
if(!valid) {
// do something when not valid
else {
valid.complete(function() {}); // this is a jqHXR when valid
}
And, as of jQuery 1.8, the use of complete is deprecated. You should consider using always instead.
Use.
...
add : function () {
var self = this;
this.model.save({'title':$("#addNew input:eq(0)").val(),'completed':$("#addNew input:eq(1)").val()},
{
success: function (model, response) {
console.log("success");
self.complete();
},
error: function (model, response) {
console.log("error");
self.complete();
}
});
},
complete: function () {
$("<div>Data sent</div>").dialog();
$('#list').empty();
},
...
model.save() performs a validation first (validate method on the model). If it successfull, it then does the POST/PUT to the server. In other words, you get a false if the client side validation fails. It won't post to server then. You can't use the deferred object if this fails because false.always() will probally result in an error.
Alsoo, if you don't pass a wait: true in the model.save options, it will update the model with its validated object. I usually pass wait: true just to be sure. (I don't want to render the element twice).
If the model fails the client side validation, then it should also fail the server side validation. In this case there is an "invalid" event to listen to. So you only should be interested in the success call. Which in theory should only be interesting if it really has updates (would fire a "change" event)
add: {
var self = this;
this.model.on('invalid', function(error){
console.log(error, 'model is invalid. Check model.validate')
});
this.model.on('change', function(model){
console.log(model.toJSON(), 'model has successfully changed')
});
this.model.on('error', function(error){
console.log("server failed to acknowledge (server connection not made)")
});
this.model.on('sync', function(resp){
console.log("server successfull acknowledged (server connection made)")
});
this.model.save(
{
title:$("#addNew input:eq(0)").val(),
completed:$("#addNew input:eq(1)").val()
},
{
wait: true,
success: function (model, response) {
console.log("success");
#fires an change event if the model is updated
self.complete();
},
error: function (model, response) {
console.log("error");
self.complete();
}
}
);
},
complete: function(){
console.log("show this")
}

Collate Backbone collection success and errors

I have two functions that loop through all models in a Backbone collection, and save those that have been changed, or destroy those that have been selected for deletion. What I need to do is collate the success and errors, so that I can notify "X number of changes/deletions were successful" and/or "There was an error changing/saving X number of domains".
I've no experience with saving/destroying Backbone collections, only models; and can't find anything on the internet that shows how to do this.
The save and remove are called by events in the parent view.
The relevant code:
App.Views.SiteDomains = Backbone.View.extend({
el: '.site-domains',
initialize: function() {
this.collection.on('all',this.render, this);
},
render: function() {
$('.site-domains').empty();
this.collection.each( function(model)
{
var view = new App.Views.SiteDomain({model: model});
this.$('.site-domains').append(view.render().el);
return this;
});
},
saveDomainChanges: function() {
this.collection.each( function(model)
{
var ref = model.get('ref');
if ($('#' + ref).val() != model.get('domain')) {
$('.save-domains').prop('disabled', true);
var fields = $(this.el).find('form').serializeArray(), data = {};
$.each(fields, function(i, pair)
{
data[pair.name] = pair.value;
});
model.save(data, {
wait:true,
success: function(model, response, event)
{
// Pass each success to notification function
},
error: function(model, response, event)
{
// Pass each error to notification function
}
});
}
});
$('.save-domains').prop('disabled', false);
},
removeDomain: function() {
this.collection.each( function(model)
{
var ref = model.get('ref');
if ($('#remove-' + ref).prop('checked'))
{
model.destroy({
wait:true,
success:function() {
// Pass each success to notification function
},
error: function() {
// Pass each error to notification function
}
});
}
});
}
});
Many, many, many thanks in advance to anyone that can help with this! :)
You could use an event aggregator and create Model/View or just POJO for the notifications according to your app design. Something like this:
// Event aggregator
App.vent = _.extend({}, Backbone.Events);
// POJO for the notifications
App.notifications = {
var onCreateSuccess = function (model, response) {
...
};
var onCreateError = function (model, response) {
...
};
App.vent.on('sitedomain:create:success', onCreateSuccess);
App.vent.on('sitedomain:create:error', onCreateError);
};
// Add event triggering to corresponding callbacks
model.save(data, {
wait:true,
success: function(model, response, event) {
App.vent.trigger('createdomain:create:success', model, response);
},
error: function(model, response, event) {
App.vent.trigger('createdomain:create:error', model, response);
}
});

Backbone fetch doesn't work as expected

When I call fetch on my collection the app is calling the server and server returns an array of object. In the success function of the fetch call I've got an empty collection and the original response holding all objects that was responded by the server.
Collection
var OpenOrders = BaseCollection.extend({
model: Order,
url: baseUrl + '/api/orders?status=1'
});
Model
var Order = BaseModel.extend(
{
url:baseUrl + "/api/order",
defaults:{
order_items: new OrderList(),
location: 1,
remark: "remark"
},
initialize: function(options) {
var orderItems = this.get('order_items');
if (orderItems instanceof Array) {
orderItems = new OrderList(orderItems);
this.set({'order_items': orderItems})
}
orderItems.bind('change', _.bind(function() {
this.trigger('change')
}, this))
.bind('remove', _.bind(function() {
this.trigger('change')
}, this));
return this;
},
sum: function() {
return this.get('order_items').sum();
},
validate: function() {
return !!this.get('order_items').length;
},
add:function(product) {
this.get('order_items').add(product);
},
remove: function(product) {
this.get('order_items').remove(product);
}
);
Fetching the collection
this.collection.fetch({success:_.bind( function(collection, response){
console.log('OpenOrdersListView', collection.toJSON())
// logs []
console.log('OpenOrdersListView', response)
// logs [Object, Object ...]
}, this)})
Damm, its the validate method in my model. I've though validate have to return a boolean, but after reading the docs, it has to return an error message only if the model is not valid.
validate: function() {
if (!this.get('order_items').length){
return 'set minium of one product before save the order'
}
},

Resources