function ddpanel(setting){
        setting.dir="up" //initial state of panel (up="contracted")
        if (setting.stateconfig.persiststate && ddpanel.getCookie(setting.ids[0])=="down"){
                setting.dir="down"
        }
        if (setting.dir=="up"){ //if "up", output CSS to hide panel contents
                document.write('<style type="text/css">\n')
                document.write('#'+setting.ids[1]+'{height:' + parseInt(setting.stateconfig.initial) + 'px; overflow:hidden}\n')
                document.write('</style>\n')
        }
        setting.stateconfig.initial=parseInt(setting.stateconfig.initial)
        this.setting=setting
        var thispanel=this
        if (window.addEventListener) //if non IE browsers, initialize panel window.onload
                ddpanel.addEvent(window, function(e){thispanel.initpanel(setting)}, "load")
        else //else if IE, add 100 millisec after window.onload before initializing
                ddpanel.addEvent(window, function(e){setTimeout(function(){thispanel.initpanel(setting)}, 100)}, "load")
        ddpanel.addEvent(window, function(e){thispanel.uninit(setting)}, "unload")
}

ddpanel.events_array=[] //object array to contain events created by script

ddpanel.addEvent=function(target, functionref, tasktype){
        var evtmodel=target.addEventListener? "w3c" : "ie"
        var evtaction=evtmodel=="w3c"? "addEventListener" : "attachEvent"
        var i=this.events_array.push({ //store event info in ddpanel.events_array[] and return current event's index within array
                target: target,
                tasktype: (evtmodel=="ie"? "on" : "")+tasktype,
                listener: evtmodel=="ie"? function(){return functionref.call(target, window.event)} : functionref
        })-1
        target[evtaction](this.events_array[i].tasktype, this.events_array[i].listener, evtmodel=="w3c"? false : null)
}

ddpanel.removeEvent=function(target, functionref, tasktype){
        var evtmodel=target.removeEventListener? "w3c" : "ie"
        var evtaction=evtmodel=="w3c"? "removeEventListener" : "detachEvent"
        target[evtaction](tasktype, functionref, evtmodel=="w3c"? false : null)
}

ddpanel.getCookie=function(Name){
        var re=new RegExp(Name+"=[^;]+", "i"); //construct RE to search for target name/value pair
        if (document.cookie.match(re)) //if cookie found
                return document.cookie.match(re)[0].split("=")[1] //return its value
        return null
}

ddpanel.setCookie=function(name, value){
        document.cookie = name+"=" + value + ";path=/"
}

ddpanel.addpointer=function(target, className, imagesrc){
        var pointer=document.createElement("img")
        pointer.src=imagesrc
        pointer.className=className
        pointer.style.borderWidth=0
        target.appendChild(pointer)
        return pointer
}

ddpanel.prototype={

togglepanel:function(dir){ //public function that toggles the panel's state. Optional dir parameter ("up" or "down") to explicitly set state.
        var setting=this.setting
        setting.dir=dir || ((setting.dir=="up")? "down" : "up")
        var pcontent=setting.pcontent, dir=setting.dir
        pcontent._currentheight=(dir=="down")? pcontent._actualheight : setting.stateconfig.initial
        pcontent.style.height=pcontent._currentheight+"px"
        if (setting.pointerimage.enabled){
                setting.arrow.src=setting.pointerimage.src[(setting.dir=="down")? 1 : 0]
                setting.arrow.style.visibility="visible"
        }
        ddpanel.setCookie(setting.ids[0], setting.dir)
},

togglepanelplus:function(dir){ //public function that toggles the panel's state w/ animation. Optional dir parameter ("up" or "down") to explicitly set state.
        var setting=this.setting
        setting.dir=dir || ((setting.dir=="up")? "down" : "up")
        if (setting.pointerimage.enabled)
                setting.arrow.style.visibility="hidden"
        clearTimeout(setting.revealtimer)
        this.revealcontent()
},

revealcontent:function(){
        var setting=this.setting
        var pcontent=setting.pcontent, curH=pcontent._currentheight, maxH=pcontent._actualheight, minH=setting.stateconfig.initial, steps=setting.animate.steps, dir=setting.dir
        if (dir=="down" && curH<maxH || dir=="up" && curH>minH){
                var newH = curH + (Math.round((maxH-curH)/steps)+1) * (dir=="up"? -1 : 1)
                newH=(dir=="down")? Math.min(maxH, newH) : Math.max(minH, newH)
                pcontent.style.height=newH+"px"
                pcontent._currentheight=newH
        }
        else{
                if (setting.pointerimage.enabled){
                        setting.arrow.src=setting.pointerimage.src[(setting.dir=="down")? 1 : 0]
                        setting.arrow.style.visibility="visible"
                }
                return
        }
        var thispanel=this
        setting.revealtimer=setTimeout(function(){thispanel.revealcontent()}, 10)
},

initpanel:function(){
        var setting=this.setting
        var pcontainer=setting.pcontainer=document.getElementById(setting.ids[0])
        var pcontent=setting.pcontent=document.getElementById(setting.ids[1])
        var tdiv=setting.tdiv=document.getElementById(setting.ids[2])
        pcontent.style.overflow="scroll"
        pcontent._actualheight=pcontent.scrollHeight
        setTimeout(function(){pcontent._actualheight=pcontent.scrollHeight}, 100)
        pcontent.style.overflow="hidden"
        pcontent._currentheight=(setting.dir=="down")? pcontent._actualheight : setting.stateconfig.initial
        var thispanel=this
        ddpanel.addEvent(tdiv, function(e){ //assign click behavior when toggle DIV tab is clicked on
                if (setting.animate.enabled)
                        thispanel.togglepanelplus()
                else
                        thispanel.togglepanel()
                if (e.preventDefault) e.preventDefault()
                return false
        }, "click")
        if (setting.pointerimage.enabled){
                var pointer1=new Image(), pointer2=new Image()
                pointer1.src=setting.pointerimage.src[0]
                pointer2.src=setting.pointerimage.src[1]
                setting.arrow=ddpanel.addpointer(tdiv.getElementsByTagName("span")[0], "pointerimage", setting.pointerimage.src[setting.dir=="down"? 1:0])
        }
        if (setting.closepanelonclick.enabled){ //assign click behavior when panel content is clicked on (links within panel or elements with class "closepanel"
                        ddpanel.addEvent(pcontent, function(e){
                                var target=e.srcElement || e.target
                                if (/(^|\s+)closepanel($|\s+)/.test(target.className) || target.tagName=="A" || (target.parentNode && target.parentNode.tagName=="A")){
                                        thispanel.togglepanel("up")
                                }
                        }, "click")
        }
},

uninit:function(){
        var setting=this.setting
        if (setting.stateconfig.persiststate){
                ddpanel.setCookie(setting.ids[0], setting.dir)
        }
        for (prop in setting){
                setting[prop]=null
        }
}



} //end of ddpanel object


