| Current Path : /home/sunflowe/www2/j15/administrator/components/com_rokdownloads/assets/tree/ |
| Current File : /home/sunflowe/www2/j15/administrator/components/com_rokdownloads/assets/tree/mootree.js |
/*
Script: mootreeWithDDandKeyNav.js
My Object Oriented Tree
- Developed by moro magmoro@mail.ru, based on MooTree rev.16 by Rasmus Schultz
- Tested with MooTools release 1.11, under Firefox 2 IE6 and Opera9.
License:
MIT-style license.
Credits:
MooTree rev.16 by Rasmus Schultz, http://www.mindplay.dk/mootree
*/
var MooTreeIcon = ['I','L','Lminus','Lplus','Rminus','Rplus','T','Tminus','Tplus','_closed','_doc','_open','minus','plus'];
/*
Class: MooTreeControl
This class implements a tree control.
Properties:
root - returns the root <MooTreeNode> object.
selected - returns the currently selected <MooTreeNode> object, or null if nothing is currently selected.
Events:
onExpand - called when a node is expanded or collapsed: function(node, state) - where node is the <MooTreeNode> object that fired the event, and state is a boolean meaning true:expanded or false:collapsed.
onSelect - called when a node is selected or deselected: function(node, state) - where node is the <MooTreeNode> object that fired the event, and state is a boolean meaning true:selected or false:deselected.
onClick - called when a node is clicked: function(node) - where node is the <MooTreeNode> object that fired the event.
onReplace - event fired when u replace node. Takes 3 parameters - [fromNode,toNode,where]. ( where='after' || 'before' || 'inside')
Options:
div - a string representing the div Element inside which to build the tree control.
mode - optional string, defaults to 'files' - specifies default icon behavior. In 'files' mode, empty nodes have a document icon - whereas, in 'folders' mode, all nodes are displayed as folders (a'la explorer).
- boolean, defaults to false. If set to true, a grid is drawn to outline the structure of the tree.
theme - string, optional, defaults to 'mootree.gif' - specifies the 'theme' GIF to use.
loader - optional, an options object for the <MooTreeNode> constructor - defaults to {icon:'mootree_loader.gif', text:'Loading...', color:'a0a0a0'}
nodeOptions - node options
onExpand - optional function (see Events above)
onSelect - optional function (see Events above)
onClick - optional function (see Events above)//deprecated. will be removed or changed soon.
onReplace - optional function (see Events above)
Note: for scrolling(when div has overflow) i'm use fixed version of Fx.Scroll (see source code of my mootools version)
*/
var MooTreeControl = new Class({
options:{
onExpand: Class.empty,
onSelect: Class.empty,
onClick: Class.empty,
onReplace: Class.empty,
onContextSelect: Class.empty,
theme : 'mootree.gif'
},
initialize: function(options) {
this.initGarbageCollector();
this.setOptions(options);
var options=this.options;
this.index = {};
this.div=$(this.options.div); // tells the root node which div to insert itself into
nodeOptions=$extend(options.nodeOptions,{'div' : this.div,'control':this});
this.root = new MooTreeNode(nodeOptions); // create the root node of this tree control
this.root.addInTree();
// used by the get() method
this.enabled = true; // enable visual updates of the control
if (options.contextMenuOptions) {
this.menu = new MooTreeContextMenu(options.contextMenuOptions);
};
this.DDinit();
this.theme = options.theme;
this.loader = options.loader || {icon:'mootree_loader.gif', text:'Loading...', color:'#a0a0a0'};
this.selected = null; // set the currently selected node to nothing
this.mode = options.mode; // mode can be "folders" or "files", and affects the default icons
this.grid = options.grid; // grid can be turned on (true) or off (false)
this.root.update(true);
this.keyNavigation();//implement key navigation. Walk with ['up','down','left','right'] keys.
},
addRootNode: function(newRootOptions) {
this.root.remove();
nodeOptions=$extend(newRootOptions,{'div' : $(this.options.div),'control':this});
this.root = new MooTreeNode(nodeOptions); // create the root node of this tree control
this.root.addInTree();
},
/*
Property: select
Sets the currently selected node.
This is called by <MooTreeNode> when a node is selected (e.g. by clicking it's title with the mouse).
Parameters:
node - the <MooTreeNode> object to select.
*/
select: function(node) {
if (this.menu) this.menu.fireEvent('end');
this.fireEvent('onClick',[node]); node.fireEvent('onClick'); // fire click events ???
if (this.selected === node) return; // already selected
if (this.selected) {
// deselect previously selected node:
this.selected.select(false);
this.fireEvent('onSelect',[this.selected, false]);
}
// select new node:
this.selected = node;
node.select(true);
this.fireEvent('onSelect',[node, true]);
},
/*
Property: contextSelect
Activates the context (right click) for a node
Parameters:
node - the <MooTreeNode> object right clicked
*/
contextSelect: function(node, event) {
this.fireEvent('onContextSelect',[node]);
node.fireEvent('onContextSelect'); //fire contextmenu click events
if (this.menu) this.menu.fireEvent('start',[node,event]);
//return false;
},
/*
Property: expand
Expands the entire tree, recursively.
*/
expand: function() {
this.root.toggle(true, true);
},
/*
Property: collapse
Collapses the entire tree, recursively.
*/
collapse: function() {
this.root.toggle(true, false);
},
/*
Property: get
Retrieves the node with the given id - or null, if no node with the given id exists.
Parameters:
id - a string, the id of the node you wish to retrieve.
Note:
Node id can be assigned via the <MooTreeNode> constructor, e.g. using the <MooTreeNode.insert> method.
*/
get: function(id) {
return this.index[id] || null;
},
/*
Property: adopt
Adopts a structure of nested ul/li/a elements as tree nodes, then removes the original elements.
Parameters:
id - a string representing the ul element to be adopted, or an element reference.
parentNode - optional, a <MooTreeNode> object under which to import the specified ul element. Defaults to the root node of the parent control.
Note:
The ul/li structure must be properly nested, and each li-element must contain one a-element, e.g.:
><ul id="mytree">
> <li><a href="test.html">Item One</a></li>
> <li><a href="test.html">Item Two</a>
> <ul>
> <li><a href="test.html">Item Two Point One</a></li>
> <li><a href="test.html">Item Two Point Two</a></li>
> </ul>
> </li>
> <li><a href="test.html"><!-- icon:_doc; color:#ff0000 -->Item Three</a></li>
></ul>
The "href", "target", "title" and "name" attributes of the a-tags are picked up and stored in the
data property of the node.
CSS-style comments inside a-tags are parsed, and treated as arguments for <MooTreeNode> constructor,
e.g. "icon", "openicon", "color", etc.
*/
adopt: function(id, parent) {
if (parent === undefined) parent = this.root;
this.disable();
this._adopt(id, parent);
parent.update(true);
$(id).remove();
this.enable();
},
_adopt: function(id, parent) {
/* adopts a structure of ul/li elements into this tree */
$(id).getChildren()
.each(function(child){
if (child.getTag() != 'li') return false;
var config={text:''}, comment='', subuls=[];
child.getChildren().each(function(child){
switch ( child.getTag() ) {
case 'a':
$A(child.childNodes).each(function(child){
switch ( child.nodeName ) {
case '#text': config.text += child.nodeValue; break;
case '#comment': comment += child.nodeValue; break;
}
});
config.data = child.getProperties('href','target','title','name');
break;
case 'ul':
subuls.push(child);
break;
}
});
config.data.url = config.data['href']; // (for backwards compatibility)
if (comment != '') {
comment.split(';')
.each(function(propValue){
var propValueArray=propValue.split(':');
if(propValueArray.length == 2) config[propValueArray[0].trim()]=propValueArray[1].trim();
});
}
var node = parent.insert(config);
if(subuls.length){
subuls.each(function(subul){
this._adopt(subul, node);
}.bind(this));
}
}.bind(this));
},
/*
Property: disable
Call this to temporarily disable visual updates -- if you need to insert/remove many nodes
at a time, many visual updates would normally occur. By temporarily disabling the control,
these visual updates will be skipped.
When you're done making changes, call <MooTreeControl.enable> to turn on visual updates
again, and automatically repaint all nodes that were changed.
*/
disable: function() {
this.enabled = false;
},
/*
Property: enable
Enables visual updates again after a call to <MooTreeControl.disable>
*/
enable: function() {
this.enabled = true;
this.root.update(true, true);
},
keyNavigation: function(){
this.div.addEvent('keydown',function(event){
var event=new Event(event);
if(!this.scrolled){
if(!this.selected){
this.select(this.root);
}else{
var current=this.selected;
switch (event.key){
case 'down': this.goForward(current);break;
case 'up': this.goBack(current);break;
case 'left':this.goLeft(current);break;
case 'right': this.goRight(current);break;
}
}
this.chkOverflow();
}
if (!event.alt && !event.meta && !event.control && !event.shift) event.stop();
}.bind(this));
},
goForward: function(current){
var forward=current.getForward();
if( forward ) this.select(forward);
},
goBack: function(current){
var back=current.getBack();
if (back) this.select(back);
},
goLeft: function(current){
if(current.isRoot()){
if(current.open){
current.toggle(false);
}else{
return false;
}
}else{
if( current.nodes.length && current.open ){
current.toggle(false);
}else{
return this.select(current.parent);
}
}
},
goRight: function(current){
if(!current.nodes.length){
return false;
}else if(!current.open){
return current.toggle(false);
}else{
return this.select(current.getFirst());
}
},
replace: function(from,to,where){
if ( from.replaceTo(to,where) ){
this.fireEvent('onReplace',[from,to,where]);
}
},
chkOverflow: function(){//still there is some issues
var treeCoords=this.div.getCoordinates();
var current=this.selected;
var node=current.divs.node;
var text=current.divs.text;
var TextPosition=text.getPosition([this.div]);
var NodePosition=node.getPosition([this.div]);
var hide=[];
var size=this.div.getSize();
var scrollSize=size.scroll;
var scrollHeight=(size.size.x-size.scrollSize.x>18 ? 0 : 25);
if( NodePosition.y +node.offsetHeight + scrollHeight> treeCoords.bottom ) hide.push('bottom');
if( NodePosition.y - node.offsetHeight+2 < treeCoords.top ) hide.push('top');
if( TextPosition.x +text.offsetWidth> treeCoords.right ) hide.push('right');
if( NodePosition.x < treeCoords.left ) hide.push('left');
if( hide.length > 0 ){
this.scrolled=true;
var scroll=new Fx.Scroll(this.div,{
overflown: [this.div],
onComplete:function(){this.scrolled=false;}.bind(this)
});
var scrollSize=this.div.getSize().scroll;
if( hide.contains('top') ) {
var back=current.getBack();
if( back ) {
scroll.toElement(back.divs.node);
}else{
scroll.toElement(node);
}
}else if( hide.contains('bottom') ){
scroll.scrollTo(scrollSize.x,scrollSize.y+node.offsetHeight);
}else{
scroll.toElement(node);
}
}
},
initGarbageCollector: function(){
//memoryLeakFix
this.GarbageCollector=[];
window.addListener('beforeunload', function(){
window.addListener('unload', function(){this.GarbageCollector.each(function(el){el.treeNode=null;});}.bind(this));
}.bind(this));
},
DDinit: function(){
this.mouse={
down:false,
drag:false
};
this.DDnotAllowed=[];
this.DDinjectEvents();
this.DDEvents();
this.DDfixUp();
},
DDEvents: function(){
this.div.addEvents({
'mousedown' : this.DDdownEvent.bindWithEvent(this),
'mouseup' : this.DDupEvent.bind(this),
'mousemove' : this.DDmoveEvent.bindWithEvent(this)
});
},
DDdownEvent: function(event){
var event=new Event(event);
if (!this.DDfindNode(event.target)) return;
this.mouse.down=true;
if(!event.rightClick) event.stop();
},
DDupEvent: function(){
if (this.mouse.down && this.mouse.drag) {
if (this.DDghost) this.DDghost.remove();
this.DDclean();
!this.DDexternalUp ? this.DDreplace() : this.DDexternalUp=false;
}
this.mouse.down=false;
this.mouse.drag=false;
},
DDmoveEvent: function(event){
if(!this.mouse.down) return false;
var event=new Event(event);
if( !this.mouse.drag ) this.DDaddGhost(event);
var target=this.DDfindNode(event.target);
if ( !target ) return;
if( !target.selected ) this.select(target.treeNode);
if ( !this.scrolled ) this.chkOverflow();
this.DDgetDropPlace(event);
},
DDfixUp: function(){
document.addEvent('mouseup',function(){
if(this.mouse.down && this.mouse.drag){
this.DDexternalUp=true;
this.div.fireEvent('mouseup');
}
}.bind(this));
},
DDinjectEvents: function(){
['After', 'Before', 'Inside', 'NotAllowed'].each(function(where){
this.addEvent('onDDinject'+where, this.DDinjectEvent.bind(this));
}.bind(this));
},
DDinjectEvent: function(where,target){
if([where,target].eq(this.DDinject)) return;
this.DDclean();
this.DDinject=[where,target];
switch(where){
case 'before':
case 'after' :
var side=(where=='after' ? 'bottom' : 'top');
this.DDinjector=new Element('div',{'styles':
{
position:'absolute',
left:0,
width:100,
height:0,
'border-top':'dashed 1px red',
overflow:'hidden',
cursor:'default',
'z-index':100
}
})
.setStyle(side,0)
.injectInside(this.DDinject[1]);
break;
case 'inside':
var text=target.getLast();
text.setStyle('border','dashed 1px blue');
this.DDinside=text;
var node=target.treeNode;
if(!node.open && !this.DDopenTimer){
target.setStyle('cursor','progress');
text.setStyle('cursor','progress');
this.DDopenTimer=function(node){
node.toggle(false,true);
this.DDopenTimer=false;
this.DDinside.setStyle('cursor','default');
this.DDinside.getParent().setStyle('cursor','default');
}.delay(600,this,[node]);
};
break;
case 'notAllowed' :
this.DDnotAllowed.push(target);
this.DDnotAllowed.push(target.getLast());
if(!target.oldCursor) target.oldCursor=target.getStyle('cursor');
target.setStyle('cursor','not-allowed');
if(!target.getLast().oldCursor) target.getLast().oldCursor=target.getLast().getStyle('cursor');
target.getLast().setStyle('cursor','not-allowed');
}
},
DDclean: function(){
if(this.DDinside){
this.DDinside.setStyles({
'border' : 'none',
'cursor' : 'default'
});
this.DDinside=false;
}
if(this.DDnotAllowed.length>0){
this.DDnotAllowed.each(function(el){
el.setStyle('cursor',el.oldCursor);
});
this.DDnotAllowed=[];
}
if(this.DDinjector){
try{this.DDinjector.remove();}catch(e){};
this.DDinjector=false;
}
if(this.DDopenTimer){
$clear(this.DDopenTimer);
this.DDopenTimer=false;
}
},
DDfindNode: function(el){
if ($type(el) == 'element') {
if($type(el) != 'element' || el.tagName == 'BODY') return false;
do {
if(el.tagName == 'BODY'){return false;}
if(el.treeNode) return el;
} while(el=(el.getParent) ? el.getParent() : false)
}
},
DDaddGhost: function(event){
node=this.DDfindNode(event.target);
if(node){
this.DDnode=node.treeNode;
clone=node.clone(true)
.injectInside($(document.body))
.addClass('mooTree_node')
.setStyles({
position:'absolute',
left:event.page.x+20,
top:event.page.y+20,
opacity:0.7,
width:node.getFirst().offsetWidth+node.getLast().offsetWidth + 100,
height:node.offsetHeight
});
clone.makeDraggable({
onComplete: function(){
var el=this.element;
if(el){try{el.remove();}catch(e){};}
}
}).start(event);
this.DDghost=clone;
}
this.mouse.drag=true;
},
DDgetDropPlace: function(event){
var target=this.DDfindNode(event.target);
if( target ){
this.DDtargetNode=target.treeNode;
var height=target.offsetHeight;
var top=target.getPosition([this.div]).y;
var bottom=top+height;
if(
event.page.y-top<1/4*height &&
!this.DDtargetNode.isRoot() &&
!this.DDnode.contain(this.DDtargetNode)
){
this.fireEvent('onDDinjectBefore',['before',target]);
}else if(
bottom-event.page.y<1/4*height &&
!this.DDtargetNode.isRoot() &&
!this.DDnode.contain(this.DDtargetNode)
){
this.fireEvent('onDDinjectAfter',['after',target]);
}else{
if(this.DDtargetNode.options.DDtype=='file' ||
( this.DDtargetNode.isRoot() &&
(event.page.y-top<1/4*height || bottom-event.page.y<1/4*height)
) ||
this.DDnode.contain(this.DDtargetNode)
){
this.fireEvent('onDDinjectNotAllowed',['notAllowed',target]);
}else{
this.fireEvent('onDDinjectInside',['inside',target]);
}
}
}else{
this.DDclean();
this.DDinject=null;
}
},
DDreplace: function(){
if(this.DDtargetNode && this.DDnode && this.DDinject){
if(this.DDinject[0]=='notAllowed') return;
this.replace(this.DDnode,this.DDtargetNode,this.DDinject[0]);
this.DDtargetNode=false;
this.DDnode=false;
}
}
});
MooTreeControl.implement(new Events, new Options);
/*
Class: MooTreeNode
This class implements the functionality of a single node in a <MooTreeControl>.
Parameters:
options - an object. See options below.
Options:
text - this is the displayed text of the node, and as such as is the only required parameter.
id - string, optional - if specified, must be a unique node identifier. Nodes with id can be retrieved using the <MooTreeControl.get> method.
color - string, optional - if specified, must be a six-digit hexadecimal RGB color code.
open - boolean value, defaults to false. Use true if you want the node open from the start.
icon - use this to customize the icon of the node. The following predefined values may be used: '_open', '_closed' and '_doc'. Alternatively, specify the URL of a GIF or PNG image to use - this should be exactly 18x18 pixels in size. If you have a strip of images, you can specify an image number (e.g. 'my_icons.gif#4' for icon number 4).
openicon - use this to customize the icon of the node when it's open.
data - an object containing whatever data you wish to associate with this node (such as an url and/or an id, etc.)
DDtype - if DDtype='file' u can't replace nodes inside this.
Events:
onExpand - called when the node is expanded or collapsed: function(state) - where state is a boolean meaning true:expanded or false:collapsed.
onSelect - called when the node is selected or deselected: function(state) - where state is a boolean meaning true:selected or false:deselected.
onClick - called when the node is clicked (no arguments).
*/
var MooTreeNode = new Class({
options:{
onExpand: Class.empty,
onSelect: Class.empty,
onClick: Class.empty,
onContextSelect: Class.empty
},
initialize: function(options) {
this.setOptions(options);
var options=this.options;
this.text = options.text; // the text displayed by this node
this.id = options.id || null; // the node's unique id
this.nodes = new Array(); // subnodes nested beneath this node (MooTreeNode objects)
this.parent = null; // this node's parent node (another MooTreeNode object)
this.control = options.control; // owner control of this node's tree
this.selected = false; // a flag telling whether this node is the currently selected node in it's tree
this.color = options.color || null; // text color of this node
this.data = options.data || {}; // optional object containing whatever data you wish to associate with the node (typically an url or an id)
this.open = options.open ? true : false; // flag: node open or closed?
this.icon = options.icon;
this.openicon = options.openicon || this.icon;
// add the node to the control's node index:
if (this.id) this.control.index[this.id] = this;
// create the necessary divs:
this.divs = {
main: new Element('div').addClass('mooTree_node').setStyle('position','relative'),
indent: new Element('div').setStyle('position','absolute'),
gadget: new Element('div'),
node: new Element('div'),
icon: new Element('div'),
text: new Element('div').addClass('mooTree_text'),
sub: new Element('div'),
context: new Element('div')
};
// put the other divs under the main div:
this.divs.main.adopt([
this.divs.indent,
this.divs.gadget,
this.divs.node.
setStyles({
'position':'relative',
'width':'auto !important',
'width':'1px',
'min-width':'1px'
})
.adopt([
this.divs.icon,
this.divs.text,
this.divs.context
])
]);
this.divs.node.treeNode=this;
this.control.GarbageCollector.push(this.divs.node);
// attach event handler to gadget:
this.divs.gadget.addEvent('click',function() {
this.toggle();
}.bind(this));
// attach event handler to icon/text click:
this.divs.node.addEvent('click',function(){
this.control.select(this);
}.bind(this));
//attach event handler to icon/text contextMenu:
this.divs.node.getParent().addEvent('contextmenu', function(e){
e = new Event(e).stop();
this.control.contextSelect(this, e);
}.bind(this));
},
addInTree: function(){
// put the main and sub divs in the specified parent div:
$(this.options.div).adopt([this.divs.main,this.divs.sub]);
this.$added=true;
},
/*
Property: insert
Creates a new node, nested inside this one.
Parameters:
options - an object containing the same options available to the <MooTreeNode> constructor.
Returns:
A new <MooTreeNode> instance.
*/
insert: function(options) {
// set the parent div and create the node:
options.div = this.divs.sub;
options.control = this.control;
var node = new MooTreeNode(options);
node.addInTree();
// set the new node's parent:
node.parent = this;
// mark this node's last node as no longer being the last, then add the new last node:
var nodes = this.nodes;
nodes.push(node);
// repaint the new node:
node.update();
if (nodes.length == 1){
this.update();// repaint the new node's parent (this node)
}else{
if (nodes.length > 1) nodes[nodes.length-2].update(true);// recursively repaint the new node's previous sibling node
}
return node;
},
isLast: function(){
if(this.parent==null) return true;
if(this.parent.nodes.getLast()==this) return true;
return false;
},
isFirst: function(){
if(this.parent==null) return true;
if(this.parent.nodes[0]==this) return true;
return false;
},
isRoot: function(){
return this.parent==null ? true : false;
},
childOrder: function(){
if( this.isRoot() ) return 0;
return this.parent.nodes.indexOf(this);
},
getNext: function(){
if(this.isLast()) return null;
return this.parent.nodes[this.childOrder()+1];
},
getPrevious: function(){
if( this.isFirst() ) return null;
return this.parent.nodes[this.childOrder()-1];
},
getFirst: function(){
if(this.nodes.length==0) return null;
return this.nodes[0];
},
getLast: function(){
if(this.nodes.length==0) return null;
return this.nodes.getLast();
},
getLevel: function(){
var p=this;
var level=0;
do{
if( p.isRoot() ) return level;
level++;
}while ( p=p.parent )
},
getForward: function(){
var current=this;
if(current.isRoot()){
if(!current.open || current.nodes.length==0) return false;
return current.getFirst();
}else{
if(current.getFirst() && current.open){
return current.getFirst();
}
return (function(current){
if(current.getNext()){
return current.getNext();
}else if(!current.isRoot()){
return arguments.callee(current.parent);
}else{
return null;
}
})(current);
}
},
getBack: function(){
var current=this;
if(current.isRoot()){
return false;
}else{
if( current.getPrevious() ){
current=current.getPrevious();
while( current.open && current.getLast() ){
current=current.getLast();
}
return current;
}else{
return current.parent;
}
}
},
copy: function(deep){
var copy=new MooTreeNode(this.options);
if(deep){
this.nodes.each(function(node){
var cnode=node.copy(true);
cnode.parent=copy;
cnode.options.div=cnode.parent.divs.sub;
copy.nodes.push(cnode);
}.bind(this));
return copy;
}else{
return copy;
}
},
injectInside: function(node){//insert code
// set the new node's parent:
this.parent = node;
this.options.div=node.divs.sub;
// add node into Tree;
this.addInTree();
// mark this node's last node as no longer being the last, then add the new last node:
var nodes = node.nodes;
nodes.push(this);
// repaint the new node:
this.update(true);
if (nodes.length == 1){
node.update();// repaint the new node's parent (this node)
}else{
if (nodes.length > 1) nodes[nodes.length-2].update(true);// recursively repaint the new node's previous sibling node
}
return this;
},
injectBefore: function(node){
this.parent = node.parent;
if(!this.parent) return;
this.options.div=node.parent.divs.sub;
this.divs.sub.injectBefore(node.divs.main);
this.divs.main.injectBefore(this.divs.sub);
this.$added=true;
// mark this node's last node as no longer being the last, then add the new last node:
var nodes = node.parent.nodes;
nodes.injectBefore(node,this);
// repaint the new node:
this.update(true);
var addedNodeIndex=nodes.indexOf(this);
if(addedNodeIndex>0) nodes[addedNodeIndex-1].update(true);// recursively repaint the new node's previous sibling node
if(addedNodeIndex<nodes.length-1) nodes[addedNodeIndex+1].update(true);
return this;
},
injectAfter: function(node){
this.parent = node.parent;
if(!this.parent) return;
this.options.div=node.parent.divs.sub;
this.divs.main.injectAfter(node.divs.sub);
this.divs.sub.injectAfter(this.divs.main);
this.$added=true;
// mark this node's last node as no longer being the last, then add the new last node:
var nodes = node.parent.nodes;
nodes.injectAfter(node,this);
// repaint the new node:
this.update(true);
var addedNodeIndex=nodes.indexOf(this);
if(addedNodeIndex>0) nodes[addedNodeIndex-1].update(true);// recursively repaint the new node's previous sibling node
if(addedNodeIndex<nodes.length-1) nodes[addedNodeIndex+1].update(true);
return this;
},
//not implemented
injectTop: function(node){
},
//repalace current node with toNode. where = 'after' || 'before' || 'inside' . 'top' not implemented
replaceTo: function(toNode,where){
if( !['after','before','inside'].contains(where) ) return false;
var oldparent=this.parent;
var oldprevious=this.getPrevious();
var oldnext=this.getNext();
switch(where){
case 'after' :
// chk isTheSamePlace?
if( toNode.getNext() && toNode.getNext()==this ) return false;
//insert main and sub divs into new place
this.divs.main.injectAfter(toNode.divs.sub);
this.divs.sub.injectAfter(this.divs.main);
// update {parent,nodes} - base tree structure.
this.parent.nodes.remove(this);
this.parent=toNode.parent;
this.parent.nodes.injectAfter(toNode,this);
break;
case 'before' :
// chk isTheSamePlace?
if( toNode.getPrevious() && toNode.getPrevious()==this ) return false;
//insert main and sub divs into new place
this.divs.sub.injectBefore(toNode.divs.main);
this.divs.main.injectBefore(this.divs.sub);
// update {parent,nodes} - base tree structure.
this.parent.nodes.remove(this);
this.parent=toNode.parent;
this.parent.nodes.injectBefore(toNode,this);
break;
case 'inside' :
// chk isTheSamePlace?
if( toNode.getLast() && toNode.getLast()==this ) return false;
//insert main and sub divs into new place
this.divs.main.injectInside(toNode.divs.sub);
this.divs.sub.injectAfter(this.divs.main);
// update {parent,nodes} - base tree structure.
this.parent.nodes.remove(this);
this.parent=toNode;
this.parent.nodes.push(this);
break;
}
//update old place
if(oldprevious) oldprevious.update(true);
//if(oldnext) oldnext.update();
if( oldparent && !oldparent.getFirst() ) oldparent.update();
//update this node and around node.
this.update(true);
var previous=this.getPrevious();
if(previous) previous.update(true);
var next=this.getNext();
if(next) next.update();
this.parent.update();
//select this node;
this.control.select(this);
return this;
},
// return true if current node parent or parent.parent etc of test node
contain: function(node){// node - test node
if(this==node) return true;
var nodes=this.nodes;
for(var i=0,l=nodes.length;i<l;i++){
if ( nodes[i].contain(node) ) return true;
}
return false;
},
/*
Property: remove
Removes this node, and all of it's child nodes. If you want to remove all the childnodes without removing the node itself, use <MooTreeNode.clear>
*/
remove: function() {
var p = this.parent;
this._remove();
if (p != null){
p.update(true);
}
},
_remove: function() {
// recursively remove this node's subnodes:
var nodes = this.nodes;
while (nodes.length) nodes[nodes.length-1]._remove();
// remove the node id from the control's index:
delete this.control.index[this.id];
// remove this node's divs:
this.divs.main.remove();
this.divs.sub.remove();
if (this.parent) {
// remove this node from the parent's collection of nodes:
var p = this.parent.nodes;
p.remove(this);
}
},
/*
Property: clear
Removes all child nodes under this node, without removing the node itself.
To remove all nodes including this one, use <MooTreeNode.remove>
*/
clear: function() {
this.control.disable();
while (this.nodes.length) this.nodes[this.nodes.length-1].remove();
this.control.enable();
},
/*
Property: update
Update the tree node's visual appearance.
Parameters:
recursive - boolean, defaults to false. If true, recursively updates all nodes beneath this one.
*/
update: function(recursive) {
if (!this.control.enabled) return;
var level=this.getLevel();// level of deep
this.updateIndent(level);// update indentations
this.updateGadget(level);// update the plus/minus gadget
this.updateShowHide();// show/hide subnodes
this.updateNode(level);//update the node
this.updateIcon(level);// update the icon
this.updateText(); // update the text
// if recursively updating, update all child nodes:
if (recursive) {
this.nodes.each( function(node) {
if(!node.$added) node.addInTree();
node.update(node.open);
});
}
},
updateIndent: function(level){
if(level==undefined) var level=this.getLevel();
this.divs.indent.empty();
var p = this,indents=[];
while (p=p.parent) {
indents.push( this.getImg(
p.isLast() || !this.control.grid
?
''
:
'I'
)
);
}
for(var i=0;i<level;i++){
indents[i].setStyles({
position:'absolute',
width:18,
height:18,
top:0,
left:(level-i)*18
});
this.divs.indent.adopt(indents[i]);
};
},
updateGadget: function(level){
if(level==undefined) var level=this.getLevel();
this.divs.gadget.empty()
.setStyles({
position:'absolute',
top:0,
width:18,
height:18,
left:(level+1)*18
});
this.getImg(
(
this.control.grid
?
(
(this.control.root == this )
?
(this.nodes.length ? 'R' : '')
:
(this.isLast() ? 'L' : 'T')
)
:
''
) +
(
this.nodes.length
?
(this.open ? 'minus' : 'plus')
:
''
)
, this.divs.gadget
);
},
updateNode: function(level){
if(level==undefined) var level=this.getLevel();
this.divs.node.setStyles({
height:18,
left:(level+2)*18
});
},
updateIcon: function(level){
this.divs.icon.empty()
.setStyles({
position:'absolute',
top:0,
width:18,
height:18,
left:0
});
this.getImg(
this.nodes.length
?
( this.open
?
(this.openicon || this.icon || '_open')
:
(this.icon || '_closed')
)
:
this.icon || (this.control.mode == 'folders' ? '_closed' : '_doc')
,this.divs.icon
);
},
updateText: function(){
this.divs.text.empty()
.setStyles({
position:'absolute',
height:18,
left:18,
padding:0,
margin:0
})
.adopt(new Element('div').setStyles({
position:'relative',
'white-space':'nowrap',
bottom:-3,
height:'100%',
color:this.color
})
.appendText(this.text)
);
},
updateShowHide: function(){
this.divs.sub.style.display = this.open ? 'block' : 'none';
},
/*
Property: getImg
Creates a new image (actually, a div Element) -- or turns a given div into an image.
You should not need to manually call this method. (though if for some reason you want to, you can)
Parameters:
name - the name of new image to create, defined by <MooTreeIcon> or located in an external file.
div - optional. A string representing an existing div element to be turned into an image, or an element reference.
Returns:
The new div Element.
*/
getImg: function(name, div) {
div = ( div || new Element('div') ) // if no div was specified, create a new one
.addClass('mooTree_img');// apply the mooTree_img CSS class:
// if a blank image was requested, simply return it now:
if (name == '') return div;
var img = this.control.theme;
var i = MooTreeIcon.indexOf(name);
if (i == -1) {
// custom (external) icon:
var x = name.split('#');
img = x[0];
i = (x.length == 2 ? parseInt(x[1], 10)-1 : 0);
}
return div.setStyles({
backgroundImage :'url(' + img + ')',
backgroundPosition : '-' + (i*18) + 'px 0px'
});
},
/*
Property: toggle
By default (with no arguments) this function toggles the node between expanded/collapsed.
Can also be used to recursively expand/collapse all or part of the tree.
Parameters:
recursive - boolean, defaults to false. With recursive set to true, all child nodes are recursively toggle to this node's new state.
state - boolean. If undefined, the node's state is toggled. If true or false, the node can be explicitly opened or closed.
*/
toggle: function(recursive, state) {
this.open = state || !this.open;
if(this.$subUpdated){
this.updateGadget();
this.updateShowHide();
}else{
this.update(true);
this.$subUpdated=true;
}
this.fireEvent('onExpand',this.open);
this.control.fireEvent('onExpand',[this, this.open]);
if (recursive) this.nodes.each( function(node) {
node.toggle(true, this.open);
}, this);
},
expandParents: function() {
if (this.parent) {
this.parent.toggle(false,true);
this.parent.expandParents();
}
},
/*
Property: select
Called by <MooTreeControl> when the selection changes.
You should not manually call this method - to set the selection, use the <MooTreeControl.select> method.
*/
select: function(state) {
this.selected = state;
this.divs.main.className = 'mooTree_node' + (this.selected ? ' mooTree_selected' : '');
this.fireEvent('onSelect',[state]);
},
/*
Property: load
Asynchronously load an XML structure into a node of this tree.
Parameters:
url - string, required, specifies the URL from which to load the XML document.
vars - query string, optional.
*/
load: function(url, vars, redraw) {
if (this.nodes.length) return; // if this node is already loading, return
this.toggle(false, true); // expand the node to make the loader visible
this.clear();
this.insert(this.control.loader);
(function(){
new XHR({
method: 'GET',
onSuccess: this._loaded.bind(this),
onFailure: this._load_err.bind(this)
}).send(url, vars || '');
}).delay(20,this);// allowing a small delay for the browser to draw the loader-icon.
},
_loaded: function(text, xml) {
// called on success - import nodes from the root element:
this.control.disable();
this.clear();
this._import(xml.documentElement);
this.control.enable();
},
_import: function(e) {
// import childnodes from an xml element:
var n = e.childNodes;
for (var i=0; i<n.length; i++) if (n[i].tagName == 'node') {
var opt = {data:{}};
var a = n[i].attributes;
for (var t=0; t<a.length; t++) {
switch (a[t].name) {
case 'text':
case 'id':
case 'icon':
case 'openicon':
case 'color':
case 'open':
opt[a[t].name] = a[t].value;
break;
default:
opt.data[a[t].name] = a[t].value;
}
}
var node = this.insert(opt);
if (node.data.load) {
node.open = false; // can't have a dynamically loading node that's already open!
node.insert(this.control.loader);
node.onExpand = function(state) {
this.load(this.data.load);
this.onExpand = new Function();
};
}
// recursively import subnodes of this node:
if (n[i].childNodes.length) node._import(n[i]);
}
},
_load_err: function(req) {
window.alert('Error loading: ' + this.text);
}
});
MooTreeNode.implement(new Events, new Options);
Array.extend({
injectBefore: function(cur,add){
if(!this.contains(cur)) return false;
var array=this.slice(0,this.indexOf(cur));
array.push(add);
this.slice(this.indexOf(cur),this.length).each(function(el){
array.push(el);
});
this.splice(0,this.length);
array.each(function(el){
this.push(el);
}.bind(this));
},
injectAfter: function(cur,add){
if(!this.contains(cur)) return false;
var array=this.slice(0,this.indexOf(cur)+1);
array.push(add);
this.slice(this.indexOf(cur)+1,this.length).each(function(el){
array.push(el);
});
this.splice(0,this.length);
array.each(function(el){
this.push(el);
}.bind(this));
},
eq: function(array){
if(!array) return false;
var thisLength=this.length;
var arrayLength=array.length;
if(!arrayLength || thisLength != arrayLength) return false;
for(var i=thisLength-1;i>=0;i--){
if(this[i] != array[i]) return false;
}
return true;
}
});
var MooTreeContextMenu = new Class({
options: {
onShow: function(context){
context.setStyle('visibility', 'visible');
},
onHide: function(context){
context.setStyle('visibility', 'hidden');
},
className: 'context',
offsets: {'x': 5, 'y': 5},
currentNode: null,
buildMenuTitle: function(div, node, event) {
div.appendChild(new Element('span').setHTML(node.text));
return div;
},
buildMenuItems: function(div, node, event) {
div.appendChild(new Element('span').setHTML('Item'));
return div;
}
},
initialize: function(options) {
this.setOptions(options);
this.contextMenu = new Element('div', {
'class': this.options.className + '-menu',
'id': this.options.className + '-menu',
'styles': {
'position': 'absolute',
'top': '0',
'left': '0',
'visibility': 'hidden'
}
}).inject(document.body);
this.wrapper = new Element('div').inject(this.contextMenu);
if (this.options.initialize) this.options.initialize.call(this);
var start = this.start.bind(this);
this.addEvent('start', start);
var end = this.end.bind(this);
this.addEvent('end', end);
this.contextMenu.timer = null;
var self = this;
this.contextMenu.addEvents({
'mouseenter': function(e) {
$clear(self.contextMenu.timer);
self.show.delay(self.options.showDelay, self);
},
'mouseleave': function(e) {
self.contextMenu.timer = (function() {self.fireEvent('end');}).delay(500);
}
});
},
start: function(node, event){
if (node.data.ignoreContext) {
return;
}
if (this.currentNode != node){
this.end();
}
var end = this.end.bind(this);
// Clear wrapper from last menu
this.wrapper.empty();
//Set up the titles div
var titleDiv = new Element('div', {'class': this.options.className + '-title'});
// build the Menu Title div up
this.wrapper.appendChild(this.options.buildMenuTitle(titleDiv, node, event));
// Set up the menu items div
var itemsDiv = new Element('div', {'class': this.options.className + '-menuItems'});
// Build the Menu Items Div up
this.wrapper.appendChild(this.options.buildMenuItems(itemsDiv, node, event));
// Set all clicks on menu items (SPANs) to close the window and
// Set the mouse cursor to a pointer when over menu items
for (var z in itemsDiv.childNodes) {
if (itemsDiv.childNodes[z].tagName == "SPAN") {
itemsDiv.childNodes[z].addEvent('click', end);
itemsDiv.childNodes[z].addEvent('mouseover', function(event) {
this.setStyle('cursor','pointer');
});
}
}
// Set the location of the Context Menu to be near the Right Mouse Click
var win = {'x': window.getWidth(), 'y': window.getHeight()};
var scroll = {'x': window.getScrollLeft(), 'y': window.getScrollTop()};
var context = {'x': this.contextMenu.offsetWidth, 'y': this.contextMenu.offsetHeight};
var prop = {'x': 'left', 'y': 'top'};
for (var z in prop){
var pos = event.page[z] + this.options.offsets[z];
if ((pos + context[z] - scroll[z]) > win[z]) pos = event.page[z] - this.options.offsets[z] - context[z];
this.contextMenu.setStyle(prop[z], pos);
};
this.contextMenu.addEvent('trash', end);
$clear(this.timer);
this.timer = this.show.delay(this.options.showDelay, this);
},
end: function(){
if (this.contextMenu) {
$clear(this.timer);
this.timer = this.hide.delay(this.options.hideDelay, this);
}
},
show: function(){
if (this.options.timeout) this.timer = this.hide.delay(this.options.timeout, this);
this.fireEvent('onShow', [this.contextMenu]);
},
hide: function(){
this.fireEvent('onHide', [this.contextMenu]);
}
});
MooTreeContextMenu.implement(new Events, new Options);