PDA

View Full Version : Static root nodes in TreePanel, then dynamic children below



bjnelson62
12 Sep 2012, 12:31 PM
I'm new to Sencha ExtJs and Architect, but evaluating them for our project.


I've looked at a lot of examples, and done a lot of searches, but haven't seen anything quite like what I'm trying to do.


I've created an application with a viewport, and a tree inside to the left and a grid to the right. The idea is as users click the plus signs for the nodes, they can explore the depth-wise. If they click on nodes with details, those will show up to the right.


I've created the initial nodes of the tree in the TreeStore's root config. The TreeStore root config looks like:



root: {
text: 'Root',
expanded: true,
children: [
{
id: 'https://localhost:8443/product1/ (https://localhost:8443/product1/)',
text: 'Product1',
nodeType: 'async',
leaf: false
},
{
id: 'https://localhost:8443/product2/ (https://localhost:8443/product2/)',
text: 'Product2',
nodeType: 'async',
leaf: false
},
{
id: 'https://localhost:8443/product3/ (https://localhost:8443/product3/)',
text: 'Product3',
nodeType: 'async',
leaf: false
},


{
id: 'https://localhost:8443/product4/ (https://localhost:8443/product4/)',
text: 'Product4',
nodeType: 'async',
leaf: false
}
]
},


This is all working, but when I click a plus sign, the plus sign disappears and nothing else happens. Clearly, it's not hitting the server (I've looked at the Network tab in the debugger, and confirmed that). I'm sure I don't have something set up correctly, I'm just not sure what.


The TreePanel is pointing to the store that contains the initial nodes. At what point, and how, could I redirect the tree to use the other store that knows how to query the server for the next level of the tree?


Is there a different/better way to set up the initial nodes to the tree? I made the "id" contain the URL for where the server API's are for each next level of the tree. I'd hoped the tree would find that and use it automatically, but it obviously doesn't.


If the user clicks on Product1, I'd like Feature1-FeatureN to show up in the tree, and so on.


The server if queried properly will return the following:



{ "links" :
[
{
"href" : "https://localhost:8443/product1/feature1/ (https://localhost:8443/product1/feature1/)",
"rel" : "feature1",
"type" : "application/json; charset=utf-8"
},
{
"href" : "https://localhost:8443/product1/feature2/ (https://localhost:8443/product1/feature2/)",
"rel" : "feature2",
"type" : "application/json; charset=utf-8"
},
{
"href" : "https://localhost:8443/product1/feature3/ (https://localhost:8443/product1/feature3/)",
"rel" : "feature3",
"type" : "application/json; charset=utf-8"
}
] }



Which, as you can see, is nothing like how the TreeLoader/TreePanel want the data. Ideally, I'd like to process the response from the server, and massage it so it's in the desired form. I have no idea how to do that either. Any ideas welcome.


I've coded a Controller action that listens to the TreePanel's onTreepanelBeforeItemExpand; but from here I'm not sure how to change things so that the next level of the tree can be fetched.


Those are the two problems I'm having at the moment. How to set up the initial nodes properly, and how to fetch the next level of the tree.


Conceptually, what I want to do is pretty simple: have some static nodes defined at the top, which when clicked will know how to fetch the next level of the tree from the server.


FYI, I also tried creating the root nodes in the TreePanel's root config (using the same code as above); they look fine, but when the plus sign is clicked nothing happens (the plus doesn't even disappear). I added an event listener for afterrender (for the TreePanel), and at that point I set the TreeStore (which is initially null) to the tree store that knows how to query the server. Still doesn't work. The tree store in question loads properly in Architect, so I think I've set it up properly.


Clearly, I'm lost at sea without a map. I could sure use some guidance as to how to go about doing this properly.


Thanks very much,




Brian

vietits
12 Sep 2012, 5:07 PM
What does your store definition look like?

bjnelson62
13 Sep 2012, 4:51 AM
Here's the full TreeStore definition (where the root nodes are defined in the TreeStore directly):