//initialize instance of DD Drop Down Panel:

var defaultpanel=new ddpanel({
        ids: ["mypanel", "mypanelcontent", "mypaneltab"], // id of main panel DIV, content DIV, and tab DIV
        stateconfig: {initial: "1px", persiststate: false}, // initial: initial reveal amount in pixels (ie: 5px)
        animate: {enabled: true, steps: 5}, //steps: number of animation steps. Int between 1-20. Smaller=faster.
        pointerimage: {enabled: true, src: ["/images/arrow-down.gif", "/images/arrow-up.gif"]},
        closepanelonclick: {enabled: true} // close panel when links or elements with CSS class="closepanel" within container is clicked on?
})



// HTML attributes names which are beeing used in the mjslib collection:
// =====================================================================

var gMJS_typeAttrName      = "mjstype";
var gMJS_linkidAttrName    = "mjslinkid";
var gMJS_labelAttrName     = "mjslabel";
var gMJS_helpAttrName      = "mjshelp";

//--------------------------------------------------------------------------------
//
//	Cross-browser adaptation layer.
//
//	*DO NOT* use detected browser identification unless you have no choice!
//
//--------------------------------------------------------------------------------

var mjs_isie=false;
var mjs_isie55=false;
var mjs_ismoz=false;
var mjs_isopera=false;

function mjs_detectBrowser()
{
	var ver;

	if(navigator.userAgent.match(/Gecko/i))
	{
		mjs_ismoz=true;
	}
	else if(navigator.userAgent.toLowerCase().indexOf('opera')>-1)
	{
		mjs_isopera=true;
	}
	else if(document.all)
	{
		// IE footprint
		mjs_isie=true;
		undefined=void(0);	// define "undefined" because it does not have it
		ver=parseFloat(navigator.userAgent.match(/MSIE (\d\.\d+)\.*/i)[1]);
		mjs_isie55=(ver>=5.5);
	}
}

mjs_detectBrowser();

function mjs_valued(value)
{
	// valued data are: non null and not undefined
	// (we don't need & use distinction between null & undefined)
	return (value !== undefined) && (value !== null) && (value !== void(0));
}

function mjs_empty(value)
{
	if(mjs_valued(value))
	{
		return (value == "");
	}
	return true;
}

function mjs_array_append(list)
{
	var j=this.length;
	var n=list.length;

	for(var i=0;i<n;i++)
	{
		this[j++]=list[i];
	}
	return this;
}
Array.prototype.append=mjs_array_append;

function mjs_array_remove(item)
{
	var i,j=0;

	for(i=0;i<this.length;i++)
	{
		if(this[i]!=item)
		{
			this[j++]=this[i];
		}
	}
	if(this.length!=j)
	{
		this.length=j;
		return this;
	}
	return null;
}
Array.prototype.remove=mjs_array_remove;

if(!mjs_valued(Array.prototype.push))
{
	function mjs_array_push(el)
	{
		this[this.length]=el;
	}
	Array.prototype.push=mjs_array_push;
}

if(!mjs_valued(Array.prototype.pop))
{
	function mjs_array_pop(list)
	{
		if(this.length)
		{
			var ret=this[this.length-1];
			this.length--;
			return ret;
		}
		return null;
	}
	Array.prototype.pop=mjs_array_pop;
}

if(!mjs_valued(Array.prototype.shift))
{
	function mjs_array_shift(list)
	{
		if(this.length)
		{
			var ret=this[0];
			for(i=1;i<this.length;i++)
			{
				this[i-1]=this[i];
			}
			this.length--;
			return ret;
		}
		return null;
	}
	Array.prototype.shift=mjs_array_shift;
}


//--------------------------------------------------------------------------------
//
//	Logging - because there is little ways of sending feedback to developer,
//	log messages are momeorized until one of the dump function is called
//
//--------------------------------------------------------------------------------

var gMJS_log=[];

function LOGSENDMSG(msg)
{
	gMJS_log.push(msg);
}

function LOGERROR(str)
{
	LOGSENDMSG("error: "+vsprintf2(arguments));
}

function LOGMSG(str)
{
	LOGSENDMSG(vsprintf2(arguments));
}

function LOGPENDING()
{
	var list=gMJS_log;
	gMJS_log=[];
	return list;
}

