JavaXT
|
|
Tree Classif(!javaxt) var javaxt={}; if(!javaxt.dhtml) javaxt.dhtml={}; //****************************************************************************** //** Tree View //****************************************************************************** /** * Used to create a hierarchical tree control with nodes and leaves. The * nodes and leaves are "left-justified". Root nodes appear on the left and * nested elements appear below and to the right of the parent nodes. * ******************************************************************************/ javaxt.dhtml.Tree = function (parent, config) { this.className = "javaxt.dhtml.Tree"; var me = this; var defaultConfig = { /** Style for individual elements within the component. The nodes and * leaves in the tree are constructed using "ul" and "li" elements. * Note that unlike most of the other components, you should provide * CSS class names nodes, leaves, and path. */ style:{ rowHeight: "20px", colWidth: "21px", backgroundColor: "white", cursor: "default", padding: 0, li: "", label: { }, //The following style are for icons that appear in the tree leaf: "leaf", node: { open: "node_open", closed: "node" }, root: { open: "root", closed: "root" }, path: { node: { open: { middle: "join_node_middle_open", last: "join_node_bottom_open" }, closed: { middle: "join_node_middle", last: "join_node_bottom" } }, leaf: { middle: "join_leaf_middle", last: "join_leaf_bottom" }, line: "join_line" } } }; /** The following sets up the "display" style for the li and ul elements. * This was added in 2024, after the tree demo stopped working on chrome. */ var display = { ul: "grid", li: "inline-table" }; //************************************************************************** //** Constructor //************************************************************************** var init = function(){ if (typeof parent === "string"){ parent = document.getElementById(parent); } if (!parent) return; //Clone the config so we don't modify the original config object var clone = {}; merge(clone, config); //Merge clone with default config merge(clone, defaultConfig); config = clone; //Create main ul var ul = createUL(parent); ul.style.cursor = config.style.cursor; ul.style.padding = config.style.padding; ul.onselectstart = function () {return false;}; ul.onmousedown = function () {return false;}; ul.setAttribute("desc", me.className); me.el = ul; //Add nodes if (config.nodes){ me.addNodes(config.nodes); } }; //************************************************************************** //** clear //************************************************************************** /** Used to remove all the nodes in the tree */ this.clear = function(){ me.el.innerHTML = ""; }; //************************************************************************** //** addNodes //************************************************************************** /** Used to add nodes to the tree. * @param An array of nodes (string or json with name, children, and * expand properties) Example: <pre> var nodes = [ { name: "javaxt-core", //root node nodes: [ { name: "javaxt.io", nodes: ["Directory", "File", "Image", "Jar", "Shell"] }, { name: "javaxt.sql", nodes:["Column", "Connection", "Recordset"], expand: false } ] }, { name: "javaxt-server", //root node nodes: [ ] } ]; </pre> * @param expandAll If true, will render all the nodes and leaves. Default * is false. */ this.addNodes = function(nodes, expandAll){ var hiddenNodes = []; addNodes(nodes, me.el, hiddenNodes); if (expandAll===true) return; for (var i=0; i<hiddenNodes.length; i++){ hide(hiddenNodes[i], true); } }; //************************************************************************** //** onClick //************************************************************************** /** Called whenever an item is clicked in the tree. * @param item The item that was clicked. See getItem() for more * information. */ this.onClick = function(item){}; //************************************************************************** //** onExpand //************************************************************************** /** Called whenever a node is expanded in the tree. * @param item The node that was expanded. See getItem() for more * information. */ this.onExpand = function(item){}; //************************************************************************** //** onCollapse //************************************************************************** /** Called whenever a node is collapsed in the tree. * @param item The node that was collapsed. See getItem() for more * information. */ this.onCollapse = function(item){}; //************************************************************************** //** getItem //************************************************************************** /** Returns a simple json object used to represent an item in the tree for a * given path. * <ul> * <li>type: Item type (e.g. "node" or "leaf")</li> * <li>name: Text/Label associated with the item</li> * <li>el: DOM element used to render the item(e.g. "li")</li> * </ul> * @param path Path to an element in the tree, Accepts either a string * array or a "/" delimited string. */ this.getItem = function(path){ if (!isArray(path)){ if (typeof path === "string"){ path = path.split("/"); } else{ return null; } } var findNode = function(key, nodes){ if (nodes){ for (var i=0; i<nodes.length; i++){ if (isLI(nodes[i])){ var li = nodes[i]; if (!li.getText){ continue; } if (key===li.getText()){ return li; } } } } return null; }; var target; var nodes = me.el.childNodes; for (var i=0; i<path.length; i++){ var key = path[i]; var node = findNode(key, nodes); if (node){ if (i==path.length-1) target = node; else{ var nextSibling = node.nextSibling; if (nextSibling){ nodes = []; for (var j=0; j<nextSibling.childNodes.length; j++){ var ul = nextSibling.childNodes[j]; if (isUL(ul)){ for (var k=0; k<ul.childNodes.length; k++){ var li = ul.childNodes[k]; if (isLI(li)){ nodes.push(li); } } } } } } } else{ return null; } } if (target){ return getItem(target); } else{ return null; } }; //************************************************************************** //** show //************************************************************************** /** Used to expand a given item in the tree. If no item is specified, then * the */ this.show = function(item){ if (item){ var li = item.el; var ul = li.parentNode; show(ul); //If the item is a node, expand children if (getNodeType(li)!="leaf"){ var nextNode = li.nextSibling; if (isUL(nextNode)){ show(nextNode); } } //Check if the node is visible if (li.offsetParent===null && ul!==me.el){ //Move up the tree while (li.offsetParent===null){ ul = ul.parentNode; if (isLI(ul)) ul = ul.parentNode; show(ul); if (me.el === ul){ break; } } } } else{ //Show the tree if it is hidden me.el.style.visibility = ""; me.el.style.display = ""; } }; //************************************************************************** //** hide //************************************************************************** this.hide = function(item){ if (item){ } else{ //Hide the tree me.el.style.visibility = "hidden"; me.el.style.display = "node"; } }; //************************************************************************** //** getPath //************************************************************************** /** Returns an array of parent nodes that form a path to the given item. */ this.getPath = function(item){ var arr = []; if (item){ arr.push(item); var el = item.el; while (el!==me.el){ el = el.parentNode; if (!el || el===me.el) break; if (isUL(el)){ el = el.parentNode.previousSibling; } if (!el || el===me.el) break; if (isLI(el)) arr.push(getItem(el)); } } return arr; }; //************************************************************************** //** getItem //************************************************************************** /** Returns a simple json object used to represent an item in the tree. */ var getItem = function(li){ return { type: getNodeType(li), name: li.getText(), node: li.node, el: li }; }; //************************************************************************** //** addNodes //************************************************************************** /** Used to add nodes under a given node (ul) in the tree. Node that this * method will be called recursively for any nodes that have children. * @param An array of nodes (string or json with name, children, and * expand properties). * @param parent DOM element. Accepts either "ul" (preferred) or "li". * @param hiddenNodes An array of "ul" nodes to hide. */ var addNodes = function(nodes, parent, hiddenNodes){ if (!nodes || nodes.length===0) return; var lastNode; for (var i=0; i<nodes.length; i++){ //Get node var node = nodes[i]; if (isString(node)){ var name = node; node = { name: name, expand: false, children: false }; } //Get children var children = node.nodes; if (!children) children = node.leaves; //Create new parent as needed if (isLI(parent)) parent = createUL(parent); //Create node var li = createLI(parent); li.node = node; li.onclick = function(){ var li = this; if (li.nextSibling){ var ul; if (li.nextSibling){ for (var j=0; j<li.nextSibling.childNodes.length; j++){ if (isUL(li.nextSibling.childNodes[j])){ ul = li.nextSibling.childNodes[j]; break; } } } if (ul) { if (ul.style.visibility === "hidden"){ show(ul); } else{ hide(ul); } } } me.onClick(getItem(this)); }; //Create container for child nodes as needed. Do this before calling //the getNodeType() method. var ul; if (children){ ul = createUL(createLI(parent)); ul.style.width = "100%"; //Add "join_line" to previous ul addLine(ul.parentNode); } //Determine nodeType (for style purposes) var nodeType = getNodeType(li); //Set join icon style if (nodeType!=="root"){ var style = getJoinStyle(nodeType, true, (i===nodes.length-1)); if (style){ //Set className to display the join icon li.className = style; //Set padding to make the join icon visible li.style.paddingLeft = config.style.colWidth; } } else{ //Root nodes don't need a join icon li.style.padding = 0; li.style.background = "none"; } //Select icon to use in the label var icon; if (nodeType=="leaf"){ icon = config.style.leaf; } else{ icon = config.style[nodeType].open; } //Add icon and label createLabel(node.name, icon, li); if (children){ if (nodeType!=="root"){ ul.style.paddingLeft = config.style.colWidth; } var expand = false; if (node.expand===true) expand = true; if (!expand) hiddenNodes.push(ul); //hide(ul); addNodes(children, ul, hiddenNodes); } lastNode = li; } //Special case for leaves that fall alongside nodes. Add "join_line" //to previous ul if (lastNode){ var nodeType = getNodeType(lastNode); if (nodeType==="leaf"){ addLine(lastNode); } } }; //************************************************************************** //** addLine //************************************************************************** /** Used to add "join_line" to previous ul. */ var addLine = function(li){ var previousSibling = li.previousSibling; while (previousSibling){ for (var j=0; j<previousSibling.childNodes.length; j++){ if (isUL(previousSibling.childNodes[j])){ var _ul = previousSibling.childNodes[j]; addStyle(_ul, config.style.path.line); _ul.style.height = "100%"; break; } } previousSibling = previousSibling.previousSibling; } }; //************************************************************************** //** showNode //************************************************************************** /** Used to expand a node and make its contents visible. */ var show = function(ul, silent){ if (ul===me.el) return; ul.parentNode.style.visibility = ""; ul.parentNode.style.display = display.li; ul.style.visibility = ""; ul.style.display = display.ul; //Update icons var li = ul.parentNode.previousSibling; if (li){ var nodeType = getNodeType(li); var style = getJoinStyle(nodeType, true, isLast(ul.parentNode)); if (style) li.className = style; if (nodeType!=="leaf") li.setIcon(config.style[nodeType].open); //Fire onExpand event if (silent===true) return; me.onExpand(getItem(li)); } }; //************************************************************************** //** hideNode //************************************************************************** /** Used to collapse a node and hide its contents. */ var hide = function(ul, silent){ if (ul===me.el) return; ul.style.visibility = "hidden"; ul.style.display = "none"; ul.parentNode.style.visibility = "hidden"; ul.parentNode.style.display = "none"; //Update icons var li = ul.parentNode.previousSibling; if (li){ var nodeType = getNodeType(li); var style = getJoinStyle(nodeType, false, isLast(ul.parentNode)); if (style) li.className = style; if (nodeType!=="leaf") li.setIcon(config.style[nodeType].closed); //Fire onCollapse event if (silent===true) return; me.onCollapse(getItem(li)); } }; //************************************************************************** //** createLabel //************************************************************************** var createLabel = function(label, icon, li){ var outerDiv = createElement("div", li); outerDiv.style.width = "100%"; outerDiv.style.backgroundColor = config.style.backgroundColor; outerDiv.style.position = "relative"; outerDiv.style.overflow = "hidden"; outerDiv.style.height = config.style.rowHeight; var iconDiv; if (icon){ iconDiv = createElement("div", outerDiv, icon); iconDiv.style.display = "inline-block"; iconDiv.style.position = "absolute"; } var labelDiv; if (label){ var labelDiv = createElement("div", outerDiv, config.style.label); labelDiv.style.display = "inline-block"; labelDiv.style.position = "absolute"; labelDiv.style.paddingLeft = config.style.colWidth; labelDiv.style.lineHeight = config.style.rowHeight; labelDiv.innerHTML = label.replace(/^\s*/, "").replace(/\s*$/, ""); //trim() } li.setIcon = function(icon){ if (iconDiv) iconDiv.className = icon; }; li.getText = function(){ return labelDiv.innerText; }; }; //************************************************************************** //** getNodeType //************************************************************************** /** Returns the type of node represented by a given li (e.g. root, node, or * leaf). */ var getNodeType = function(li){ if (li.parentNode===me.el){ return "root"; } else{ var hasChildren = false; if (li.nextSibling){ for (var i=0; i<li.nextSibling.childNodes.length; i++){ if (isUL(li.nextSibling.childNodes[i])){ hasChildren = true; break; } } } if (hasChildren) return "node"; else return "leaf"; } }; //************************************************************************** //** isLast //************************************************************************** /** Used to determine if an li has any siblings below it. */ var isLast = function(li){ var siblings = li.parentNode.childNodes; for (var i=0; i<siblings.length; i++){ if (siblings[i]===li){ var foundSibling = false; for (var j=i+1; j<siblings.length; j++){ if (isLI(siblings[j])){ foundSibling = true; break; } } if (foundSibling) return false; } } return true; }; //************************************************************************** //** getJoinStyle //************************************************************************** /** Returns the join icon style/class for a given nodeType. */ var getJoinStyle = function(nodeType, isOpen, isLast){ if (nodeType!=="root"){ var joinStyle = isLast ? "last" : "middle"; var style = config.style.path[nodeType]; if (nodeType!=="leaf"){ if (isOpen) style = style.open; else style = style.closed; } style = style[joinStyle]; if (typeof style === "string"){ return style; } else{ //TODO: Create style? We cannot rely on setStyle b/c we need //to update the style whenever we expand/collapse the node. } } return null; }; //************************************************************************** //** createUL //************************************************************************** /** Used to create a "ul" element for the tree */ var createUL = function(parent){ if (parent.tagName.toLowerCase()==="ul"){ parent = createLI(parent); } return createElement("ul", parent, { listStyleType: "none", padding: 0, margin: 0, display: display.ul, backgroundColor: config.style.backgroundColor }); }; //************************************************************************** //** createLI //************************************************************************** /** Used to create a "li" element for the tree */ var createLI = function(parent){ var li = createElement("li", parent, config.style.li); li.style.width = "100%"; li.style.display = display.li; li.setIcon = function(icon){}; li.getText = function(){}; return li; }; var isLI = function(el){ return isTag(el, "li"); }; var isUL = function(el){ return isTag(el, "ul"); }; var isTag = function(el, tag){ return el.tagName.toLowerCase()===tag; }; //************************************************************************** //** Utils //************************************************************************** var merge = javaxt.dhtml.utils.merge; var addStyle = javaxt.dhtml.utils.addStyle; var isArray = javaxt.dhtml.utils.isArray; var isString = javaxt.dhtml.utils.isString; var createElement = javaxt.dhtml.utils.createElement; init(); }; |