PDA

View Full Version : TreeGrid showing hierarchical json data



JayCrossler
21 Sep 2011, 12:17 PM
I'm really struggling with this one. I've google-searched everything I can find, as well as read many articles on the forums about how to do so. I've tried all of the Sencha examples, and still haven't had any luck. I've hit my 8 hours trying with it, and figured I'd ask for help.


I'm trying to take an existing set of JSON and build a TreeGrid (or even just a tree) with it, but can't seem to get the nodes to show the correct titles. I'm using ExtJS 4.0.2.


I verified all of the obvious things - I can view the json through firebug/chrome, I can see that the treegrid object is being called, I have a number of other widgets on the page that are all working, etc.


Here's one of my tries. Ideally, I'd like to have a treegrid that shows each Room (the higher-level objects), and have each conversation as a treenode under them, then a count of users in a column to the right. I'd settle for just a list of the rooms/conversations. Bonus points if you can show me how to also add a "+" icon to add a conversation to a room.


Thanks much for any help in advance, I'm at laptop-flinging stage here.


Source JSON:



[
{
"links": "[{\"title\":\"Google\",\"url\":\"http://google.com\",\"refreshes\":\"false\"}]",
"visibility": "on",
"description": "Fun",
"name": "Glee",
"_id": "4e793d957270340000000003",
"conversations": [
{
"title": "Intro",
"body": "",
"blips": [
],
"created_at": "2011-09-21T01:27:49.942Z"
}
],
"users": [
"4e72c4433aad74c628000005"
]
},
{
"links": "[]",
"visibility": "on",
"description": "Fun2",
"name": "Test",
"_id": "4e793da07270340000000006",
"conversations": [
{
"title": "Intro",
"body": "",
"blips": [
],
"created_at": "2011-09-21T01:28:00.307Z"
}
],
"users": [
"4e72c4433aad74c628000005"
]
}
]




Treeview code:



Ext.define('app.convStore', {
extend: 'Ext.data.Model',
fields: ['name', 'title']
});

var roomStore = Ext.create('Ext.data.TreeStore', {
model: 'app.convStore',
proxy: {
type: 'ajax', //TODO: Move to Rest? Or maybe it can't support
url: 'http://localhost:3000/test.json',
reader: {
type: 'json'
}
},
id: 'roomStore'
});

//Created Object (hosting code removed):
{
xtype: 'treepanel',
height: 504,
title: 'Rooms',
id: 'treegrid_rooms_item',
useArrows: true,
store: roomStore,
rootVisible: false,
columns: [{
header: 'Room',
sortable: true,
dataIndex: ['name']
},{
header: 'Conversations',
sortable: true,
dataIndex: ['title'],
flex: 1
}],
displayField: 'name'
}

mberrie
21 Sep 2011, 10:35 PM
I have a question for you ;)

When reading your input JSON, how does the tree component know that you consider 'conversations' as the child items of your room nodes?