function LOGFLUSH()
{
	var str=gMJS_log.join("\n");
	gMJS_log=[];
	return str;
}

function LOGALERT()
{
	var str=LOGFLUSH();

	if(str.length)
	{
		alertf("Error while initializing library:\n%s",str);
	}
}

//--------------------------------------------------------------------------------
//
//	A few libc imported stuff
//
//	(why on earth did they forgot sprintf' like functions ???)
//
//--------------------------------------------------------------------------------

var gMJS_sprintfre=/^([^%]*)%([-+])?(0)?(\d+)?(\.(\d+))?([cds])(.*)$/;
var gMJS_digits=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];

function _mjs_sprintf_number(val,base,pad,sign,width)
{
	val=parseInt(val);
	if(isNaN(val))
	{
		return "NaN";
	}
	aval=(val<0)?-val:val;
	var ret="";

	if(aval==0)
	{
		ret="0";
	}
	else
	{
		while(aval>0)
		{
			ret=gMJS_digits[aval%base]+ret;
			aval=mjs_int(aval/base);
		}
	}
	if(val<0)
	{
		ret="-"+ret;
	}
	if(sign=="-")
	{
		pad=" ";
	}
	return _mjs_sprintf_string(ret,pad,sign,width,-1);
}

function _mjs_sprintf_string(val,pad,sign,width,prec)
{
	var npad;

	if(!mjs_valued(val))
	{
		return "(undefined)";
	}
	if((npad=width-val.length)>0)
	{
		if(sign=="-")
		{
			while(npad>0)
			{
				val+=pad;
				npad--;
			}
		}
		else
		{
			while(npad>0)
			{
				val=pad+val;
				npad--;
			}
		}
	}
	if(prec>0)
	{
		return val.substr(0,prec);
	}
	return val;
}

// Yet another wheel re-invention

function _mjs_vsprintf(fmt,av,index)
{
	var output="";
	var i,m,line;

	line=fmt.split("\n");
	for(i=0;i<line.length;i++)
	{
		if(i>0)
		{
			output+="\n";
		}
		fmt=line[i];
		while(mjs_valued(match=gMJS_sprintfre.exec(fmt)))
		{
			var sign="";
			var pad=" ";

			if(!mjs_empty(match[1])) // the left part
			{
				// You can't add this blindly because mozilla set the value to <undefined> when
				// there is no match, and we don't want the "undefined" string be returned !
				output+=match[1];
			}
			if(!mjs_empty(match[2])) // the sign (like in %-15s)
			{
				sign=match[2];
			}
			if(!mjs_empty(match[3])) // the "0" char for padding (like in %03d)
			{
				pad="0";
			}

			var width=match[4];	// the with (32 in %032d)
			var prec=match[6];	// the precision (10 in %.10s)

			fmt=match[8];

			if(index>=av.length)
			{
				output += "<missing parameter>";
				continue;
			}

			var val=av[index++];

			switch(match[7]) // the type
			{
			case "d":
				output += _mjs_sprintf_number(val,10,pad,sign,width);
				break;
			case "o":
				output += _mjs_sprintf_number(val,8,pad,sign,width);
				break;
			case "x":
				output += _mjs_sprintf_number(val,16,pad,sign,width);
				break;
			case "X":
				output += _mjs_sprintf_number(val,16,pad,sign,width).toUpperCase();
				break;
			case "c":
				output += String.fromCharCode(parseInt(val));
				break;
			case "s":
				output += _mjs_sprintf_string(val,pad,sign,width,prec);
				break;
			default:
				//alert("unknown format %"+match[7]);
				break;
			}
		}
		output+=fmt;
	}
	return output;
}

function vsprintf2(av)
{
	return _mjs_vsprintf(av[0],av,1)
}

function vsprintf(fmt,av)
{
	return _mjs_vsprintf(fmt,av,0)
}

function sprintf(fmt)
{
	return _mjs_vsprintf(fmt,arguments,1);
}

function alertf(fmt)
{
	return alert(_mjs_vsprintf(fmt,arguments,1));
}

//--------------------------------------------------------------------------------
//
//	A few string utilities
//
//--------------------------------------------------------------------------------

function strcmp(a,b)
{
	// Turn a and b into strings
	a+="";
	b+="";
	if(a==b)
	{
		return 0;
	}
	else if(a>b)
	{
		return 1;
	}
	return -1;
}

function strcasecmp(a,b)
{
	// Turn a and b into strings
	a+="";
	b+="";
	return strcmp(a.toUpperCase(),b.toUpperCase());
}

function mjs_lc(s)
{
	return s.toLowerCase(s);
}

function mjs_uc(s)
{
	return s.toUpperCase(s);
}

function mjs_isspace(c)
{
	if(c==' ' || c=='\r' || c=='\n' || c=='\t')
	{
		return true;
	}
	return false;
}

function mjs_trim(str)
{
	var count;
	for(count=0;mjs_isspace(str.charAt(count));count++);
	str=str.substring(count);
	for(count=str.length-1;count>=0 && mjs_isspace(str.charAt(count));count--);
	str=str.substring(0,count+1);
	return str;
}

function mjs_rextractStr(val,re)
{
	var match;
	if(mjs_valued(match=re.exec(val)))
	{
		if(match.length>1)
		{
			return match[1];
		}
	}
	return null;
}

function mjs_rextract(val,re)
{
	return re.exec(val);
}

function mjs_match(str,re)
{
	return re.test(str);
}

//--------------------------------------------------------------------------------
//
//	Module initialization and dependencies
//
//	FIXME: support module dependencies
//
//--------------------------------------------------------------------------------

