PDA

View Full Version : TreeXMLLoader



lokiofragnar
3 Jun 2008, 11:41 PM
Hi All,

Since I do not like loading JSON very much I have create a TreeXMLLoader that is as close to the normal loader is I could get it. Since my DomQuery skills are not up to notch yet I am sure this can be done better.

The extension looks like:





/**
* @class Ext.ux.TreeXMLLoader
* @extends Ext.tree.TreeLoader
* A TreeXMLLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child
* nodes from a specified URL. The response must be wellformed XML bit
* whose elements are node definition following a tree like structure
* <pre><code>
<nodes>
<node>
<id>1</id>
<text>"A leaf Node"</text>
<leaf>true</leaf>
</node>
<node >
<id>2</id>
<text>"A folder Node"</text>
<children>
<node>
<id>3</id>
<text>"a child node"</text>
<leaf>leaf</leaf>
</node>
</children>
</node>
* </code></pre>
* <br><br>
* A server request is sent, and child nodes are loaded only when a node is expanded.
* The loading node's id is passed to the server under the parameter name "node" to
* enable the server to produce the correct child nodes.
* <br><br>
* To pass extra parameters, an event handler may be attached to the "beforeload"
* event, and the parameters specified in the TreeLoader's baseParams property:
* <pre><code>
myTreeLoader.on("beforeload", function(treeLoader, node) {
this.baseParams.category = node.attributes.category;
}, this);
</code></pre><
* This would pass an HTTP parameter called "category" to the server containing
* the value of the Node's "category" attribute.
* @constructor
* Creates a new Treeloader.
* @param {Object} config A config object containing config properties.
*/

Ext.ux.TreeXMLLoader = function(config){
this.baseParams = {};
this.requestMethod = "POST";
Ext.apply(this, config);

this.addEvents(
/**
* @event beforeload
* Fires before a network request is made to retrieve the Json text which specifies a node's children.
* @param {Object} This TreeLoader object.
* @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
* @param {Object} callback The callback function specified in the {@link #load} call.
*/
"beforeload",
/**
* @event load
* Fires when the node has been successfuly loaded.
* @param {Object} This TreeLoader object.
* @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
* @param {Object} response The response object containing the data from the server.
*/
"load",
/**
* @event loadexception
* Fires if the network request failed.
* @param {Object} This TreeLoader object.
* @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
* @param {Object} response The response object containing the data from the server.
*/
"loadexception"
);

Ext.ux.TreeXMLLoader.superclass.constructor.call(this);
};

