I have a search bar docked onto my tree panel. When I write something and press enter an Ajax request fires off and returns the folder ids required to expand the tree up to the point of the folder requested. Inside the success config of the ajax.request I call the expand function of each node via getNodeById using a loop. However after the first expansion ExtJS fires itself an ajax request from the proxy to fetch the folder data (since it hasn't been loaded yet). Since AJAX is asynchronous the loop is faster than the server response and it tries to call the .expand() function of the node before the node itself has been loaded and gives an undefined error. How should I tackle this? I know that generally with AJAX you have to use callback functions for everything you want to run AFTER the request has been processed but I'm not really sure how to do this in this case...
Ext.define('treeStore',
{
extend : 'Ext.data.TreeStore',
alias: 'widget.treeStore',
autoLoad : false,
model : 'treeModel',
root : {
id: 0,
name : 'Root',
expanded : true,
loaded: true
},
proxy : {
type : 'ajax',
url : 'MyServlet',
reader : {
type : 'json',
root : 'children'
}
},
folderSort: true
});
Ext.define('Ext.tree.Panel',{
.
.
.
//Stuff about the tree panel etc.
dockedItems: {
xtype: 'textfield',
name: 'Search',
allowBlank: true,
enableKeys: true,
listeners: {
specialkey: function (txtField, e) {
if (e.getKey() == e.ENTER){
var searchValue = txtField.getValue();
Ext.Ajax.request({
url: 'MyServlet',
params: {
caseType: 'search',
value: searchValue
},
success: function(response) { //ATTENTION: When calling the .expand() the AJAX hasn't finished and cannot find the node.
response = Ext.decode(response.responseText);
var panel = txtField.up();
response.IDs.forEach(function(entry){
panel.getStore().getNodeById(entry.folderId).expand(); <-problem here
});
}
});
}
}
}
}
Often when writing my Ext apps, I find that even though things should fire in order, there are times where calling things immediately after an AJAX call is sometimes just too fast. Maybe the tree nodes have not rendered properly yet, or some other issue.
Try wrapping a delayed task around that code:
new Ext.util.DelayedTask(function()
{
response = Ext.decode(response.responseText);
var panel = txtField.up();
response.IDs.forEach(function(entry){
panel.getStore().getNodeById(entry.folderId).expand(); <-problem here
});
}, this).delay(100);
You'll have to work around those scope issues though. The "this" just before the delay function is your scope.
Related
Good day.
I am writing a tree control with Ext.tree.Panel, which loads its configuration from external .json file, while data is being loaded as .json from the server.
So, I've got the view like this:
Ext.define('App.tree.TreeView', {
extend: 'Ext.tree.Panel',
...
initFilters: function(cfg) {
/*several this.store.addFilter() based on cfg*/
},
initComponent: function () {
var me = this;
me.cfg = getSomeCfgFromSomewhere();
me.initFilters(me.cfg);
}
});
The store:
Ext.define('Site.widgets.tree.TreeStore', {
extend: 'Ext.data.TreeStore',
...
proxy: {
type: App.cfg.proxy,
reader: { type: 'json', rootProperty: 'data.children' },
format: App.cfg.proxy.urlEnd,
url: App.cfg.treeRoot,
noCache: false
}
lazyFill: true,
rootVisible: true,
root: {
full_path: '/', /*set as idProperty in model*/
'display-name': 'root',
expanded: true
},
});
The problem is: my root is explicitly set in the store, which loads its contents because of expanded: true. But then only these contents are shown in the tree, without the root itself. When filtering is removed, the tre is loaded OK.
Possible reason: from debugging of Sencha code in ext-all-debug.js: view's initComponent is called before the store starts loading the data, therefore it adds filters before the data is loaded, when the root is still empty, and then calls
onNodeFilter: function(root, childNodes) {
var me = this,
data = me.getData(),
toAdd = [];
if (me.getRootVisible()) {
if (childNodes.length) {
toAdd.push(root);
} else {
root.set('visible', false, me._silentOptions);
}
}
...
}
, i.e. the root is set invisible because it is still empty, and only then root's children are loaded.
The question is: is there an initial design mistake in my filters initialisation, and how can I fix it?
Considering that the problem is that view's initComponent is called before the store starts loading the data, I would try:
Calling initFilters inside store load event, like this:
Ext.define('Site.widgets.tree.TreeStore', {
extend: 'Ext.data.TreeStore',
...
proxy: {
type: App.cfg.proxy,
reader: { type: 'json', rootProperty: 'data.children' },
format: App.cfg.proxy.urlEnd,
url: App.cfg.treeRoot,
noCache: false
}
lazyFill: true,
rootVisible: true,
root: {
full_path: '/', /*set as idProperty in model*/
'display-name': 'root',
expanded: true
},
listeners : {
load : function() {
//Call initFilters.
//initFilters should call addFilter just once,
//with array of filters as first parameter,
//to avoid filtering the hole tree more than once!
}
}
});
This approach has a problem, because you have to deal with initFilters cfg parameter scope.
Ok, turned out that part of the answer was just inside the question: I was able to fix this thing by simply overloading onNodeFilter() method inside my store, since I know that I want to always show the root. If anyone could advise anything better - be my guest, please.
I'm upgrading to ExtJS 5 and can't solve this issue. I've got a function that manages everything after the login and loads store after login with the new parameters from login.
...
Ext.create('MyApp.store.MainStore').load({
params: {
auth: sessionStorage.authSessionId,
hostname: sessionStorage.hostname
},
callback: function(records, operation, success) {
//some callback
}
})
...
However this loads the store, but without parameters, which causes an error on server side.
My store definition:
Ext.define('MyApp.store.MainStore', {
extend: 'Ext.data.TreeStore',
storeId: 'MainStore',
autoSync: false,
autoLoad: false,
proxy : {
type : 'ajax',
url : '/menu',
reader: {
type: 'json',
rootProperty: 'children',
},
listeners : {
exception : function(proxy, response, operation) {
//exception handling
}
}
},
fields: ['labelText','dbName','corpName','isLeaf', 'page']
});
Any suggestions?
Thanks for any help.
Try declaring a root node on your store definition.
Ext.define('MyApp.store.MainStore', {
extend: 'Ext.data.TreeStore',
storeId: 'MainStore',
autoSync: false,
autoLoad: false,
proxy : {
type : 'ajax',
url : '/menu',
reader: {
type: 'json',
rootProperty: 'children',
},
listeners : {
exception : function(proxy, response, operation) {
//exception handling
}
}
},
fields: ['labelText','dbName','corpName','isLeaf', 'page'],
root: {}
});
It's a bug. If you call load on an empty TreeStore, it will load just fine, but it won't pay attention to any options you passed in, such as the parameters.
Setting a root node first allows the load to work - but then, as you've seen, you can't use it in a TreePanel (and why else would you have a TreeStore). Kind of silly, huh?
I reported this to Sencha - http://www.sencha.com/forum/showthread.php?288818-5.0.0.970-TreeStore.load()-doesn-t-call-callback-if-there-is-no-root-node.
As for workarounds:
you can provide the parameters by using the params configuration on the proxy. Annoying, but it does work. (Actually, extraParams might be the better choice, as it allows you to preserve the authentication token and change the main parameters if and when you use reload).
you can get the callback by using event handling.
BTW, because you're using autoLoad: false, you'll need to load the store at some point. Do that before you add it to the TreePanel - the only way to get a root node that works with the TreePanel seems to be to get the TreePanel to make it for you.
Im working on a project with Sencha Touch and the sqLite proxy you can find here. This website is in a phonegap environment, but it's not used when i run it from the browser.
I have this WorkShift model which is used by WorkShifts store.
Model:
Ext.define('KCS.model.WorkShift', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'id', type: 'int' },
{ name: 'WorkShiftID', type: 'int' },
{ name: 'StartDate', type: 'date' },
{ name: 'ClosureDate', type: 'date' },
],
proxy: {
type: 'sqlitestorage',
dbConfig: {
tablename: 'tbl_WorkShift',
dbConn: KCS.util.InitSQLite.getConnection()
}
}
}
});
And the Store:
Ext.define('KCS.store.WorkShifts', {
extend: 'Ext.data.Store',
requires: ['KCS.model.WorkShift'],
config: {
model: 'KCS.model.WorkShift',
autoLoad: true,
storeId: 'WorkShifts',
pageSize: 1000
}
});
Now, in my Controller, i want to see if there is an opened WorkShift (if the app crashed or was closed without closing the last Workshift.) So i use the launch callback like this:
launch : function(){
var workShifts = Ext.getStore('WorkShifts');
workShifts.clearFilter(true);
var openedWS = workShifts.findBy( function( record ){
return (record.get("StartDate") != null) &&
(record.get("ClosureDate") == null);
});
if( openedWS != -1 ){
// do stuff when an opened WS is found
}
else{
// do normal stuff
}
},
I did a bunch of tests, First, there is a bunch of valid entries in sqLite, and i can create WS from the store and model. There is also an entry that meets the findBy bool test. I've tried workShifts.getCount() and even workShifts.getAllCount() but both functions return 0. What have i done wrong?
EDIT:
I have also searched for things like the launch func running before the store can load data from the proxy or even cordova not firing the deviceReady callback. I have tried to apply a filter on the store and check with getFirst() if one survived to the test, but i think there is not even a single record to test in the first place even tho they show up in the SqLite overview from the Resources tab (in chrome webTools).
I used the manual load function of the store and used the callback to do what i needed. The autoload parameter in the store creation is also asynchronous. That was my problem.
http://docs.sencha.com/touch/2.2.1/#!/api/Ext.data.Store-method-load
like so:
launch : function(){
var workShifts = Ext.getStore('WorkShifts');
workShifts.load({
callback: function(records, operation, success) {
workShifts.clearFilter(true);
var openedWS = workShifts.findBy( function( record ){
return (record.get("StartDate") != null) &&
(record.get("ClosureDate") == null);
});
if( openedWS != -1 ){
// do stuff when an opened WS is found
}
else{
// do normal stuff
}
},
scope: this
});
},
I am building Sencha touch 2.1 application.
In my application I have one global variable which keeps reference of my controller class. This reference is used to execute a controller function on load of one of the store but the problem comes when I deploy this on slow remote server and store's load is fired before this global variable gets reference of controller object. To provide this reference before store's load I tried putting initialization logic in controllers init method
init : function(){
bossController = this.getApplication().getController('Boss');
},
in init method of controller but view & store are loaded before this init is called and hence I get this error:
Cannot call method 'loadMagazines' of undefined
This is my on load listener in store:
listeners:{
load: function( me, records, successful, operation, eOpts ){
bossController.loadMagazines(this, records);
}
}
I tried initializing this variable in app.js launch() method instead of controller's init method but that also didn't work.
Please note both approaches works fine when I put my code in local apache and access it using my chrome browser but they doesn't work when I put it on slow remote server.
EDIT
This is how application flow happens
0. All the models, views, stores & controllers are defined in app.js
Launch function in app.js add main view to the viewport.
Main view creates magazine view and add it to itself.
In initialize method of magazine view, store is crated and loaded.
In load listener of store, controller is used.
This is my view:
Ext.define('myshop.view.MagazinePanel', {
extend : 'Ext.Panel',
requires : [
'myshop.model.MagazinePage',
'myshop.store.MagazineStore',
'Ext.XTemplate',
'Ext.data.Store'
],
alias : 'widget.magazinepanelview',
xtype : 'magazinepanelview',
config : {
layout : 'hbox',
id : 'hc',
scrollable: 'horizontal',
directionLock : true,
masked: {
xtype: 'loadmask',
message: 'Loading'
},
inline: {
wrap: false
},
items : [{
xtype : 'panel',
html : ''
}]
},
initialize: function() {
var me = this;
var myStore = Ext.create('myshop.store.MagazineStore');
myStore.load({
callback: function(records, operation, success) {
me.setMasked(false);
},
scope: this
});
this.callParent();
}
});
and this is the store:
Ext.define('myshop.store.MagazineStore',{
extend:'Ext.data.Store',
requires: [
'myshop.model.MagazinePage',
'Ext.data.proxy.JsonP'
],
config:{
storeId : 'ms',
model:'myshop.model.MagazinePage',
autoLoad :false,
pageSize: 30,
proxy : {
type: 'jsonp',
url: Properties.PORTAL_SERVICE_BASE_URL+'test/categories/list',
callbackKey: 'callback',
startParam: false, //to remove param "start" and making it constant
extraParams: {
start : 0,
_type : 'json'
},
reader: {
type: 'json',
rootProperty: 'categories.data',
totalProperty: 'categories.status.totalCount'
}
},
listeners:{
load: function( me, records, successful, operation, eOpts ){
bossController.loadMagazines(this, records);
}
}
}
});
There are some parts missing in your example code so just a hint/guess. I guess your store has autoLoad property to true and get's therefore loaded as soon as it gets initialized. Turn it of and try something like this.
init : function(){
bossController = this.getApplication().getController('Boss'); // bossController where is this var defined?
// fire a ready event
// or
Ext.StoreMgr.lookup('YourStoreName').load();
}
Or provide more information about the store, who loads it, when it is loaded and in which scope.
Loading data should do controller but not view.
Ext.define("myshop.controller.Boss", {
extend: 'Ext.app.Controller',
config: {
refs: {
magazinePanel: '#hc'
},
control: {
magazinePanel: {
initialize: 'initializeMagazinePanel'
}
}
},
initializeMagazinePanel: function() {
var me = this;
var myStore = Ext.create('myshop.store.MagazineStore', {
listeners:{
scope: this,
load: this.onMagazineStoreLoad
}
});
myStore.load();
},
onMagazineStoreLoad: function(me, records, successful, operation, eOpts) {
this.getMagazinePanel().setMasked(false);
this.loadMagazines(records)
},
loadMagazines: function(records) {
}
});
Couldn't you put your loadMagazines function in the success callback of an explicit load() function in your controller, and disable autoLoad as #sra suggests?
Something like:
//in controller
init: function() {
myStore.load({
callback: function(recs, op, success) {
// do the load magazines thing
}
})
}
I am curious if what I suggested here: getController() doesn't load file and doesn't fire init and lauch helps with this too. You can remove the view and store from app.js and put it in the controller itself. I think that should make the view and store be defined only after the controller is defined.
In my javascript code, I keep getting the following error:
Uncaught TypeError: Cannot call method 'request' of undefined
My Javascript is below. Any assistance would be greatly appreciated!
myJsonStore = {
store1: new Ext.data.JsonStore({
root: 'rootstore1',
fields: ['UserID', 'UserName']
})
};
//------My panel------
items: [{
xtype: 'combo',
id: 'UName',
fieldLabel: 'User',
emptyText: 'All',
store: myJsonStore.store1,
displayField: 'UserName',
valueField: 'UserID'
}]
//--------------------
Ext.Ajax.request({
url: "rPages/rLogMatchOdds.aspx",
params: {
m: 'init'
},
success: function(response) {
var data = Ext.decode(response.responseText);
myJsonStore.store1.loadData(data);
}
});
Ext.getCmp('UName').store.on('load', function(my, rec) {
Ext.getCmp('UName').setValue(rec[0].get('UserName'));
}, this);
Usually, when the error is of the form Cannot call method 'X' of undefined, it means that whatever object you are attempting to call X from does not exist.
In your case, it appears as though Ext.Ajax is undefined. The easiest way to resolve this involves two simple steps:
Make sure that you've included the javascript file that creates Ext.Ajax. If you're using the ext-all.js file, then you shouldn't have to worry about this.
Make sure that none of your code executes until the browser is ready. The best way to do this is to wrap all of your code within a
Ext.onReady() call. I've provided an example below.
Ext.onReady( function() { //your code goes here });
You can see more examples of this at the ExtJS Examples page.
Got bitten by this problem too.
The solution is for you to call Ext.require('Ext.Ajax') before Ext.onReady like so:
Ext.require('Ext.Ajax');
Ext.onReady(function() {
Ext.Ajax.request({
// your code here...
});