var gMJS_initModules=[];
var gMJS_initModulesDone=false;

function mjs_registerModule(name,code)
{
	if(gMJS_initModulesDone)
	{
		alertf("Attempt to register module %s after framework initialization",name);
		return;
	}
	gMJS_initModules.push({ func:code, name: name });
}

function mjs_initialize()
{
	var i,dur,str="";

	if(gMJS_initModulesDone)
	{
		return true;
	}

	gMJS_initModulesDone=true;
	mjs_benchStart();
	for(i=0;i<gMJS_initModules.length;i++)
	{
		mjs_benchStart();
		gMJS_initModules[i]['func']();
		dur=mjs_benchStop();
		if(dur>10)
		{
			str += sprintf("%s:%dms ",gMJS_initModules[i]['name'],dur);
		}
	}
	dur=mjs_benchStop();
	LOGTRACE("page initialization duration is %dms (%s)",dur,str);
	return true;
}

function mjs_shutdown()
{
	mjs_cancelAllTimedCall();
	mjs_cancelAllEventCallbacks();
}


//--------------------------------------------------------------------------------
//
//	Cross platform event handler registration mecanism
//
//	Browser act differently too: IE don't pass the object that triggered the
//	event (despite what happens when using "onsomething=code" inline
//	with the HTML).
//
//	To keep in touch with context, I created an indirection mecanism which
//	passes the object that is related to the callback. For this purpose,
//	a function is created on the fly with a unique name, identifying
//	the callback association within the document (i.e. two "onclick" on
//	different input fields will have distinct callback handler function).
//
//	Second tough problem: cancelling default actions. This is automatic when
//	using inline event handlers (ex: <form onsubmit='return false;'>)
//	Returning false means: do not process the default action (here: do not
//	submit the form). IE event handler follow the same principle: when you
//	returns false, it also discard the default behaviors. Mozilla don't
//	do this, as said by W3C. You need to call the "preventDefault()" method
//	which is associated with the event.
//
//	The "_mjs_runCallBack" adapt to those differences and make your event
//	handler working like with IE: when it returns false, the default behavior
//	will be discarded.
//
//	This is a little heavy, but it "solves" the issues (well, workaround...)
//
//--------------------------------------------------------------------------------

var gMJS_callbackRegistry={};
var gMJS_callbackRegistryList=[];
var gMJS_callbackObjRegistry={};

// This is the cross-browser glue code
function _mjs_runCallBack(id,ev)
{
	var hook=gMJS_callbackRegistry[id];
	var ret=false;

	LOGDEBUG("received event %s",id);
	if(mjs_valued(hook))
	{
		// Get context element and call the function
		var cb=hook['callback'];
		var recv=hook['object'];
		var uref=hook['uref'];
		var ret=cb(recv,ev,uref);
	}
	if(!ret && ev && ev.preventDefault)
	{
		// We have an event object passe, and it has the 'preventDefault' method ?
		// => great, use it when our callback returned false.
		ev.preventDefault();
	}
	return ret;
}

function mjs_runCallback(obj,evname)
{
	var id=mjs_allocateElementID(obj);
	var list;
	
	if(mjs_valued(list=gMJS_callbackObjRegistry[id]))
	{
		for(var i=0;i<list.length;i++)
		{
			if(list[i].evname == evname)
			{
				_mjs_runCallBack(list[i].cbid,undefined);
			}
		}
	}
}

function mjs_cancelAllEventCallbacks()
{
	for(var i=gMJS_callbackRegistryList.length-1;i>=0;i--)
	{
		var entry=gMJS_callbackRegistryList[i];
		var obj=entry['object'];

		if(obj.addEventListener)
		{
			obj.removeEventListener(entry['evname'],entry['func'],false);
		}
		else if(obj.attachEvent)
		{
			obj.detachEvent('on'+entry['evname'],entry['func']);
		}
	}
	gMJS_callbackRegistryList=[];
	gMJS_callbackRegistry={};
	gMJS_callbackObjRegistry={};
}

function mjs_setOnClickCallback(name,obj,code,uref)
{
	return mjs_setEventCallback("click",name,obj,code,uref);
}

function mjs_setEventCallback(evname,name,obj,code,uref)
{
	var id=mjs_allocateElementID(obj);
	var cbid=evname+'('+name+')'+' on:'+id;
	
	if(mjs_valued(gMJS_callbackRegistry[cbid]))
	{
		// Do not register the same thing twice
		return;
	}

	// Where 'eval' is a fantastic useful tool...
	eval("var func=function(ev){ return _mjs_runCallBack('"+cbid+"',ev); };");
	var entry={ callback:code, object:obj, cbid: cbid, uref:uref, func:func, evname:evname };
	gMJS_callbackRegistry[cbid]=entry;

	if(!mjs_valued(gMJS_callbackObjRegistry[id]))
	{
		gMJS_callbackObjRegistry[id]=[];
	}

	gMJS_callbackObjRegistry[id].push(entry);
	gMJS_callbackRegistryList.push(entry);

	if(obj.addEventListener)
	{
		obj.addEventListener(evname,func,false);
	}
	else if(obj.attachEvent)
	{
		// IE probably. On IE, event names are like this: "on<event>"
		obj.attachEvent('on'+evname,func);
	}
	else
	{
		LOGERROR("could not find a working method to attach event handlers");
	}
}

function mjs_eventMousex(ev)
{
	if(mjs_valued(ev.pageX))
	{
		return ev.pageX;
	}
	if(document.documentElement)
	{
		// IE6 in "standards compliant mode" or opera
		return ev.clientX+document.documentElement.scrollLeft;
	}
	return ev.clientX+document.body.scrollLeft;
}

