PDA

View Full Version : Tree and root node



Araberen
14 Dec 2011, 1:44 AM
Hi,

It's been hours that I'm trying to solve this problem, but I can't find a viable solution.

Here is the minimum code, simple:

Ext.ModelManager.getModel('App.model.Menu').load(1, // Download the root menu
success: function(menu) {
alert(menu.get('name')); // display "Home"

// Now we create the tree:
var store = Ext.create('App.store.Menus', {
root: menu
});
var tree = Ext.create('Ext.tree.Panel', {
hideHeaders: false,
rootVisible: true,
store: store,
columns: [{
xtype: 'treecolumn',
flex:1,
text:'Name of the menu',
sortable:false,
dataIndex:'name'
}]
});
// + code to add the tree to a container
}
}

The problem is that it creates a fake empty root node with one child which is my root node.
So my tree is like:
- ""
| - "Home"

So I could set rootVisible to false and the problem could be solved and indeed it is for that part, but still, I don't understand why it does that. In the doc of the TreeStore is written:
root : Ext.data.Model/Ext.data.NodeInterface/Object

The real problem behind this is that my model uses associations that are not accessible anymore for the fake second root node (and that doesn't exist for the fake autocreated first root node).
So setting rootVisible to false is just a trick and doesn't solve the background problem... that I'm not gonna detail here because I think if there is a solution to avoid the fake root node, it will solve the rest.

Any idea?

mankz
14 Dec 2011, 11:55 AM
What data is loaded?

Araberen
14 Dec 2011, 12:37 PM
{success:true, data:{id:1, name:'Home',children:[{id:2, name:'First child},{id:3, name:'Second child']}}

And of course, I have precised the data root node in the proxy reader:


proxy:
// ...
reader: { type:"json", root:"data" }


My point is that there is a distance between what's written in the doc (it says that the root config accept a model object, a record), so it should work with a model matching the model used for the treestore, right?

PS: I also changed the nodeParam of the treestore to use the "id" of my data.

mankz
14 Dec 2011, 12:42 PM
'root' on a reader doesn't work as you expect when it comes to TreeStore. the reason for the empty node is the wrapper including 'success'. Try _only_ returning the tree nodes, no message.

Araberen
14 Dec 2011, 3:44 PM
It doesn't change a thing.
I've tried what you say. So, and just for the root node, the JSON (fetched via a REST proxy) is:

{id:1, name:'Home', /* and more */ }
I've checked the requests. The good JSON is downloaded and data is presented as I just wrote.

In addition, I've compared the JavaScript objects for the downloaded root node for both JSON models and they are exactly the same:
- a data and a raw fields as children of the object given by the success function of the load function (of the model).
- no direct data (name, id) as direct children of that object
And of course, if I let the "root" config in the proxy's reader to "data", the given object is undefined because the data field in the given object is just a dynamic construction made by Ext.

mankz
14 Dec 2011, 9:13 PM
Response should still be an array...

Araberen
14 Dec 2011, 9:28 PM
{id:1, name:'Home', /* and more */ }
or

[{id:1, name:'Home', /* and more */ }]
as a response for the root node
=> same problem/result
=> creates the fake empty root node (and my root node is its first child)

I've tried to remove all the root config option in my model and store proxies, no difference...

:s

mankz
14 Dec 2011, 9:38 PM
Post a full testcase?

Araberen
14 Dec 2011, 10:52 PM
Here is the sample, just copy it in an empty HTML file and open it.

In this code, the object is directly created with Ext.create.


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Tree Grid test</title>
<link rel="stylesheet" type="text/css" href="http://dev.sencha.com/deploy/ext-4.0.7-gpl/resources/css/ext-all.css" />
<script type="text/javascript" src="http://dev.sencha.com/deploy/ext-4.0.7-gpl/bootstrap.js"></script>
<!-- <script type="text/javascript" src="treeroot.js"></script>-->
</head>
<body>
<h1>Tree Grid test</h1>
<div id="tree-grid"></div>
<script type="text/javascript">

Ext.onReady(function(){

Ext.define('Lang', {
extend: 'Ext.data.Model',
fields: [
{ name:'lang', type:'string' },
{ name:'title', type:'string' }
]
});

Ext.define('Menu', {
extend: 'Ext.data.Model',
fields: [
{ name:'id' , type:'int' },
{ name:'name', type: 'string'}
],
associations: [
{ type: 'hasMany', name:'children', model:'Menu' },
{ type: 'hasMany', name:'langs' , model:'Lang' }
]
});

Ext.define('MenuStore', {
extend: 'Ext.data.TreeStore',
model: 'Menu',
nodeParam: 'id',
proxy: {
type: 'memory'
}
});

var rootNode = Ext.create('Menu', {
id:1,
name:'Home',
children:[
{
id:2,
name:'First child',
children: [ ],
langs: [
{ lang:'fr', title:'Titre de page' },
{ lang:'en', title:'Page\'s title' }
]
}
],
langs: [
{ lang:'fr', title:'Accueil' },
{ lang:'en', title:'Home' }
]
});

console.log(rootNode.langs()); // OK
// console.log(rootNode.data.langs()); // Not OK of course
var store = Ext.create('MenuStore', {
// Here is the problem:
// 1. if I just use rootNode, it will create an empty root node
// In my full version, I'm using REST to fetch data, so I can open this
// fake root node. Here not. I don't understand why...
// 2. if I use rootNode.data, it works, but partially. It gives me the raw
// object so I can access the id and the name. But I can't get languages
// or children like for the other nodes. See the english/french columns.
root: rootNode.data
});

var tree = Ext.create('Ext.tree.Panel', {
height:400,
hideHeaders: false,
rootVisible: true,
useArrows:true,
renderTo: 'tree-grid',
store: store,
columns: [
{
xtype: 'treecolumn',
header: 'Name',
dataIndex: 'name'
},
{
header: 'Français',
// Here appears the problem, only for the root node.
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
var l = record.langs().getAt(colIndex - 1);
// l doesn't exist for rootNode.data, but exists for rootNode.
if (l != undefined) {
return l.get('title');
}
return "#";
}
},
{
header: 'English',
// Here appears the problem, only for the root node.
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
var l = record.langs().getAt(colIndex - 1);
// l doesn't exist for rootNode.data, but exists for rootNode.
if (l != undefined) {
return l.get('title');
}
return "#";
}
}
]
});
});



</script>
</body>
</html>

So, as I said in my first post, my problem could be partially solved by using rootNode.data (see the above example), but then, I don't have access anymore to the associated data...
And I really don't understand why the root config of the treestore (or the tree itself) doesn't want to accept an object that match the model...

skirtle
15 Dec 2011, 3:23 PM
Your test case is pretty difficult to follow. Is it really necessary to include all that associations stuff just to reproduce this problem? It would also have been easier if you'd provided it in the 'broken' state rather than the 'half-fixed' state.

Your children association looks very dubious to me. Not sure it explains any of the problems you're seeing but it looks like it's asking for trouble.

I believe the answer to your original question is that the record you're passing in hasn't been decorated with the NodeInterface, so TreeStore gets confused and thinks the record is a config. Try adding this line before you create the store:


Ext.data.NodeInterface.decorate(rootNode);

From the small amount of digging I've done I'd say this appears to be a bug in TreeStore, though it needs further investigation.

Araberen
16 Dec 2011, 6:36 AM
Well as I explained before, the associations are not mandatory to reproduce the basic problem. But they are to understand why the solution "rootNode.data" is not a good one.
And as I wrote in comments in the example, the broken state is just :
root: rootNode
whereas the half-fixed state is
root: rootNode.data

Hereafter is the same example, without the associations, broken state by default => You'll just see an empty root node.


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Tree Grid test</title>
<link rel="stylesheet" type="text/css" href="http://dev.sencha.com/deploy/ext-4.0.7-gpl/resources/css/ext-all.css" />
<script type="text/javascript" src="http://dev.sencha.com/deploy/ext-4.0.7-gpl/bootstrap.js"></script>
<!-- <script type="text/javascript" src="treeroot.js"></script>-->
</head>
<body>
<h1>Tree Grid test</h1>
<div id="tree-grid"></div>
<script type="text/javascript">

Ext.onReady(function(){

Ext.define('Menu', {
extend: 'Ext.data.Model',
fields: [
{ name:'id' , type:'int' },
{ name:'name', type: 'string'}
]
});

Ext.define('MenuStore', {
extend: 'Ext.data.TreeStore',
model: 'Menu',
nodeParam: 'id',
proxy: {
type: 'memory'
}
});

var rootNode = Ext.create('Menu', {
id:1,
name:'Home'
});

// console.log(rootNode.data.langs()); // Not OK of course
var store = Ext.create('MenuStore', {
// Here is the problem:
// 1. if I just use rootNode, it will create an empty root node
// In my full version, I'm using REST to fetch data, so I can open this
// fake root node. Here not. I don't understand why...
// 2. if I use rootNode.data, it works, but partially. It gives me the raw
// object so I can access the id and the name. But I can't get languages
// or children like for the other nodes. See the english/french columns.
root: rootNode // broken state
// root: rootNode.data // half-fixed state
});

var tree = Ext.create('Ext.tree.Panel', {
height:400,
hideHeaders: false,
rootVisible: true,
useArrows:true,
renderTo: 'tree-grid',
store: store,
columns: [
{
xtype: 'treecolumn',
header: 'Name',
dataIndex: 'name'
}
]
});
});

</script>
</body>
</html>


I'm gonna try your solution…

Araberen
16 Dec 2011, 6:43 AM
… which perfectlly works !

Ext.data.NodeInterface.decorate(rootNode); is the solution! It solved everything… and all my associations works perfectly fine with the root node. :)

So it's a bug in the TreeStore (which is supposed to do that automatically I guess)… or the documentation shouldn't say that the root config accepts an Ext.data.Model…

skirtle
16 Dec 2011, 7:08 AM
I think the problem occurs here in Ext.data.TreeStore:


setRootNode: function(root) {
var me = this;

root = root || {};
if (!root.isNode) {
// create a default rootNode and create internal data struct.
Ext.applyIf(root, {
id: me.defaultRootId,
text: 'Root',
allowDrag: false
});
root = Ext.ModelManager.create(root, me.model);
}
Ext.data.NodeInterface.decorate(root);
...
}

Models that have been decorated will have isRoot set to true but the model for your root node won't have been decorated yet as its the first time the tree has seen that model. I suspect there needs to be a check for isModel somewhere but I'm not exactly sure where, possibly it should just replace the current isNode check.