Collection fetch doesnt fire reset - backbone.js

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.

Related

Fetching data from the server to Backbone giving me a weird json

I'm trying to fetching data from the server with backbone, this is my request:
I initialize the view with this:
// Backbone view initialize
this.collection = new Dropdown.Collections.Brands();
this.listenTo(this.collection, 'reset', this.render);
I send the data to my server from my Backbone View:
// after the input text fires keypress event
this.collection.fetch({data: {limit: 10, term:value}, reset: true});
I response this from PHP:
return json_encode($data);
This is what's inside the response:
"{\"brands\":{\"0\":{\"type\":\"brand\",\"id\":\"3\",\"text\":\"Smith\",\"manufacturer\":{\"text\":\"Proctor\",\"id\":\"1\"},\"image\":null}}}"
In the render function i log this to see what I get:
console.log(this.collection.toJSON());
This is the Chrome debugger that gives me this:
I'm not sure where is the error, I've tried different working ways by skipping the fetch method and using a jquery plugin handler for the input text, but i would do it with the right way, where is the problem?
Also Response Headers are the same for both methods.
I've solved the problem by overriding the parse method of the collection:
parse:function (response) {
return jQuery.parseJSON(response);
}
With that it works, but shouldn't it works correctly by default?
Lol the problem was a json_ecode called twice in different places, now everything works perfect.

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.

Qunit + Sinon to test Backbone's model events

These days I'm trying to put up some tests for my first serious Backbone app. I had no problem so far with normal test but now I'm stuck trying to setting up an async test.
Basically my server API return a page with a 500 HTTP code error if I try to save a model with invalid attributes and I want to check if this trigger the right "error" state in Backbone.
I've tried to set-up the test in this way:
asyncTest("Test save Model function", function(){
expect(1);
var user = new User({});
var err_spy = this.spy();
user.on('error',err_spy);
user.save(user,{error:function(){
start();
equal( err_spy.callCount, 1, "Callback 'error' called once");
}});
});
The problem is that the error callback of the save function overrides the one in the model, so the only way to trigger it would be to do it manually:
user.trigger("error");
I don't think it is a right way to test because in my production environment there is no error callback for model's save function, but on the other hand I don't know how to tell Qunit to wait the ajax response to evaluate the test assertion.
Can someone suggest me a way to make it work? Thank you!
Something like this should do the trick. I'm going from memory here, but the sinon fake server should allow you to immediately return the 500 error state and subsequently invoke the spied-on function. You might need to tweak the server.respondWith(...) call.
asyncTest("Test save Model function", function(){
expect(1);
var user = new User({});
// Set up a fake 500 response.
var server = sinon.fakeServer.create();
server.respondWith(500, {}, "");
// Create the error callback.
var err_callback = function(){};
var err_spy = sinon.spy(err_callback);
user.save(user, {error: err_callback});
server.respond();
equal( err_spy.callCount, 1, "Callback 'error' called once");
server.restore();
});

Wait for the collection to fetch everything in backbone

I have two set of collections. One is for the categories and the other is for the Items. I ned to wait for the categories to finish fetching everything for me to set the category for the Items to be fetched.
Also i everytime i click a category i must re-fetch a new Items Collection because i have a pagination going on everytime i click on a category it doesn't refresh or re-fetch the collection so the pagination code is messing with the wrong collection. Any ideas?
this.categoryCollection = new CategoryCollection();
this.categoryCollection.fetch();
this.itemCollection = new ItemCollection();
this.itemCollection.fetch();
Just ran into a similar situation. I ended up passing jquery.ajax parameters to the fetch() call. You can make the first fetch synchronous. From the backbone docs:
jQuery.ajax options can also be passed directly as fetch options
Your code could be simplified to something like:
this.categoryCollection.fetch({async:false});
this.itemCollection.fetch();
One quick way would be to just pass a callback into the first fetch() call that invokes the second. fetch() takes an options object that supports a success (and error) callback.
var self = this;
this.categoryCollection = new CategoryCollection();
this.categoryCollection.fetch({
success: function () {
self.itemCollection = new ItemCollection();
self.itemCollection.fetch();
}
});
Not the most elegant, but it works. You could probably do some creative stuff with deferreds since fetch() returns the jQuery deferred that gets created by the $.ajax call that happens.
For the pagination issue, it's difficult to tell without seeing what your pagination code is doing. You're going to have to roll the pagination stuff yourself since Backbone doesn't support it natively. What I'd probably do is create a new Collection for the page criteria that are being queried and probably create a server action I could hit that would support the pagination (mapping the Collection's url to the paginated server action). I haven't put a ton of thought into that, though.
I had to react to this thread because of the answers there.
This is ONLY WAY OF DOING THIS RIGHT!!!
this.categoryCollection = new CategoryCollection();
this.itemCollection = new ItemCollection();
var d1 = this.categoryCollection.fetch();
var d2 = this.itemCollection.fetch();
jQuery.when(d1, d2).done(function () {
// moment when both collections are populated
alert('YOUR COLLECTIONS ARE LOADED :)');
});
By doing that you are fetching both collections at same time and you can have event when both are ready. So you don't wait to finish loading first collections in order to fetch other, you are not making ajax calls sync etc that you can see in other answers!
Here is a doc on Deferred objects.
Note: in this example case when one or more deferred object fails it's not covered. If you want to cover that case also beside .done you will have to add .fail callback on .when and also add error handler that will mark failed d1 or d2 in this example.
I am using RelationalModel and I created a queued fetch, that only calls the 'change' event when done loading:
var MySuperClass = Backbone.RelationalModel.extend({
//...
_fetchQueue : [],
fetchQueueingChange : function(name){
//Add property to the queue
this._fetchQueue.push(name);
var me = this;
//On fetch finished, remove it
var oncomplete = function(model, response){
//From _fetchQueue remove 'name'
var i = me._fetchQueue.indexOf(name);
me._fetchQueue.splice(i, 1);
//If done, trigger change
if (me._fetchQueue.length == 0){
me.trigger('change');
}
};
this.get(name).fetch({
success: oncomplete,
error : oncomplete
});
},
//...
});
The class would call:
this.fetchQueueingChange('categories');
this.fetchQueueingChange('items');
I hope you can improve on this, it worked well for me.
I ended up with the same problem today and figured out a solution to this:
var self = this;
this.collection = new WineCollection();
this.collection.url = ApiConfig.winetards.getWineList;
this.collection.on("reset", function(){self.render()});
this.collection.fetch({reset: true});
Now when the fetch on the collection is complete a "reset" is triggered and upon "reset" call the render() method for the view.
Using {async: false} is not the ideal way to deal with Backbone's fetch().
just set jQuery to become synchronous
$.ajaxSetup({
async: false
});
this.categoryCollection.fetch();
this.itemCollection.fetch();
$.ajaxSetup({
async: true
});
This is the simplest solution, I guess. Of course, starting new requests while these fetches run will be started as synchronous too, which might be something you don't like.

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