Collection fetch and model parse - backbone.js

I override function parse() in my model, when name or surname from the database is empty I'm asking Facebook API for that:
var Friend = Backbone.Model.extend({
parse : function(response) {
var self = response,
that = this;
if(!response.first_name) {
FB.api('/'+response.fbid, function(response) {
self.first_name = response.first_name;
self.surname = response.last_name;
});
}
return self;
}
});
My problem is that during fetch-ing in collection this values (first_name and surname) are still empty (though console.log in the model shows it properly). How could I resolve it?

Javascript call to FB.api is async, so basically there is no delay between FB.api and return self. Since your console.log(model) is probably immediately after fetch there is no returned data from FB.api since the request is not over.
What you could do is set try to put some callback when your model is updated and listen to it, and if you change the model trigger update method something like ...
Friend.fetch( { success: function(model, response) {
if ( !model.get('first_name') ) {
FB.api('/'+model.get('fbid'), function(fb_response) {
model.set('first_name', fb_response.first_name);
model.set('last_name', fb_response.last_name);
console.log('model updated with facbook info', model);
});
}
}});
Try running ( in your current code ) console.log('updated'); in your FB.api callback to see the delay I'm talking about.

Related

Angular.js, $resource, how to manage multiple asynchronous requests

I am implementing a "copy on write" CRUD system meaning i never overwrite a database entry but mark as inactive and write a new record. When editing an existing record this means i write to the old record deactivating then create a new record. My controller code is below:
$scope.save = function() {
if(!$scope.newDevice){
var editDevice = $scope.device;
$scope.delete(editDevice);
$scope.device = {name: editDevice.name, type: editDevice.type, hash: editDevice.hash};
}
var newDevice = new DeviceService($scope.device);
newDevice = newDevice.$save(function(newDevice, putResponseHeaders) {
DeviceService.query({active : true}, function(devices){
$scope.devices = devices;
});
});
};
When i call to get the list of active devices with DeviceService.query({active : true} I still get the old record as active since it executes and returns before the delete method has been processed and returned.
I think i should be using promise maybe. How do i write this code to be better and work?
thanks
Yes, you want to use promises. You have two options:
Use the success/failure callbacks that all $resource methods supply. Note you're using this when you call $save. You could do the same when you call $delete on the resource, so that your remaining code only executes when the $delete() succeeds. These callbacks are automatically invoked when the $resource's built-in promise is resolved or rejected.
Make your $scope.delete() function return a promise. It sounds like this might be better, because you do not always want to make the delete request.
The code for #2 might look like this:
// this function use the '$q' service, which you need to inject
// in your controller
$scope.delete = function(item) {
var deferred = $q.defer();
item.$delete({},
function(response) {
// the delete succeeded, resolve the promise
deferred.resolve(response);
},
function(error) {
// failed, reject the promise
deferred.reject(error);
}
);
return deferred.promise;
}
$scope.save = function() {
if(!$scope.newDevice){
var editDevice = $scope.device;
$scope.delete(editDevice).then(function(response) {
$scope.device = {name: editDevice.name, type: editDevice.type, hash: editDevice.hash};
// now trigger the code to save the new device (or whatever)
$scope.doTheActualSave();
},
function(error) { });
} else {
// there was nothing to delete, just trigger the code to save
$scope.doTheActualSave();
}
};
$scope.doTheActualSave = function() {
var newDevice = new DeviceService($scope.device);
newDevice = newDevice.$save(function(newDevice, putResponseHeaders) {
DeviceService.query({active : true}, function(devices){
$scope.devices = devices;
});
});
}

backbone collection: _.after isnt called after async requests

I want to update a collection of posts individually and after each request has finished then re-render my collection.
Currently the code below never triggers renderPosts and I have no idea why
onClickCopyPosts: function(posts, publish_target) {
var self = this;
var renderPosts = _.after(posts.length, function() {
console.log('got here');
self.collection.fetch({reset: true});
});
_.each(posts, function(post) {
post = new PostModel(post);
post.set('id', null);
post.set('publish_target', publish_target);
post.save({success: renderPosts});
});
},
The signature of save is model.save([attrs], [options]) .. And success handler is an option here.
So when you define the signature as post.save({success: renderPosts}); the success handler is passed into the attributes which does not perform any action.
Change it to
post.save({}, {success: renderPosts});
or
post.save(null, {success: renderPosts});
Passing the option as the second parameter.

Backbone model doesn't fetch response instant but renders view first

I've model with a parse function
var File = Backbone.Model.extend({
...
parse: function(response) {
console.log('Parsing response')
}
});
var FileView = Backbone.View.extend({
...
initialize: function(id) {
this.file = new File({id: id.id});
console.log('Fetching object')
this.file.fetch();
this.render();
},
render: function() {
console.log('Rendering view');
this.$el.html(this.template(this.file.JSON()));
}
})
The expected result would be:
Fetching object
Parsing response
Rendering view
But this is what I got:
Fetching object
Rendering view
Parsing response
Why is this? According to the docs:
parse is called by Backbone whenever a collection's models are
returned by the server, in fetch.
Why isn't the parse function called directly after fetching the model? How force it to do this?
Yeah, but you're making an async call to the server to fetch the model, so render is actually being called before you get answer. You have to do, something like this:
this.file.fetch({success: function () {
//call render here
}});

Backbone model.destroy(): Is explicit removal from collection necessary?

I have a simple question. I am looking at a function with 2 lines of code:
deleteTask: function() {
this.parent.collection.remove(this.model);
this.model.destroy();
}
If I comment out the first line, which is supposed to remove the model from its collection, things seem to work as intended (as in, the model is removed automatically). From Backbone's website, this is the relevant discription for a model's "destroy" function:
Triggers a "destroy" event on the model, which will bubble up through any collections that contain it.
Am I safe to assume that the removal of this.parent.collection.remove(this.model); will not affect the functionality of the code in any way? This is what I think, but I wanted to make sure of it.
Thank you!
If you destroy a model, it is removed from any collections that was containing it. You can see that in the backbone source
//Internal method called every time a model in the set fires an event.
_onModelEvent: function(event, model, collection, options) {
...
if (event === 'destroy') this.remove(model, options);
So yes, I wouldn't think you would need to remove the model from your collection explicitly.
But don't trust me, test for yourself :)
deleteTask: function() {
that = this;
this.model.destroy({
success: function() {
console.log(that.parent.collection);
}
});
}
Check the console for yourself to see whether the model was removed from the collection.
The solution is to override the Backbone model destroy function. I made this on an abstract model with success and callback strategy:
Parameter "data" corresponds to the original parameter "resp".
destroy: function(successCallback, errorCallback)
{
var options = { wait: true };
var model = this;
successCallback = successCallback || function() {};
errorCallback = errorCallback || function() {};
var destroy = function()
{
model.trigger('destroy', model, model.collection, options);
};
options.success = function(data)
{
if ('SUCCESS' === data.responseCode)
{
if (options.wait || model.isNew())
destroy();
successCallback(data);
if (!model.isNew())
model.trigger('sync', model, data, options);
}
else
{
errorCallback(data);
}
};
if (this.isNew())
{
options.success();
return false;
}
var xhr = this.sync('delete', this, options);
if (!options.wait)
destroy();
return xhr;
}

Backbone fetch success callback

I'm new to backbone.js and I'm having some issues with giving my collection a success callback. I'm overriding fetch in order to have a url with a parameter in it. As I understand it I should be able to assign a success callback in the options I pass to Backbone.Collection.prototype.fetch.call()... but, my code isn't working. Fetch works correctly, but the callback function is not called.
Here is a bit of my code:
App.ChartController = {
load: function(userConceptId) {
App.chartPointList.fetch(userConceptId);
}
};
App.ChartPointList = Backbone.Collection.extend({
model: App.ChartPoint,
url: function() {
return '/chartpoints/' + this.userConceptId;
},
fetch: function(userConceptId, options) {
console.log("fetch chart point");
typeof(options) != 'undefined' || (options = {});
options.success = this.postProcess;
options.error = this.handleError;
this.userConceptId = userConceptId;
return Backbone.Collection.prototype.fetch.call(this, options);
},
postProcess : function (resp, status, xhr) {
console.log("postprocess"); // never gets called
/**
... whole bunch of stuff...
**/
new App.Views.ChartView({ collection: this });
},
handleError : function (resp, status, xhr) {
alert("could not load chart data!"); // also not called
}
});
Any idea what I'm doing wrong? Thanks!
#fguillen's comment and another SO thread helped me figure this out. Specifically:
Collection.fetch() will call reset() on success, which in turn will trigger a 'reset' event. Any subscribers to the collections reset event should receive the event.
The issue wasn't with my success callback at all. Turns out I had an problem in a view that was subscribed to the ChartPointList reset event. A function in that view was being called before the success callback and throwing an error, and thus the success callback was not being called.

Resources