Ext.define('MyApp.store.treeRootStore', {
extend: 'Ext.data.TreeStore',


constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([Ext.apply({
storeId: 'treeRootStore',
root: {
text: 'Root',
expanded: true,
children: [
{
id: 'https://localhost:8443/product1/',
text: 'product1',
nodeType: 'async',
leaf: false
},
{
id: 'https://localhost:8443/product2/',
text: 'product2',
nodeType: 'async',
leaf: false
},
{
id: 'https://localhost:8443/product3',
text: 'product3',
nodeType: 'async',
leaf: false
},
{
id: 'https://localhost:8443/product4/',
text: 'product4',
nodeType: 'async',
leaf: false
}
]
},
fields: [
{
name: 'id'
},
{
name: 'text'
},
{
name: 'nodeType'
},
{
name: 'leaf'
}
]
}, cfg)]);
}
});

vietits
13 Sep 2012, 5:39 AM
It seems that you did not define a server proxy for your store. If this is the case, default proxy used for store will be 'memory' and there will be no request is sent to server to load data when you open a folder node. When you define a server proxy such as 'ajax', then each time user click a folder node that is not expanded, a request will be sent to your server side script with parameter node=NODE_ID where NODE_ID is the id of the node.

bjnelson62
13 Sep 2012, 7:18 AM
I did, but due to some weirdness it didn't show up. Here's the TreeStore definition:



Ext.define('MyApp.store.treeRootStore', {
extend: 'Ext.data.TreeStore',


requires: [
'MyApp.model.treeReplyModel'
],


constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([Ext.apply({
storeId: 'treeRootStore',
model: 'MyApp.model.treeReplyModel',
root: {
text: 'Root',
expanded: true,
children: [
{
id: 'https://localhost:8443/product1/',
text: 'product1',
nodeType: 'async',
leaf: false
},
{
id: 'https://localhost:8443/product2',
text: 'product2',
nodeType: 'async',
leaf: false
},
{
id: 'https://localhost:8443/product3',
text: 'product3',
nodeType: 'async',
leaf: false
},


{
id: 'https://localhost:8443/product4',
text: 'product4',
nodeType: 'async',
leaf: false
}
]
},
fields: [
{
name: 'id'
},
{
name: 'text'
},
{
name: 'nodeType'
},
{
name: 'leaf'
}
]
}, cfg)]);
}
});


And the code for the model:



Ext.define('MyApp.model.treeReplyModel', {
extend: 'Ext.data.Model',


fields: [
{
name: 'text',
mapping: 'rel'
},
{
name: 'href'
},
{
name: 'type'
}
],


proxy: {
type: 'rest',
noCache: false,
url: 'https://localhost:8443/',
reader: {
type: 'json',
root: 'links'
}
}
});


It's worth mentioning our server is a RESTful server, as shown by the proxy on the model.

However, with this configuration I'm not able to load the store in Architect (it says "Unable to load data using the supplied configuration.\nOpen in browser https://localhost:8443/"), and when doing a Preview nothing shows up in the tree at all.

I guess one way would be to define an id for the (hidden) root like: "https://localhost:8443/", but then I can't load the store.

As you can see, I'm not sure how/where to define the static nodes in such a way that when the user clicks the plus sign, a server call is made.

vietits
13 Sep 2012, 4:35 PM
Basing on your code, I have created a test case and its works fine. The code below has some modifications:
- Move fields definition from store to model definition: If you config both fields and model for store, fields definition will be ignored.
- Basing on your data, root config for proxy reader should be 'children', not 'link'.


Ext.onReady(function(){
Ext.define('MyApp.model.treeReplyModel', {
extend: 'Ext.data.Model',
// fields: [{ <- fields definition does not match given data
// name: 'text',
// mapping: 'rel'
// },{
// name: 'href'
// },{
// name: 'type'
// }],
fields: [{
name: 'id'
}, {
name: 'text'
}, {
name: 'nodeType'
}, {
name: 'leaf'
}],
proxy: {
type: 'rest',
noCache: false,
url: 'https://localhost:8443/',
reader: {
type: 'json',
root: 'children' // 'link'
}
}
});


Ext.define('MyApp.store.treeRootStore', {
extend: 'Ext.data.TreeStore',
// requires: [
// 'MyApp.model.treeReplyModel'
// ],
constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([ Ext.apply({
storeId: 'treeRootStore',
model: 'MyApp.model.treeReplyModel',
root: {
text: 'Root',
expanded: true,
children: [{ // <- this must match root config of proxy reader
id: 'https://localhost:8443/product1/',
text: 'product1',
nodeType: 'async',
leaf: false
},{
id: 'https://localhost:8443/product2',
text: 'product2',
nodeType: 'async',
leaf: false
},{
id: 'https://localhost:8443/product3',
text: 'product3',
nodeType: 'async',
leaf: false
},{
id: 'https://localhost:8443/product4',
text: 'product4',
nodeType: 'async',
leaf: false
}]
},
// fields: [{ <- Move to model definition
// name: 'id'
// }, {
// name: 'text'
// }, {
// name: 'nodeType'
// }, {
// name: 'leaf'
// }]
}, cfg) ]);
}
});


var store = Ext.create('MyApp.store.treeRootStore');

Ext.create('Ext.tree.Panel', {
renderTo: Ext.getBody(),
width: 400,
height: 400,
store: store
});
});

