summaryrefslogtreecommitdiff
path: root/frontend/beta/js/YUI-extensions/tree
Side-by-side diff
Diffstat (limited to 'frontend/beta/js/YUI-extensions/tree') (more/less context) (ignore whitespace changes)
-rw-r--r--frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js58
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeDragZone.js43
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeDropZone.js228
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeFilter.js105
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeLoader.js107
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeNode.js300
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js452
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreePanel.js213
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js195
-rw-r--r--frontend/beta/js/YUI-extensions/tree/TreeSorter.js49
10 files changed, 1750 insertions, 0 deletions
diff --git a/frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js b/frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js
new file mode 100644
index 0000000..5d48b00
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/AsyncTreeNode.js
@@ -0,0 +1,58 @@
+YAHOO.ext.tree.AsyncTreeNode = function(config){
+ this.loaded = false;
+ this.loading = false;
+ YAHOO.ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
+ this.events['beforeload'] = true;
+ this.events['load'] = true;
+};
+YAHOO.extendX(YAHOO.ext.tree.AsyncTreeNode, YAHOO.ext.tree.TreeNode, {
+ expand : function(deep, anim, callback){
+ if(this.loading){ // if an async load is already running, waiting til it's done
+ var timer;
+ var f = function(){
+ if(!this.loading){ // done loading
+ clearInterval(timer);
+ this.expand(deep, anim, callback);
+ }
+ }.createDelegate(this);
+ timer = setInterval(f, 200);
+ }
+ if(!this.loaded){
+ if(this.fireEvent('beforeload', this) === false){
+ return;
+ }
+ this.loading = true;
+ this.ui.beforeLoad(this);
+ var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
+ if(loader){
+ loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback]));
+ return;
+ }
+ }
+ YAHOO.ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback);
+ },
+
+ isLoading : function(){
+ return this.loading;
+ },
+
+ loadComplete : function(deep, anim, callback){
+ this.loading = false;
+ this.loaded = true;
+ this.ui.afterLoad(this);
+ this.fireEvent('load', this);
+ this.expand(deep, anim, callback);
+ },
+
+ isLoaded : function(){
+ return this.loaded;
+ },
+
+ hasChildNodes : function(){
+ if(!this.isLeaf() && !this.loaded){
+ return true;
+ }else{
+ return YAHOO.ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeDragZone.js b/frontend/beta/js/YUI-extensions/tree/TreeDragZone.js
new file mode 100644
index 0000000..9b77b3c
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeDragZone.js
@@ -0,0 +1,43 @@
+YAHOO.ext.tree.TreeDragZone = function(tree, config){
+ YAHOO.ext.tree.TreeDragZone.superclass.constructor.call(this, tree.getEl(), config);
+ this.tree = tree;
+};
+
+YAHOO.extendX(YAHOO.ext.tree.TreeDragZone, YAHOO.ext.dd.DragZone, {
+ ddGroup : 'TreeDD',
+
+ onBeforeDrag : function(data, e){
+ var n = data.node;
+ return n && n.draggable && !n.disabled;
+ },
+
+ onInitDrag : function(e){
+ var data = this.dragData;
+ this.tree.getSelectionModel().select(data.node);
+ this.proxy.update('');
+ data.node.ui.appendDDGhost(this.proxy.ghost.dom);
+ this.tree.fireEvent('startdrag', this.tree, data.node, e);
+ },
+
+ getRepairXY : function(e, data){
+ return data.node.ui.getDDRepairXY();
+ },
+
+ onEndDrag : function(data, e){
+ this.tree.fireEvent('enddrag', this.tree, data.node, e);
+ },
+
+ onValidDrop : function(dd, e, id){
+ this.tree.fireEvent('dragdrop', this.tree, this.dragData.node, dd, e);
+ this.hideProxy();
+ },
+
+ beforeInvalidDrop : function(e, id){
+ if(YAHOO.util.Anim){
+ // this scrolls the original position back into view
+ var sm = this.tree.getSelectionModel();
+ sm.clearSelections();
+ sm.select(this.dragData.node);
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeDropZone.js b/frontend/beta/js/YUI-extensions/tree/TreeDropZone.js
new file mode 100644
index 0000000..91c24e1
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeDropZone.js
@@ -0,0 +1,228 @@
+YAHOO.ext.tree.TreeDropZone = function(tree, config){
+ this.allowParentInsert = false;
+ this.allowContainerDrop = false;
+ this.appendOnly = false;
+ YAHOO.ext.tree.TreeDropZone.superclass.constructor.call(this, tree.container, config);
+ this.tree = tree;
+ this.lastInsertClass = 'ytree-no-status';
+ this.dragOverData = {};
+};
+
+YAHOO.extendX(YAHOO.ext.tree.TreeDropZone, YAHOO.ext.dd.DropZone, {
+ ddGroup : 'TreeDD',
+
+ expandDelay : 1000,
+
+ expandNode : function(node){
+ if(node.hasChildNodes() && !node.isExpanded()){
+ node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
+ }
+ },
+
+ queueExpand : function(node){
+ this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
+ },
+
+ cancelExpand : function(){
+ if(this.expandProcId){
+ clearTimeout(this.expandProcId);
+ this.expandProcId = false;
+ }
+ },
+
+ isValidDropPoint : function(n, pt, dd, e, data){
+ if(!n || !data){ return false; }
+ var targetNode = n.node;
+ var dropNode = data.node;
+ // default drop rules
+ if(!(targetNode && targetNode.isTarget && pt)){
+ return false;
+ }
+ if(pt == 'append' && targetNode.allowChildren === false){
+ return false;
+ }
+ if((pt == 'above' || pt == 'below') && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
+ return false;
+ }
+ if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
+ return false;
+ }
+ // reuse the object
+ var overEvent = this.dragOverData;
+ overEvent.tree = this.tree;
+ overEvent.target = targetNode;
+ overEvent.data = data;
+ overEvent.point = pt;
+ overEvent.source = dd;
+ overEvent.rawEvent = e;
+ overEvent.dropNode = dropNode;
+ overEvent.cancel = false;
+ var result = this.tree.fireEvent('nodedragover', overEvent);
+ return overEvent.cancel === false && result !== false;
+ },
+
+ getDropPoint : function(e, n, dd){
+ var tn = n.node;
+ if(tn.isRoot){
+ return tn.allowChildren !== false ? 'ap-pend' : false; // always append for root
+ }
+ var dragEl = n.ddel;
+ var t = YAHOO.util.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
+ var y = YAHOO.util.Event.getPageY(e);
+ var noAppend = tn.allowChildren === false || tn.isLeaf();
+ if(this.appendOnly || tn.parentNode.allowChildren === false){
+ return noAppend ? false : 'append';
+ }
+ var noBelow = false;
+ if(!this.allowParentInsert){
+ noBelow = tn.hasChildNodes() && tn.isExpanded();
+ }
+ var q = (b - t) / (noAppend ? 2 : 3);
+ if(y >= t && y < t + q){
+ return 'above';
+ }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
+ return 'below';
+ }else{
+ return 'append';
+ }
+ return false;
+ },
+
+ onNodeEnter : function(n, dd, e, data){
+ this.cancelExpand();
+ },
+
+ onNodeOver : function(n, dd, e, data){
+ var pt = this.getDropPoint(e, n, dd);
+ var node = n.node;
+
+ // auto node expand check
+ if(!this.expandProcId && pt == 'append' && node.hasChildNodes() && !n.node.isExpanded()){
+ this.queueExpand(node);
+ }else if(pt != 'append'){
+ this.cancelExpand();
+ }
+
+ // set the insert point style on the target node
+ var returnCls = this.dropNotAllowed;
+ if(this.isValidDropPoint(n, pt, dd, e, data)){
+ if(pt){
+ var el = n.ddel;
+ var cls, returnCls;
+ if(pt == 'above'){
+ returnCls = n.node.isFirst() ? 'ytree-drop-ok-above' : 'ytree-drop-ok-between';
+ cls = 'ytree-drag-insert-above';
+ }else if(pt == 'below'){
+ returnCls = n.node.isLast() ? 'ytree-drop-ok-below' : 'ytree-drop-ok-between';
+ cls = 'ytree-drag-insert-below';
+ }else{
+ returnCls = 'ytree-drop-ok-append';
+ cls = 'ytree-drag-append';
+ }
+ if(this.lastInsertClass != cls){
+ YAHOO.util.Dom.replaceClass(el, this.lastInsertClass, cls);
+ this.lastInsertClass = cls;
+ }
+ }
+ }
+ return returnCls;
+ },
+
+ onNodeOut : function(n, dd, e, data){
+ this.cancelExpand();
+ this.removeDropIndicators(n);
+ },
+
+ onNodeDrop : function(n, dd, e, data){
+ var point = this.getDropPoint(e, n, dd);
+ var targetNode = n.node;
+ targetNode.ui.startDrop();
+ if(!this.isValidDropPoint(n, point, dd, e, data)){
+ targetNode.ui.endDrop();
+ return false;
+ }
+ // first try to find the drop node
+ var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
+ var dropEvent = {
+ tree : this.tree,
+ target: targetNode,
+ data: data,
+ point: point,
+ source: dd,
+ rawEvent: e,
+ dropNode: dropNode,
+ cancel: dropNode ? false : true
+ };
+ var retval = this.tree.fireEvent('beforenodedrop', dropEvent);
+ if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
+ targetNode.ui.endDrop();
+ return false;
+ }
+ if(point == 'append' && !targetNode.isExpanded()){
+ targetNode.expand(false, null, function(){
+ this.completeDrop(dropEvent);
+ }.createDelegate(this));
+ }else{
+ this.completeDrop(dropEvent);
+ }
+ return true;
+ },
+
+ completeDrop : function(de){
+ var ns = de.dropNode, p = de.point, t = de.target;
+ if(!(ns instanceof Array)){
+ ns = [ns];
+ }
+ var n;
+ for(var i = 0, len = ns.length; i < len; i++){
+ n = ns[i];
+ if(p == 'above'){
+ t.parentNode.insertBefore(n, t);
+ }else if(p == 'below'){
+ t.parentNode.insertBefore(n, t.nextSibling);
+ }else{
+ t.appendChild(n);
+ }
+ }
+ n.select(); // select and highlight the last insert
+ if(this.tree.hlDrop){
+ n.ui.highlight();
+ }
+ t.ui.endDrop();
+ this.tree.fireEvent('nodedrop', de);
+ },
+
+ afterNodeMoved : function(dd, data, e, targetNode, dropNode){
+ if(this.tree.hlDrop){
+ dropNode.select();
+ dropNode.ui.highlight();
+ }
+ this.tree.fireEvent('nodedrop', this.tree, targetNode, data, dd, e);
+ },
+
+ getTree : function(){
+ return this.tree;
+ },
+
+ removeDropIndicators : function(n){
+ if(n && n.ddel){
+ var el = n.ddel;
+ YAHOO.util.Dom.removeClass(el, 'ytree-drag-insert-above');
+ YAHOO.util.Dom.removeClass(el, 'ytree-drag-insert-below');
+ YAHOO.util.Dom.removeClass(el, 'ytree-drag-append');
+ this.lastInsertClass = '_noclass';
+ }
+ },
+
+ beforeDragDrop : function(target, e, id){
+ this.cancelExpand();
+ return true;
+ },
+
+ afterRepair : function(data){
+ if(data){
+ data.node.ui.highlight();
+ }
+ this.hideProxy();
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeFilter.js b/frontend/beta/js/YUI-extensions/tree/TreeFilter.js
new file mode 100644
index 0000000..9eeb274
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeFilter.js
@@ -0,0 +1,105 @@
+/**
+ * This doesn't update the indent (lines) or expand collapse icons of the nodes
+ */
+YAHOO.ext.tree.TreeFilter = function(tree, config){
+ this.tree = tree;
+ this.filtered = {};
+ YAHOO.ext.util.Config.apply(this, config, {
+ clearBlank:false,
+ reverse:false,
+ autoClear:false,
+ remove:false
+ });
+};
+
+YAHOO.ext.tree.TreeFilter.prototype = {
+ /**
+ * Filter the data by a specific attribute.
+ * @param {String/RegExp} value Either string that the attribute value
+ * should start with or a RegExp to test against the attribute
+ * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
+ * @param {TreeNode} startNode (optional) The node to start the filter at.
+ */
+ filter : function(value, attr, startNode){
+ attr = attr || 'text';
+ var f;
+ if(typeof value == 'string'){
+ var vlen = value.length;
+ // auto clear empty filter
+ if(vlen == 0 && this.clearBlank){
+ this.clearFilter();
+ return;
+ }
+ value = value.toLowerCase();
+ f = function(n){
+ return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
+ }
+ }else if(value.exec){ // regex?
+ f = function(n){
+ return value.test(n.attributes[attr]);
+ }
+ }else{
+ throw 'Illegal filter type, must be string or regex';
+ }
+ this.filterBy(f, null, startNode);
+ },
+
+ /**
+ * Filter by a function. The passed function will be called with each
+ * node in the tree (or from the startNode). If the function returns true, the node is kept
+ * otherwise it is filtered. If a node is filtered, it's children are also filtered.
+ * @param {Function} fn The filter function
+ * @param {Object} scope (optional) The scope of the function (defaults to the current node)
+ */
+ filterBy : function(fn, scope, startNode){
+ startNode = startNode || this.tree.root;
+ if(this.autoClear){
+ this.clearFilter();
+ }
+ var af = this.filtered, rv = this.reverse;
+ var f = function(n){
+ if(n == startNode){
+ return true;
+ }
+ if(af[n.id]){
+ return false;
+ }
+ var m = fn.call(scope || n, n);
+ if(!m || rv){
+ af[n.id] = n;
+ n.ui.hide();
+ return false;
+ }
+ return true;
+ }
+ startNode.cascade(f);
+ if(this.remove){
+ for(var id in af){
+ if(typeof id != 'function'){
+ var n = af[id];
+ if(n && n.parentNode){
+ n.parentNode.removeChild(n);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Clears the current filter. Note: with the "remove" option
+ * set a filter cannot be cleared.
+ */
+ clear : function(){
+ var t = this.tree;
+ var af = this.filtered;
+ for(var id in af){
+ if(typeof id != 'function'){
+ var n = af[id];
+ if(n){
+ n.ui.show();
+ }
+ }
+ }
+ this.filtered = {};
+ }
+};
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeLoader.js b/frontend/beta/js/YUI-extensions/tree/TreeLoader.js
new file mode 100644
index 0000000..34989bd
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeLoader.js
@@ -0,0 +1,107 @@
+YAHOO.ext.tree.TreeLoader = function(config){
+ this.baseParams = {};
+ this.requestMethod = 'POST';
+ YAHOO.ext.util.Config.apply(this, config);
+
+ this.events = {
+ 'beforeload' : true,
+ 'load' : true,
+ 'loadexception' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.tree.TreeLoader, YAHOO.ext.util.Observable, {
+ load : function(node, callback){
+ if(node.attributes.children){ // preloaded json children
+ var cs = node.attributes.children;
+ for(var i = 0, len = cs.length; i < len; i++){
+ node.appendChild(this.createNode(cs[i]));
+ }
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }else if(this.dataUrl){
+ this.requestData(node, callback);
+ }
+ },
+
+ 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){
+ var params = this.getParams(node);
+ var cb = {
+ success: this.handleResponse,
+ failure: this.handleFailure,
+ scope: this,
+ argument: {callback: callback, node: node}
+ };
+ this.transId = YAHOO.util.Connect.asyncRequest(this.requestMethod, this.dataUrl, cb, params);
+ }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()){
+ YAHOO.util.Connect.abort(this.transId);
+ }
+ },
+
+ createNode : function(attr){
+ if(this.applyLoader !== false){
+ attr.loader = this;
+ }
+ return(attr.leaf ?
+ new YAHOO.ext.tree.TreeNode(attr) :
+ new YAHOO.ext.tree.AsyncTreeNode(attr));
+ },
+
+ processResponse : function(response, node, callback){
+ var json = response.responseText;
+ try {
+ var o = eval('('+json+')');
+ for(var i = 0, len = o.length; i < len; i++){
+ node.appendChild(this.createNode(o[i]));
+ }
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }catch(e){
+ this.handleFailure(response);
+ }
+ },
+
+ 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();
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeNode.js b/frontend/beta/js/YUI-extensions/tree/TreeNode.js
new file mode 100644
index 0000000..c676481
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeNode.js
@@ -0,0 +1,300 @@
+/**
+ * @class YAHOO.ext.tree.TreeNode
+ * @extends YAHOO.ext.data.Node
+ * @cfg {Boolean} leaf true if this node is a leaf and does not have or cannot have children
+ * @cfg {Boolean} expanded true to start the node expanded
+ * @cfg {Boolean} draggable false to make this node undraggable if DD is on (default to true)
+ * @cfg {Boolean} isTarget false if this node cannot be drop on
+ * @cfg {Boolean} disabled true to start the node disabled
+ * @constructor
+ * @param {Object} attributes The attributes/config for the node
+ */
+YAHOO.ext.tree.TreeNode = function(attributes){
+ attributes = attributes || {};
+ if(typeof attributes == 'string'){
+ attributes = {text: attributes};
+ }
+ this.el = null;
+ this.childrenRendered = false;
+ this.rendered = false;
+ YAHOO.ext.tree.TreeNode.superclass.constructor.call(this, attributes);
+ this.expanded = attributes.expanded === true;
+ this.isTarget = attributes.isTarget !== false;
+ this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
+ this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
+ this.text = attributes.text;
+ this.disabled = attributes.disabled === true;
+
+ YAHOO.ext.util.Config.apply(this.events, {
+ 'textchange' : true,
+ 'beforeexpand' : true,
+ 'beforecollapse' : true,
+ 'expand' : true,
+ 'disabledchange' : true,
+ 'collapse' : true,
+ 'beforeclick':true,
+ 'click':true,
+ 'dblclick':true,
+ 'contentmenu':true,
+ 'beforechildrenrendered':true
+ });
+
+ var uiClass = this.attributes.uiProvider || YAHOO.ext.tree.TreeNodeUI;
+ this.ui = new uiClass(this);
+};
+YAHOO.extendX(YAHOO.ext.tree.TreeNode, YAHOO.ext.data.Node, {
+ isExpanded : function(){
+ return this.expanded;
+ },
+
+ getUI : function(){
+ return this.ui;
+ },
+
+ setFirstChild : function(node){
+ var of = this.firstChild;
+ YAHOO.ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
+ if(this.childrenRendered && of && node != of){
+ of.renderIndent(true, true);
+ }
+ if(this.rendered){
+ this.renderIndent(true, true);
+ }
+ },
+
+ setLastChild : function(node){
+ var ol = this.lastChild;
+ YAHOO.ext.tree.TreeNode.superclass.setLastChild.call(this, node);
+ if(this.childrenRendered && ol && node != ol){
+ ol.renderIndent(true, true);
+ }
+ if(this.rendered){
+ this.renderIndent(true, true);
+ }
+ },
+
+ // these methods are overridden to provide lazy rendering support
+ appendChild : function(){
+ var node = YAHOO.ext.tree.TreeNode.superclass.appendChild.apply(this, arguments);
+ if(node && this.childrenRendered){
+ node.render();
+ }
+ this.ui.updateExpandIcon();
+ return node;
+ },
+
+ removeChild : function(node){
+ this.ownerTree.getSelectionModel().unselect(node);
+ YAHOO.ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
+ // if it's been rendered remove dom node
+ if(this.childrenRendered){
+ node.ui.remove();
+ }
+ if(this.childNodes.length < 1){
+ this.collapse(false, false);
+ }else{
+ this.ui.updateExpandIcon();
+ }
+ return node;
+ },
+
+ insertBefore : function(node, refNode){
+ var newNode = YAHOO.ext.tree.TreeNode.superclass.insertBefore.apply(this, arguments);
+ if(newNode && refNode && this.childrenRendered){
+ node.render();
+ }
+ this.ui.updateExpandIcon();
+ return newNode;
+ },
+
+ setText : function(text){
+ var oldText = this.text;
+ this.text = text;
+ this.attributes.text = text;
+ if(this.rendered){ // event without subscribing
+ this.ui.onTextChange(this, text, oldText);
+ }
+ this.fireEvent('textchange', this, text, oldText);
+ },
+
+ select : function(){
+ this.getOwnerTree().getSelectionModel().select(this);
+ },
+
+ unselect : function(){
+ this.getOwnerTree().getSelectionModel().unselect(this);
+ },
+
+ isSelected : function(){
+ return this.getOwnerTree().getSelectionModel().isSelected(node);
+ },
+
+ expand : function(deep, anim, callback){
+ if(!this.expanded){
+ if(this.fireEvent('beforeexpand', this, deep, anim) === false){
+ return;
+ }
+ if(!this.childrenRendered){
+ this.renderChildren();
+ }
+ this.expanded = true;
+ if((this.getOwnerTree().animate && anim !== false) || anim){
+ this.ui.animExpand(function(){
+ this.fireEvent('expand', this);
+ if(typeof callback == 'function'){
+ callback(this);
+ }
+ if(deep === true){
+ this.expandChildNodes(true);
+ }
+ }.createDelegate(this));
+ return;
+ }else{
+ this.ui.expand();
+ this.fireEvent('expand', this);
+ if(typeof callback == 'function'){
+ callback(this);
+ }
+ }
+ }else{
+ if(typeof callback == 'function'){
+ callback(this);
+ }
+ }
+ if(deep === true){
+ this.expandChildNodes(true);
+ }
+ },
+
+ collapse : function(deep, anim){
+ if(this.expanded && (!this.isRoot || (this.isRoot && this.getOwnerTree().rootVisible))){
+ if(this.fireEvent('beforecollapse', this, deep, anim) === false){
+ return;
+ }
+ this.expanded = false;
+ if((this.getOwnerTree().animate && anim !== false) || anim){
+ this.ui.animCollapse(function(){
+ this.fireEvent('collapse', this);
+ if(deep === true){
+ this.collapseChildNodes(true);
+ }
+ }.createDelegate(this));
+ return;
+ }else{
+ this.ui.collapse();
+ this.fireEvent('collapse', this);
+ }
+ }
+ if(deep === true){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].collapse(true)
+ }
+ }
+ },
+
+ delayedExpand : function(delay){
+ if(!this.expandProcId){
+ this.expandProcId = this.expand.defer(delay, this);
+ }
+ },
+
+ cancelExpand : function(){
+ if(this.expandProcId){
+ clearTimeout(this.expandProcId);
+ }
+ this.expandProcId = false;
+ },
+
+ toggle : function(){
+ if(this.expanded){
+ this.collapse();
+ }else{
+ this.expand();
+ }
+ },
+
+ ensureVisible : function(){
+ if(this.parentNode){
+ this.parentNode.bubble(function(){
+ this.expand(false, false);
+ });
+ }
+ },
+
+ expandChildNodes : function(deep){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].expand(deep);
+ }
+ },
+
+ collapseChildNodes : function(deep){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].expand(deep);
+ }
+ },
+
+ disable : function(){
+ this.disabled = true;
+ this.unselect();
+ if(this.rendered && this.ui.onDisableChange){ // event without subscribing
+ this.ui.onDisableChange(this, true);
+ }
+ this.fireEvent('disabledchange', this, true);
+ },
+
+ enable : function(){
+ this.disabled = false;
+ if(this.rendered && this.ui.onDisableChange){ // event without subscribing
+ this.ui.onDisableChange(this, false);
+ }
+ this.fireEvent('disabledchange', this, false);
+ },
+
+ renderChildren : function(suppressEvent){
+ if(suppressEvent !== false){
+ this.fireEvent('beforechildrenrendered', this);
+ }
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].render(true);
+ }
+ this.childrenRendered = true;
+ },
+
+ sort : function(fn, scope){
+ YAHOO.ext.tree.TreeNode.superclass.sort.apply(this, arguments);
+ if(this.childrenRendered){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].render(true);
+ }
+ }
+ },
+
+ render : function(bulkRender){
+ this.ui.render(bulkRender);
+ if(!this.rendered){
+ this.rendered = true;
+ if(this.expanded){
+ this.expanded = false;
+ this.expand(false, false);
+ }
+ }
+ },
+
+ renderIndent : function(deep, refresh){
+ if(refresh){
+ this.ui.childIndent = null;
+ }
+ this.ui.renderIndent();
+ if(deep === true && this.childrenRendered){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].renderIndent(true, refresh);
+ }
+ }
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js b/frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js
new file mode 100644
index 0000000..80927f4
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeNodeUI.js
@@ -0,0 +1,452 @@
+/**
+ * The TreeNode UI implementation is separate from the
+ * tree implementation. Unless you are customizing the tree UI,
+ * you should never have to use this directly.
+ */
+YAHOO.ext.tree.TreeNodeUI = function(node){
+ this.node = node;
+ this.rendered = false;
+ this.animating = false;
+};
+
+YAHOO.ext.tree.TreeNodeUI.prototype = {
+ emptyIcon : Ext.BLANK_IMAGE_URL,
+
+ removeChild : function(node){
+ if(this.rendered){
+ this.ctNode.removeChild(node.ui.getEl());
+ }
+ },
+
+ beforeLoad : function(){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-loading');
+ },
+
+ afterLoad : function(){
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-node-loading');
+ },
+
+ onTextChange : function(node, text, oldText){
+ if(this.rendered){
+ this.textNode.innerHTML = text;
+ }
+ },
+
+ onDisableChange : function(node, state){
+ this.disabled = state;
+ if(state){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-disabled');
+ }else{
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-node-disabled');
+ }
+ },
+
+ onSelectedChange : function(state){
+ if(state){
+ this.focus();
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-selected');
+ }else{
+ this.blur();
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-selected');
+ }
+ },
+
+ onMove : function(tree, node, oldParent, newParent, index, refNode){
+ this.childIndent = null;
+ if(this.rendered){
+ var targetNode = newParent.ui.getContainer();
+ if(!targetNode){//target not rendered
+ this.holder = document.createElement('div');
+ this.holder.appendChild(this.wrap);
+ return;
+ }
+ var insertBefore = refNode ? refNode.ui.getEl() : null;
+ if(insertBefore){
+ targetNode.insertBefore(this.wrap, insertBefore);
+ }else{
+ targetNode.appendChild(this.wrap);
+ }
+ this.node.renderIndent(true);
+ }
+ },
+
+ remove : function(){
+ if(this.rendered){
+ this.holder = document.createElement('div');
+ this.holder.appendChild(this.wrap);
+ }
+ },
+
+ fireEvent : function(){
+ this.node.fireEvent.apply(this.node, arguments);
+ },
+
+ initEvents : function(){
+ this.node.on('move', this.onMove, this, true);
+ //this.node.on('hiddenchange', this.onHiddenChange, this, true);
+
+ // these were optimized out but a custom UI could use them
+ //this.node.on('remove', this.onChildRemoved, this, true);
+ //this.node.on('selectedstatechange', this.onSelectedChange, this, true);
+ //this.node.on('disabledchange', this.onDisableChange, this, true);
+ //this.node.on('textchange', this.onTextChange, this, true);
+
+ var E = YAHOO.util.Event;
+ var a = this.anchor;
+
+ var el = YAHOO.ext.Element.fly(a);
+
+ if(YAHOO.ext.util.Browser.isOpera){ // opera render bug ignores the CSS
+ el.setStyle('text-decoration', 'none');
+ }
+
+ el.mon('click', this.onClick, this, true);
+ el.mon('dblclick', this.onDblClick, this, true);
+ el.mon('contextmenu', this.onContextMenu, this, true);
+
+ //el.on('focus', function(){
+ // this.node.getOwnerTree().getSelectionModel().select(this.node);
+ //}, this, true);
+
+ var icon = YAHOO.ext.Element.fly(this.iconNode);
+ icon.mon('click', this.onClick, this, true);
+ icon.mon('dblclick', this.onDblClick, this, true);
+ icon.mon('contextmenu', this.onContextMenu, this, true);
+ E.on(this.ecNode, 'click', this.ecClick, this, true);
+
+ if(this.node.disabled){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-disabled');
+ }
+ if(this.node.hidden){
+ YAHOO.util.Dom.addClass(this.elNode, 'ytree-node-disabled');
+ }
+ var dd = this.node.ownerTree.enableDD || this.node.ownerTree.enableDrag || this.node.ownerTree.enableDrop;
+ if(dd && (!this.node.isRoot || this.node.ownerTree.rootVisible)){
+ YAHOO.ext.dd.Registry.register(this.elNode, {
+ node: this.node,
+ handles: [this.iconNode, this.textNode],
+ isHandle: false
+ });
+ }
+ },
+
+ hide : function(){
+ if(this.rendered){
+ this.wrap.style.display = 'none';
+ }
+ },
+
+ show : function(){
+ if(this.rendered){
+ this.wrap.style.display = '';
+ }
+ },
+
+ onContextMenu : function(e){
+ e.preventDefault();
+ this.focus();
+ this.fireEvent('contextmenu', this.node, e);
+ },
+
+ onClick : function(e){
+ if(this.dropping){
+ return;
+ }
+ if(this.fireEvent('beforeclick', this.node, e) !== false){
+ if(!this.disabled && this.node.attributes.href){
+ this.focus();
+ this.fireEvent('click', this.node, e);
+ return;
+ }
+ e.preventDefault();
+ if(this.disabled){
+ return;
+ }
+ this.focus();
+ this.fireEvent('click', this.node, e);
+ }else{
+ e.stopEvent();
+ }
+ },
+
+ onDblClick : function(e){
+ e.preventDefault();
+ if(this.disabled){
+ return;
+ }
+ if(!this.animating && this.node.hasChildNodes()){
+ this.node.toggle();
+ }
+ this.fireEvent('dblclick', this.node, e);
+ },
+
+ ecClick : function(e){
+ if(!this.animating && this.node.hasChildNodes()){
+ this.node.toggle();
+ }
+ },
+
+ startDrop : function(){
+ this.dropping = true;
+ },
+
+ // delayed drop so the click event doesn't get fired on a drop
+ endDrop : function(){
+ setTimeout(function(){
+ this.dropping = false;
+ }.createDelegate(this), 50);
+ },
+
+ expand : function(){
+ this.updateExpandIcon();
+ this.ctNode.style.display = '';
+ },
+
+ focus : function(){
+ try{
+ this.anchor.focus();
+ }catch(e){}
+ },
+
+ blur : function(){
+ try{
+ this.anchor.blur();
+ }catch(e){}
+ },
+
+ animExpand : function(callback){
+ if(this.animating && this.anim){
+ this.anim.stop();
+ }
+ this.animating = true;
+ this.updateExpandIcon();
+ var ct = this.ctNode;
+ var cs = ct.style;
+ cs.position = 'absolute';
+ cs.visibility = 'hidden';
+ cs.display = '';
+ var h = ct.clientHeight;
+ cs.overflow = 'hidden';
+ cs.height = '1px';
+ cs.position = '';
+ cs.visibility = '';
+ var anim = new YAHOO.util.Anim(ct, {
+ height: {to: h}
+ }, this.node.ownerTree.duration || .25, YAHOO.util.Easing.easeOut);
+ anim.onComplete.subscribe(function(){
+ cs.overflow = '';
+ cs.height = '';
+ this.animating = false;
+ this.anim = null;
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }, this, true);
+ this.anim = anim;
+ anim.animate();
+ },
+
+ highlight : function(){
+ var tree = this.node.getOwnerTree();
+ var hlColor = tree.hlColor || 'C3DAF9';
+ var hlBaseColor = tree.hlBaseColor || 'FFFFFF';
+ var anim = new YAHOO.util.ColorAnim(this.wrap, {
+ backgroundColor: {from: hlColor, to: hlBaseColor}
+ }, .75, YAHOO.util.Easing.easeNone);
+ anim.onComplete.subscribe(function(){
+ YAHOO.util.Dom.setStyle(this.wrap, 'background-color', '');
+ }, this, true);
+ anim.animate();
+ },
+
+ collapse : function(){
+ this.updateExpandIcon();
+ this.ctNode.style.display = 'none';
+ },
+
+ animCollapse : function(callback){
+ if(this.animating && this.anim){
+ this.anim.stop();
+ }
+ this.animating = true;
+ this.updateExpandIcon();
+ var ct = this.ctNode;
+ var cs = ct.style;
+ cs.height = ct.offsetHeight +'px';
+ cs.overflow = 'hidden';
+ var anim = new YAHOO.util.Anim(ct, {
+ height: {to: 1}
+ }, this.node.ownerTree.duration || .25, YAHOO.util.Easing.easeOut);
+ anim.onComplete.subscribe(function(){
+ cs.display = 'none';
+ cs.overflow = '';
+ cs.height = '';
+ this.animating = false;
+ this.anim = null;
+ if(typeof callback == 'function'){
+ callback();
+ }
+ }, this, true);
+ this.anim = anim;
+ anim.animate();
+ },
+
+ getContainer : function(){
+ return this.ctNode;
+ },
+
+ getEl : function(){
+ return this.wrap;
+ },
+
+ appendDDGhost : function(ghostNode){
+ ghostNode.appendChild(this.elNode.cloneNode(true));
+ },
+
+ getDDRepairXY : function(){
+ return YAHOO.util.Dom.getXY(this.iconNode);
+ },
+
+ onRender : function(){
+ this.render();
+ },
+
+ render : function(bulkRender){
+ var n = this.node;
+ var targetNode = n.parentNode ?
+ n.parentNode.ui.getContainer() : n.ownerTree.container.dom;
+ if(!this.rendered){
+ this.rendered = true;
+ var a = n.attributes;
+
+ // add some indent caching, this helps performance when rendering a large tree
+ this.indentMarkup = '';
+ if(n.parentNode){
+ this.indentMarkup = n.parentNode.ui.getChildIndent();
+ }
+
+ var buf = ['<li class="ytree-node"><div class="ytree-node-el ', n.attributes.cls,'">',
+ '<span class="ytree-node-indent">',this.indentMarkup,'</span>',
+ '<img src="', this.emptyIcon, '" class="ytree-ec-icon">',
+ '<img src="', a.icon || this.emptyIcon, '" class="ytree-node-icon',(a.icon ? ' ytree-node-inline-icon' : ''),'" unselectable="on">',
+ '<a href="',a.href ? a.href : '#','" tabIndex="1" ',
+ a.hrefTarget ? ' target="'+a.hrefTarget+'"' : '','><span unselectable="on">',n.text,'</span></a></div>',
+ '<ul class="ytree-node-ct" style="display:none;"></ul>',
+ '</li>'];
+
+ if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
+ this.wrap = YAHOO.ext.DomHelper.insertHtml('beforeBegin',
+ n.nextSibling.ui.getEl(), buf.join(''));
+ }else{
+ this.wrap = YAHOO.ext.DomHelper.insertHtml('beforeEnd', targetNode, buf.join(''));
+ }
+ this.elNode = this.wrap.childNodes[0];
+ this.ctNode = this.wrap.childNodes[1];
+ var cs = this.elNode.childNodes;
+ this.indentNode = cs[0];
+ this.ecNode = cs[1];
+ this.iconNode = cs[2];
+ this.anchor = cs[3];
+ this.textNode = cs[3].firstChild;
+ if(a.qtip){
+ if(this.textNode.setAttributeNS){
+ this.textNode.setAttributeNS('y', 'qtip', a.qtip);
+ if(a.qtipTitle){
+ this.textNode.setAttributeNS('y', 'qtitle', a.qtipTitle);
+ }
+ }else{
+ this.textNode.setAttribute('y:qtip', a.qtip);
+ if(a.qtipTitle){
+ this.textNode.setAttribute('y:qtitle', a.qtipTitle);
+ }
+ }
+ }
+ this.initEvents();
+ //this.renderIndent(); cached above now instead call updateExpandIcon
+ this.updateExpandIcon();
+ }else{
+ if(bulkRender === true) {
+ targetNode.appendChild(this.wrap);
+ }
+ }
+ },
+
+ getAnchor : function(){
+ return this.anchor;
+ },
+
+ getTextEl : function(){
+ return this.textNode;
+ },
+
+ getIconEl : function(){
+ return this.iconNode;
+ },
+
+ updateExpandIcon : function(){
+ if(this.rendered){
+ var n = this.node;
+ var cls = n.isLast() ? "ytree-elbow-end" : "ytree-elbow";
+ var hasChild = n.hasChildNodes();
+ if(hasChild){
+ cls += n.expanded ? '-minus' : '-plus';
+ var c1 = n.expanded ? 'ytree-node-collapsed' : 'ytree-node-expanded';
+ var c2 = n.expanded ? 'ytree-node-expanded' : 'ytree-node-collapsed';
+ YAHOO.util.Dom.removeClass(this.elNode, 'ytree-node-leaf');
+ YAHOO.util.Dom.replaceClass(this.elNode, c1, c2);
+ }else{
+ YAHOO.util.Dom.replaceClass(this.elNode, 'ytree-node-expanded', 'ytree-node-leaf');
+ }
+ this.ecNode.className = 'ytree-ec-icon '+cls;
+ }
+ },
+
+ getChildIndent : function(){
+ if(!this.childIndent){
+ var buf = [];
+ var p = this.node;
+ while(p){
+ if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
+ if(!p.isLast()) {
+ buf.unshift('<img src="'+this.emptyIcon+'" class="ytree-elbow-line">');
+ } else {
+ buf.unshift('<img src="'+this.emptyIcon+'" class="ytree-icon">');
+ }
+ }
+ p = p.parentNode;
+ }
+ this.childIndent = buf.join('');
+ }
+ return this.childIndent;
+ },
+
+ renderIndent : function(){
+ if(this.rendered){
+ var indent = '';
+ var p = this.node.parentNode;
+ if(p){
+ indent = p.ui.getChildIndent();
+ }
+ if(this.indentMarkup != indent){ // don't rerender if not required
+ this.indentNode.innerHTML = indent;
+ this.indentMarkup = indent;
+ }
+ this.updateExpandIcon();
+ }
+ }
+};
+
+YAHOO.ext.tree.RootTreeNodeUI = function(){
+ YAHOO.ext.tree.RootTreeNodeUI.superclass.constructor.apply(this, arguments);
+};
+YAHOO.extendX(YAHOO.ext.tree.RootTreeNodeUI, YAHOO.ext.tree.TreeNodeUI);
+YAHOO.ext.tree.RootTreeNodeUI.prototype.render = function(){
+ if(!this.rendered){
+ var targetNode = this.node.ownerTree.container.dom;
+ this.node.expanded = true;
+ targetNode.innerHTML = '<div class="ytree-root-node"></div>';
+ this.wrap = this.ctNode = targetNode.firstChild;
+ }
+};
+YAHOO.ext.tree.RootTreeNodeUI.prototype.collapse = function(){
+};
diff --git a/frontend/beta/js/YUI-extensions/tree/TreePanel.js b/frontend/beta/js/YUI-extensions/tree/TreePanel.js
new file mode 100644
index 0000000..202c0d0
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreePanel.js
@@ -0,0 +1,213 @@
+YAHOO.namespace('ext.tree');
+
+YAHOO.ext.tree.TreePanel = function(el, config){
+ YAHOO.ext.tree.TreePanel.superclass.constructor.call(this);
+ this.el = getEl(el);
+ this.id = this.el.id;
+ YAHOO.ext.util.Config.apply(this, config || {}, {
+ rootVisible : true,
+ lines : true,
+ enableDD : false,
+ hlDrop : true/*,
+ hlColor: null,
+ ddGroup : 'TreeDD'
+ hlBaseColor : 'FFFFFF'*/
+
+ });
+ YAHOO.ext.util.Config.apply(this.events, {
+ 'beforeload' : true,
+ 'load' : true,
+ 'textchange' : true,
+ 'beforeexpand' : true,
+ 'beforecollapse' : true,
+ 'expand' : true,
+ 'collapse' : true,
+ 'disabledchange' : true,
+ 'beforeclick':true,
+ 'click':true,
+ 'dblclick':true,
+ 'contentmenu':true,
+ 'beforechildrenrendered':true,
+ /**
+ * @event startdrag
+ * Fires when a node starts being dragged
+ * @param {YAHOO.ext.tree.TreePanel} this
+ * @param {YAHOO.ext.tree.TreeNode} node
+ * @param {event} e The raw browser event
+ */
+ 'startdrag' : true,
+ /**
+ * @event enddrag
+ * Fires when a drag operation is complete
+ * @param {YAHOO.ext.tree.TreePanel} this
+ * @param {YAHOO.ext.tree.TreeNode} node
+ * @param {event} e The raw browser event
+ */
+ 'enddrag' : true,
+ /**
+ * @event dragdrop
+ * Fires when a dragged node is dropped on a valid DD target
+ * @param {YAHOO.ext.tree.TreePanel} this
+ * @param {YAHOO.ext.tree.TreeNode} node
+ * @param {DD} dd The dd it was dropped on
+ * @param {event} e The raw browser event
+ */
+ 'dragdrop' : true,
+ /**
+ * @event beforenodedrop
+ * Fires when a DD object is dropped on a node in this tree for preprocessing. This event can cancel.
+ * @param {Object} dropEvent
+ */
+ 'beforenodedrop' : true,
+ /**
+ * @event nodedrop
+ * Fires after a DD object is dropped on a node in this tree
+ * @param {Object} dropEvent
+ */
+ 'nodedrop' : true,
+ /**
+ * @event nodedragover
+ * Fires when a tree node is being target
+ * @param {Object} dragOverEvent
+ */
+ 'nodedragover' : true
+ });
+ if(this.singleExpand){
+ this.on('beforeexpand', this.restrictExpand, this, true);
+ }
+ // problem with safari and animation
+ // I am investigating
+ if(YAHOO.ext.util.Browser.isSafari){
+ this.animate = false;
+ }
+};
+YAHOO.extendX(YAHOO.ext.tree.TreePanel, YAHOO.ext.data.Tree, {
+ restrictExpand : function(node){
+ var p = node.parentNode;
+ if(p){
+ if(p.expandedChild && p.expandedChild.parentNode == p){
+ p.expandedChild.collapse();
+ }
+ p.expandedChild = node;
+ }
+ },
+
+ setRootNode : function(node){
+ YAHOO.ext.tree.TreePanel.superclass.setRootNode.call(this, node);
+ if(!this.rootVisible){
+ node.ui = new YAHOO.ext.tree.RootTreeNodeUI(node);
+ }
+ return node;
+ },
+
+ getEl : function(){
+ return this.el;
+ },
+
+ getLoader : function(){
+ return this.loader;
+ },
+
+ expandAll : function(){
+ this.root.expand(true);
+ },
+
+ collapseAll : function(){
+ this.root.collapse(true);
+ },
+
+ getSelectionModel : function(){
+ if(!this.selModel){
+ this.selModel = new YAHOO.ext.tree.DefaultSelectionModel();
+ }
+ return this.selModel;
+ },
+
+ expandPath : function(path, attr, callback){
+ attr = attr || 'id';
+ var keys = path.split(this.pathSeparator);
+ var curNode = this.root;
+ if(curNode.attributes[attr] != keys[1]){ // invalid root
+ if(callback){
+ callback(false, null);
+ }
+ return;
+ }
+ var index = 1;
+ var f = function(){
+ if(++index == keys.length){
+ if(callback){
+ callback(true, curNode);
+ }
+ return;
+ }
+ var c = curNode.findChild(attr, keys[index]);
+ if(!c){
+ if(callback){
+ callback(false, curNode);
+ }
+ return;
+ }
+ curNode = c;
+ c.expand(false, false, f);
+ }
+ curNode.expand(false, false, f);
+ },
+
+ selectPath : function(path, attr, callback){
+ attr = attr || 'id';
+ var keys = path.split(this.pathSeparator);
+ var v = keys.pop();
+ if(keys.length > 0){
+ var f = function(success, node){
+ if(success && node){
+ var n = node.findChild(attr, v);
+ if(n){
+ n.select();
+ if(callback){
+ callback(true, n);
+ }
+ }
+ }else{
+ if(callback){
+ callback(false, n);
+ }
+ }
+ };
+ this.expandPath(keys.join(this.pathSeparator), attr, f);
+ }else{
+ this.root.select();
+ if(callback){
+ callback(true, this.root);
+ }
+ }
+ },
+
+ render : function(){
+ this.container = this.el.createChild({tag:'ul',
+ cls:'ytree-root-ct ' +
+ (this.lines ? 'ytree-lines' : 'ytree-no-lines')});
+
+ if(this.containerScroll){
+ YAHOO.ext.dd.ScrollManager.register(this.el);
+ }
+
+ if((this.enableDD || this.enableDrop) && !this.dropZone){
+ this.dropZone = new YAHOO.ext.tree.TreeDropZone(this, this.dropConfig || {
+ ddGroup: this.ddGroup || 'TreeDD'
+ });
+ }
+ if((this.enableDD || this.enableDrag) && !this.dragZone){
+ this.dragZone = new YAHOO.ext.tree.TreeDragZone(this, this.dragConfig || {
+ ddGroup: this.ddGroup || 'TreeDD',
+ scroll: this.ddScroll
+ });
+ }
+ this.getSelectionModel().init(this);
+ this.root.render();
+ if(!this.rootVisible){
+ this.root.renderChildren();
+ }
+ return this;
+ }
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js b/frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js
new file mode 100644
index 0000000..4fed88e
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeSelectionModel.js
@@ -0,0 +1,195 @@
+YAHOO.ext.tree.DefaultSelectionModel = function(){
+ this.selNode = null;
+
+ this.events = {
+ 'selectionchange' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.tree.DefaultSelectionModel, YAHOO.ext.util.Observable, {
+ init : function(tree){
+ this.tree = tree;
+ tree.el.mon('keydown', this.onKeyDown, this, true);
+ tree.on('click', this.onNodeClick, this, true);
+ },
+
+ onNodeClick : function(node, e){
+ this.select(node);
+ },
+
+ select : function(node){
+ if(this.selNode && this.selNode != node){
+ this.selNode.ui.onSelectedChange(false);
+ }
+ this.selNode = node;
+ node.ui.onSelectedChange(true);
+ this.fireEvent('selectionchange', this, node);
+ return node;
+ },
+
+ unselect : function(node){
+ if(this.selNode == node){
+ this.clearSelections();
+ }
+ },
+
+ clearSelections : function(){
+ var n = this.selNode;
+ if(n){
+ n.ui.onSelectedChange(false);
+ this.selNode = null;
+ this.fireEvent('selectionchange', this, null);
+ }
+ return n;
+ },
+
+ getSelectedNode : function(){
+ return this.selNode;
+ },
+
+ isSelected : function(node){
+ return this.selNode == node;
+ },
+
+ onKeyDown : function(e){
+ var s = this.selNode || this.lastSelNode;
+ // undesirable, but required
+ var sm = this;
+ if(!s){
+ return;
+ }
+ var k = e.getKey();
+ //alert(k)
+ switch(k){
+ case e.DOWN:
+ e.preventDefault();
+ if(s.firstChild && s.isExpanded()){
+ this.select(s.firstChild, e);
+ }else if(s.nextSibling){
+ this.select(s.nextSibling, e);
+ }else if(s.parentNode){
+ s.parentNode.bubble(function(){
+ if(this.nextSibling){
+ sm.select(this.nextSibling, e);
+ return false;
+ }
+ });
+ }
+ break;
+ case e.UP:
+ e.preventDefault();
+ var ps = s.previousSibling;
+ if(ps){
+ if(!ps.isExpanded()){
+ this.select(ps, e);
+ }else{
+ var lc = ps.lastChild;
+ while(lc && lc.isExpanded()){
+ lc = lc.lastChild;
+ }
+ this.select(lc, e);
+ }
+ }else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
+ this.select(s.parentNode, e);
+ }
+ break;
+ case e.RIGHT:
+ e.preventDefault();
+ if(s.hasChildNodes()){
+ if(!s.isExpanded()){
+ s.expand();
+ }else if(s.firstChild){
+ this.select(s.firstChild, e);
+ }
+ }
+ break;
+ case e.LEFT:
+ e.preventDefault();
+ if(s.hasChildNodes() && s.isExpanded()){
+ s.collapse();
+ }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
+ this.select(s.parentNode, e);
+ }
+ break;
+ };
+ }
+});
+
+YAHOO.ext.tree.MultiSelectionModel = function(){
+ this.selNodes = [];
+ this.selMap = {};
+ this.events = {
+ 'selectionchange' : true
+ };
+};
+
+YAHOO.extendX(YAHOO.ext.tree.MultiSelectionModel, YAHOO.ext.util.Observable, {
+ init : function(tree){
+ this.tree = tree;
+ tree.el.mon('keydown', this.onKeyDown, this, true);
+ tree.on('click', this.onNodeClick, this, true);
+ },
+
+ onNodeClick : function(node, e){
+ this.select(node, e, e.ctrlKey);
+ },
+
+ select : function(node, e, keepExisting){
+ if(keepExisting !== true){
+ this.clearSelections(true);
+ }
+ this.selNodes.push(node);
+ this.selMap[node.id] = node;
+ this.lastSelNode = node;
+ node.ui.onSelectedChange(true);
+ this.fireEvent('selectionchange', this, this.selNodes);
+ return node;
+ },
+
+ unselect : function(node){
+ if(this.selMap[node.id]){
+ node.ui.onSelectedChange(false);
+ var sn = this.selNodes;
+ var index = -1;
+ if(sn.indexOf){
+ index = sn.indexOf(node);
+ }else{
+ for(var i = 0, len = sn.length; i < len; i++){
+ if(sn[i] == node){
+ index = i;
+ break;
+ }
+ }
+ }
+ if(index != -1){
+ this.selNodes.splice(index, 1);
+ }
+ delete this.selMap[node.id];
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ },
+
+ clearSelections : function(suppressEvent){
+ var sn = this.selNodes;
+ if(sn.length > 0){
+ for(var i = 0, len = sn.length; i < len; i++){
+ sn[i].ui.onSelectedChange(false);
+ }
+ this.selNodes = [];
+ this.selMap = {};
+ if(suppressEvent !== true){
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ }
+ },
+
+ isSelected : function(node){
+ return this.selMap[node.id] ? true : false;
+ },
+
+ getSelectedNodes : function(){
+ return this.selNodes;
+ },
+
+ onKeyDown : YAHOO.ext.tree.DefaultSelectionModel.prototype.onKeyDown
+});
diff --git a/frontend/beta/js/YUI-extensions/tree/TreeSorter.js b/frontend/beta/js/YUI-extensions/tree/TreeSorter.js
new file mode 100644
index 0000000..9960703
--- a/dev/null
+++ b/frontend/beta/js/YUI-extensions/tree/TreeSorter.js
@@ -0,0 +1,49 @@
+YAHOO.ext.tree.TreeSorter = function(tree, config){
+ YAHOO.ext.util.Config.apply(this, config);
+ tree.on('beforechildrenrendered', this.doSort, this, true);
+ tree.on('append', this.updateSort, this, true);
+ tree.on('insert', this.updateSort, this, true);
+
+ var dsc = this.dir && this.dir.toLowerCase() == 'desc';
+ var p = this.property || 'text';
+ var sortType = this.sortType;
+ var fs = this.folderSort;
+ var cs = this.caseSensitive === true;
+
+ this.sortFn = function(n1, n2){
+ if(fs){
+ if(n1.leaf && !n2.leaf){
+ return 1;
+ }
+ if(!n1.leaf && n2.leaf){
+ return -1;
+ }
+ }
+ var v1 = sortType ? sortType(n1) : (cs ? n1[p] : n1[p].toUpperCase());
+ var v2 = sortType ? sortType(n2) : (cs ? n2[p] : n2[p].toUpperCase());
+ if(v1 < v2){
+ return dsc ? +1 : -1;
+ }else if(v1 > v2){
+ return dsc ? -1 : +1;
+ }else{
+ return 0;
+ }
+ };
+};
+
+YAHOO.ext.tree.TreeSorter.prototype = {
+ doSort : function(node){
+ node.sort(this.sortFn);
+ },
+
+ compareNodes : function(n1, n2){
+
+ return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
+ },
+
+ updateSort : function(tree, node){
+ if(node.childrenRendered){
+ this.doSort.defer(1, this, [node]);
+ }
+ }
+};