function mjs_eventMousey(ev)
{
	if(mjs_valued(ev.pageY))
	{
		return ev.pageY;
	}
	if(document.documentElement)
	{
		// IE6 in "standards compliant mode" or opera
		return ev.clientY+document.documentElement.scrollTop;
	}
	return ev.clientY+document.body.scrollTop;
}

function mjs_documentWidth()
{
	if(mjs_isopera)
	{
		// Note: this does not work on IE6 !
		return mjs_width(document.documentElement);
	}
	return mjs_width(document.body);
}

function mjs_documentHeight()
{
	if(mjs_isopera)
	{
		// Note: this does not work on IE6 !
		return mjs_height(document.documentElement);
	}
	return mjs_height(document.body);
}

//--------------------------------------------------------------------------------
//
//	Cross platform timer mecanism
//
//--------------------------------------------------------------------------------

var gMJS_timerRegistry={};
var gMJS_jobid=1;
var gMJS_timerCurJobid="";

function mjs_currentTimerCallback()
{
	return gMJS_timerCurJobid;
}

function _mjs_cancelTimedCall(jobname,label)
{
	var jobid=jobname;
	var a=jobid.split("|");
	if(a.length>1)
	{
		jobid=a[0];
	}
	var rec=gMJS_timerRegistry[jobid];
	if(mjs_valued(rec))
	{
		LOGDEBUG("%s job #%s (%s) (system time #%s)",label,rec['jobname'],jobname,rec['timerid']);
		gMJS_timerRegistry[jobid]=null;
		window.clearTimeout(rec['timerid']);
	}
}

function mjs_cancelAllTimedCall()
{
	var jobid;
	for(jobid in gMJS_timerRegistry)
	{
		mjs_cancelTimedCall(jobid);
	}
}

function mjs_cancelTimedCall(jobid)
{
	return _mjs_cancelTimedCall(jobid,"cancelling");
}

function _mjs_runTimerCallBack(jobid,seq)
{
	var rec=gMJS_timerRegistry[jobid];
	if(mjs_valued(rec))
	{
		if(seq != rec['sequence'])
		{
			// Hm, we should not be receiving this !
			LOGERROR("received out of sequence callback %s",rec['jobname']);
			return;
		}
		LOGDEBUG("running job #%s",rec['jobname']);
		gMJS_timerRegistry[jobid]=null;
		var dur=(new Date)-rec['date'];
		var oldJob=gMJS_timerCurJobid;
		gMJS_timerCurJobid=sprintf("%s %dms/%dms",rec['jobname'],dur,rec['delay']);
		(rec['callback'])(rec['uref'],rec['uref2']);
		gMJS_timerCurJobid=oldJob;
	}
}

function mjs_timedCall(delay,jobid,proc,uref,uref2)
{
	var seq=gMJS_jobid++;
	var jobname=jobid;
	var a=jobid.split("|");

	if(a.length>1)
	{
		jobid=a[0];
	}
	if(!mjs_valued(jobid))
	{
		jobid="timer_"+seq;
	}
	if(mjs_valued(gMJS_timerRegistry[jobid]))
	{
		// Unregister previously armed timer
		_mjs_cancelTimedCall(jobid,"replacing");
	}
	if(!delay)
	{
		// Immediate call
		proc(uref,uref2);
		return null;
	}
	LOGDEBUG("arming  job #%s %d ms from now",jobname,delay);
	var timerId=window.setTimeout("_mjs_runTimerCallBack('"+jobid+"',"+seq+");",delay);
	gMJS_timerRegistry[jobid]={ delay: delay, date: new Date, callback:proc, jobid: jobid, jobname: jobname,
		uref:uref, uref2:uref2, timerid: timerId, sequence: seq };
	return jobid;
}


//--------------------------------------------------------------------------------
//
//	Searches elements that correspond to specific tags from a given node
//
//	BEWARE: this can be a long processing since the element tree is parsed
//	entirely (I did not found a portable possibility to do otherwise)
//
//	search tag through the whole document:
//		elementList=mjs_lookupTags(tag)
//
//	search tag1, tag2 & tag3 through the whole document:
//		elementList=mjs_lookupTags(null,tag1,tag2,tag3);
//
//	search tag1 & tag2 from the given location:
//		elementList=mjs_lookupTags(root,tag1,tag2);
//
//	The following function does the same, but retrieve a liste of element which
//	are not sorted in the document order (it's much faster):
//
//		elementList=mjs_lookupUnsortedTags(tag)
//		elementList=mjs_lookupUnsortedTags(null,tag1,tag2,...)
//		elementList=mjs_lookupUnsortedTags(root,tag1,tag2,...)
//
//--------------------------------------------------------------------------------
//
//	FIXME: on DOM3 browser, we can improve this by using the "compareDocumentPosition()
//	interface in conjunction with getElementsByTagName();
//
//--------------------------------------------------------------------------------

function mjs_lookupTags()
{
	var index=1;
	var where=null;
	var tagName;

	function _mjs_lookupTags(el,olist,table)
	{
		var l=el.childNodes;

		for(var i=0;i<l.length;i++)
		{
			var child=l[i];
			if(table[child.tagName])
			{
				olist.push(child);
			}
			_mjs_lookupTags(child,olist,table);
		}
	}

	if(arguments.length==1)
	{
		// mjs_lookupTags(tagName)
		index=0;
	}
	else
	{
		index=1;
		where=arguments[0];
	}

	if(!mjs_valued(where))
	{
		// Search everywhere
		where=document.body;
	}

	if((arguments.length-index)==1)
	{
		// Only one tag searched ? optimize by using getElementsByTagName()
		return where.getElementsByTagName(mjs_uc(arguments[index]));
	}

	// Multiple tag searched ? 
	// => we need element recursion so that we preserve element order

	var olist=[];
	var table={};
	var i;

	for(;index<arguments.length;index++)
	{
		table[mjs_uc(arguments[index])]=true;
	}

	_mjs_lookupTags(where,olist,table);
	return olist;
}


