PDA

View Full Version : Nodes on TreePanel Do NOT Show



Cliff
23 Feb 2011, 7:18 AM
This new project I'm on here uses a framework built over ExtJS.
I register a Panel to the framework as part of the config. Its layout is fit and it contains the TreePanel subclass that I wrote. The TreePanel inits itself at construction {incl. initComponent()}.
When the user logs in
1. The framework constructs but does not show a window that will contain my objects.
2. Through logging I know that the subclassed TreePanel's init process does everything it's supposed to.
3. When the user clicks the object that shows this window, the TreePanel is there but its Nodes don't show. The rootNode is not visible (and its not supposed to be).

Other Details:
The framework constructs and initializes my TreePanel and loads the data into the Root Node when the user logs in. None of this is visible. I know the rootNode is getting populated with its children because the logging says everything is working as expected. In fact, I added logging in the TreeLoader's load event handler which loops through the children and shows their hidden properties are set to false. Also, calling getUI().show() on these children did not correct the problem either. So I fired an event from this same load event handler in the TreeLoader which is caught in the TreePanel. I know that this registered event handler gets called because of logging; there I called this.doLayout() on the TreePanel. Again, all of this happens when the user logs in and nothing is yet visible to the user. The this.doLayout() call is made without its optional arguments and 'this' is the TreePanel subclass.

When an object is clicked on the screen, the window containing this TreePanel then shows. The nodes are there but not visible. The children of the root node need to show. What am I missing?

johnathanhebert
23 Feb 2011, 10:51 AM
can you do a getHeight() on the treepanel and confirm it is not 0 (or small)?

Cliff
23 Feb 2011, 11:29 AM
Good question, Johnathan. The height logged at 514. I also gave the TreePanel a border so I knew that wasn't it. Here's something else I've observed that may be a hint ...

When complete, this TreePanel will be rootVisible==false. But since I'm having this problem, I reversed the rootVisible to true for debugging. Then, the rootNode does display when the window shows. When I attempt to expand this root node, it invokes the Ajax request, creates the nodes and the logs are full of happy stuff. Same result as in my original post. The HINT that I see from this is that as all this processing completes successfully, but the circular Load Mask is invoked and never gets done.

Why does the load mask not know the Ajax request has completed? Is this why it does not render the child nodes? I did subclass the TreeLoader and will happily post that src, but I don't think I messed that up.

johnathanhebert
23 Feb 2011, 11:36 AM
Interesting... please post source if possible, otherwise it will be difficult to address the problem.

Cliff
23 Feb 2011, 12:44 PM
The following is the source you are asking for. It is a Ext.tree.TeeLoader subclass. Functions like debugLog() and logObj() are just logging calls to FireBug console.log. Only comments and the one function that does not get called as part of this are removed. Most of the logging will be removed when I get done with this dev stage. The javaDoc-type comment blocks are accurate.



Ext.namespace('CWB.modules');

