/** * @version $Id: tree.js 81 2009-06-05 16:31:31Z happynoodleboy $ * @package JCE * @copyright Copyright (C) 2005 - 2009 Ryan Demmer. All rights reserved. * @author Ryan Demmer * @license GNU/GPL * JCE is free software. This version may have been modified pursuant * to the GNU General Public License, and as distributed it includes or * is derivative of works licensed under the GNU General Public License or * other free or open source software licenses. */ /* Class: Tree Note: The Sortables require an XHTML doctype. Arguments: container - the container element options - an Object, see options below. Options: rootName - the root name. Defaults to 'Root' rootClass - the root node class. Defaults to 'root' collapseTree - collapse all other tree nodes on the same level when one is opened. Defaults to false. useLink - Use the link href Events: onNodeToggle - function executed when a node is toggled onNodeClick - function executed when a node is clicked onNodeLoad - function executed when a node is loaded onNodeCreate - function executed when a node is created */ var Tree = new Class({ getOptions : function(){ return { rootName: 'Root', rootClass: 'root', loaderClass: 'load', collapseTree: false, charLength: false, onInit: Class.empty, onNodeClick: Class.empty, onNodeLoad: Class.empty, onNodeCreate: Class.empty }; }, initialize : function(container, options){ this.setOptions(this.getOptions(), options); this.container = $(container); this.fireEvent('onInit', function(){ this.nodeEvents(); }.bind(this)); return this; }, nodeEvents : function(parent){ if(!parent){ parent = this.container; } $ES('div.tree-row', parent).each(function(el){ el.addEvent('mouseover', function(){ el.addClass('hover'); }) el.addEvent('mouseout', function(){ el.removeClass('hover'); }) }); $ES('div.tree-image', parent).each(function(el){ var p = this.findParent(el); el.addEvent('click', function(e){ this.toggleNode(e, p); }.bind(this)) if(this.getNode(p)){ el.addClass('open'); } }.bind(this)); $ES('span', this.container).each(function(el){ if(window.ie){ el.onselectstart = function(){ return false; } } if(window.gecko){ el.setStyle('moz-user-select', 'none'); } if(this.getNode(el.getParent())){ el.addClass('open'); } }.bind(this)); $ES('a', this.container).each(function(el){ var p = this.findParent(el); el.addEvent('click', function(e){ this.fireEvent('onNodeClick', [e, p]); }.bind(this)) }.bind(this)); }, /** * Does a parent (ul) have childnodes * @param {String} The parent * @return {Boolean}. */ hasNodes : function(parent){ if($type(parent) == 'string'){ parent = this.findParent(parent); } var c = parent.childNodes; return c.length > 1 || (c.length == 1 && c[0].className != 'spacer'); }, /** * Does the node exist? * @param {String} The node title * @param {String or Element} The parent node * @return {Boolean}. */ isNode : function(id, parent){ return this.findNode(id, parent) ? true : false; }, /** * Does a parent have subnodes? * @param {String or Element} The parent node * @return {Boolean}. */ getNode : function(parent){ if($type(parent) == 'string'){ parent = this.findParent(parent); } return $E('ul.tree-node', parent); }, /** * Reset all nodes. Set to closed */ resetNodes : function(){ $ES('span', this.container).each(function(el){ el.removeClass('open'); }); $ES('div.tree-image', this.container).each(function(el){ el.removeClass('open'); }); }, /** * Rename a node * @param {String} The node title * @param {String} The new title */ renameNode : function(id, name){ var parent = string.dirname(id); var node = this.findNode(id, parent); // Rename the node node.setProperty('id', name); // Rename the span $E('a', node).setHTML(string.basename(name)); // Rename each of the child nodes $ES('li[id^='+ this._escape(encodeURI(id)) +']', node).each(function(n){ var nt = n.getProperty('id'); n.setProperty('id', nt.replace(id, name)); }); }, /** * Remove a node * @param {String} The node title */ removeNode : function(id){ var parent = string.dirname(id); var node = this.findNode(id, parent); var ul = node.getParent(); // Remove the node node.remove(); // Remove it if it is now empty if(ul && !this.hasNodes(ul)){ ul.remove(); } }, /** * Create a node * @param {String or Element} The parent node * @return {Array} An array of nodes to create. */ createNode : function(nodes, parent){ var e, p, h, l, np, i; // If parent is not an element, find the parent element if(!parent){ parent = string.dirname(nodes[0].id); } if($type(parent) == 'string'){ parent = this.findParent(parent); } /* Create the nodes from the array *
  • node
  • */ if(nodes && nodes.length){ // Get parent ul var ul = $E('ul.tree-node', parent); // Create it if it doesn't exist if(!ul){ ul = new Element('ul').addClass('tree-node').adopt( new Element('li').addClass('spacer') ).injectInside(parent); } // Iterate through nodes array nodes.each(function(node){ if(!this.isNode(node.id, parent)){ // Set default if(!node['class']){ node['class'] = 'folder'; } // title and link html t = node.name || node.id; // decode t = string.decode(t); h = t; l = this.options.charLength; // shorten if(l){ if(h.length > l){ h = h.substring(0, l) + '...'; } } ul.adopt( new Element('li', { 'id': this._escape(encodeURI(node.id)) }).adopt( new Element('div', { 'class': 'tree-row', events: { mouseover: function(){ this.addClass('hover'); }, mouseout: function(){ this.removeClass('hover'); } } }).adopt( new Element('div', { 'class': node['class'].contains('folder') ? 'tree-image' : 'tree-noimage', events: { 'click': function(e){ e = new Event(e); p = this.findParent(e.target); this.toggleNode(e, p); }.bind(this) } }) ).adopt( new Element('span', { 'class': node['class'] }).adopt( new Element('a', { 'href': !node.url ? 'javascript:;' : node.url, 'tabindex': '1', 'title': t, events : { 'click': function(e){ e = new Event(e); p = this.findParent(e.target); this.fireEvent('onNodeClick', [e, p]); e.stop(); }.bind(this) } }).setHTML(h) ) ) ) ) this.toggleNodeState(parent, 1); this.fireEvent('onNodeCreate'); }else{ // Node exists, set as open this.toggleNodeState(parent, 1); } }.bind(this)) }else{ // No new nodes, set as open this.toggleNodeState(parent, 1); } }, /** * Find the parent node * @param {String} The child node id * @return {Element} The parent node. */ findParent : function(el){ if($type(el) == 'element'){ n = el.parentNode; while (n) { if(n.nodeName == 'LI'){ return n; } n = n.parentNode; } }else{ return $E('li[id='+ this._escape(encodeURI(el)) +']', this.container); } }, /** * Find a node by id * @param {String} The child node title * @param {String / Element} The parent node * @return {Element} The node. */ findNode : function(id, parent){ if(!parent || parent == '/'){ parent = this.container; } if($type(parent) == 'string'){ parent = this.findParent(parent); } return $E('li[id='+ this._escape(encodeURI(id)) +']', parent) || false; }, /** * Toggle the loader class on the node span element * @param {Element} The target node */ toggleLoader : function(node){ var span = $E('span', node), cls = this.options.loaderClass; if(!span.hasClass(cls)){ span.$tmp = span.className || ''; span.className = cls; }else{ span.removeClass(cls).addClass(span.$tmp); span.$tmp = null; } }, /** * Collapse all tree nodes except one excluded * @param {Element} The excluded node */ collapseNodes : function(ex){ if(!ex) this.resetNodes(); var parent = ex.getParent(); $ES('li', parent).each(function(el){ if(el != ex){ if(el.getParent() == parent){ this.toggleNodeState(el, 0); var child = this.getNode(el); if(child){ child.addClass('hide'); } } } }.bind(this)); }, /** * Toggle a node's state, open or closed * @param {Element} The node */ toggleNodeState : function(node, state){ var span = $E('span', node); var div = $E('div.tree-image', node); [span, div].each(function(el){ if(el){ if(state == 1){ el.addClass('open'); }else if(state == 0){ el.removeClass('open'); }else{ el.toggleClass('open'); } } }); if (state == 1) { if(node.id == '/'){ return; } var c = $E('ul.tree-node', node); if (c) { if (span.hasClass('open')) { c.removeClass('hide'); } else { c.addClass('hide'); } } } }, /** * Toggle a node * @param {Element} The node */ toggleNode : function(e, node){ e = new Event(e); // Force reload if(e.shift){ return this.fireEvent('onNodeLoad', node); } var child = this.getNode(node); // No children load or close if(!child){ if($E('div.tree-image', node).hasClass('open')){ this.toggleNodeState(node); }else{ this.fireEvent('onNodeLoad', node); } // Hide children, toggle node }else{ child.toggleClass('hide'); this.toggleNodeState(node); } // Collpase the all other tree nodes if(this.options.collapseTree){ this.collapseNodes(node); } }, /** * Private function * Escape a string * @param {String} The string * @return {String} The escaped string */ _escape : function(s){ return s.replace(/'/, '%27'); } }); Tree.implement(new Events, new Options);