Your IP : 216.73.216.240


Current Path : /home/s/u/n/sunflowe/www2/j15/administrator/components/com_rokdownloads/assets/tree/
Upload File :
Current File : /home/s/u/n/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);