bjnelson62
14 Sep 2012, 10:19 AM
Thank you!

That cleared up some things I was obviously confused about.

I've modified your code to more closely represent what I'm looking for:



Ext.onReady(function(){
Ext.define('MyApp.model.treeReplyModel', {
extend: 'Ext.data.Model',
fields: [{
name: 'text',
mapping: 'rel'
}, {
name: 'id',
mapping: 'href'
}],
proxy: {
type: 'rest',
noCache: false,
url: 'https://localhost:8443/',
reader: {
type: 'json',
root: 'links'
}
}
});




Ext.define('MyApp.store.treeRootStore', {
extend: 'Ext.data.TreeStore',
constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([ Ext.apply({
storeId: 'treeRootStore',
model: 'MyApp.model.treeReplyModel',
root: {
text: 'Root',
expanded: true,
links: [{ // <- this must match root config of proxy reader
href: 'product1',
rel: 'Product1'
},{
href: 'product2',
rel: 'Product2'
},{
href: 'product3',
rel: 'Product3'
},{
href: 'product4',
rel: 'Product4'
}]
},
}, cfg) ]);
}
});




var store = Ext.create('MyApp.store.treeRootStore');


Ext.create('Ext.tree.Panel', {
renderTo: Ext.getBody(),
width: 400,
height: 400,
store: store,
rootVisible: false
});
});

I see the outgoing request also has "?node=...", but fortunately our server doesn't seem to notice that. Whew! I believe this is a bug in the ExtJs framework, as I found a reference to it on this site.

However, I still see 2 problems:

1. Because of the href value coming back from the server, all the nodes under the 4 root nodes are shown as links.

2. Worse, the server returns a full URL for the href value; so for the nodes below, the resulting URLs that are sent to the server are wrong:

https://localhost:8443/product1/https://localhost:8443/product1/feature1

A solution I can think of to solve both problems would be to take the name of the node, say 'feature1', and stuff it into the href value of the node. That way it's no longer a link, and URL duplication is solved too.

It's too bad I can't designate a given field, 'rel' as both "text" and "id". In this case, it is both.

What would be a good place to make a change like this? Or do you know a better way?

Unfortunately, changes to the server are not within my power.

Thanks again,


Brian

vietits
14 Sep 2012, 4:41 PM
Do you mean you want 'rel' field to play a role of 'id' and 'text' fields? If so, you just modify your code as below:


Ext.onReady(function(){
Ext.define('MyApp.model.treeReplyModel', {
extend: 'Ext.data.Model',
// fields: [{
// name: 'text',
// mapping: 'rel'
// }, {
// name: 'id',
// mapping: 'href'
// }],
idProperty: 'rel',
fields: ['rel'],
proxy: {
type: 'rest',
noCache: false,
url: 'https://localhost:8443/',
reader: {
type: 'json',
root: 'links'
}
}
});


Ext.define('MyApp.store.treeRootStore', {
extend: 'Ext.data.TreeStore',
constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([ Ext.apply({
storeId: 'treeRootStore',
model: 'MyApp.model.treeReplyModel',
root: {
text: 'Root',
expanded: true,
links: [{ // <- this must match root config of proxy reader
href: 'product1',
rel: 'Product1'
},{
href: 'product2',
rel: 'Product2'
},{
href: 'product3',
rel: 'Product3'
},{
href: 'product4',
rel: 'Product4'
}]
},
}, cfg) ]);
}
});


var store = Ext.create('MyApp.store.treeRootStore');


Ext.create('Ext.tree.Panel', {
renderTo: Ext.getBody(),
width: 400,
height: 400,
store: store,
rootVisible: false,
columns: [{
xtype: 'treecolumn',
flex: 1,
dataIndex: 'rel'
}]
});
});

bjnelson62
21 Sep 2012, 6:39 AM
Thank you, I appreciate your time in learning this.