Backbone.js validation broken (0.9.9 vs. 0.9.10) - backbone.js

Backbone changed a few things about validation between these versions, first off, you now have to explicitly pass {validation: true} with a set call for validation to trigger. There must have been another change too, because this doesn't work no longer.
model.set(obj, {
error : function(model, error){
//Do stuff with error
}
})
I found this ticket on backbone's github, but it only answers the issue if using save, not set.
https://github.com/jashkenas/backbone/issues/2153

Here's the solution I've discovered.
1) Assign the set to a variable called success (or whatever you like)
var success = model.set(obj, {validate : true});
2) Check the status of success, and then use model.validationError
if(!success){
var error = model.validationError;
// Do stuff with error
}

Related

Validation doesn't work?

I have some code:
var Person = new Backbone.Model({name: 'Jeremy'});
Person.validate = function(attrs) {
if (!attrs.name) {
return 'I need your name';
}
};
Person.on("invalid", function(model, error) {
alert(model.get("title") + " " + error);
});
Person.set({name: 'Samuel'});
console.log(Person.get('name'));
// 'Samuel'
Person.unset('name', {validate: true});
console.log(Person.get('name'));//Why can i print name here if it unsetted?
When i type unset method i see an error alert. It's correct. But why can i print in console the name if it was unsetted?
The name is still there because the validation failure stopped the unset from doing anything.
The documentation isn't very explicit about how validation works with set and unset but it is quite explicit with save:
validate model.validate(attributes, options)
[...] If validate returns an error, save will not continue, and the model attributes will not be modified on the server.
So it is reasonable to think that validation errors will prevent the current operation (set, unset, save, ...) from changing anything.
You can see how it works by examining the Backbone source code. First, you need to know that unset is just a set call in disguise:
unset: function(attr, options) {
return this.set(attr, void 0, _.extend({}, options, {unset: true}));
}
So we look at set:
set: function(key, val, options) {
// A bunch of boring bookkeeping stuff...
// Run validation.
if (!this._validate(attrs, options)) return false;
// The stuff that changes attributes and triggers events.
}
The validations happen as soon as set knows what it is working with and set returns without changing anything if the validations fail.
The Backbone documentation leaves a lot of important things out so you need to be passingly familiar with the Backbone source if you're going to use Backbone. The source is fairly straight forward, don't be afraid to jump into it to see what's going on.
Do this: To not trigger events you can use the silent:true option. I believe there might be some issue upstream in your code. Anyways, do the following - it should work. ( in my tests, it did ).
Person.unset('name',{validate: true,silent:true})
p.s.: Mu ( below ) gives great information.

Understanding BackboneJS flow

