Sencha Touch loading from store after login - race issue? - extjs

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

Related

Rally App onLoad

I have a Rally app that requires considerable loading time and it has some loops containing Data Stores. I need to find a condition where the app has completed loading. Once the execution of the app is complete, I want to refresh my page. Is there something like onLoad() that I can use to notify that the app has loaded completely and then I could add window.location.reload at the bottom?
I would suggest using either a callback function, for a single data store, or a series of promises for multiple data stores. The former is pretty straight forward but this is how I usually handle the latter:
In this case, I'm simultaneously loading all the User Stories, Defects and Test Cases in the current scope, then when all the records have been loaded successfully, the "success" function will be called.
launch: function() {
Deft.Promise.all([
this.loadRecords('UserStory'),
this.loadRecords('Defect'),
this.loadRecords('TestCase')
]).then({
success: function(recordSets) {
// recordSets = [
// UserStoryRecords,
// DefectRecords,
// TestCaseRecords
// ];
},
failure: function() {
//Handle error loading the store here...
}
});
},
loadRecords: function(model) {
var deferred = Ext.create('Deft.Deferred');
Ext.create('Rally.data.WsapiDataStore', {
limit : Infinity,
model : model,
fetch : ['Name','ObjectID']
}).load({
callback : function(records, operation, success) {
if (operation.wasSuccessful()) {
deferred.resolve(records);
} else {
deferred.reject();
}
}
});
return deferred.promise;
}
Hope this helps!

backbone: trying to set error message on "this" in failed ajax request

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

Query Database with Backbone Collection

I need to query the database using a backbone collection. I have no idea how to do this. I assume that I need to set a url somewhere, but I don't know where that is. I apologize that this must be a very basic question, but I took a backbone course on CodeSchool.com and I still don't know where to begin.
This is the code that I have for the collection:
var NewCollection = Backbone.Collection.extend({
//INITIALIZE
initialize: function(){
_.bindAll(this);
// Bind global events
global_event_hub.bind('refresh_collection', this.on_request_refresh_collection);
}
// On refresh collection event
on_request_refresh_collection: function(query_args){
// This is where I am lost. I do not know how to take the "query_args"
// and use them to query the server and refresh the collection <------
}
})
The simple answer is you would define a URL property or function to your Backbone.Collection like so:
initialize: function() {
// Code
},
on_request_refresh_collection: function() {
// Code
},
url: 'myURL/whateverItIs'
OR
url: function() {
return 'moreComplex/' + whateverID + '/orWhatever/' + youWant;
}
After your URL function is defined all you would have to do is run a fetch() on that collection instance and it will use whatever you set your URL to.
EDIT ------- Making Collection Queries
So once you set the URL you can easily make queries using the native fetch() method.
fetch() takes an option called data:{} where you can send to the server your query arguments like so:
userCollection.fetch({
data: {
queryTerms: arrayOfTerms[], // Or whatever you want to send
page: userCollection.page, // Pagination data
length: userCollection.length // How many per page data
// The above are all just examples. You can make up your own data.properties
},
success: function() {
},
error: function() {
}
});
Then on your sever end you'd just want to make sure to get the parameters of your request and voila.

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

Extjs Load panel content from ajax

I have an Ext.Panel with a listener set to 'afterrender'. The callback function is a small ajax code which checks an url, grabs it's contents and add it to the panel. Problem is, the content does not get insterted. If I use the same insert code right above the ajax call, it works. Here's my callback function:
Not working:
function afterrenderCallback () {
// This does not work
var logPanel = Ext.getCmp('aP_ServerLogs');
Ext.Ajax.request({
url: AP_ROOT_URL + '/index.php?r=server/logs',
success: function (r) {
logPanel.add({
html: 'dummy html i don\'t care about the response'
});
}
});
}
Working:
function afterrenderCallback () {
// This does work
var logPanel = Ext.getCmp('aP_ServerLogs');
logPanel.add({
html: 'dummy html i don\'t care about the response'
});
}
You might need to call doLayout() on the panel. However check out Ext.Updater:
http://dev.sencha.com/deploy/dev/docs/?class=Ext.Updater
Panels have this automatically such as:
var panel = new Ext.Panel({
});
panel.body.load(...);
panel.body.update(...);
I'd suspect the callback isn't getting called. You could add a failure case with a simple alert call to check it's not going down that path.
However probably better, similar to what #Lloyd said, you should look at the autoLoad config property.
The autoLoad config is what you want, as mentioned. I wanted to add that doing a logPanel.add({...}) just to insert markup is not appropriate, even though it "works". There is no reason to nest a panel within a panel for this. If you are loading HTML content like this you'd preferably do logPanel.body.update('content');.
As #bmoeskau says, the autoLoad config is what we need. It took me quite a while to find the correct syntax though. So here is an example on how to define such a panel with ajax content:
Ext.define('MyApp.view.aP_ServerLogs', {
extend : 'Ext.panel.Panel',
id: 'aP_ServerLogs',
loader: {
url: AP_ROOT_URL + '/index.php?r=server/logs',
autoLoad: true
}
});

Resources