function mjs_lookupUnsortedTags()
{
	var index=1;
	var where=null;
	var tagName;

	if(arguments.length==1)
	{
		// mjs_lookupUnsortedTags(tagName)
		index=0;
	}
	else
	{
		index=1;
		where=arguments[0];
	}

	if(!mjs_valued(where))
	{
		// Search everywhere
		where=document.body;
	}

	if((arguments.length-index)==1)
	{
		// Only one tag searched
		return where.getElementsByTagName(mjs_uc(arguments[index]));
	}

	var list=[];
	for(;index<arguments.length;index++)
	{
		list.append(where.getElementsByTagName(mjs_uc(arguments[index])));
	}
	return list;
}


//--------------------------------------------------------------------------------
//
//	Searches for UI elements that hold editable data within a form
//
//	BEWARE: for the sake of efficiency, order of elements in the returned
//	list does not follow document sequence ! If the order matters,
//	use the mjs_lookupTags() instead.
//
//--------------------------------------------------------------------------------

var gMJS_ctrlTypes=['INPUT','SELECT','TEXTAREA'];

function mjs_lookupEditableElements(rootNode)
{
	var list=new Array;
	var j=0,i,t,nodes;
	
	if(!mjs_valued(rootNode))
	{
		// Searches everywhere
		rootNode=document.body;
	}
	for(t=0;t<gMJS_ctrlTypes.length;t++)
	{
		var type=gMJS_ctrlTypes[t];
		if(rootNode.tagName == type)
		{
			// The node itself is such a control 
			// => adds it
			list[j++]=rootNode;
		}
		else
		{
			nodes=rootNode.getElementsByTagName(type);
			for(i=0;i<nodes.length;i++)
			{
				list[j++]=nodes[i];
			}
		}
	}
	return list;
}

function mjs_formElementList(from)
{
	var formlist;
	var olist=new Array;
	var i;
	
	if(!mjs_valued(from))
	{
		formlist=document.forms;
	}
	else
	{
		formlist=from.getElementsByTagName("FORM");
	}
	for(i=0;i<formlist.length;i++)
	{
		olist.append(formlist[i].elements);
	}
	return olist;
}



//--------------------------------------------------------------------------------
//
//	Searches for TAG having a given "mjstype"
//
//	Restriction: only SPAN tag can be associated with mjstype; this is to
//	avoid recusrion through the complete HTML tree when possible
//
//--------------------------------------------------------------------------------

function mjs_lookupTypedElements(mjstype)
{
	var list=mjs_lookupTags(null,"span","div");
	var el,olist=[],type;

	for(var i=list.length-1;i>=0;i--)
	{
		el=list[i];
		if(!mjs_valued(type=el.getAttribute(gMJS_typeAttrName)))
		{
			continue;
		}
		if(strcasecmp(type,mjstype))
		{
			continue;
		}
		olist.push(el);
	}
	return olist;
}


//--------------------------------------------------------------------------------
//
//	CSS tricks
//
//--------------------------------------------------------------------------------

function mjs_addElementClass(el,cls)
{
	var val=el.className;

	// Optimize the most common cases
	if(!mjs_valued(cls))
	{
		return true;
	}
	if(val==cls)
	{
		return true;
	}
	if(val=="")
	{
		el.className=cls;
		return true;
	}
	// Use RE for the rest
	var re=new RegExp("(^|\\s)"+cls+"(\\s|$)","i");
	if(mjs_rextract(val,re))
	{
		// Already contains the class
		return false;
	}
	el.className=val+" "+cls;
	return true;
}

function mjs_rmElementClass(el,cls)
{
	var val=el.className;

	// Optimize the most common cases
	if(!mjs_valued(cls))
	{
		return true;
	}
	if(val==cls)
	{
		el.className="";
		return true;
	}
	if(val=="")
	{
		return true;
	}

	// Use RE for the rest
	var i,list=val.split(" "),str="";
	cls=cls.toUpperCase();

	for(i=0;i<list.length;i++)
	{
		if(list[i].toUpperCase()==cls)
		{
			continue;
		}
		if(str.length)
		{
			str+=" "+list[i];
		}
		else
		{
			str+=list[i];
		}
	}

	el.className=str;
	return true;
}

function mjs_setElementClass(el,state,cls)
{
	if(state)
	{
		mjs_addElementClass(el,cls);
	}
	else
	{
		mjs_rmElementClass(el,cls);
	}
}


//--------------------------------------------------------------------------------
//
//	Benchmarking stuff
//
//--------------------------------------------------------------------------------

var gMJS_benchStack=[];

function mjs_benchStart()
{
	gMJS_benchStack.push(new Date);
}

function mjs_benchStop(label)
{
	var t1=gMJS_benchStack.pop();
	var t2=new Date;
    var dur=t2.getTime()-t1.getTime();

	if(mjs_valued(label))
	{
		LOGTRACE("benchmark <%s> : %dms",label,dur);
	}
	return dur;
}



// ==========

function mjs_isTextField(ctrl)
{
	if(typeof(ctrl)=="object")
	{
		if(ctrl.tagName=="TEXTAREA")
		{
			return true;
		}
		if((ctrl.tagName=="INPUT") && (ctrl.getAttribute('type')=="text"))
		{
			return true;
		}
	}
	return false;
}