When I compare the code from the TreeGrid example (http://docs.sencha.com/ext-js/4-0/#!/example/tree/treegrid.html) to your code, I can spot quite a view differences:




Ext.define('Task', {
extend: 'Ext.data.Model',
fields: [
{name: 'task', type: 'string'},
{name: 'user', type: 'string'},
{name: 'duration', type: 'string'}
]
});

var store = Ext.create('Ext.data.TreeStore', {
model: 'Task',
proxy: {
type: 'ajax',
//the store will get the content from the .json file
url: 'treegrid.json'
},
folderSort: true
});

//Ext.ux.tree.TreeGrid is no longer a Ux. You can simply use a tree.TreePanel
var tree = Ext.create('Ext.tree.Panel', {
title: 'Core Team Projects',
width: 500,
height: 300,
renderTo: Ext.getBody(),
collapsible: true,
useArrows: true,
rootVisible: false,
store: store,
multiSelect: true,
singleExpand: true,
//the 'columns' property is now 'headers'
columns: [{
xtype: 'treecolumn', //this is so we know which column will show the tree
text: 'Task',
flex: 2,
sortable: true,
dataIndex: 'task'
},{
//we must use the templateheader component so we can use a custom tpl
xtype: 'templatecolumn',
text: 'Duration',
flex: 1,
sortable: true,
dataIndex: 'duration',
align: 'center',
//add in the custom tpl for the rows
tpl: ..
},{
text: 'Assigned To',
flex: 1,
dataIndex: 'user',
sortable: true
}]
});


And the JSON:

{"text":".","children": [
{
task:'Project: Shopping',
duration:13.25,
user:'Tommy Maintz',
iconCls:'task-folder',
expanded: true,
children:[{
task:'Housewares',
duration:1.25,
user:'Tommy Maintz',
iconCls:'task-folder',
children:[{
task:'Kitchen supplies',
duration:0.25,
user:'Tommy Maintz',
leaf:true,
iconCls:'task'
},{
task:'Groceries',
duration:.4,
user:'Tommy Maintz',
leaf:true,
iconCls:'task'
},{
task:'Cleaning supplies',
duration:.4,
user:'Tommy Maintz',
leaf:true,
iconCls:'task'
},{
task: 'Office supplies',
duration: .2,
user: 'Tommy Maintz',
leaf: true,
iconCls: 'task'
}]
}, {
task:'Remodeling',
duration:12,
user:'Tommy Maintz',
iconCls:'task-folder',
expanded: true,
children:[{
task:'Retile kitchen',
duration:6.5,
user:'Tommy Maintz',
leaf:true,
iconCls:'task'
},{
task:'Paint bedroom',
duration: 2.75,
user:'Tommy Maintz',
iconCls:'task-folder',
children: [{
task: 'Ceiling',
duration: 1.25,
user: 'Tommy Maintz',
iconCls: 'task',
leaf: true
}, {
task: 'Walls',
duration: 1.5,
user: 'Tommy Maintz',
iconCls: 'task',
leaf: true
}]
},{
task:'Decorate living room',
duration:2.75,
user:'Tommy Maintz',
leaf:true,
iconCls:'task'
},{
task: 'Fix lights',
duration: .75,
user: 'Tommy Maintz',
leaf: true,
iconCls: 'task'
}, {
task: 'Reattach screen door',
duration: 2,
user: 'Tommy Maintz',
leaf: true,
iconCls: 'task'
}]
}]
},{
task:'Project: Testing',
duration:2,
user:'Core Team',
iconCls:'task-folder',
children:[{
task: 'Mac OSX',
duration: 0.75,
user: 'Tommy Maintz',
iconCls: 'task-folder',
children: [{
task: 'FireFox',
duration: 0.25,
user: 'Tommy Maintz',
iconCls: 'task',
leaf: true
}, {
task: 'Safari',
duration: 0.25,
user: 'Tommy Maintz',
iconCls: 'task',
leaf: true
}, {
task: 'Chrome',
duration: 0.25,
user: 'Tommy Maintz',
iconCls: 'task',
leaf: true
}]
},{
task: 'Windows',
duration: 3.75,
user: 'Darrell Meyer',
iconCls: 'task-folder',
children: [{
task: 'FireFox',
duration: 0.25,
user: 'Darrell Meyer',
iconCls: 'task',
leaf: true
}, {
task: 'Safari',
duration: 0.25,
user: 'Darrell Meyer',
iconCls: 'task',
leaf: true
}, {
task: 'Chrome',
duration: 0.25,
user: 'Darrell Meyer',
iconCls: 'task',
leaf: true
},{
task: 'Internet Exploder',
duration: 3,
user: 'Darrell Meyer',
iconCls: 'task',
leaf: true
}]
},{
task: 'Linux',
duration: 0.5,
user: 'Aaron Conran',
iconCls: 'task-folder',
children: [{
task: 'FireFox',
duration: 0.25,
user: 'Aaron Conran',
iconCls: 'task',
leaf: true
}, {
task: 'Chrome',
duration: 0.25,
user: 'Aaron Conran',
iconCls: 'task',
leaf: true
}]
}]
}
]}

JayCrossler
22 Sep 2011, 4:44 AM
You make a good point - I haven't told the component how to know what the children object is, or how to show it.

Is there a way to have different names in the model for the treegrid to show both the parent or the child? Or, is my best option to go in and change the source json so that it follows that structure closer, with the same names for parents and children?

tvanzoelen
22 Sep 2011, 5:25 AM
A node is a node. In the docs is written
"For the tree to read nested data, the Ext.data.reader.Reader must be configured with a root property, so the reader can find nested data for each node. If a root is not specified, it will default to 'children'.". In http://docs.sencha.com/ext-js/4-0/#!/api/Ext.data.TreeStore.

So always return the same JSON structure.

specify the root in the reader and your children are under the same root



{success: true,
data: [{id: '1', text: 'text',
data:[{id: '2', text: 'text'}]
},
{ id: '3', text: 'text' }
]}

or if you define children as root

{success: true,
children: [{id: '1', text: 'text',
children:[{id: '2', text: 'text'}]
},
{ id: '3', text: 'text' }
]}

mberrie
22 Sep 2011, 5:46 AM
Exactly!

And Jay, your other misunderstanding is the column configuration. You cannot specify 'conversations' as a column like in the code you posted.

When you look at the TreeGrid example above, you can see that they actually have a hierarchy of nodes that all are of the same 'type', that is they match the structure of the model!

They specify one column as the 'treecolumn' and additional columns that will show up in the grid.

I am no expert on the TreePanel and TreeGrid. But it seems that there is no official support for Tree nodes of mixed type. You can specify one Model, and if your nodes have different columns you most likely have to specify a model with a superset of ALL fields - if the incoming data has no value for this field it will just stay blank.

Maybe tvanzoelen knows more about this!

tvanzoelen
22 Sep 2011, 6:34 AM
I am sorry...mberry .. I did not use tree grids yet. I am migrating this moment, but I am interested in the solution.

remeez.persent
22 Sep 2011, 8:04 AM
Hi,

I am currently in the exact same situation, only I am trying to build it up using an aspx handler, but I am struggling getting it to write the JSON out in the right format.

Any help would be much appreciated.

Regards
Remeez

mario60
2 Oct 2011, 6:21 AM
I have a question for you ;)