I have been given a Project which is written entirely in Backbone.js, which I am supposed to change according to our specific needs. I have been studying Backbone.js for the past 2 weeks. I have changed the basic skeleton UI and a few of the features as needed. However I am trying to understand the flow of the code so that I can make further changes.
Specifically, I am trying to search some content on Youtube. I have a controller which uses a collection to specify the url and parse and return the response. The code is vast and I get lost where to look into after I get the response. I tried to look into views but it only has a div element set. Could someone help me to proceed. I wont be able to share the code here, but a general idea of where to look into might be useful.
Code Snippet
define([
'models/youtubeModelForSearch',
'coretv/config',
'libs/temp/pagedcollection',
'coretv/coretv'
],function( youtubeModelForSearch, Config, PagedCollection, CoreTV ) {
"use strict";
return PagedCollection.extend({
model: youtubeModelForSearch,
initialize: function() {
this.url = 'http://gdata.youtube.com/feeds/api/videos/?v=2&alt=json&max-results=20';
},
fetch: function(options) {
if (options === undefined) options = {};
if (options.data === undefined) options.data = {};
//options.data.cmdc = Config.getCMDCHost();
//CoreTV.applyAccessToken(options);
PagedCollection.prototype.fetch.call(this, options);
},
parse: function(response) {
var temp = response.feed
/*temp["total"] = 20;
temp["start"] = 0;
temp["count"] = 10; */
console.log(temp);
return temp.entry;
},
inputChangeFetch: function(query) {
this.resetAll();
if(query) {
this.options.data.q = query;
// this.options.data.region = Config.api.region;
//this.options.data.catalogueId = Config.api.catalogueId;
this.setPosition(0);
}
}
});
});
Let's assume your collection endpoint is correctly set and working. When you want to get the data from the server you can call .fetch() on you collection.
When you do this, it will trigger an request event. Your views or anybody else can listen to it to perform any action.
When the data arrives from the server, your parse function is called, it is set using set or reset, depending the options you passed along fetch(). This will trigger any event related to the set/reset (see the documentation). During set/reset, the data retrieved from your server will be parsed using parse (you can skip it, passing { parse: false }.
Right after that, if you passed any success callback to your fetch, it will be called with (collection, response, options) as parameters.
And, finally, it will trigger a sync event.
If your server does not respond, it will trigger an error event instead of all this.
Hope, I've helped.

Collection fetch doesnt fire reset

I am just going crazy as why is this not working. I have a collection that fetches the data. The collection is bind to run render function on reset. But render isnt called.
this.uploadTemplate = _.template(UploadTemplate);
this.artistCollection = new ArtistCollections();
this.artistCollection.bind('reset', this.render, this);
this.artistCollection.fetch({success: function(){console.log('jsjbajsb');},
error: function(collection, response, options){ console.log(response)}});
// this.artistCollection.fetch({reset: true});
this.me = options.me;
Neither is the success function called nor the commented line works
Edit :1 Added error option
So, I was not sending a correct Json-fied response. Lots of thnks to #mu is too short.
I wish backbone would have provided a verbose error in this case.

Site wide error management with backbone

The problem
I want to have a default error handler in my app that handles all unexpected errors but some time (for example when saving a model) there are many errors that can be expected so I want to handle them in a custom way rather than show a generic error page.
My previous solution
My Backbone.sync function used to have this:
if(options.error)
options.error(response)
else
app.vent.trigger('api:error', response) # This is the global event channel
However, this no longer works since backbone always wraps the error function so it can trigger the error event on models.
New solution 1
I could overwrite the fetch and save methods on models and collections to wrap options.error and have the code above there but this feels kinda ugly.
New solution 2
Listen to error on models, this won't allow me to override the default error handler though.
New solution 3
Pass in a custom option to disable the global triggering of the errors, this feels redundant though.
Have I missed anything? Is there a recommended way of doing this?
I can add that I'm using the latest version from their git repo, not the latest from their home page.
Could you do this in your overridden sync? This seems to accomplish the same thing you did before.
// error is the error callback you passed to fetch, save, etc.
var error = options.error;
options.error = function(xhr) {
if (error) error(model, xhr, options);
// added line below.
// no error callback passed into sync.
else app.vent.trigger('api:error', xhr);
model.trigger('error', model, xhr, options);
};
This code is from Backbone source, I only add the else line.
EDIT:
This is not the prettiest solution, but might work for you. Create a new Model base class to use, instead of extending Backbone.Model, extend this.
var Model = Backbone.Model.extend({
// override fetch. Do something similar for save, destroy.
fetch: function(options){
options = options ? _.clone(options) : {};
var error = options.error;
options.error = function(model, resp) {
if (error) error(model, resp);
else app.vent.trigger('api:error', resp);
};
return Backbone.Model.prototype.fetch.apply(this, [options]);
},
});
var MyModel = Model.extend({});
var model = new MyModel();
model.fetch(); // error will trigger 'api:error'
Actually, this might be better than overriding sync anyways.
Possible alternative is to use this: http://api.jquery.com/ajaxError/.
But with that, you will get the error regardless of whether you passed in an error callback to backbone fetch/save/destroy.

Backbone.Model.save does not set my model with the server response

I'm calling 'save' on my model and returning the new model as json in my PHP backend. When I step through the Backbone.Model.save method, I can see that it successfully gets the server response and then sets the model (in the options.success closure below). But when the execution is returned to my click handler, the model has the old properties (ie. the id is not set). What could be happening?
Here is my click handler:
addButtonClick: function(e) {
var data = $(e.target).closest('form').serializeObject();
var p = new Domain.Page; // this extends Backbone.Model
p.set(data);
p.save();
// ****
// **** AFTER p.save, the model p still has the OLD ATTRIBUTES... why??
// ****
return false;
}
Here is Backbone's save method:
// 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.
save : function(attrs, options) {
options || (options = {});
if (attrs && !this.set(attrs, options)) return false;
var model = this;
var success = options.success;
options.success = function(resp, status, xhr) {
// ****
// **** NEXT LINE SUCCESSFULLY SETS THE MODEL WITH THE SERVER RESPONSE
// ****
if (!model.set(model.parse(resp, xhr), options)) return false;
if (success) success(model, resp, xhr);
};
options.error = wrapError(options.error, model, options);
var method = this.isNew() ? 'create' : 'update';
return (this.sync || Backbone.sync).call(this, method, this, options);
},
The save method is asynchronous. In other words, the model.set call inside save happens after the server has responded.
You are asking why the values are the same immediately after save is called. The answer is: at that point in time, the response has not been received by your code yet. The callback hasn't been called. model.set hasn't been called.
When you continue on and the event loop gets the response from the server (this may be a fraction of a second, it may be several seconds) later, your values will get set.
I think I figured out what was wrong here. And Brian you were right to say it had something to do with the async nature of the Backbone.save call. The thing is that I was using the DEBUGGER. This stops all execution. I actually don't really understand how an async call works under the hood, perhaps with threads? I assumed that after I stepped over the call to 'save' and then waited a sec, then the async part (whatever that is) of the 'save' call would execute in the background. But this is not the case. The debugger halts everything. So the options.success closure within 'save' always gets called sometime after stepping over 'save'. In short, this whole thing is due to me not understanding javascript and javascript debugging properly.

Resources