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);
Related
I intend to load data from server only after the user is Authenticated.
For simplicity, let us assume that the user is already authenticated.
I put the load data function call (that loads data from a store) in the Main.js initialize function as you can see below.
However, the getStore('storeId').load() function is async, which makes me worried in case the data store finished loading only after the Main view finished loading which might make the view load without the data (fix me if I am wrong, maybe sencha can deal with this, the view has reference to the storeId).
What is the best practice to target such issues?
Trivial solution: calling the store load synchronously, but does it make any difference? and just in case, how to do it? I tried to add synchronous variable set to true but doesn't work.
app.js
launch: function() {
// Destroy the #appLoadingIndicator element
Ext.fly('appLoadingIndicator').destroy();
Ext.Ajax.request({
url: MyApp.app.baseUrl + 'session/mobileCheckAuth',
method: "POST",
useDefaultXhrHeader: false,
withCredentials: true,
success: function(response, opts) {
if (response && response.status === 200) {
Ext.Viewport.add(Ext.create('MyApp.view.Main'));
} else {
Ext.Viewport.add(Ext.create('MyApp.view.LoginPanel'));
}
},
failure: function(response, opts) {
alert('Unexpected failure detected');
Ext.Viewport.add(Ext.create('MyApp.view.LoginPanel'));
}
});
},
Main.js
Ext.define('MyApp.view.Main', {
...
initialize: function() {
console.log('main.initialize');
this.callParent(arguments);
// Load app data
MyApp.utils.Functions.loadData();
}
There are two ways to solve this :
If your view consists of a grid, there will not be a race condition. If the grid renders before the store has finished loading, the grid will update itself with the new data that is added. For this to work, you have to declare the store as 'store: "mystore";' .
If you have a complex view that is not directly bound to the store, you have to load the store first, and in the on 'load' event of the store you initialize the rendering of the view.
store.load({callback: function(){
Ext.Viewport.add(Ext.create('MyApp.view.Main'}
});
I have a User model in a Backbone application that makes an ajax request. In the error callback, I wish to set an error message to pass to the view. However, if I try do
this.set({errors: result.errors});
I'm told "this" doesn't have a method set. In this case, I believe "this" is the ajax response object (rather than the User model which does have a set method)
Object {url: "/users.json", isLocal: false, global: true, type: "POST", contentType: "application/x-www-form-urlencoded; charset=UTF-8"…}
However, I also tried to do
this.model.set({errors: result.errors});
but it said I can't call "set" of undefined. I'm assuming it doesn't make sense to say "this.model" from within the model, but, as mentioned above, if I just say "this," it refers to the response object.
Is this the wrong way to go about it?
I am assuming you are doing something like this when you are saving your model
model.save({
success: function() {},
error: function() {
this.set({ errors: result.errors });
}
});
If that is the case, then you can change this.set to model.set, and everything will work.
However it doesn't really make that much sense to be storing the error message as a model attribute.
The model will fire an event when its save call fails on the server (check out the backbone events catalogue).
Therefore if you have a view with an attached model, you can tell the view to listen to this error event.
var MyView = Backbone.View.extend({
initialize: function() {
// if your using backbone v0.9.10
this.listenTo(this.model, 'error', this.handleModelError);
// or for earlier versions
this.model.on('error', this.handleModelError, this);
},
handleModelError: function(model, xhr, options) {
// show an error message, or whatever
}
});
var view = new MyView({ model: aModel });
// if the server returns an error, view.handleModelError will be called
aModel.save();
I think this probably loses context. Try using var self = this. Something like:
var self = this;
model.save("author", "F.D.R.",
{error: function()
{
self.model.set({errors: result.errors});
}});
I wish to read a whole database table to fill a Backbone.js Collection, before updating a View.
I am using fetch and listening to the reset event.
My problem is the reset event fires up before the http request is made to the server.
My question is: how can I render the view AFTER the data is received back from the server on a fetch?
Here is a jsfiddle showing the problem (with a debugger placed at reset):
http://jsfiddle.net/GhaPF/16/
The code:
$(document).ready(function() {
var Item = Backbone.Model.extend({
urlRoot : './items'
});
var ItemList = Backbone.Collection.extend({
model: Item,
url: './items/',
});
var ItemListView = Backbone.View.extend({
el: 'body',
initialize: function(myitemList) {
this.itemlist = myitemList;
this.itemlist.bind('reset', this.debuggThis());
},
debuggThis: function() {
debugger;
},
render: function() {
},
events: {
"keypress #new-item": "createOnEnter"
},
createOnEnter: function(e) {
}
});
$("#new-item").focus();
var itemlist = new ItemList();
var myitemListView = new ItemListView(itemlist);
itemlist.fetch();
});
The following code works, but it just doesn't feel like proper backbone.js (MVC) code since it would be placed outside of the View definition:
itemlist.fetch().complete(function(){
Maybe the issue is this line:
this.itemlist.bind('reset', this.debuggThis());
Should actually be:
this.itemlist.bind('reset', this.debuggThis);
Your debugThis function was getting run at the time you set up the listener for the 'reset' event - not when the event is triggered. This was telling JavaScript that you wanted debugThis to return a callback function instead of having debugThis "be" the callback function.
Also, orangewarp's comment about passing 'this' as the third parameter is probably relevant too. Sot it would end up as:
this.itemlist.bind('reset', this.debuggThis, this);
That's strange. When you fetch() the reset event should be triggered AFTER your collection is populated. So I'm thinking the phenomena that reset happens before the http request is fired up may not be what you think it is.
Instead of using the complete... you could always just use the success callback option like this:
itemlist.fetch({
success: function() {
// Whatever code you want to run.
itemlist.debuggThis();
}
});
Also, when binding your reset you probably want this:
this.itemlist.bind('reset', this.debuggThis, this);
suppose I have a model and a view ,ths view have two method:one is bind the document mousemove event and the other is unbind method,defalut I give the document mousemove event, once the model's enable value changed I will call the view's unbind method:
window.ConfigModel = Backbone.Model.extend({
defaults: {
'enable':0
},
initialize: function(){
this.bind("change:enable", function () {
var portView2 = new PortView();
portView2.viewOff();
});
},
change:function () {
this.set('enable', 9);
}
})
window.PortView = Backbone.View.extend({
viewOn: function () {
$(document).on('mousemove', function () {
console.log('move')
})
},
viewOff: function () {
$(document).off('mousemove');
}
})
then I put an input on the document to call the model changed:
$('input').click(function () {
var configModel = new ConfigModel();
configModel.change();
})
the boot script is :
var portView1 = new PortView();
portView1.viewOn();
The problem is once I call the click the input button ,the chrome would tell me an error:Maximum call stack size exceeded it seems the change be invoke many times.So what's the problem with my problem ,how can I solve this problem
Backbone models already have a change method:
change model.change()
Manually trigger the "change" event and a "change:attribute" event for each attribute that has changed. If you've been passing {silent: true} to the set function in order to aggregate rapid changes to a model, you'll want to call model.change() when you're all finished.
Presumably something inside Backbone is trying to call configModel.change() and getting your version of change which triggers another change() call inside Backbone which runs your change which ... until the stack blows up.
You should use a different name for your change method.
That said, your code structure is somewhat bizarre. A model listening to events on itself is well and good but a model creating a view is odd:
initialize: function() {
this.bind("change:enable", function () {
var portView2 = new PortView();
portView2.viewOff();
});
}
And instantiating a view simply to call a single method and then throw it away is strange as is creating a new model just to trigger an event.
I think you probably want to have a single ConfigModel instance as part of your application state, say app.config. Then your click handler would talk to that model:
$('input').click(function () {
app.config.enable_level_9(); // or whatever your 'change' gets renamed to
});
Then you'd have some other part of your application (not necessarily a view) that listens for changes to app.config and acts appropriately:
app.viewOn = function() {
$(document).on('mousemove', function() {
console.log('move')
});
};
app.viewOff = function() {
$(document).off('mousemove');
};
app.init = function() {
app.config = new ConfigModel();
app.viewOn();
$('input').click(function () {
app.config.enable_level_9();
});
// ...
};
And then start the application with a single app.init() call:
$(function() {
app.init();
});
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.