When reading your input JSON, how does the tree component know that you consider 'conversations' as the child items of your room nodes?


I need to solve the same issue.
How did you do eventually?
thks so much
mario :-?

JayCrossler
2 Oct 2011, 8:07 AM
I eventually just crafted the JSON on the server so that it fir the way I wanted to show it in the tree. Works great.

Here's example JSON:

[{"text":"Room: Lobby","_id":"424242424242424242424242","cls":"folder","expanded":true,"children":[{"text":"Conv: Lobby Chat","_id":"4e87ceb18b8ca50ffb000005","room_id":"424242424242424242424242","leaf":true,"checked":false},{"text":"*Add new Conversation","_id":"424242424242424242424242","leaf":true,"checked":false}]},{"text":"Room: Snores","_id":"4e86be0867960172c700000c","cls":"folder","expanded":true,"children":[{"text":"Conv: Yee Ha!","_id":"4e86be0867960172c700000e","room_id":"4e86be0867960172c700000c","leaf":true,"checked":false},{"text":"*Add new Conversation","_id":"4e86be0867960172c700000c","leaf":true,"checked":false}]},{"text":"Room: Pug Farts","_id":"4e8713cb7aac6cd6d1000004","cls":"folder","expanded":true,"children":[{"text":"Conv: 1111","_id":"4e8713cb7aac6cd6d1000006","room_id":"4e8713cb7aac6cd6d1000004","leaf":true,"checked":false},{"text":"Conv: 2","_id":"4e8713e57aac6cd6d1000012","room_id":"4e8713cb7aac6cd6d1000004","leaf":true,"checked":false},{"text":"Conv: 3333","_id":"4e8724d09a8b9077e4000005","room_id":"4e8713cb7aac6cd6d1000004","leaf":true,"checked":false},{"text":"*Add new Conversation","_id":"4e8713cb7aac6cd6d1000004","leaf":true,"checked":false}]},{"text":"*Add new Room","_id":"42","cls":"folder","expanded":false,"children":[]}]
And the code for viewing it, along with event handlers (I'm still working on it to get the style just right, but it functions for now):
Ext.define("MyDesktop.roomsConversations", {
extend: "Ext.ux.desktop.Module",
requires: ['Ext.tree.*','Ext.data.*','Ext.window.MessageBox'],


id: "roomsConversations",


init: function () {
this.launcher = {
text: "Rooms & Conversations",
iconCls: "cpu-shortcut",
handler: this.createWindow,
scope: this
}
},
createWindow: function () {
var store = Ext.create('Ext.data.TreeStore', {
proxy: {
type: 'ajax',
url: '/desktop/userPrefs.json'
},
fields: ['_id','leaf','text'],
autoSync: true
});


var b = this.app.getDesktop();
var tree = b.createWindow({
xtype: 'autoload-win',
height: 272,
width: 277,
id: 'treegrid_rooms_window',
autoScroll:true,
title:'My Rooms and Conversations',
items:[{
xtype:'treepanel',
rootVisible: false,
title: 'Check a conversation to show it',
id: 'treegrid_rooms_tree',
listeners:{
itemclick : function(view, rec, item, index, eventObj) {


if (eventObj && eventObj.target && eventObj.target.className && eventObj.target.className.indexOf("x-tree-checkbox") > -1) {
//Clicked on the check box
return;
}
var clickedon = {
"type": rec.raw.cls == 'folder' ? 'room' : 'conv',
"_id":rec.raw._id,
"room_id":rec.raw.room_id || 0,
"name": item.innerText,
"checked": rec.data.checked};


if (clickedon.name.indexOf('Add new Conversation') > -1) {
//And new conversation to room _id
appLaunchWidget('convSetup', clickedon._id);


} else if (clickedon.name.indexOf('Add new Room') > -1) {
//Show New Room Form
appLaunchWidget('roomSetup');
} else {
//Edit room or conv
if (clickedon.type == "room") {
appLaunchWidget('roomSetup', clickedon._id);
} else {
appLaunchWidget('convSetup', clickedon.room_id, clickedon._id);
}
}


},
'checkchange': function(node, checked) {
var type = node.raw.cls == 'folder' ? 'room' : 'conv';
var _ID = node.raw._id;
if (checked) {
if (type == 'conv') {
// var roomName = roomNameFromConvID(_ID);
var convTitle = convNameFromConvID(_ID);
createConversationWindow(_ID, convTitle);
//Show this conversation
}
} else {
//Hide this conv
if (type == 'conv') {
closeConversationWindow(_ID);
}
}
}
},


height: 230,
width: 260,
useArrows: true,
autoScroll:true,
animate:true,
containerScroll: true,
store: store
}]
});
tree.show();




}
});

mario60
2 Oct 2011, 9:01 AM
Hi JayCrossler, All,
i see, and thks a lot for your reply.

I still miss something very basic (I suspect)

Consider the json example at ext-4.0.2a/docs/index.html#/api/Ext.data.reader.Reader


{
"users": [
{
"id": 123,
"name": "Ed",
"orders": [
{
"id": 50,
"total": 100,
"order_items": [
{
"id" : 20,
"price" : 40,
"quantity": 2,
"product" : {
"id": 1000,
"name": "MacBook Pro"
}
},
{
"id" : 21,
"price" : 20,
"quantity": 3,
"product" : {
"id": 1001,
"name": "iPhone"
}
}
]
}
]
}
]
}


How do you show it as a tree whose first level nodes are users, second level are orders, and so on? I am unable to instruct extJS to consider 'orders' as children of a user.

thanks cheers
mario ~o)

ps the point is: why do you give up to use


"conversations": [