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

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")
}

Related

Populating data from two different url in a backbone collection

I have a Marionette.LayoutView which calls a backbone collection and fetches the data and renders based on response. Now the issue that I am facing is, this collection needs to get data from two different endpoints, both should be independent, and then return the combined result. Below is my code:
My Marionette.LayoutView
var View = Marionette.LayoutView.extend({
template: _.template(some.html),
regions: {
div1: '[data-region="div1"]',
div2: '[data-region="div2"]',
},
initialize: function () {
this.collection = new MovieCollection();
},
onRender: function () {
if (this.collection.length) {
this.div1.show(new TopMoviesByLikesView({
collection: this.collection,
movieCount: 10,
}));
this.div2.show(new TopMovieByRatingsView({
collection: this.collection,
movieCount: 10,
}));
}
},
});
module.exports = AsyncView.extend({
ViewConstructor: View,
});
My Collection
module.exports = Backbone.Collection.extend({
model: TopMovieModel,
initialize: function (response) {
let movieCollection = [];
let movieSourceOne = new TopMovieFromSourceOne();
movieSourceOne.fetch({
success: function (collection, response) {
movieCollection = [...movieCollection, ...response.data];
},
error: function (collection, response, options) {
console.info('~ Response::ERROR', collection, response, options);
}
});
let movieSourceTwo = new movieSourceTwo();
movieSourceTwo.fetch({
success: function (collection, response, options) {
movieCollection = [...movieCollection, ...response.data];
},
error: function(collection, response, options) {
console.info('~ Response::ERROR', collection, response, options);
}
});
this.collection = movieCollection;
},
The error I get is A “url” property or function must be specified is there a way where I can do this without using a url in backbone collection? Note: I want to keep two endpoints independent since I don't want the collection to fail if primary API fails.
To avoid that error with url, you should override your fetch method, to call both collections fetch instead.
function promisifyFetch(collection) {
return new Promise(function(resolve, reject) {
collection.fetch({
success() {
resolve(collection);
},
error() {
reject();
}
});
});
}
module.exports = Backbone.Collection.extend({
model: TopMovieModel,
initialize() {
this.movieSourceOne = new TopMovieFromSourceOne();
this.movieSourceTwo = new movieSourceTwo();
},
fetch(options) {
return Promise.all([
promisifyFetch(this.movieSourceOne),
promisifyFetch(this.movieSourceTwo)
]).then(([one, two]) => {
const response = [
...one.toJSON(),
...two.toJSON()
];
this.set(response, options);
this.trigger('sync', this, response, options);
});
}
});
You probably want to handle errors here aswell.

Backbone save model success and error

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
});

Showing 500 (Internal Server Error) using backbone.js with slim.php when only adding new model

I got this code from https://github.com/ccoenraets/backbone-cellar as I am trying to learn backbone.js. When I am trying to add new model to database using slim.php, it is showing 500 (Internal Server Error). But when I am trying to fetch, update, delete its working good. Why it is showing error only on adding ?
Plz help me, Thanks.
window.WineView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events: {
"click .save" : "beforeSave",
"click .delete" : "deleteWine"
},
beforeSave: function () {
var self = this;
var check = this.model.validateAll();
if (check.isValid === false) {
utils.displayValidationErrors(check.messages);
return false;
}
// Upload picture file if a new file was dropped in the drop area
if (this.pictureFile) {
this.model.set("picture", this.pictureFile.name);
utils.uploadFile(this.pictureFile,
function () {
self.saveWine();
}
);
} else {
this.saveWine();
}
return false;
},
saveWine: function () {
var self = this;
this.model.save(null, {
success: function (model) {
self.render();
app.navigate('wines/' + model.id, false);
utils.showAlert('Success!', 'Wine saved successfully', 'alert-success');
},
error: function () {
utils.showAlert('Error', 'An error occurred while trying to add this item', 'alert-error');
}
});
},
deleteWine: function () {
this.model.destroy({
success: function () {
alert('Wine deleted successfully');
window.history.back();
}
});
return false;
}
});
I had the same problem. In your /tutorial/api directory; open index.php and look for the first line in the function addWine():
error_log('addWine\n', 3, '/var/tmp/php.log');
Comment that line:
//error_log('addWine\n', 3, '/var/tmp/php.log');
This should solve your problem, it certainly did mine.

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