Backbone fetch success callback - backbone.js

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.

Related

ExtJS call controller method inside Ext.util.Observable.observe

I have my 4.2.1 ExtJS application listen to every Ajax request (Ext.data.Connection) so I can do custom error handling and other stuff.
Im doing that inside my Main Controller
Ext.define('App.controller.Main', {
extend: 'Ext.app.Controller',
init: function (application) {
var me = this;
this.control({
'[xtype=login] button#btnLogin': {
click: me.onLogin
},
});
Ext.util.Observable.observe(Ext.data.Connection, {
requestcomplete: function (conn, response, options) {
// Do stuff on success
},
requestexception: me.handleRequestException // Do stuff on failure
});
},
handleRequestException: function (conn, response, options) {
var me = this;
if (response.status == 401) {
// here i need to fire the logoutApplication method
// have tried: me.fireEvent('logoutApplication'); but fireEvent is undefined because "me" scope is data.Connection
// it doesnt know about the controller.
}
},
logoutApplication: function () {
var me = this;
// do somestuff here!!
}
Inside the handleRequestException function Im trying to do me.fireEvent(...) but fireEvent is undefined because the scope of the function is data.Connection.
I don't want to use:
var mainController = App.app.getController('App.controller.Main');
Because when calling that the init is fired and I get issues with event firing twice.
Im new in ExtJS so Im sure there is a better way to do this. Appreciate any advice.
You can add a scope to the listeners configuration:
Ext.util.Observable.observe(Ext.data.Connection, {
requestcomplete: function (conn, response, options) {
// Do stuff on success
},
requestexception: me.handleRequestException, // Do stuff on failure
scope: me // add the scope here
});
Now you listeners get executed with that scope instead of the caller scope.

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.

Collection fetch and model parse

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.

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

How to handle async code in a backbone marionette initializer

I'm trying to put together backbone application using the marionette plugin, and am having some trouble getting initializers to work the way I expected them to. I have the following code:
var MyApp = new Backbone.Marionette.Application();
MyApp.addRegions({
region1 : '#div1',
region2 : '#div2'
});
MyApp.Resources = { };
MyApp.bind('initialize:before', function (options) {
// display a modal dialog for app initialization
options.initMessageId = noty({
text : 'Initializing MyApp (this should only take a second or two)',
layout : 'center',
speed : 1,
timeout : false,
modal : true,
closeOnSelfClick : false
});
});
MyApp.addInitializer(function (options) {
$.ajax({
url: options.apiUrl + '/my-app-api-module',
type: 'GET',
contentType: 'application/json; charset=utf-8',
success: function (results) {
MyApp.Resources.urls = results;
console.log(MyApp.Resources.urls); // <- THIS returns an object
}
});
});
MyApp.bind('initialize:after', function (options) {
// initialization is done...close the modal dialog
if (options.initMessageId) {
$.noty.close(options.initMessageId);
}
if (Backbone.history) {
Backbone.history.start();
}
console.log(MyApp.Resources.urls); // <- THIS returns 'undefined' BEFORE the console.log in the initializer above
});
Note in the code above that I have two console.log calls, one in the initializer, and one in the initialize:after handler. Both log the same object property. As you can see, what I'm experiencing is that the console.log call in the initialize:after handler is getting called before the one in the success handler of the initializer. I realize that this is because the initializer has an async call in it...what I need to know is, how can I make sure that all of the async code in my initializer(s) is complete before doing anything else in the application? Is there a good pattern for this? I've not found anything in the docs indicating how to handle this correctly.
Thanks.
how can I make sure that all of the async code in my initializer(s) is complete before doing anything else in the application?
Don't use the initialize:after event. Instead, trigger your own event from the success call, and then bind your app start up code from that one.
MyApp.addInitializer(function (options) {
$.ajax({
url: options.apiUrl + '/my-app-api-module',
type: 'GET',
contentType: 'application/json; charset=utf-8',
success: function (results) {
MyApp.Resources.urls = results;
// trigger custom event here
MyApp.vent.trigger("some:event:to:say:it:is:done")
}
});
});
// bind to your event here, instead of initialize:after
MyApp.vent.bind('some:event:to:say:it:is:done', function (options) {
// initialization is done...close the modal dialog
if (options.initMessageId) {
$.noty.close(options.initMessageId);
}
if (Backbone.history) {
Backbone.history.start();
}
console.log(MyApp.Resources.urls);
});
This way you are triggering an event after your async stuff has finished, meaning the code in the handler will not run until after the initial async call has returned and things are set up.
I wrote an override to the start method using jQuery deffereds so you can specify an Async initializer like authentication. The start method then waits til all deferreds are resolved and then finishes the start.
I replace marionette callbacks with my new sync callbacks class so I can use the regular methods calls in the app. Take a look at my solution and see if that helps at all. https://github.com/AlexmReynolds/Marionette.Callbacks
This can be used to accomplish tasks before the rest of your application begins.
Check the documentation.
// Create our Application
var app = new Mn.Application();
// Start history when our application is ready
app.on('start', function() {
Backbone.history.start();
});
// Load some initial data, and then start our application
loadInitialData().then(app.start);

Resources