function mjs_isCheckBox(ctrl)
{
	if((typeof(ctrl)=="object") && (ctrl.tagName=="INPUT") && (ctrl.getAttribute('type')=="checkbox"))
	{
		return true;
	}
	return false;
}

function mjs_isRadioButton(ctrl)
{
	if((typeof(ctrl)=="object") && (ctrl.tagName=="INPUT") && (ctrl.getAttribute('type')=="radio"))
	{
		return true;
	}
	return false;
}

function mjs_isButton(ctrl)
{
	if((typeof(ctrl)=="object") && (ctrl.tagName=="INPUT") && (ctrl.getAttribute('type')=="button"))
	{
		return true;
	}
	return false;
}

function mjs_isCombo(ctrl)
{
	if((typeof(ctrl)=="object") && (ctrl.tagName=="SELECT") && (ctrl.size<=1))
	{
		return true;
	}
	return false;
}

function mjs_isscalar(v)
{
	switch(typeof(v))
	{
	case "string":
	case "number":
		return true;
	}
	return false;
}

function mjs_getFieldValue(el)
{
	switch(el.tagName)
	{
	case "INPUT":
		switch(el.getAttribute('type'))
		{
		case "checkbox":
		case "radio":
			return el.checked;
		}
		break;
	}
	return el.value;
}

function mjs_emptyFieldValue(el)
{
	if(mjs_isRadioButton(el))
	{
		return;
	}
	return mjs_setFieldValue(el,"");
}

function mjs_setFieldValue(el,value)
{
	if(!mjs_valued(value))
	{
		value="";
	}
	switch(el.tagName)
	{
	case "INPUT":
		switch(el.getAttribute('type'))
		{
		case "checkbox":
		case "radio":
			if(typeof(value)=="boolean")
			{
				el.checked=value;
			}
			else if(typeof(value)=="number")
			{
				el.checked=value!=0;
			}
			else if(value.match(/(yes|true)/i))
			{
				el.checked=true;
			}
			else
			{
				el.checked=false;
			}
			return true;
		}
		break;

	case "SELECT":
		// el.value=value works almost everywhere, except on Konqueror
		// Doing via options shoulw work more universally
		for(var i=0;i<el.options.length;i++)
		{
			if(el.options[i].value == value)
			{
				el.options[i].selected=true;
				break;
			}
		}
		return true;
	}
	el.value=value;
	return true;
}

function mjs_hasFieldChanged(el)
{
	// Unified API anyone? grrrrr
	if(el.tagName != "SELECT")
	{
		switch(el.getAttribute('type'))
		{
		case "checkbox":
		case "radio":
			return (el.checked != el.defaultChecked);
		}
		return (el.value!=el.defaultValue);
	}

	// Select objects are a little tricker because we need to go through
	// the option list

	for(var i=el.options.length-1;i>=0;i--)
	{
		var o=el.options[i];
		if(o.selected != o.defaultSelected)
		{
			return true;
		}
	}
	return false;
}

function mjs_isFormField(el)
{
	switch(el.tagName)
	{
	case "INPUT":
	case "TEXTAREA":
	case "SELECT":
		return true;
	}
	return false;
}

function mjs_allocateElementID(el,proposedId)
{
	var id,name;

	if(el==window)
	{
		return "window";
	}
	if(el==document)
	{
		return "document";
	}
	if(!mjs_empty(id=mjs_id(el)))
	{
		return id;
	}
	if(mjs_empty(id=proposedId))
	{
		if(!mjs_empty(name=mjs_name(el)))
		{
			// Creates an ID based on the name
			id=name;
		}
		else
		{
			switch(el.tagName)
			{
			case "INPUT":
				id=el.getAttribute('type');
				break;
			case "TEXTAREA":
				id="textarea";
				break;
			case "SELECT":
				id="list";
				break;
			default:
				id=mjs_lc(el.tagName);
				break;
			}
		}
	}

	// Ok, we've a base for the ID; let's make sure it's not already in use

	if(document.getElementById(id))
	{
		// Collision - use index
		var i,nid;
		for(i=2;;i++)
		{
			nid=id+i;
			if(!document.getElementById(nid))
			{
				el.id=nid;
				return nid;
			}
		}
	}

	el.id=id;
	return id;
}

function mjs_labelid(el)
{
	if(!mjs_empty(el.id))
	{
		return sprintf("%s[id=%s]",el.tagName,el.id);
	}
	if(!mjs_empty(el.name))
	{
		return sprintf("%s[name=%s]",el.tagName,el.name);
	}
	return el.tagName;
}


function _mjs_string(str)
{
	if(mjs_valued(str) && (typeof(str)=='string'))
	{
		return str;
	}
	return undefined;
}

function mjs_id(el)
{
	return _mjs_string(el.id);
}

function mjs_name(el)
{
	return _mjs_string(el.getAttribute('name'));
}

function mjs_nameOrId(el)
{
	var str;

	if(!mjs_empty(str=mjs_name(el)))
	{
		return str;
	}
	return mjs_id(el);
}

function mjs_idOrName(el)
{
	var str;

	if(!mjs_empty(str=mjs_name(el)))
	{
		return str;
	}
	return mjs_id(el);
}

//--------------------------------------------------------------------------------
//	
//	Deals with attributes
//	
//--------------------------------------------------------------------------------

function mjs_attr(el,attrname)
{
	if(el.getAttribute)
	{
		var str=el.getAttribute(attrname);
		if(mjs_valued(str) && (typeof(str)=='string') && str.length>0)
		{
			return str;
		}
	}
	return undefined;
}

