Main Page | Directories | File List | File Members

tree.js

Go to the documentation of this file.
00001 
00013 // if (_zapatec_tree_url)
00014 //      _zapatec_tree_url = _zapatec_tree_url.replace(/\/*$/, '/');
00015 
00063 Zapatec.Tree = function(el, config) {
00064         if (typeof config == "undefined"){
00065                 config = {};
00066         }
00067 
00068         function param_default(name, value) {
00069                 if (typeof config[name] == "undefined"){
00070                         config[name] = value;
00071                 }
00072         };
00073 
00074         param_default('d_profile', false);
00075         param_default('hiliteSelectedNode', true);
00076         param_default('compact', false);
00077         param_default('dynamic', false);
00078         param_default('initLevel', false);
00079 
00080         //expand/collapse the tree when the text or image are clicked
00081         param_default('expandOnLabel', true); 
00082 
00083         //Keep track, using cookies, of the last location the user opened
00084         param_default('saveState', false); 
00085         
00086         if(config.dynamic){
00087                 config.initLevel = 0;
00088         }
00089 
00090         if(
00091                 config.dynamic ||
00092                 config.saveState && 
00093                 (
00094                         typeof(config.saveId) != "string" || 
00095                         (
00096                                 typeof(config.saveId) == "string" &&
00097                                 config.saveId.length == 0
00098                         )
00099                 )
00100         ){
00101                 config.saveState = false;
00102         }
00103 
00104         this.config = config;
00105         
00106         // <PROFILE>
00107         if (this.config.d_profile) {
00108                 var T1 = new Date().getTime();
00109         
00110                 profile = {
00111                         items : 0,
00112                         trees : 0,
00113                         icons : 0
00114                 };
00115         }
00116         // </PROFILE>
00117 
00118         if (typeof el == "string"){
00119                 el = document.getElementById(el);
00120         }
00121 
00122         this.list = el;
00123         this.items = {};
00124         this.trees = {};
00125         this.selectedItem = null;
00126         this.id = el.id || Zapatec.Utils.generateID("tree");
00127         var top = this.top_parent = Zapatec.Utils.createElement("div");
00128         top.className = "tree tree-top";
00129         this.createTree(el, top, 0);
00130         el.parentNode.insertBefore(top, el);
00131         el.parentNode.removeChild(el);
00132         Zapatec.Tree.all[this.id] = this;
00133 
00134         // check if we have an initially selected node and sync. the tree if so
00135         if (this.selectedItem){
00136                 this.sync(this.selectedItem.__msh_item);
00137         }
00138 
00139         //if we're keeping track of state, and we have saved state information
00140         if(this.config.saveState) {
00141                 //restore to previous node
00142                 var txt = Zapatec.Utils.getCookie("Zapatec.Tree-" + config.saveId)
00143 
00144                 if (txt) {
00145                         this.sync(txt);
00146                 }
00147         }
00148 
00149         // <PROFILE>
00150         if (this.config.d_profile) {
00151                 alert("Generated in " + (new Date().getTime() - T1) + " milliseconds\n" +
00152                       profile.items + " total tree items\n" +
00153                       profile.trees + " total (sub)trees\n" +
00154                       profile.icons + " total icons");
00155         }
00156         // </PROFILE>
00157 };
00158 
00178 Zapatec.Tree.all = {};
00179 
00194 Zapatec.Tree.prototype.createTree = function(list, parent, level) {
00195         // PROFILE
00196         if (this.config.d_profile){
00197                 ++profile.trees;
00198         }
00199 
00200         var id = list.id || Zapatec.Utils.generateID("tree.sub");
00201         var self = this;
00202 
00203         function _makeIt() {
00204                 self.creating_now = true;
00205                 var last_li = null;
00206                 var next_li = null;
00207                 var i = list.firstChild;
00208         var items = parent.__msh_items = [];
00209 
00210                 self.trees[id] = parent;
00211                 parent.__msh_level = level;
00212                 parent.__msh_treeid = id;
00213 
00214                 while (i) {
00215                         if (last_li){
00216                                 last_li.className += " tree-lines-c";
00217                         }
00218 
00219                         if (i.nodeType != 1){
00220                                 i = i.nextSibling;
00221                         } else {
00222                                 next_li = Zapatec.Utils.getNextSibling(i, 'li');
00223                         
00224                                 if (i.tagName.toLowerCase() == 'li') {
00225                                         last_li = self.createItem(i, parent, next_li, level);
00226                                 
00227                                         if (last_li) { //false when webmaster creates malformed tree 
00228                                                 items[items.length] = last_li.__msh_item;
00229                                         }
00230                                 }
00231 
00232                                 i = next_li;
00233                         }
00234                 }
00235 
00236                 i = parent.firstChild;
00237                 
00238                 if (i && !level) {
00239                         i.className = i.className.replace(/ tree-lines-./g, "");
00240                         i.className += (i === last_li) ? " tree-lines-s" : " tree-lines-t";
00241                 }
00242                 
00243                 if (last_li && (level || last_li !== i)) {
00244                         last_li.className = last_li.className.replace(/ tree-lines-./g, "");
00245                         last_li.className += " tree-lines-b";
00246                 }
00247                 
00248                 self.creating_now = false;
00249         };
00250 
00251         if (this.config.dynamic && level > 0){
00252                 (this.trees[id] = _makeIt);
00253         } else {
00254                 _makeIt();
00255         }
00256 
00257         return id;
00258 };
00259 
00276 Zapatec.Tree.prototype.createItem = function(li, parent, next_li, level, atStart) {
00277     // PROFILE
00278         if (this.config.d_profile){
00279                 ++profile.items;
00280         }
00281 
00282         if (!li.firstChild){
00283                 return;
00284         }
00285 
00286         var afterNode = null; 
00287         if (atStart) { //Optional parameter after the fourth parameter to allow the new created node to be inserted before it, instead of appending to the parent 
00288                 afterNode = atStart; 
00289         } 
00290 
00291         var
00292                 id = li.id || Zapatec.Utils.generateID("tree.item"),
00293                 item = this.items[id] = ((afterNode == null) ? Zapatec.Utils.createElement("div", parent) : Zapatec.Utils.createElement("div")), //Do not append the new div element to the parent, the new node in the div will be inserted before the 'afterNode'.
00294                 t = Zapatec.Utils.createElement("table", item),
00295                 tb = Zapatec.Utils.createElement("tbody", t),
00296                 tr = Zapatec.Utils.createElement("tr", tb),
00297                 td = Zapatec.Utils.createElement("td", tr),
00298                 is_list,
00299                 tmp,
00300                 i = li.firstChild,
00301                 has_icon = false;
00302 
00303         t.className = "tree-table";
00304         t.cellSpacing = 0;
00305         t.cellPadding = 0;
00306         td.className = "label";
00307         item.className = li.className + " tree-item";
00308         item.__msh_item = id;
00309         item.__msh_tree = this.id;
00310         item.__msh_parent = parent.__msh_treeid;
00311 
00312         if (afterNode) { //A child node from the same parent is sent in to let the new child node inserted before it. 
00313                 parent.insertBefore(item, afterNode); //New item inserted before the 'afterNode'
00314         }
00315 
00316         while (i) {
00317                 is_list = i.nodeType == 1 && /^[ou]l$/i.test(i.tagName);
00318 
00319                 if (i.nodeType != 1 || !is_list) {
00320                         if (i.nodeType == 3) {
00321                                 // remove whitespace, it seems to cause layout trouble
00322                                 tmp = i.data.replace(/^\s+/, '');
00323                                 tmp = tmp.replace(/\s+$/, '');
00324                                 li.removeChild(i);
00325 
00326                                 if (tmp) {
00327                                         i = Zapatec.Utils.createElement("span");
00328                                         i.className = "label";
00329                                         i.innerHTML = tmp;
00330                                         i.onclick = Zapatec.Tree.onItemToggle;
00331                                         td.appendChild(i);
00332                                 }
00333                         } else if (i.tagName.toLowerCase() == 'img') {
00334                                 this.item_addIcon(item, i);
00335                                 has_icon = true;
00336                         } else {
00337                                 i.onclick = Zapatec.Tree.onItemToggle;
00338                                 td.appendChild(i);
00339                         }
00340 
00341                         i = li.firstChild;
00342                         
00343                         continue;
00344                 }
00345 
00346                 if (is_list) {
00347                         this.item_addIcon(item, null);
00348 
00349                         var np; 
00350                         if (afterNode != null) { 
00351                                 np = Zapatec.Utils.createElement("div"); 
00352                                 parent.insertBefore(np, afterNode); //New item inserted before the 'afterNode' 
00353                         } else { 
00354                                 np = Zapatec.Utils.createElement("div", item.parentNode); 
00355                         }
00356 
00357                         np.__msh_item = id;
00358                         np.className = "tree";
00359 
00360                         if (next_li){
00361                                 np.className += " tree-lined";
00362                         }
00363 
00364                         item.__msh_subtree = this.createTree(i, np, level + 1);
00365 
00366                         if ((this.config.initLevel !== false && this.config.initLevel <= level) ||
00367                                 (this.config.compact && !/(^|\s)expanded(\s|$)/i.test(li.className))
00368                                 || /(^|\s)collapsed(\s|$)/i.test(li.className)
00369                         ){
00370                                 item.className += " tree-item-collapsed";
00371                                 this.toggleItem(id, false);
00372                         } else {
00373                                 item.className += " tree-item-expanded";
00374                         }
00375                         
00376                         if (/(^|\s)selected(\s|$)/i.test(li.className)){
00377                                 this.selectedItem = item;
00378                         }
00379 
00380                         break;
00381                 }
00382         }
00383 
00384         if (!has_icon){
00385                 this.item_addDefaultIcon(item, this.config.defaultIcons);
00386         }
00387 
00388         return item;
00389 };
00390 
00397 Zapatec.Tree.prototype.makeNode = function(html, type) { 
00398         if (!type) { 
00399            type = "li"; //Make it a <LI> node if the type is not specified.      
00400         } 
00401         var node = Zapatec.Utils.createElement(type); 
00402         if (html) {  
00403                 node.innerHTML = html; //Assign the inner html of the node if it is specified. 
00404         } 
00405         return node; 
00406 } 
00407  
00421 Zapatec.Tree.prototype.getParent = function(id, mode) { 
00422         var parent = null; 
00423         for (var i in this.trees) { 
00424             //id sent in may be name of the top tree or one of the tree item's or subtree parent's name  
00425             if ( (this.trees[i].__msh_treeid == id) || (this.trees[i].__msh_item == id) ) { 
00426                parent = this.trees[i]; //Get the body of a subtree 
00427                break; 
00428             } 
00429         } 
00430         //For inserting a new child before the referece child, the reference child should be the tree item,  
00431         //not the subtree under it (in case it has tree nodes under it). This is because inserting a new child  
00432         //should be 'before' the subtree (if the tree item has tree nodes under it), not inside or become part of the subtree. 
00433         if ( (mode != null) && ((mode.toUpperCase() == "I") || (mode.toUpperCase() == "R")) ) {  //At this point, if p not null, then it must be body of the subtree. So, get the tree item (root node of the subtree) instead.  
00434            //Otherwise id doesn't refer to a subtree. In this case, get the tree item instead. 
00435            if (parent != null) { //A subtree (not include its root) has been retrieved. That means the insertion or removal operation got to be performed before or at the root of the subtree. This warrants the retrival of the root of the subtree. 
00436               if (parent.className != this.top_parent.className) //As long as p is not the top parent, get the previous sibling or node of p. The previous sibling will be the root of the subtree. 
00437                  parent = parent.previousSibling; 
00438            } else parent = this.items[id.toLowerCase()]; //If no subtree is retrieved, then it must an item node, then get it from array this.items. 
00439         } 
00440         if (!parent) { //If the node matching the id still cannot be found, then look into each item under a subtree  
00441            parent = this.items[id.toLowerCase()]; 
00442         } 
00443         return parent;   
00444 } 
00445  
00453 Zapatec.Tree.prototype.appendChild = function(parent, newChild, atStart) { 
00454         atStart = (atStart == true); 
00455 
00456     // Abort operation when either parent/child is empty or the child node is 
00457     //   already added to the tree. 
00458         if (
00459                 parent == null || 
00460                 newChild == null || 
00461                 typeof(parent) == "undefined" ||
00462                 typeof(newChild) == "undefined" ||
00463                 this.items[newChild.id]
00464         ){
00465                 return; 
00466         }     
00467 
00468         var item = null; 
00469 
00470         if(parent.firstChild == null){
00471                 atStart = false;
00472         }
00473         
00474         if (atStart) { //Append new child before first child of the parent 
00475                 item = this.createItem(newChild, parent, parent.firstChild.nextSibling, parent.__msh_level, parent.firstChild); 
00476         } else { //Append new child after last child of the parent 
00477                 item = this.createItem(newChild, parent, null, parent.__msh_level); 
00478         } 
00479          
00480         // After adding a child, re-draw tree lines that connect the new child to 
00481         //   the tree. This is necessary because the tree structure has been 
00482         //   changed due to addition or insertion of a new child node. 
00483         if (item) { //Child added has no subtree 
00484                 var this_node = null; 
00485                 var next_node = null; 
00486                 var prev_node = null; 
00487                 var subtree   = false; 
00488                  
00489                 if (atStart) { //Child appended at start of the tree 
00490                         this_node = parent.childNodes[0]; //Always the first node regardless of whether has a subtree under it 
00491                         
00492                         if (item.__msh_subtree==null) { //New child appended has no subtree 
00493                                 next_node = parent.childNodes[1]; //Next node will be the second node - the one after the new child node. 
00494                         } else { //New child appended has a subtree 
00495                                 next_node = parent.childNodes[2]; //Next node will be the third node - two nodes after the new child node 
00496                         } 
00497                 } else { //Child appended at end of the tree 
00498                         //Get the appropriate tree node the new node going to attach to 
00499                         if (!item.__msh_subtree) { //Child appended has no subtree 
00500                                 this_node = parent.childNodes[parent.childNodes.length-1]; //Last node 
00501 
00502                                 if(parent.childNodes.length > 1){
00503                                         prev_node = parent.childNodes[parent.childNodes.length-2]; 
00504                                         subtree = (prev_node.className != null && prev_node.className == "tree"); 
00505                                         
00506                                         if (subtree) { //prev_node is a not a tree item, it has a subtree 
00507                                                 prev_node.className += " tree-lined"; //Add vertical tree line to the tree 
00508                                                 prev_node = parent.childNodes[parent.childNodes.length-3]; //Get its parent instead  
00509                                         } 
00510                                 }
00511                         } else { 
00512                                 this_node = parent.childNodes[parent.childNodes.length-2]; //Second last node 
00513                                           
00514                                 prev_node = parent.childNodes[parent.childNodes.length-3]; 
00515                                 subtree = (prev_node.className != null && prev_node.className == "tree"); 
00516                                 
00517                                 if (subtree) { //prev_node is a not a tree item, it has a subtree 
00518                                         prev_node.className += " tree-lined"; //Add vertical tree line to the tree 
00519                                         prev_node = parent.childNodes[parent.childNodes.length-4]; //Get its parent instead 
00520                                 } 
00521                         } 
00522                 } 
00523                  
00524                 //Draw tree lines between the child and the parent 
00525                 if (this_node) { //Make sure the child is in the parent (sub-)tree 
00526                         this_node.className = this_node.className.replace(/ tree-lines-./g, ""); 
00527                         
00528                         if (atStart) { 
00529                                 this_node.className += " tree-lines-t"; 
00530                                 
00531                                 if (next_node) { 
00532                                         next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 
00533                                         next_node.className += " tree-lines-c"; 
00534                                 } 
00535                         } else { 
00536                                 this_node.className += " tree-lines-b"; 
00537 
00538                                 if (prev_node) { 
00539                                         prev_node.className = prev_node.className.replace(/ tree-lines-./g, ""); 
00540                                           
00541                                         prev_node.className += " tree-lines-c"; 
00542                                         
00543                                         if (subtree) { 
00544                                                 prev_node.className += " tree-lines-c"; 
00545                                         } 
00546                                 } 
00547                         } 
00548                 } 
00549 
00550                 return item;
00551         } 
00552 };  
00553  
00561 Zapatec.Tree.prototype.insertBefore = function(newChild, refChild) { 
00562         //Abort operation when either newChild/refChild is empty or the child node is already added to the tree..   
00563         if(
00564                 newChild == null || 
00565                 refChild == null || 
00566                 typeof(newChild) == "undefined" ||
00567                 typeof(refChild) == "undefined" || 
00568                 this.items[newChild.id]
00569         ){
00570                 return; 
00571         } 
00572 
00573         var parent = refChild.parentNode; 
00574         var item = this.createItem(newChild, parent, parent.firstChild.nextSibling, parent.__msh_level, refChild); 
00575         var nodeBefore = false, nodeAfter = false; 
00576         var next_node  = null; 
00577  
00578         if(item.previousSibling){
00579                 nodeBefore = true; 
00580         }
00581 
00582         if(item.nextSibling){
00583                 nodeAfter = true;
00584         }
00585  
00586         item.className = item.className.replace(/ tree-lines-./g, ""); 
00587 
00588         if (nodeBefore && nodeAfter){
00589                 item.className += " tree-lines-c";
00590         } else if (nodeBefore){
00591                 item.className += " tree-lines-b"; 
00592         } else if (nodeAfter) {
00593                 //Insert new child at the start of the tree. 
00594                 item.className += " tree-lines-t"; 
00595 
00596                 //Since the node now after the new child was the first node before, the tree line from it is not connected to the new child. 
00597                 //So it needs to redraw the tree line for the second node (formerly the first node in the tree). 
00598                 if (item.className.indexOf("tree-item-more tree-item")>-1) { //New child has a subtree under it. 
00599                         next_node = item.nextSibling.nextSibling; //Get the next node at the 'same' level as the new child, skip the new child's subtree. 
00600                 } else { 
00601                         next_node = item.nextSibling; //Get the next node at the 'same' level. 
00602                 } 
00603 
00604                 next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 
00605 
00606                 //To find out whether next node has next node after it. 
00607                 if (next_node.className.indexOf("tree-item-more tree-item")>-1) { //Next node has a subtree 
00608                         if (next_node.nextSibling.nextSibling != null){
00609                                 next_node.className += " tree-lines-c";  
00610                         } else {
00611                                 next_node.className += " tree-lines-b"; 
00612                         }
00613                 } else { //Next node is an tree item. 
00614                         if (next_node.nextSibling != null){
00615                                 next_node.className += " tree-lines-c";  
00616                         } else {
00617                                 next_node.className += " tree-lines-b"; 
00618                         }
00619                 } 
00620         } 
00621 }; 
00622  
00636 Zapatec.Tree.prototype.removeChild = function(oldChild) { 
00637         if (
00638                 oldChild == null || 
00639                 typeof(oldChild) == "undefined"
00640         ){
00641                 //No child to remove 
00642                 return;
00643         } else if (oldChild.className == this.top_parent.className) {
00644                 //Top root node is not allowed 
00645                 alert("Removing root node not allowed.") 
00646                 return;  
00647         } 
00648          
00649         //Get the child node's parent node 
00650         var p = oldChild.parentNode; 
00651          
00652         //Remove node(s) - check if the old child represents a root node of a subtree. If it does, remove the child nodes in the subtree as well as the root node. 
00653         //If it does not represents a root node of a subtree, just remove it from the parent it attached to. 
00654         //Clean up - remove the subtree's id from this.trees or the item node's id from this.items. 
00655         if (oldChild.__msh_item && oldChild.__msh_tree && oldChild.__msh_parent) { //Make sure all common attributes of root node (of a subtree) or item node are there. 
00656                 var prev_node = oldChild.previousSibling; 
00657                 var next_node = oldChild.nextSibling; 
00658                 var hasPrevNode = false; 
00659                 var hasNextNode = false; 
00660             
00661                 //Find out if there is a node before the old node at the same tree level. 
00662                 if (prev_node) { 
00663                         //If the node before the old child is the entire subtree at next level, get the root node of that subtree. 
00664                         //The root node will be at the same level as the old child. Otherwise, the node before the old child is an item node. 
00665 
00666                         if (prev_node.__msh_treeid) { //The node before the old child is a tree consists of all tree nodes in a subtree. 
00667                             //Get the root node of that subtree. It should have the same level as the old child. 
00668                                 prev_node = prev_node.previousSibling; 
00669                         } 
00670 
00671                         hasPrevNode = true; 
00672                 } else { //No child before the old child at the same level. 
00673                         //Check if the old child has a parent node such as root of a subtree and the parent node is not the top of the tree 
00674                         if (oldChild.parentNode && oldChild.parentNode.className != this.top_parent.className) {                 
00675                             //This is needed in order to 'not' change any tree line for the child node(s) under a subtree.  
00676                                 hasPrevNode = true; 
00677                         } 
00678                 } 
00679       
00680                 //Find out if there is a node after the old node at the same tree level. 
00681                 if (next_node) { 
00682                         if (oldChild.__msh_subtree) { //Old child is a subtree. So old node is the root of a subtree. Get the node after the subtree then. 
00683                             //Get the node after the old node at the same level. 
00684                                 next_node = next_node.nextSibling; 
00685                                 
00686                                 if (next_node) {
00687                                         //There is a next node at same level as the old node. 
00688                                         hasNextNode = true; 
00689                                 }
00690                 } else {
00691                         //Old child is an item node. So old node is an item node, not a subtree. Old node and next node are at the same level. 
00692                         hasNextNode = true; //There is a next node at same level as the old node. 
00693                 } 
00694         } //else no next node 
00695        
00696         if (oldChild.__msh_subtree) { //Root node of a subtree; has child node(s) under it. 
00697             //Try to get the whole subtree of the old child node 
00698                 var subtreeNode = oldChild.nextSibling; 
00699           
00700                 if (subtreeNode && oldChild.__msh_subtree == subtreeNode.__msh_treeid) { //Subtree node exists 
00701                         for (var i = 0; i < subtreeNode.childNodes.length; i++) { //Loop through each children of the subtree  
00702                                 if (subtreeNode.childNodes[i]){
00703                                         //and delete the corresponding object in this.items 
00704                                         delete this.items[subtreeNode.childNodes[i].__msh_item];
00705                                 }
00706             } 
00707 
00708                         //Remove the corresponding item in this.items for the root of the subtree 
00709                         delete this.items[subtreeNode.__msh_item]; 
00710 
00711                         //Remove the whole subtree 
00712                         p.removeChild(subtreeNode); 
00713                 } 
00714                 
00715                 //Remove the corresponding subtree in this.trees 
00716                 delete(this.trees[oldChild.__msh_subtree]); 
00717 
00718                 //Remove the child node from its parent
00719                 p.removeChild(oldChild); 
00720         } else {
00721                 //An item node, not a subtree 
00722                 delete this.items[oldChild.__msh_item]; //Remove the corresponding item in this.items for the item node 
00723                 p.removeChild(oldChild); //Remove the child node from its parent 
00724         } 
00725 
00726         //Re-draw tree lines nodes/subtrees between removed node if necessary 
00727         //If there is a previous node and next node between the old node, there is no need to redraw thr tree lines 
00728         //as the previous and next node will become joined together after the old node is removed. 
00729         //If 'before' removal of the old node, there is a next node after the old node but no previous node before  
00730         //the old node, then it needs to redraw the tree line of the next node. The next node thus become the first node of the tree. 
00731         if (!hasPrevNode && hasNextNode) { 
00732                 if (next_node) { 
00733                         next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 
00734                         next_node.className += " tree-lines-t"; 
00735                 } 
00736         } else if (hasPrevNode && !hasNextNode) { 
00737                 if (prev_node) { 
00738                         if (prev_node.__msh_subtree) { 
00739                                 prev_node.nextSibling.className = prev_node.nextSibling.className.replace(/ tree-lined/g, ""); 
00740                         } //else not a subtree  
00741 
00742                         prev_node.className = prev_node.className.replace(/ tree-lines-./g, ""); 
00743                         prev_node.className += " tree-lines-b"; 
00744                 } 
00745         }
00746 } 
00747 }; 
00748 
00779 Zapatec.Tree.prototype.item_addDefaultIcon = function(item, className) {
00780         if (!className){
00781                 return;
00782         }
00783 
00784         var last_td = item.firstChild.firstChild.firstChild.lastChild, td;
00785         var td = Zapatec.Utils.createElement("td");
00786         
00787         td.className = "tgb icon " + className;
00788         td.onclick = Zapatec.Tree.onItemToggle;
00789         last_td.parentNode.insertBefore(td, last_td);
00790 };
00791 
00801 Zapatec.Tree.prototype.item_addIcon = function(item, img) {
00802         // PROFILE
00803         if (this.config.d_profile){
00804                 ++profile.icons;
00805         }
00806         
00807         var last_td = item.firstChild.firstChild.firstChild, td;
00808         last_td = img ? last_td.lastChild : last_td.firstChild;
00809         
00810         if (!img || !item.__msh_icon) {
00811                 td = Zapatec.Utils.createElement("td");
00812                 td.className = "tgb " + (img ? "icon" : "minus");
00813                 last_td.parentNode.insertBefore(td, last_td);
00814                 td.onclick = Zapatec.Tree.onItemToggle;
00815         } else {
00816                 td = item.__msh_icon;
00817                 img.style.display = "none";
00818         }
00819         
00820         if (!img) {
00821                 td.innerHTML = "&nbsp;";
00822                 item.className += " tree-item-more";
00823                 item.__msh_state = false; // collapsed
00824                 item.__msh_expand = td;
00825         } else {
00826                 td.appendChild(img);
00827                 item.__msh_icon = td;
00828         }
00829 };
00830 
00838 Zapatec.Tree.prototype.itemClicked = function(item_id, expand) {
00839         this.selectedItem = this.toggleItem(item_id, null, expand);
00840         Zapatec.Utils.writeCookie("Zapatec.Tree-" + this.config.saveId, this.selectedItem.__msh_item, null, '/', 7);
00841 
00842         if (this.config.hiliteSelectedNode && this.selectedItem){
00843                 Zapatec.Utils.addClass(this.selectedItem, "tree-item-selected");
00844         }
00845 
00846         this.onItemSelect(item_id);
00847 };
00848 
00860 Zapatec.Tree.prototype.toggleItem = function(item_id, state, expand) {
00861         if (item_id) {
00862                 var stateDefined = false;
00863 
00864                 if(this.config.saveState) {
00865                         stateDefined = true;
00866                 }
00867 
00868                 if (this.config.hiliteSelectedNode && this.selectedItem){
00869                         Zapatec.Utils.removeClass(this.selectedItem, "tree-item-selected");
00870                 }
00871                 var item = this.items[item_id];
00872 
00873                 if (typeof(state) == "undefined" || state == null) {
00874                         state = !item.__msh_state;
00875                         stateDefined = false;
00876                 }
00877                 
00878                 // Expand is true when the '+' or '-' are clicked. 
00879                 // If expandOnLabel is true we will expand even when the label is clicked
00880                 // We will also expand when the state is explictally passed 
00881 
00882                 if (
00883                         (expand || this.config.expandOnLabel || stateDefined)
00884                 ){
00885                         var subtree = this._getTree(item.__msh_subtree, this.creating_now);
00886 
00887                         if (subtree) {
00888                                 subtree.style.display = state ? "block" : "none";
00889                                 Zapatec.Utils.removeClass(item, "tree-item-expanded");
00890                                 Zapatec.Utils.removeClass(item, "tree-item-collapsed");
00891                                 Zapatec.Utils.addClass(item, state ?
00892                                         "tree-item-expanded" : "tree-item-collapsed");
00893                         }
00894 
00895                         var img = item.__msh_expand;
00896 
00897                         if (img){
00898                                 img.className = "tgb " + (state ? "minus" : "plus");
00899                         }
00900 
00901                         item.__msh_state = state;
00902                         img = item.__msh_icon;
00903 
00904                         if (img) {
00905                                 img.firstChild.style.display = "none";
00906                                 img.appendChild(img.firstChild);
00907                                 img.firstChild.style.display = "block";
00908                         }
00909 
00910                         if (this.config.compact && state) {
00911                                 var a = this._getTree(item.__msh_parent).__msh_items;
00912                                 for (var i = a.length; --i >= 0;)
00913                                         if (a[i] != item_id)
00914                                                 this.toggleItem(a[i], false);
00915                         }
00916 
00917                         if(/zpLoad(JSON|HTML)=([^ $]*)/.test(item.className) && state){
00918                                 var dataType = RegExp.$1;
00919                                 var url = RegExp.$2;
00920 
00921                                 item.className = item.className.replace(/zpLoad(JSON|HTML)=([^ $]*)/g, "");
00922                                 var loadingLabel = this.appendChild(this.getParent(item.__msh_item), this.makeNode("...loading..."), true);
00923                                 var errorFunc = function (objError){
00924                                         alert(objError.errorDescription);
00925                                         item.className += "zpLoad" + dataType + "=" + url;
00926 
00927                                         self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
00928                                 }
00929 
00930                                 var self = this;
00931 
00932                                 if(dataType == "JSON"){
00933                                         Zapatec.Transport.fetchJsonObj({
00934                                                 url: url,
00935                                                 method: "GET",
00936                                                 onLoad: function(objResponse){
00937                                                         function json2html(json){
00938                                                                 if(json == null || json.length == 0){
00939                                                                         return "";
00940                                                                 }
00941 
00942                                                                 var res = "<ul>";
00943 
00944                                                                 for(var ii = 0; ii < json.length; ii++){
00945                                                                         var currVal = json[ii];
00946 
00947                                                                         if(typeof(currVal) == "string"){
00948                                                                                 currVal = [currVal];
00949                                                                         }
00950 
00951                                                                         res += "<li>" + currVal[0] + json2html(currVal[1]) + "</li>"
00952                                                                 }
00953 
00954                                                                 res += "</ul>";
00955 
00956                                                                 return res
00957                                                         }
00958 
00959                                                         var arr = objResponse;
00960 
00961                                                         for(var ii =0; ii < arr.length; ii++){
00962                                                                 var currVal = arr[ii];
00963 
00964                                                                 if(typeof(currVal) == "object" && currVal.length == 1){
00965                                                                         currVal = currVal[0];
00966                                                                 } else if (typeof(currVal) == "object" && currVal.length == 2){
00967                                                                         currVal = currVal[0] + json2html(currVal[1]);
00968                                                                 }
00969                                                                 self.appendChild(self.getParent(item.__msh_item), self.makeNode(currVal));
00970                                                         }
00971 
00972                                                         self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
00973                                                 },
00974                                                 onError : errorFunc
00975                                         });
00976                                 } else if(dataType == "HTML"){
00977                                         Zapatec.Transport.fetchXmlDoc({
00978                                                 url: url,
00979                                                 method: "GET",
00980                                                 onLoad: function(xmlDoc){
00981                                                         var cNodes = xmlDoc.documentElement.childNodes;
00982 
00983                                                         for(var jj = 0; jj < cNodes.length; jj++){
00984                                                                 var currentNode = cNodes[jj];
00985 
00986                                                                 if(currentNode.nodeType != 1){
00987                                                                         continue;
00988                                                                 }
00989 
00990                                                                 var li = self.makeNode(Zapatec.Tree.serializeNode(currentNode, true));
00991 
00992                                                                 for (var ii = 0; ii < currentNode.attributes.length; ii++){
00993                                                                         var attr = currentNode.attributes[ii];
00994                                                                         if(attr.name == 'class'){
00995                                                                                 li.className = currentNode.getAttribute(attr.name);
00996                                                                         } else {
00997                                                                                 li.setAttribute(attr.name, currentNode.getAttribute(attr.name));
00998                                                                         }
00999                                                                 }
01000 
01001                                                                 self.appendChild(self.getParent(item.__msh_item), li);
01002                                                         }
01003 
01004                                                         self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
01005                                                 },
01006                                                 onError : errorFunc
01007                                         });
01008                                 }
01009                         }
01010                 }
01011 
01012                 return item;
01013         }
01014 
01015         return null;
01016 };
01017 
01021 Zapatec.Tree.prototype.collapseAll = function() {
01022         for (var i in this.trees){
01023                 this.toggleItem(this._getTree(i).__msh_item, false, true);
01024         }
01025 };
01026 
01030 Zapatec.Tree.prototype.expandAll = function() {
01031         for (var i in this.trees) {
01032                 this.toggleItem(this._getTree(i).__msh_item, true, true);
01033         }
01034 };
01035 
01039 Zapatec.Tree.prototype.toggleAll = function() {
01040         for (var i in this.trees) {
01041                 this.toggleItem(this._getTree(i).__msh_item);
01042         }
01043 };
01044 
01051 Zapatec.Tree.prototype.sync = function(item_id) {
01052         var item = this.items[item_id];
01053         
01054         if (item) {
01055                 this.collapseAll();
01056                 this.selectedItem = item;
01057                 var a = [];
01058         
01059                 while (item.__msh_parent) {
01060                         a[a.length] = item;
01061                         var pt = this._getTree(item.__msh_parent);
01062                 
01063                         if (pt.__msh_item){
01064                                 item = this.items[pt.__msh_item];
01065                         } else {
01066                                 break;
01067                         }
01068                 }
01069 
01070                 for (var i = a.length; --i >= 0;){
01071                         this.toggleItem(a[i].__msh_item, true);
01072                 }
01073 
01074                 Zapatec.Utils.addClass(this.selectedItem, "tree-item-selected");
01075         }
01076 };
01077 
01082 Zapatec.Tree.prototype.destroy = function() {
01083         var p = this.top_parent;
01084         p.parentNode.removeChild(p);
01085 };
01086 
01097 Zapatec.Tree.prototype._getTree = function(tree_id, dont_call) {
01098         var tree = this.trees[tree_id];
01099 
01100         if (typeof(tree) == "function") {
01101                 if (dont_call){
01102                         tree = null;
01103                 } else {
01104                         tree();
01105                         tree = this.trees[tree_id];
01106                         tree.__msh_state = false;
01107                 }
01108         }
01109 
01110         return tree;
01111 };
01112 
01113 // CUSTOMIZABLE EVENT HANDLERS; default action is "do nothing"
01114 
01120 Zapatec.Tree.prototype.onItemSelect = function() {};
01121 
01122 // GLOBAL EVENT HANDLERS (to workaround the stupid Microsoft memory leak)
01123 
01128 Zapatec.Tree.onItemToggle = function() {
01129         var item = this;
01130         var body = document.body;
01131         var expand = false;
01132 
01133         if(/tgb (minus|plus)/.test(this.className)) {
01134                 expand = true;
01135         }
01136         
01137         while (item && item !== body && !/tree-item/.test(item.className)){
01138                 item = item.parentNode;
01139         }
01140 
01141         Zapatec.Tree.all[item.__msh_tree].itemClicked(item.__msh_item, expand);
01142 };
01143 
01147 Zapatec.Tree.serializeNode = function(currentNode, omitTopLevel){
01148         if(currentNode == null){
01149                 return;
01150         }
01151 
01152         if(currentNode.nodeType == 3){ // text node
01153                 return currentNode.nodeValue;
01154         }
01155 
01156         if(omitTopLevel){
01157                 var res = "";
01158 
01159                 if(currentNode.hasChildNodes()){
01160                         for(var ii = 0; ii < currentNode.childNodes.length; ii++){
01161                                 res += Zapatec.Tree.serializeNode(currentNode.childNodes[ii]);
01162                         }
01163                 }
01164         } else {
01165                 var res = "<" + currentNode.tagName;
01166                 
01167                 for (var ii = 0; ii < currentNode.attributes.length; ii++){
01168                         var attr = currentNode.attributes[ii];
01169                         res += " " + attr.name + "=\"" + currentNode.getAttribute(attr.name).replace(/"/g, '\\"') +  "\"";
01170                 }
01171             
01172                 if(currentNode.hasChildNodes()){
01173                         res += ">";
01174             
01175                         for(var ii = 0; ii < currentNode.childNodes.length; ii++){
01176                                 res += Zapatec.Tree.serializeNode(currentNode.childNodes[ii]);
01177                         }
01178             
01179                         res += "</" + currentNode.tagName + ">"
01180                 } else {
01181                         res += "/>"
01182                 }
01183         }
01184             
01185         return res;
01186 }