>>>>(whilst we're at it how about using the Ext 4 way in all your examples? Seriously, throw away all the ones that use Ext 3 class syntax...)
this is a good point - hopefully all the examples will be fully converted to the ext4 "way"
>>>>(whilst we're at it how about using the Ext 4 way in all your examples? Seriously, throw away all the ones that use Ext 3 class syntax...)
this is a good point - hopefully all the examples will be fully converted to the ext4 "way"
i think it's too early for support more than one model to a tree. Think of the TreeWriter - on change, to which model belongs the change? The complexity just multiplies.
But i see the usecases, and a tree is just another way of a view presenting existing data.
As far as i see, i would write a connector store which build the data from the given models and handles the connections. It's not possible to define this inside the models.
I may be wrong, but we got promised to get better documentation of the tree classes to understand how the tree works and how the underlaying classes interact.
Tree on store base is great, but definitive adds more complexity, which can't be done the way you handle data in grids or other views.
I'm in agreement with most people there. The first thing I thought of when I saw the new data package was representing a subsection of my model hierarchy with a Tree. This was particularly exiting for me because I have for a long time written truly RESTful applications where you can navigate the entire resource hierarchy from one starting URL without any prior knowledge.
If it isn't going to be supported then some examples of the workarounds that you talk about would be good. I look forward to the day the Ext JS supports RESTful applications properly. It is such a large topic I will start a new thread.
I have a pretty basic tree. I use a model and a tree store and I'm able to populate the data. I have a method handler that can give me the node - but this is only part of the equation. From what I can tell the Nodes are not populated with the data from my model that is fed into the TreeStore. I wouldn't necessarily expect this to be. But how can I determine by the node clicked what record it is in the data store and then how can I get that record so I can have access to all the properties in my model. Can someone help with this? I'm fairly new to Ext and I'm making headway but this has got me stumped for a couple of days now.
this is the method handler I got from another example:
Also, where are these parameters documented. Other than finding this snippet somewhere I can'tCode:this.treePanel.getSelectionModel().on('select', function (sm, node, index) { }
tell what events have what parameters and what they are. "node" and "index" are fairly obvious
but what is "sm"? SelectionModel maybe? Any help would be appreciated.
For my application, I needed to get information into the markup to help me style it by node depth, specifically to get the depth on the markup as a class. What I had to do was something like this:
1) Create a copy of Ext.view.TableChunker - this produces the wrapper for rows
2) Copy it because it's a singleton and I think extending a singleton is kinda screwy (but that's another topic)
3) Modify the metaRowTpl and metaTableTpl to put in additional properties you need in the markup.
4) Add "embed" functions because you're creating a template that creates templates, and that's the only way to get {variables} in the template.
5) Modify getTableTpl to expose your variable functions in tableTplMemberFns and/or memberFns
6) Extend Ext.tree.View
7) Set the view's chunker config to an instance of your TableChunker copy
8) Override collectData to get any information you need (like depth) into the row
9) Modify your TreePanel to use the new View
Mine looked (roughly, not a full working example), like:
Note that, if I could extend Ext.view.TableChunker via Ext.define (which I can't because it wants a class name, not an Ext.getClass of a singleton instance), I would have, so it would have been much smaller.PHP Code:
Ext.define('My.TreeChunker', {
requires: ['Ext.XTemplate'],
// see this.embedDepth in the template
metaRowTpl: [
'<tr class="' + Ext.baseCSSPrefix + 'grid-row {addlSelector} {[this.embedRowCls()]} tree-depth-{[this.embedDepth()]}">',
'<tpl for="columns">',
'<td class="' + Ext.baseCSSPrefix + 'grid-cell ' + Ext.baseCSSPrefix + 'grid-cell-{id} {{id}-modified} {{id}-tdCls}" {{id}-tdAttr}><div unselectable="on" class="' + Ext.baseCSSPrefix + 'grid-cell-inner ' + Ext.baseCSSPrefix + 'unselectable" style="{{id}-style}; text-align: {align};">{{id}}</div></td>',
'</tpl>',
'</tr>'
],
metaTableTpl: [
'{[this.openTableWrap()]}',
'<table class="' + Ext.baseCSSPrefix + 'grid-table ' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth()]}>',
'<tbody>',
'<tr>',
'<tpl for="columns">',
'<th class="' + Ext.baseCSSPrefix + 'grid-col-resizer-{id}" style="width: {width}px; height: 0px;"></th>',
'</tpl>',
'</tr>',
'{[this.openRows()]}',
'{row}',
'<tpl for="features">',
'{[this.embedFeature(values, parent, xindex, xcount)]}',
'</tpl>',
'{[this.closeRows()]}',
'</tbody>',
'</table>',
'{[this.closeTableWrap()]}'
],
constructor: function () {
Ext.XTemplate.prototype.recurse = function (values, reference) {
return this.apply(reference ? values[reference] : values);
};
},
// My function to support depth in the template
embedDepth: function (values, parent, x, xcount) {
return '{depth}';
},
embedFeature: function (values, parent, x, xcount) {
var tpl = '';
if (!values.disabled) {
tpl = values.getFeatureTpl(values, parent, x, xcount);
}
return tpl;
},
embedFullWidth: function () {
return 'style="width: {fullWidth}px;"';
},
openRows: function () {
return '<tpl for="rows">';
},
closeRows: function () {
return '</tpl>';
},
embedRowCls: function () {
return '{rowCls}';
},
openTableWrap: function () {
return '';
},
closeTableWrap: function () {
return '';
},
getTableTpl: function (cfg, textOnly) {
var tpl,
tableTplMemberFns = {
openRows: this.openRows,
closeRows: this.closeRows,
embedFeature: this.embedFeature,
embedFullWidth: this.embedFullWidth,
openTableWrap: this.openTableWrap,
closeTableWrap: this.closeTableWrap
},
tplMemberFns = {},
features = cfg.features || [],
ln = features.length,
i = 0,
memberFns = {
embedDepth: this.embedDepth, // add my embedder to the row template
embedRowCls: this.embedRowCls
},
metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
metaTableTpl;
for (; i < ln; i++) {
if (!features[i].disabled) {
features[i].mutateMetaRowTpl(metaRowTpl);
Ext.apply(memberFns, features[i].getMetaRowTplFragments());
Ext.apply(tplMemberFns, features[i].getTplFragments());
Ext.apply(tableTplMemberFns, features[i].getTableFragments());
}
}
metaRowTpl = new Ext.XTemplate(metaRowTpl.join(''), memberFns);
cfg.row = metaRowTpl.applyTemplate(cfg);
metaTableTpl = new Ext.XTemplate(this.metaTableTpl.join(''), tableTplMemberFns);
tpl = metaTableTpl.applyTemplate(cfg);
if (!textOnly) {
tpl = new Ext.XTemplate(tpl, tplMemberFns);
}
return tpl;
}
});
Ext.define('My.TreeView', {
extend: 'Ext.tree.View',
alias: 'widget.mytreeview',
chunker: new My.TreeChunker(),
collectData: function (records) {
var data = this.callParent(arguments),
rows = data.rows,
ln = rows.length,
i;
for (i = 0; i < ln; i++) {
// set depth on row data to that of tree node
// this assumes, of course that your data source
// doesn't provide depth, in which case you
// wouldn't need this
rows[i].depth = records[i].getDepth();
}
return data;
}
});
var tree = new Ext.tree.Panel( {
view: 'My.TreeView',
... rest of your config
});
stevil
And remember, it's better with CHUNKS!
Last edited by stevil; 19 Apr 2011 at 6:15 AM. Reason: "Go advanced" posts, doesn't let you edit
Thanks Stevil, I hope to get back to trees this afternoon, after finally getting my app all wired up with beta3.
Will take a look at the chunker to see if it helps my case![]()
@westy - one last thing - the chunker I supplied overrides the overall ROW structure - it does NOT handle the actual node rendering - for that, you'll need to override TreeColumn, like:
and in the tree panel config that needs it:PHP Code:
Ext.define(My.TreeColumn', {
extend: 'Ext.grid.column.Column',
alias: 'widget.mytreecolumn',
initComponent: function () {
var origRenderer = this.renderer || this.defaultRenderer,
origScope = this.scope || window;
this.renderer = function (value, metaData, record, rowIdx, colIdx, store, view) {
var buf = [],
format = Ext.String.format,
depth = record.getDepth(),
treePrefix = Ext.baseCSSPrefix + 'tree-',
elbowPrefix = treePrefix + 'elbow-',
expanderCls = treePrefix + 'expander',
imgText = '<img src="{1}" class="{0}" />',
checkboxText = '<input type="checkbox" class="{0}" {1} />',
formattedValue = origRenderer.apply(origScope, arguments),
href = record.get('href'),
target = record.get('hrefTarget');
while (record) {
if (!record.isRoot() || (record.isRoot() && view.rootVisible)) {
if (record.getDepth() === depth) {
buf.unshift(format(imgText,
treePrefix + 'icon ' +
treePrefix + 'icon' + (record.get('icon') ? '-inline ' : (record.isLeaf() ? '-leaf ' : '-parent ')) +
(record.get('iconCls') || ''),
record.get('icon') || Ext.BLANK_IMAGE_URL
));
if (record.get('checked') !== null) {
buf.unshift(format(checkboxText, (treePrefix + 'checkbox'), record.get('checked') ? 'checked="checked"' : ''));
if (record.get('checked')) {
metaData.tdCls += (' ' + Ext.baseCSSPrefix + 'tree-checked');
}
}
if (record.isLast()) {
if (record.isLeaf()) {
buf.unshift(format(imgText, (elbowPrefix + 'end'), Ext.BLANK_IMAGE_URL));
} else {
buf.unshift(format(imgText, (elbowPrefix + 'end-plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
}
} else {
if (record.isLeaf()) {
buf.unshift(format(imgText, (treePrefix + 'elbow'), Ext.BLANK_IMAGE_URL));
} else {
buf.unshift(format(imgText, (elbowPrefix + 'plus ' + expanderCls), Ext.BLANK_IMAGE_URL));
}
}
} else {
if (record.isLast() || record.getDepth() === 0) {
buf.unshift(format(imgText, (elbowPrefix + 'empty'), Ext.BLANK_IMAGE_URL));
} else if (record.getDepth() !== 0) {
buf.unshift(format(imgText, (elbowPrefix + 'line'), Ext.BLANK_IMAGE_URL));
}
}
}
record = record.parentNode;
}
// I commented this out in mine, as href is returned in my payload, and I want that
// handled in the itemclick and itemcontextmenu events, so can't have Ext making
// an anchor tag!
// if (href) {
// formattedValue = format('<a href="{0}" target="{1}">{2}</a>', href, target, formattedValue);
// }
return buf.join("") + formattedValue;
};
this.callParent(arguments);
},
defaultRenderer: function (value) {
return value;
}
});
PHP Code:
...
hideHeaders: true,
columns : [
{ xtype: 'mytreecolumn', flex: 1, sortable: false, dataIndex: 'text' }
],
...
As a postscript, I'm not really sure how much I like having to extend/override one part of the framework to get my node to render, and ANOTHER part of the framework to render its HTML container!
Please Sencha, could you give us an update on 4.1 plans and the support of multiple models? At least when decorating child elements with TreeNode behaviour, it would be nice if you could follow the associations set in models and apply correct fields/properties for each node?
The current TreeStore/TreePanel might be usefull for manual data setups with all TreeNode properties coming from your proxy resource. But many data sources don't provide Node properties at all, so it would be great if this information could be created on the fly according to the associations set in the models!
It's SenchaCon week, I wouldn't be surprised if not too many thread update requests like this got updates... If you don't, you might want to ping them at the end of the week... I only say this because 4.0 was released a couple of days before they went to SenchaCon (Split), and they were mostly, but not completely, off the radar.
stevil