Ext.extend(Ext.ux.TreeXMLLoader, Ext.tree.TreeLoader, {
/**
* @cfg {String} dataUrl The URL from which to request an XML string which
* specifies an array of node definition objects representing the child nodes
* to be loaded.
*/
/**
* @cfg {String} requestMethod The HTTP request method for loading data (defaults to 'POST').
*/
/**
* @cfg {String} url Equivalent to {@link #dataUrl}.
*/
/**
* @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
*/
/**
* @cfg {Object} baseParams (optional) An object containing properties which
* specify HTTP parameters to be passed to each request for child nodes.
*/
/**
* @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
* created by this loader. If the attributes sent by the server have an attribute in this object,
* they take priority.
*/
/**
* @cfg {Object} uiProviders (optional) An object containing properties which
* specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
* <i>uiProvider</i> attribute of a returned child node is a string rather
* than a reference to a TreeNodeUI implementation, this that string value
* is used as a property name in the uiProviders object.
*/

/**
* @cfg {Object} nodes (mandatory) The repeating dom element that
* describes a single node.
*/

/**
* @cfg {Object} children (mandatory) the single dom element in a node
* that holds the children.
*/

/**
* @cfg {Object} uiProviders (optional) An object containing properties which
* specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
* <i>uiProvider</i> attribute of a returned child node is a string rather
* than a reference to a TreeNodeUI implementation, this that string value
* is used as a property name in the uiProviders object.
*/


uiProviders : {},

/**
* @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
* child nodes before loading.
*/
clearOnLoad : true,

/**
* Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
* This is called automatically when a node is expanded, but may be used to reload
* a node (or append new children if the {@link #clearOnLoad} option is false.)
* @param {Ext.tree.TreeNode} node
* @param {Function} callback
*/
load : function(node, callback){
if(this.clearOnLoad){
while(node.firstChild){
node.removeChild(node.firstChild);
}
}
if(this.doPreload(node)){ // preloaded json children
if(typeof callback == "function"){
callback();
}
}else if(this.dataUrl||this.url){
this.requestData(node, callback);
}
},

doPreload : function(node){
if(node.attributes.children){
if(node.childNodes.length < 1){ // preloaded?
var cs = node.attributes.children;
node.beginUpdate();
for(var i = 0, len = cs.length; i < len; i++){
var cn = node.appendChild(this.createNode(cs[i]));
if(this.preloadChildren){
this.doPreload(cn);
}
}
node.endUpdate();
}
return true;
}else {
return false;
}
},

getParams: function(node){
var buf = [], bp = this.baseParams;
for(var key in bp){
if(typeof bp[key] != "function"){
buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");
}
}
buf.push("node=", encodeURIComponent(node.id));
return buf.join("");
},

requestData : function(node, callback){
if(this.fireEvent("beforeload", this, node, callback) !== false){
this.transId = Ext.Ajax.request({
method:this.requestMethod,
url: this.dataUrl||this.url,
success: this.handleResponse,
failure: this.handleFailure,
scope: this,
argument: {callback: callback, node: node},
params: this.getParams(node)
});
}else{
// if the load is cancelled, make sure we notify
// the node that we are done
if(typeof callback == "function"){
callback();
}
}
},

isLoading : function(){
return this.transId ? true : false;
},

abort : function(){
if(this.isLoading()){
Ext.Ajax.abort(this.transId);
}
},

/**
* Override this function for custom TreeNode node implementation
*/
createNode : function(attr){
if(this.baseAttrs){
Ext.applyIf(attr, this.baseAttrs);
}
if(this.applyLoader !== false){
attr.loader = this;
}
if(typeof attr.uiProvider == 'string'){
attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
}
return(attr.leaf ?
new Ext.tree.TreeNode(attr) :
new Ext.tree.AsyncTreeNode(attr));
},

processResponse : function(response, node, callback){
var doc = response.responseXML;
try {
var root = doc.documentElement || doc;
// find the first element that has a 'this.node' as a child
root = this.findNodesRoot(root);
if(!root){
return this.handleFailure(response);
}
this.makeNodes(root, node);
if(typeof callback == "function"){
callback(this, node);
}
}catch(e){
this.handleFailure(response);
}
},
// creates a attribute array based on all elements that are not children
// perhaps an 'attributes' element will all additional attributes might be added later
makeAttributes: function(xmlElements) {
var attributes = new Object();
for(var e = 0; e < xmlElements.length; e++){
var xmlEl = xmlElements[e];
// only go skin deep if it is a text or CDATA section process else skip
if(xmlEl.nodeType == 1 && this.children != xmlEl.tagName){
var name = xmlEl.tagName;
var value = '';
var inside = xmlEl.childNodes;
for(var i = 0; i < inside.length; i++){
if(inside[i].nodeType ==3 || inside[i].nodeType == 4){
value = inside[i].nodeValue;
break;
}
}
// set the attributes
eval('attributes.'+name+'=\''+value+'\'');
}
}
return attributes;
},

findNodesRoot: function(root){
var q = Ext.DomQuery;
var ns = q.select('> '+this.nodes, root);
if(ns.length == 0){
var deeper = q.select('> *', root);
for(var p = 0; p < deeper.length; p++){
if(deeper[p].nodeType == 1) {
return this.findNodesRoot(deeper[p]);
}
}
return; // nothing found
}else{
return root;
}
},


makeNodes: function (root, node){
var q = Ext.DomQuery;
// search from the root for elements of type 'this.nodes'
var ns = q.select('> '+this.nodes, root);
var subs = new Array();
for(var i =0; i < ns.length; i++){
var elements = q.select( "> *", ns[i]);
// search inside the nodes for elements of type 'this.children'
var children = q.select( "> "+this.children, ns[i]);
// make an attribute list to stay in synch with the TreeLoader
var attr = this.makeAttributes(elements);
// create the node based on the attributes
var sub = this.createNode(attr);
subs[subs.length] = sub;
// process those children present in the xml document, a typical
// response would only return children one level deep and leave
// the others for a second request.
if(children){
this.makeNodes(children, sub);
}
}
// now update the node
node.beginUpdate();
for(var s = 0; s < subs.length; s++){
var cn = node.appendChild(subs[s]);
if(this.preloadChildren){
this.doPreload(cn);
}
}
node.endUpdate();
},


handleResponse : function(response){
this.transId = false;
var a = response.argument;
this.processResponse(response, a.node, a.callback);
this.fireEvent("load", this, a.node, response);
},

handleFailure : function(response){
this.transId = false;
var a = response.argument;
this.fireEvent("loadexception", this, a.node, response);
if(typeof a.callback == "function"){
a.callback(this, a.node);
}
}
});


Calling it is no different from calling the normal tree loader with the exception that:

1. a nodes element is expected with the name of a 'node'
2. a children element is expected with the name of the element use for the children

The setup is recursive so it will explore the full XML document however you don't have to send it all at once ;-)

Enjoy,
Aad