CWB.modules.FilePickerTreeLoader = Ext.extend(Ext.tree.TreeLoader, {
/**
* Constructor for this TreeLoader subclass.
* @param config This class sets most of its own config params. The following member adds to those of the superclass:
* rootNodeId Required string; the ID of the Root Node as set in the containing TreePanel.
*/
constructor: function(config) {
debugLog('Enter CWB.modules.FilePickerTreeLoader.constructor().');

var defaults = {
baseParams : {start:0, limit:CWB.modules.FilePickerConfig.FILE_LIMIT_PER_GET, expand:false},
dataUrl: CWB.modules.FilePickerConfig.URL_INGEST_DIR,
listeners: {
beforeload: this.onBeforeLoad,
load: this.onLoad,
loadexception: this.onLoadException,
scope: this
},
/* The nodeParameter must be set only after the load of the root node. Leaving this unset now, causes the path
param in the first request to be absent, which is what the REST API specifies for the initial load of root.
This parameter is set, instead, in the load event handler when applicable.
nodeParameter: 'path',*/
requestMethod: 'GET'
};

Ext.apply(config, defaults);
CWB.modules.FilePickerTreeLoader.superclass.constructor.call(this, config);

},
/**
* Overrides the ExtJS's createNode() method on the TreeLoader.
* @param attr JSON response from REST API. Should be the syntax for either a File or a Directory node.
* @return If successful, a TreeNode is returned for either a File or a Directory. If a boolean false is returned,
* an error has occurred (check the content of the argument) Returning false should cause no node to
* be created in the calling function.
*/
createNode: function(attr) {
// This fn() will be called in the context of the TreeLoader ...
debugLog('Enter CWB.modules.FilePickerTreeLoader.createNode().');
this.logObj('In createNode() eval attr arg', attr);
var returnNode = false;
var nodeConfig;
var label = '';
// Default properties for both types of nodes ...
var defaultConfig = {
allowDrag: true,
allowDrop: false,
checked: false,
disabled: false,
draggable: true,
editable: false,
expanded: false,
hidden: false,
isTarget: false
};
/* Must figure out:
icon vs iconCls = Needed, but what is needed?
Need to attach the REST JSON to the Node object?
*/

if (attr.totalFileCount && attr.path){
// Directory Node to be created ...
label = attr.path;
var pathSep = '\\';
var startIdx = label.lastIndexOf(pathSep);
if (startIdx > -1){
// Pull the file name out of the path ...
startIdx += pathSep.length;
label = label.slice(startIdx);
}
label += ' ('+attr.totalFileCount+' Files)';
debugLog('CWB DEBUG: DirNode label to be, "'+label+'"; startIdx='+startIdx+'.');

var hasChildren = (attr.totalFileCount > 0);

nodeConfig = {
id: attr.path,
allowChildren: hasChildren,
expandable: true,
leaf: (! hasChildren),
listeners: {
beforechildrenrendered: this.onBeforeChildrenRendered,
scope: this
},
singleClickExpand: true,
text: label
};

Ext.applyIf(nodeConfig, defaultConfig);


returnNode = new Ext.tree.AsyncTreeNode(nodeConfig);
// Add filePicker member for requirements specific to this FilePicker ...
attr.lastStart = 0;
returnNode.filePicker = attr;
}
else if (attr.name){
// File Node to be created ...
label = attr.name;
if (attr.size){
label += ' (';
var bytes = attr.size;
if (bytes >= 1000){
label += ((bytes / 1000).toFixed(1) +' MB');
}
else{
label += (bytes +' KB');
}
}

nodeConfig = {
id: attr.name,
allowChildren: false,
expandable: false,
leaf: true,
text: label
};

Ext.applyIf(nodeConfig, defaultConfig);

returnNode = new Ext.tree.TreeNode(nodeConfig);
}
else{
// Error condition. Invalid members in argument, not a file or a directory.
debugLog('ERROR: Encountered invalid attr argument in createNode(). No node will be created.');
}


debugLog(' *******printing new node *******');
debugLog(returnNode);

return returnNode;
/* This function overrides the createNode() method in Ext.tree.TreeLoader. That original function can be viewed
in Ext's treeLoader.js file. The orignal source can be reviewed there. Note that the following attrbutes are
used in the original createNode(), but not here. They could be used here if needed:
{ this.baseAttrs, this.applyLoader, this.uiProviders[], Ext.tree.TreePanel.nodeTypes[] }
*/
},
/**
* Handler for the beforechildrenrendered event on a Directory Node being populated. This removes/appends the
* Paging Node, as appropriate. It always keeps the Paging Node as the last leaf, if it is needed at all.
* @param node The node whose children are about to be rendered. (Always a Directory Node with children.)
* @return void The firing method ignores any return value from this method.
*/
onBeforeChildrenRendered: function(node){
// The source for this one is removed. This function does not get called as part of the problem here.
// This would never be called while loading root.
},
/**
* Handler for the beforeload event of the TreeLoader. Sets the start param of the request. Also stashes it in the
* "lastStart" member of the node being loaded.
* @param thisLdr The TreeLoader invoking the load process.
* @param node The TreeNode/AsyncTreeNode object of the invoking Node.
* @param callbackFn Callback function {that was passed into the initial call to load()}. Ignored here.
* @return Boolean, returning false aborts the load prior to invoking the request.
*/
onBeforeLoad: function(thisLdr, node, callbackFn){
debugLog('Enter onBeforeLoad() where thisLdr isValid:'+(thisLdr!=undefined)+'"; and node.id="'+node.id+'".');
debugLog('In onBeforeLoad() the callbackFn arg typeof: '+(typeof callbackFn));
debugLog('In onBeforeLoad() and PagingNode is clicked: '+(node.text == CWB.modules.FilePickerConfig.PAGING_NODE_TEXT));
this.logObj('In onBeforeLoad() the thisLdr.baseParams: ', thisLdr.baseParams);
var continueWithLoad = true;

if (node.text == CWB.modules.FilePickerConfig.PAGING_NODE_TEXT){
/* User has requested the next page of files ...
Divert the load to the parentNode, set the start params in the places needed, then return false ... */
var nodeToLoad = node.parentNode;
if (! nodeToLoad.filePicker.lastStart){
// This probably will ever happen, so don't bother with fixing the missing object members. etc. ...
debugLog('ERROR: Either node.filePicker or node.filePicker.lastStart is not defined. This process '+
'will not abort, but this should never happen. Paging will probably not work completely.');
nodeToLoad.filePicker.lastStart = 0;
}

nodeToLoad.filePicker.lastStart += thisLdr.baseParams.limit;
thisLdr.baseParams.start = nodeToLoad.filePicker.lastStart;
thisLdr.load(nodeToLoad);
continueWithLoad = false;
}
else{
thisLdr.baseParams.start = 0;
}
debugLog('CWB DEBUG, to return '+continueWithLoad+' from onBeforeLoad().');
return continueWithLoad;
},
/**
* Handler for the load event of the TreeLoader.
* @param thisLdr The TreeLoader invoking the load process.
* @param node The referencde of the invoking Node.
* @param response
* @return void The TreeLoader ignores any return value from this method.
*/
onLoad: function(thisLdr, node, response){
debugLog('Enter onLoad() where arg thisLdr isValid:'+(thisLdr != undefined)+'"; and node.id="'+node.id+
'"; this.rootNodeId="'+this.rootNodeId+'".');
debugLog('In onLoad() the response.responseText="'+response.responseText+'".');

if (node.id == this.rootNodeId){
if (node.hasChildNodes()){
debugLog('In onLoad(), the Root Node Child count='+node.childNodes.length);
thisLdr.nodeParameter = 'path';
var child;
for (var i=0; i<node.childNodes.length; i++){
child = node.childNodes[i];
debugLog('In onLoad() BEFORE show(), child #'+i+' is hidden:'+child.hidden);
// child.getUI().show();
// debugLog('In onLoad() AFTER show(), child #'+i+' is hidden:'+child.hidden);
}

CWB.event.EventManager.fireEvent(this.rootNodeId);
}
else{
debugLog('ERROR: Initial load of FilePicker has failed. No directories or root drives were returned from URL: '+
CWB.modules.FilePickerConfig.URL_INGEST_DIR+' GET request.');
}
}
else{
debugLog('CWB DEBUG - The Root Node was not the one being operated on.');
}
},
/**
* Handler for the loadexception event of the TreeLoader. Invoked from the failure handler for the Ajax Request.
* @param thisLdr The TreeLoader invoking the load process.
* @param node The referencde of the invoking Node.
* @param response
* @return void The TreeLoader ignores any return value from this method.
*/
onLoadException: function(thisLdr, node, response){
debugLog('Enter onLoadException() where node.id="'+node.id+'".');
debugLog('In onLoadException() the response arg status='+response.status+'; statusText="'+response.statusText+'".');
this.logObj('In onLoadException() the thisLdr.baseParams', thisLdr.baseParams);
},
/**
* Must override Ext JS processResponse() to accomodate our internal response.responseText.
* @param response
* @param node
* @param callback
* @param scope
*/
processResponse : function(response, node, callback, scope){
debugLog('Enter CWB.modules.FilePickerTreeLoader.processResponse().');
// var json = response.responseText;
var json = response.responseText;
debugLog('The pre-decoded text="'+json+'".');
var cwb = Ext.decode(json);
var o = cwb.responseText;
debugLog('The new decoded object, o.responseText, is an array with length='+o.length);
// The next assignment is just hard-coded test data, used as needed. It gets same error when loading.
// var o = [
// {path:'C:', totalFileCount:'6'},
// {path:'E:', totalFileCount:'2'}
// ];

try {
// var o = response.responseData || Ext.decode(json);
node.beginUpdate();
for(var i = 0, len = o.length; i < len; i++){
var n = this.createNode(o[i]);
if(n){
node.appendChild(n);
}
}
node.endUpdate();
//this.runCallback(callback, scope || node, [node]);// CWB: Optional post createNode process, if needed.
}catch(e){
this.handleFailure(response);
}
},
/**
* Generic function to log the content of JS objects. Debugging only. I remove this before check-in.
* @param msg String, pass the beginning part of the message to be logged.
* @param obj Object whose members are to be logged.
*/
logObj : function(msg, obj){
var type = (typeof obj);
msg += '; obj arg is:'+type;

if (type == 'object'){
for (var prop in obj){
msg += '; '+prop+':"'+obj[prop]+'"';
}
}
else if ((type == 'string')||(type == 'number')||(type == 'boolean')){
msg += '="'+obj+'".';
}
debugLog(msg);
}
});