function mjs_setAttr(el,attrname,value)
{
	return el.setAttribute(attrname,value);
}

function fgr_getAttrDfl(el,attrName,dfl)
{
	var value;

	if(!mjs_empty(value=el.getAttribute(attrName)))
	{
		return value;
	}
	return dfl;
}

function mjs_inheritAttrDfl(el,attr,dfl)
{
	var value;

	if(!mjs_empty(value=mjs_inheritAttr(el,attr)))
	{
		return value;
	}
	return dfl;
}

function mjs_inheritAttr(obj,name)
{
	var parent=obj,value;

	if(!(mjs_valued(value=obj.getAttribute(name))))
	{
		while(mjs_valued(parent) && (parent!=document))
		{
			if(mjs_valued(value=parent.getAttribute(name)))
			{
				if(parent != obj)
				{
					obj.setAttribute(name,value);
					return value;
				}
			}
			parent=parent.parentNode;
		}
		return undefined;
	}
	return value;
}



//--------------------------------------------------------------------------------
//	
//	Alter elements
//	
//--------------------------------------------------------------------------------

function mjs_hide(el)
{
	if(mjs_valued(el))
	{
		el.style.visibility="hidden";
	}
}

function mjs_show(el)
{
	if(mjs_valued(el))
	{
		el.style.visibility="visible";
	}
}

function mjs_focus(el)
{
	if(mjs_valued(el))
	{
		return el.focus();
	}
}

function mjs_select(el)
{
	if(mjs_valued(el))
	{
		return el.select();
	}
}

function mjs_elementLabel(el)
{
	var str;

	if(mjs_valued(str=mjs_attr(el,gMJS_labelAttrName)))
	{
		return str;
	}
	return mjs_attr(el,"name");
}

function mjs_elementHelp(el)
{
	var str;

	if(mjs_valued(str=mjs_attr(el,gMJS_helpAttrName)))
	{
		return str;
	}
	return "";
}

function mjs_x(el)
{
	var x=0;

	if(el.offsetParent)
	{
		for(;el.offsetParent;el=el.offsetParent)
		{
			x += el.offsetLeft;
		}
	}
	return x;
}

function mjs_y(el)
{
	var y=0;

	if(el.offsetParent)
	{
		for(;el.offsetParent;el=el.offsetParent)
		{
			y += el.offsetTop;
		}
	}
	return y;
}

function mjs_height(el)
{
	return el.offsetHeight;
}

function mjs_width(el)
{
	return el.offsetWidth;
}

function mjs_style(el,prop)
{
	if(window.getComputedStyle)
	{
		return window.getComputedStyle(el,null).getPropertyValue(prop);
	}
	if(el.currentStyle)
	{
		return eval('el.currentStyle.'+prop);
	}
	LOGERROR("cannot retrieve style '%s' for element '%s'",prop,mjs_labelid(el));
	return undefined;
}

function mjs_stylePosition(el)
{
	if(window.getComputedStyle)
	{
		return window.getComputedStyle(el,null).getPropertyValue("position");
	}
	if(el.currentStyle)
	{
		return el.currentStyle.position;
	}
	return undefined;
}

function mjs_moveTo(el,x,y)
{
	var parent;

	if(!mjs_valued(el))
	{
		return false;
	}
	if(parent=el.offsetParent)
	{
		for(;parent;parent=parent.offsetParent)
		{
			if(mjs_stylePosition(parent) == "absolute")
			{
				// One of the ancestor has absolute positioning
				// => the one we are moving now will use this ancestor coordinates
				// => adjust x consequently
				x -= mjs_x(parent);
				y -= mjs_y(parent);
				break;
			}
		}
	}
	el.style.top=y+"px";
	el.style.left=x+"px";
}

function mjs_resize(el,dx,dy)
{
	if(mjs_valued(el))
	{
		if(mjs_valued(dx))
		{
			if(!mjs_empty(dx))
			{
				el.style.width=dx+"px";
			}
			else
			{
				// FIXME: This does not work on Opera :(
				el.style.width="auto";
			}
		}
		if(mjs_valued(dy))
		{
			if(!mjs_empty(dy))
			{
				el.style.height=dy+"px";
			}
			else
			{
				// FIXME: This does not work on Opera :(
				el.style.height="auto";
			}
		}
	}
}

//--------------------------------------------------------------------------------
//
//	Simple data type conversion
//
//--------------------------------------------------------------------------------

function mjs_int(val)
{
	// Outch, ugly hacks...
	// val-0 => makes the value a number (beware, val+0 would not!)
	// |0 => using the logical OR turns values into integer
	return (val-0)|0;
}

function mjs_float(val)
{
	return (val-0);
}

function mjs_str(val)
{
	return ""+val;
}

//--------------------------------------------------------------------------------
//
//	Stub functions, which are replaced by something more useful
//	when loading the var module.
//
//--------------------------------------------------------------------------------

function mjs_setVariable()
{
	return null;
}

function mjs_getVariable()
{
	return null;
}

function mjs_registerConstraintType()
{
	LOGERROR("mjs_registerConstraintType() is not yet loaded");
	return null;
}

function LOGDEBUG()
{
	return null;
}

function LOGTRACE()
{
	return null;
}


//--------------------------------------------------------------------------------
//
// Register the "onload" callback so that it get self-initialized
//
//--------------------------------------------------------------------------------

mjs_setEventCallback("load",'init',window,mjs_initialize);
mjs_setEventCallback("unload",'shutdown',window,mjs_shutdown);

