
function ntaxaMenu(ulid, options) {

	var menu = $(ulid);

	menu.options = Object.extend({
	setulpositions:   		true,
	draggableclass:   		'',
	draghandleclass:		'',
    folderclass:      		'',
	overclass: 				'',
	draggedclass:     		'',
	dragoverclass:			'',
	newsubmenudummyclass:	'', 
    newsubmenudummytext:  	'Drop here', 
	
	irrelevantclass:		'', 
	irrelevantfolderclass:	'',
	irrelevantopenclass:	'', 
	irrelevantoverclass:	'', 
    
	isFolder:	function(lie,def)	{return def;},
	onOpen:		function(lie,def) 	{return def;},
	onClose: 	function(lie,def) 	{return def;},
	onOver:		function(lie,def) 	{return def;},
	onOut: 		function(lie,def) 	{return def;},
	
	onChange:	function(lie) 	{return;}, // should be reall callback (user should have possibility to cancel action)
	
	isDropable:	function(lie,parent,before,after,def)	{return def;},
	
	onStartDragging: function (lie, ule) {return;}, // should be reall callback (user should have possibility to cancel action)
	onEndDragging: function (lie, ule, index) {return;},  // should be reall callback (user should have possibility to cancel action)
	timeout: 200
    }, arguments[1] || { });
	
	var randommark = '_' + ulid + '_' + Math.round(1000000000*Math.random());
	if (!menu.options.newsubmenudummyclass) menu.options.newsubmenudummyclass = 'newsubmenudummyclass' + randommark;
	if (!menu.options.openedclass) menu.options.openedclass = 'openedclass' + randommark;
	if (!menu.options.irrelevantclass) menu.options.irrelevantclass = 'irrelevantclass' + randommark;
	if (!menu.options.irrelevantfolderclass) menu.options.irrelevantfolderclass = 'irrelevantfolderclass' + randommark;
	if (!menu.options.irrelevantopenclass) menu.options.irrelevantopenclass = 'irrelevantopenclass' + randommark;
	if (!menu.options.irrelevantoverclass) menu.options.irrelevantoverclass = 'irrelevantoverclass' + randommark;

	menu.menuout = true;
	menu.froze = false;
	menu.ulvisibledummyli = null;
	menu.currentli = null;
	menu.dragged = false;
	menu.timer = null;
	
	menu.dragParentLI = null;
	menu.dragNextLI = null;
	menu.dragParentLI = null;
	
	menu.sortableCreated = false;
		
	
	menu.setULFolderClass = function(ule) {
		if (!ule || !this.options.folderclass || !(tmp=ule.up('li')) || (!tmp.descendantOf(this)) ) return;
		var tmpval=ule.childElements().any(function (childe) {
				return ( (childe.nodeName=='LI') && childe.visible() && !childe.hasClassName(menu.options.irrelevantfolderclass) && !childe.hasClassName(menu.options.irrelevantclass) );
				});
		var tmpret=this.options.isFolder(tmp,tmpval);
		if ((tmpret===true) || ((tmpret!==false) && tmpval))
			tmp.addClassName(this.options.folderclass);
		else 
			tmp.removeClassName(this.options.folderclass);
		}
	
	menu.overLI = function(lie) {
		var tmpval=(!lie.hasClassName(this.options.irrelevantoverclass)  && !lie.hasClassName(menu.options.irrelevantclass));
		var tmpret=this.options.onOver(lie,tmpval);
		if ((tmpret===true) || ((tmpret!==false) && tmpval)) {
			lie.addClassName(this.dragging?this.options.dragoverclass:this.options.overclass);
			}
		};
	
	menu.outLI = function(lie) {
		var tmpval=(!lie.hasClassName(this.options.irrelevantoverclass)  && !lie.hasClassName(menu.options.irrelevantclass));
		var tmpret=this.options.onOut(lie,tmpval);
		if ((tmpret===true) || ((tmpret!==false) && tmpval)) {
			lie.removeClassName(this.options.dragoverclass);
			lie.removeClassName(this.options.overclass);
			}
		};
	
	menu.calculatePositionUL = function (ule) {
		if (!ule || (ule==this)) return;
		ule.clonePosition(ule.parentNode, {offsetLeft: ule.parentNode.getWidth(), setHeight: false, setWidth: false});
		try {
			ule.clonePosition(ule.parentNode, {offsetLeft: ule.parentNode.getWidth(), setHeight: false, setWidth: false});
			}
		catch (err)
			{
			ule.clonePosition(ule.parentNode, {offsetLeft: ule.parentNode.getWidth(), setHeight: false, setWidth: false});
			}
		// yes it seem stupid. ask prototype developer why clonePosition in IE 6 work only from second approach
		};

	menu.showUL = function (ule) {
		if (this.options.setulpositions) this.calculatePositionUL(ule);
		ule.show();
		};

	menu.hideUL = function (ule) {
		ule.hide();
		};
	
	menu.openLI = function(lie) {
		lie.addClassName(this.options.openedclass);
		};
		
	menu.closeLI = function(lie) {
		lie.removeClassName(this.options.openedclass);
		};
	
	menu.openItem = function(lie) {
		var ule=lie.down('ul'); //childElements().find( function(childe) { return ( (childe.nodeName=='UL') && childe.descendantOf(menu)); } );
		if (ule && !(ule.parentNode==lie)) ule = null;
		var tmpval=false;
		if (ule) tmpval=ule.childElements().any(function (lice) {return (lice.visible() && !lice.hasClassName(menu.options.irrelevantopenclass) && !lice.hasClassName(menu.options.irrelevantclass)); } );
		var tmpret=this.options.onOpen(lie,tmpval);
		if ((tmpret===true) || ((tmpret!==false) && tmpval)) {
			this.openLI(lie);
			if (ule) this.showUL(ule);
			}
		};
	
	menu.closeItem = function(lie) {
		var ule=lie.down('ul');//.childElements().find( function(childe) { return ( (childe.nodeName=='UL') && childe.descendantOf(menu)); } );
		if (ule && !(ule.parentNode==lie)) ule = null;
		var tmpval=true;
		var tmpret=this.options.onClose(lie,tmpval);
		if ((tmpret===true) || ((tmpret!==false) && tmpval)) {
			if (ule) this.hideUL(ule);
			this.closeLI(lie);
			}
		};
	
	menu.startDragging = function (lie) {
		this.oldule = lie.up('ul');
		this.options.onStartDragging(lie, this.oldule);

		this.dragParentLI = lie.parentNode;
		this.dragNextLI = lie.previousSiblings().find(function (childe) {
				return (lie.nodeName=='LI') && !lie.hasClassName(menu.options.newsubmenudummyclass) && !lie.hasClassName(menu.options.irrelevantclass) && lie.visible();
				} ) || null;
		this.dragParentLI = lie.nextSiblings().find(function (childe) {
				return (lie.nodeName=='LI') && !lie.hasClassName(menu.options.newsubmenudummyclass) && !lie.hasClassName(menu.options.irrelevantclass) && lie.visible();
				} ) || null;
		
		this.dragging = true;
		this.menuout = false;
		var tmp = lie.up('li');//ancestors().find(function (el) {return (el.nodeName=='LI');});
		tmp = tmp?tmp:null;
		this.changeItem(tmp);
		lie.addClassName(this.options.draggedclass);
		};
		
	menu.endDragging = function (lie) {
		lie.removeClassName(this.options.draggedclass);
		this.changeItem(null);
		this.dragging = false;
		this.menuout = false;
		this.changeItem(lie);
		var tmp = lie.parentNode;
		this.setULFolderClass(this.oldule);
		this.setULFolderClass(tmp);
		var position = 0;
		var positionfound = false;
		this.options.onEndDragging(lie,tmp,tmp.childElements().findAll(function (childe) { return (!childe.hasClassName(menu.options.newsubmenudummyclass) && childe.hasClassName(menu.options.draggableclass) && (childe.nodeName=='LI') ); }).indexOf(lie));
		};
		
	menu.showDummyLI = function(ule) {
		if (this.ulvisibledummyli && (this.ulvisibledummyli.parrentNode != ule)) this.hideDummyLI();
		var dummyli=ule.down("li."+menu.options.newsubmenudummyclass);//find(function (childe) { return ( (childe.nodeName=='LI') && childe.hasClassName(menu.options.newsubmenudummyclass)); } );
		if (!dummyli || !(dummyli.parentNode==ule)) return;
		var otherli=ule.childElements().find(function (childe) { return ((childe.nodeName=='LI') && childe.hasClassName(menu.options.draggableclass) && !childe.hasClassName(menu.options.irrelevantclass) && !childe.hasClassName(menu.options.newsubmenudummyclass) && childe.visible() ) } ) ;
		if (otherli) {
			this.ulvisibledummyli = null;
			dummyli.hide();
			}
		else {
			this.ulvisibledummyli = dummyli;
			dummyli.show();
			}
		};
		
	menu.hideDummyLI = function() {
		if (this.ulvisibledummyli) 
			this.ulvisibledummyli.hide();
		this.ulvisibledummyli = null;
		}
		
	
	menu.changeItem = function(newe) {
		var olde = this.currentli;
		if (olde) {
			if (olde!=newe) this.outLI(olde);
			if ( (!newe || !newe.descendantOf(olde)) && olde.descendantOf(this) ) 
				{
				this.closeItem(olde);
				if (this.dragging)
					this.hideDummyLI();
				}
			// close old opened LI if new is NOT child of old.
			olde.ancestors().find(function (ae) {
				if (!ae.descendantOf(menu)) return true;
				if ((ae.nodeName=='LI') && !(newe && newe.descendantOf(ae))) menu.closeItem(ae);
				return false;
				} );
			// if old LI opened close ancestors
			}
		if (newe) {
			newe.ancestors().reverse().find(function (ae) {
				// if (!ae.descendantOf(menu)) return true;
				if ( ae.descendantOf(menu) && (ae.nodeName=='LI') ) menu.openItem(ae);
				return false;
				} );
			if (this.dragging) {
					tmp = newe.down('ul');
					if (tmp.parentNode==newe)
						this.showDummyLI(tmp);
					}
			this.openItem(newe);
			this.overLI(newe);
			}
		else {
			if (this.dragging)
					this.hideDummyLI();
			}
		this.currentli = newe;
		};
		
	
	menu.mouseoveritem = function (elt) {
//		_d(elt.id);
		this.menuout = false;
		this.changeItem(elt);
		};
	
	menu.mouseovermenu = function(event) {
		if (this.froze) return;
		if (this.dragging) return;
		var elt = Event.element(event); 
		if (elt.nodeName!='LI') {
			elt = elt.up('li');//ancestors().find(function (ae) {if (ae.nodeName=='LI') return true;});
			}

		if (elt && elt.descendantOf(this)) {
			if (event) Event.stop(event);
			this.mouseoveritem(elt);
			}		
		};

	menu.mouseoutmenu = function(event) {
//		_d(this.froze);
		if (this.froze) return;
		if (this.dragging) return;
		this.menuout = true;
		var _self = this;
		if (this.timer) clearTimeout(this.timer);
		this.timer = setTimeout(function () {_self.mouseoutmenutimeout()},this.options.timeout);
		};

	menu.mouseoutmenutimeout = function() {
		if (this.froze) return;
		if (this.dragging) return;
		if (this.menuout) {
			if (this.timer) clearTimeout(this.timer);
			this.menuout = false;
			this.changeItem(null);
			}
		};

	menu.createDummyUL = function(lie) {
		var tmp = lie.down('ul');
		if (!tmp || !(tmp.parentNode==lie))
			lie.insert({ top: '<ul style="display: none;"></ul>'});
		//if (!lie.childElements().find(function (fule) {return (fule.tagName=='UL')} ))
		}
	
	menu.createDummyLI = function(ule) {
		ule.insert({ top: '<li style="display: none;" id="ntaxaMenuHiddenDummy_' + ule.id + '" class="' + menu.options.draggableclass + ' ' + menu.options.newsubmenudummyclass + ' ' + menu.options.irrelevantfolderclass + '">' + menu.options.newsubmenudummytext + '</li>'});
		}
		
	menu.addItem = function(itemcontent,elem,position) {
		elem = $(elem);
		if (!elem) return;
		if (elem.nodeName=='LI') {
			if (position=='top') elem.insert({'before': itemcontent});
			else elem.insert({'after': itemcontent});
			par = elem.parentNode;
			}
		if (elem.nodeName=='UL') {
			if (position=='top') elem.insert({'top': itemcontent});
			else elem.insert({'bottom': itemcontent});
			par = elem;
			}
		
		if (menu.options.folderclass) this.setULFolderClass(par);
		if (this.sortableCreated) {
			par.select('li').each(this.createDummyUL);
			par.select('ul').each(this.createDummyLI);		
			this.createSortable();
			}
		}
	
	menu.removeItem = function(lie) {
		if ($(lie)) {
			var tmp = $(lie).parentNode;
			$(lie).remove();
			if (menu.options.folderclass) this.setULFolderClass(tmp);
			}
		}
	
	
	menu.createSortable = function()
		{
		if (!Sortable.onHover_old) {
			Sortable.onHover_old = Sortable.onHover;

			Sortable.onHover = function (element, dropon, overlap) {
				if(Element.isParent(dropon, element)) return;
				if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) return;
				var tmpmenu = Sortable._findRootElement(element);
				var nextelement = null;
				var prevelement = null;
				var parentelement = dropon.parentNode;
				
				var notFakeLI = function (lie) {
					return (lie.nodeName=='LI') && !lie.hasClassName(tmpmenu.options.newsubmenudummyclass) && !lie.hasClassName(tmpmenu.options.irrelevantclass) && lie.visible();
					}
					
				var realprevelement = dropon.previousSiblings().find(notFakeLI) || null;
				var realnextelement = dropon.nextSiblings().find(notFakeLI) || null;
				
				if (overlap>0.5) {
					nextelement = notFakeLI(dropon)?dropon:realnextelement;
					prevelement = realprevelement;
				  }
				else {
					nextelement = realnextelement;
					prevelement = notFakeLI(dropon)?dropon:realprevelement;
				  }

				if ((prevelement!=tmpmenu.dragPrevLI) || (nextelement!=tmpmenu.dragNextLI) || (parentelement!=tmpmenu.dragParentLI)) {
					var tmpval=true;
					var tmpret=tmpmenu.options.isDropable(element,parentelement,prevelement,nextelement,tmpval);
					if ((tmpret===true) || ((tmpret!==false) && tmpval)) {
						tmpmenu.dragPrevLI = prevelement;
						tmpmenu.dragNextLI = nextelement;
						tmpmenu.dragParentLI = parentelement;
						Sortable.onHover_old(element, dropon, overlap);
						}
					if (!dropon.hasClassName(tmpmenu.options.newsubmenudummyclass)) 
						tmpmenu.changeItem(dropon);
					else 
						tmpmenu.showDummyLI(dropon.parentNode);
					}
				}
			}

		if (Prototype.Browser.Opera) $(ulid).select('li.' + menu.options.draggableclass).each(function (el) {el.style.position='static';} );

		Sortable.create(menu.id,
						{
						tree: true,
						handle: menu.options.draghandleclass,
						only: menu.options.draggableclass,
						ghosting: false, 
						constraint: false,
						starteffect: function (e) {
							menu.startDragging(e);
							},
						endeffect: function (e) {
							menu.endDragging(e);
							},
						onChange: menu.options.onChange
						}
					);
		}
	
	menu.makeDragable = function () {
		if (!this.options.draggableclass)
			{
			this.options.draggableclass = 'draggableclass_' + ulid + '_' + Math.round(1000000000*Math.random());
			$(ulid).select('li').each(function(cli) {
					if (!cli.addClassName(menu.options.irrelevantclass))
						cli.addClassName(menu.options.draggableclass);
				} );
			}
	
		$(ulid).select('li').each(this.createDummyUL);
		$(ulid).select('ul').each(this.createDummyLI);
		this.createSortable();
		this.sortableCreated = true;
		
		
		}
	
	
	Event.observe(menu,"mouseover",menu.mouseovermenu);
	Event.observe(menu,"mouseout",menu.mouseoutmenu);
	
	return menu;
	}