johnathanhebert
23 Feb 2011, 1:58 PM
You commented out this line in your override of processResponse:


this.runCallback(callback, scope || node, [node]);// CWB: Optional post createNode process, if needed.

Uncomment that line and I think it will work... you put a note that the "callback" is an optional post createNode process... but it is not -- that is done in another spot -- this callback is actually created by the response in the internal ajax lib

johnathanhebert
23 Feb 2011, 2:37 PM
FYI, it turns out the callback referred to in that line is the loadComplete method of the AsyncTreeNode:



loadComplete : function(deep, anim, callback, scope){
this.loading = false;
this.loaded = true;
this.ui.afterLoad(this);
this.fireEvent("load", this);
this.expand(deep, anim, callback, scope);
},

Cliff
23 Feb 2011, 2:45 PM
Thank you Johnathan! Thank You! That was a day and a half of misery.

I was closing in on where the loadComplete stuff in the AsyncTreeNode was getting invoked from. Since I wasn't passing a callBack fn to a load() call, I erroneously figured that that line wasn't needed. Thank you again. Glad I'm past that one.

johnathanhebert
23 Feb 2011, 3:00 PM
I've found that the best way to debug this stuff is to use firebug and put breakpoints at certain places, then when the breakpoint is hit, click the "Stack" tab and click on a few methods up the stack and inspect variables in their scope, and it generally leads you to the problem pretty quickly