/* * Content-separated javascript tree widget * * Usage: * behaveAs(someUL, Tree) * OR behaveAs(someUL, DraggableTree) * * Extended by Steven J. DeRose, deroses@mail.nih.gov, sderose@acm.org. * * INPUT REQUIREMENTS: * Put class="tree" on topmost UL(s). * Can put class="closed" on LIs to have them collapsed on startup. * * The structure we build is: * li class="children last closed" <=== original li from source * children: there's a UL child * last: no following LI * closed: is collapsed (may be in src) * span class="a children spanClosed" <=== contains children before UL * children: there's a UL (now a sib) * spanClosed: is collapsed * span class="b" <=== +/- click is caught here * span class="c" <=== for spacing and lines * a href="..." <=== original pre-UL stuff (e.g., a) * ul... * */ Tree = Class.create(); Tree.prototype = { /* * Initialise a tree node, converting all its LIs appropriately. * This means go through all li children, and move the content of each * (before any UL child) down into 3 intermediate spans, classes a/b/c. */ initialize: function(options) { this.isDraggable = false; var i,li; this.options = options ? options : {}; if(!this.options.tree) this.options.tree = this; this.tree = this.options.tree; // Set up observer if(this == this.tree) Observable.applyTo(this); // Find all LIs to process // Don't let it re-do a node it's already done. for(i=0;i tag into a suitable tree node */ castAsTreeNode: function(li) { behaveAs(li, TreeNode, this.options); }, getIdxOf : function(el) { if(!el.treeNode) el.treeNode = el; // Special case for TreeMultiselectField if(el.treeNode.id.match(/^selector-([^-]+)-([0-9]+)$/)) return RegExp.$2; // Other case for LHS tree of CMS if(el.treeNode.id.match(/([^-]+)-(.+)$/)) return RegExp.$1; else return el.treeNode.id; }, childTreeNodes: function() { var i,item, children = []; for(i=0;item=this.childNodes[i];i++) { if(item.tagName && item.tagName.toLowerCase() == 'li') children.push(item); } return children; }, hasChildren: function() { return this.childTreeNodes().length > 0; }, /** * Turn a normal tree into a draggable one. */ makeDraggable: function() { this.isDraggable = true; var i,item,x; var trees = this.getElementsByTagName('ul'); for(x in DraggableTree.prototype) this[x] = DraggableTree.prototype[x]; DraggableTree.prototype.setUpDragability.apply(this); var nodes = this.getElementsByTagName('li'); for(i=0;item=nodes[i];i++) { for(x in DraggableTreeNode.prototype) item[x] = DraggableTreeNode.prototype[x]; } for(i=0;item=trees[i];i++) { for(x in DraggableTree.prototype) item[x] = DraggableTree.prototype[x]; } for(i=0;item=nodes[i];i++) { DraggableTreeNode.prototype.setUpDragability.apply(item); } for(i=0;item=trees[i];i++) { DraggableTree.prototype.setUpDragability.apply(item); } }, /** * Add the given child node to this tree node. * If 'before' is specified, then it will be inserted before that. */ appendTreeNode : function(child, before) { if(!child) return; // Remove from the old parent node - this will ensure that the classes of the old tree // item are updated accordingly if(child && child.parentTreeNode) { var oldParent = child.parentTreeNode; oldParent.removeTreeNode(child); } var lastNode, i, holder = this; if(lastNode = this.lastTreeNode()) lastNode.removeNodeClass('last'); // Do the actual moving if(before) { child.removeNodeClass('last'); if(holder != before.parentNode) { throw("TreeNode.appendTreeNode: 'before' not contained within the holder"); holder.appendChild(child); } else { holder.insertBefore(child, before); } } else { holder.appendChild(child); } if(this.parentNode && this.parentNode.fixDragHelperDivs) this.parentNode.fixDragHelperDivs(); if(oldParent && oldParent.fixDragHelperDivs) oldParent.fixDragHelperDivs(); // Update the helper classes if(this.parentNode && this.parentNode.tagName.toLowerCase() == 'li') { if(this.parentNode.className.indexOf('closed') == -1) this.parentNode.addNodeClass('children'); this.lastTreeNode().addNodeClass('last'); } // Update the helper variables if(this.parentNode.tagName.toLowerCase() == 'li') child.parentTreeNode = this.parentNode; else child.parentTreeNode = null; if(this.isDraggable) { for(x in DraggableTreeNode.prototype) child[x] = DraggableTreeNode.prototype[x]; DraggableTreeNode.prototype.setUpDragability.apply(child); } }, lastTreeNode : function() { var i, holder = this; for(i=holder.childNodes.length-1;i>=0;i--) { if(holder.childNodes[i].tagName && holder.childNodes[i].tagName.toLowerCase() == 'li') return holder.childNodes[i]; } }, /** * Remove the given child node from this tree node. */ removeTreeNode : function(child) { // Remove the child var holder = this; try { holder.removeChild(child); } catch(er) { } // Look for remaining children var i, hasChildren = false; for(i=0;i would give a recordID of 6 if(this.id && this.id.match(/([^-]+)-(.+)$/)) this.recordID = RegExp.$1; // Create our extra spans spanA = document.createElement('span'); spanB = document.createElement('span'); spanC = document.createElement('span'); spanA.appendChild(spanB); spanB.appendChild(spanC); spanA.className = 'a ' + li.className.replace('closed','spanClosed'); spanB.className = 'b'; spanB.onclick = TreeNode_bSpan_onclick; spanC.className = 'c'; this.castAsSpanA(spanA); // Add +/- icon to select node that has children if (li.hasChildren() && li.className.indexOf('current') > -1) { li.className = li.className + ' children'; spanA.className = spanA.className + ' children'; } // Find the UL within the LI, if it exists stoppingPoint = li.childNodes.length; startingPoint = 0; childUL = null; for(j=0;j startingPoint) li.insertBefore(spanA, li.childNodes[startingPoint]); else li.appendChild(spanA); // Create appropriate node references; if(li.parentNode && li.parentNode.parentNode && li.parentNode.parentNode.tagName.toLowerCase() == 'li') { li.parentTreeNode = li.parentNode.parentNode; } li.aSpan = spanA; li.bSpan = spanB; li.cSpan = spanC; li.treeNode = spanA.treeNode = spanB.treeNode = spanC.treeNode = li; var aTag = spanC.getElementsByTagName('a')[0]; if(aTag) { aTag.treeNode = li; li.aTag = aTag; } else { throw("Tree creation: A tree needs tags inside the
  • s to work properly."); } aTag.onclick = TreeNode_aTag_onclick.bindAsEventListener(aTag); // Process the children if(childUL != null) { if(this.castAsTree(childUL)) { /* ***** RECURSE ***** */ if(this.className.indexOf('closed') == -1) { this.addNodeClass('children'); } } } else { this.removeNodeClass('closed'); } this.setIconByClass(); }, destroy: function() { // Debug.show(this); this.tree = null; this.treeNode = null; this.parentTreeNode = null; if(this.options) this.options.tree = null; this.options = null; if(this.aTag) { this.aTag.treeNode = null; this.aTag.onclick = null; } if(this.aSpan) { this.aSpan.treeNode = null; this.aSpan.onmouseover = null; this.aSpan.onmouseout = null; } if(this.bSpan) { this.bSpan.treeNode = null; this.bSpan.onclick = null; } if(this.cSpan) this.cSpan.treeNode = null; this.aSpan = null; this.bSpan = null; this.cSpan = null; this.aTag = null; }, /** * Cast the given span as the item for this tree. */ castAsSpanA: function(spanA) { var x; for(x in TreeNode_SpanA) spanA[x] = TreeNode_SpanA[x]; }, /** * Cast the child