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.
Related
I'm very new to Sencha Architect 3.0, I want to set a MyApp.globals variable(initiated at launch method) as the parameter of my store. Like so:
Ext.define('MyApp.store.myCustomer', {
extend: 'Ext.data.Store',
requires: [
'MyApp.model.Customer',
'Ext.data.proxy.Ajax',
'Ext.data.reader.Json'
],
config: {
model: 'MyApp.model.Customer',
storeId: 'myCustomer',
proxy: {
type: 'ajax',
//global variable as parameter
extraParams: {
salesman: MyApp.globals.Salesman
},
url: 'http://localhost:96/OrdApplication/customer_list.php',
reader: {
type: 'json',
rootProperty: 'items'
}
}
}
});
This is my launch method
launch: function() {
MyApp.globals = {
count: 1,
Salesman :0
};
Ext.create('MyApp.view.MainView', {fullscreen: true});
}
});
Although this store is not bound to load with anything on launch,neither with the first view, but its still seems to be loading as i'm getting the following error on launch:
Uncaught TypeError: Cannot read property 'Salesman' of undefined
sencha-touch.js:8619 Uncaught Error: The following classes are not declared even if their files have been loaded: 'MyApp.store.myCustomer'.
how do I handle this issue of not having the store loaded before the declaration of variable takes place. In fact it would be great if someone could tell me to load a store dynamically on button press and fill selectfield with it.
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.
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.
I'm using Extjs 4.1 MVC, I have a simple store :
Ext.define('Proj.store.GraphData', {
extend: 'Ext.data.Store',
model: 'Proj.model.GraphData',
autoLoad: false,
proxy: {
type: "ajax",
reader: {
type: 'json',
root: 'data'
}
}});
I want to handle its update event from the controller, so this is the controller :
Ext.define('Proj.controller.RenderGraph', {
extend: 'Ext.app.Controller',
stores: ['GraphData'],
models : ['GraphData'],
init: function () {
var me = this;
me.getGraphDataStore().addListener('update',this.onStoreUpdate, this);
this.control({
....
})
},
onStoreUpdate : function () {
alert('OKK');
}
But when I update the store, it doesn't show anything, what am I doing wrong please?
The first thing would be to use the full path name of your store when you are defining it in the controller
...
stores: ['Proj.store.GraphData'],
...
Also, I think listener you are looking for would be load. According to the docs, update fires when the model instance has been updated. load fires whenever the store reads data from a remote data source.
http://docs.sencha.com/extjs/4.2.0/#!/api/Ext.data.Store-event-load
me.getGraphDataStore().addListener('load',this.onStoreUpdate, 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.