/*##############################################################################
#    ____________________________________________________________________
#   /                                                                    \
#  |               ____  __      ___          _____  /     ___    ___     |
#  |     ____       /  \/  \  ' /   \      / /      /__   /   \  /   \    |
#  |    / _  \     /   /   / / /    /  ___/  \__   /     /____/ /    /    |
#  |   / |_  /    /   /   / / /    / /   /      \ /     /      /____/     |
#  |   \____/    /   /    \/_/    /  \__/  _____/ \__/  \___/ /           |
#  |                                                         /            |
#  |                                                                      |
#  |   Copyright (c) 1999-2007                        MindStep SCOP SARL  |
#  |   Herve Masson                                                       |
#  |                                                                      |
#  |      www.mindstep.com                              www.mjslib.com    |
#  |   info-oss@mindstep.com                           mjslib@mjslib.com  |
#   \____________________________________________________________________/
#
#  Version: $Id: main.js 3416 2007-08-06 16:52:31Z herve $
#
#  [This product is distributed under a BSD-like license]
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions
#  are met:
#
#     1. Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#
#     2. Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in
#        the documentation and/or other materials provided with the
#        distribution.
#
#  THIS SOFTWARE IS PROVIDED BY THE MINDSTEP CORP PROJECT ``AS IS'' AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
#  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
#  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MINDSTEP CORP OR CONTRIBUTORS
#  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
#  OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
#  OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
#  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
#  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
#  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#  The views and conclusions contained in the software and documentation
#  are those of the authors and should not be interpreted as representing
#  official policies, either expressed or implied, of MindStep Corp.
#
##############################################################################*/


// 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;			// IE
var mjs_isie6=false;		// IE 6.*
var mjs_isie7=false;		// IE 7.*
var mjs_isielt7=false;		// IE <7.*
var mjs_isiegt55=false;		// IE >=5.5
var mjs_isiege7=false;		// IE >=7
var mjs_ismoz=false;		// Mozilla/firefox
var mjs_isopera=false;		// Opera
var mjs_iskhtml=false;

function mjs_detectBrowser()
{
	var ver;
	if(navigator.userAgent.match(/KHTML/i))
	{
		mjs_isktml=true;
	}
	else if(navigator.userAgent.match(/Gecko/i))
	{
		mjs_ismoz=true;
	}
	else if(navigator.userAgent.toLowerCase().indexOf('opera')>-1)
	{
		mjs_isopera=true;
	}
	else
	{
		ver=parseFloat(navigator.userAgent.match(/MSIE (\d\.\d+)\.*/i)[1]);
		if(ver>0)
		{
			// IE footprint
			undefined=void(0);	// define "undefined" because it does not have it
			mjs_isie=true;
			mjs_isiegt55=(ver>=5.5);
			mjs_isie6=(ver>=6 && ver<7);
			mjs_isie7=(ver>=7 && ver<8);
			mjs_isielt7=(ver<7)
			mjs_isiege7=(ver>=7)
		}
	}
}

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;
}

if(!mjs_valued(Array.prototype.toSource))
{
	function mjs_array_toSource()
	{
		var i;
		var src = [];

		for (i=0;i<this.length;i++){
			src.push(this[i].toSource());
		}

		return('([' + src.join(',') + '])');
	}
	Array.prototype.toSource=mjs_array_toSource;
}

if(!mjs_valued(Object.prototype.toSource))
{
	function mjs_object_toSource()
	{
		var i,tmp;
		var src = [];

		var keys= this.keys();

		for (i=0; i<keys.length;i++)
		{
			src.push(keys[i] + ':' + this[keys[i]].toSource());
		}
		return('({' + src.join(',') + '})');
	}
	Object.prototype.toSource=mjs_object_toSource;
}

function mjs_object_keys()
{
	var keys=[];

	for(var k in this)
	{
		if(typeof(this[k]) != "function")
		{
			keys.push(k);
		}
	}
	return keys;
}
Object.prototype.keys=mjs_object_keys;

function mjs_string_toSource()
{
	 return ('"' + this + '"')
}
String.prototype.toSource=mjs_string_toSource;

function mjs_number_toSource()
{
	 return (this)
}
Number.prototype.toSource=mjs_number_toSource;

//--------------------------------------------------------------------------------
//
//  Declare a few general purpose "classes"
//
//--------------------------------------------------------------------------------

function Point(x,y)
{
	this.x=x;
	this.y=y;
}

Point.prototype.moveTo=function(x,y)
{
	if(mjs_valued(x))
	{
		this.x=x;
	}
	if(mjs_valued(y))
	{
		this.y=y;
	}
}

function Size(dx,dy)
{
	this.dx=dx;
	this.dy=dy;
}

//--------------------------------------------------------------------------------
//
//  Logging - uses firebug console interface (it won't work without firebug)
//
//--------------------------------------------------------------------------------

var mjs_logBuffer=[];

function _mjs_logFwd(level,type,args)
{
	var str="["+type+"] "+vsprintf2(args);
	if("console" in window)
	{
		if(mjs_logBuffer.length>0)
		{
			console.error(sprintf("%d messages pending",mjs_logBuffer.length));
		}
		if(level<1)
		{
			console.error(str);
		}
		else if(level<2)
		{
			console.info(str);
		}
		else
		{
			console.debug(str);
		}
	}
	else
	{
		mjs_logBuffer.push(str);
	}
	return null;
}

function LOGERROR()
{
	return _mjs_logFwd(0,"error",arguments);
}

function LOGLOG()
{
	return _mjs_logFwd(1,"log",arguments);
}

function LOGWARN()
{
	return _mjs_logFwd(1,"warn",arguments);
}

function LOGDEBUG()
{
	// By default, we don't want debugging messages
	return null;
}

function LOGTRACE()
{
	// By default, we don't want traces messages
	return null;
}

function LOGSTACK()
{
	if("console" in window)
	{
		console.trace(vsprintf2(arguments));
	}
	return null;
}


//--------------------------------------------------------------------------------
//
//	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,10);
	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,10));
				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_onLoad(code)
{
	mjs_registerModule("anonymous",code);
}

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={};
var gMJS_callbackNameIdnum=1;

// 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_cancelEventCallback(cbid)
{
	var entry=gMJS_callbackRegistry[cbid];

	if(mjs_valued(entry))
	{
		gMJS_callbackRegistryList.remove(entry);
		delete gMJS_callbackRegistry[cbid];
		delete gMJS_callbackObjRegistry[cbid];

		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']);
		}
	}
	else
	{
		LOGERROR("cannot remove event callback #%s",cbid);
	}
}

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);
	
	if(!mjs_valued(name))
	{
		name="noname"+gMJS_callbackNameIdnum++;
	}

	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");
	}
	return cbid;
}

function mjs_onEvent(el,evname,func,/*optional*/uref)
{
	return mjs_setEventCallback(evname,null,el,func,uref);
}

function mjs_onClick(el,func,/*optional*/uref)
{
	return mjs_setEventCallback("click",null,el,func,uref);
}

function mjs_sendEvent(el,evname)
{
	var id=mjs_allocateElementID(el);
	var list=gMJS_callbackObjRegistry[id];

	for(var i=0;i<list.length;i++)
	{
		var cb=list[i];
		if(cb['evname'] == evname)
		{
			_mjs_runCallBack(cb['cbid'],null);
		}
	}
}

function mjs_eventMousex(ev)
{
	if(ev.pageX)
	{
		return ev.pageX;
	}
	return ev.clientX + document.body.scrollLeft - document.body.clientLeft;
}

function mjs_eventMousey(ev)
{
	if(ev.pageY)
	{
		return ev.pageY;
	}
	return ev.clientY + document.body.scrollTop  - document.body.clientTop;
}

function mjs_eventMousePosition(ev)
{
	return new Point(mjs_eventMousex(ev),mjs_eventMousey(ev));
}

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);
}

function mjs_viewportSize()
{
	// Beware: it includes the scrollbars (Can't find a way to avoid that)
	if(window.innerWidth)
	{
		// SHould be ok for mozilla/netscape/opera/IE7
		return new Size(window.innerWidth,window.innerHeight);
	}
	if(document.documentElement)
	{
		// IE6 in "standards compliant mode"
		 return new Size(document.documentElement.clientWidth,document.documentElement.clientHeight);
	}
	return new Size(document.body.clientWidth,document.body.clientHeight);
}

function mjs_viewportScrollOffset()
{
	return new Point(window.pageXOffset,window.pageYOffset);
}

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

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

function _mjs_freeHash(value)
{
	var k;
	for (k in value)
	{
		delete value[k];
	}
}

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']);
		delete gMJS_timerRegistry[jobid];
		window.clearTimeout(rec['timerid']);
		_mjs_freeHash(rec);
	}
}

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))
	{
		delete gMJS_timerRegistry[jobid];

		if(seq != rec['sequence'])
		{
			// Hm, we should not be receiving this !
			_mjs_freeHash(rec);
			LOGERROR("received out of sequence callback %s",rec['jobname']);
			return;
		}

		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;
		_mjs_freeHash(rec);
	}
	else
	{
		LOGERROR("received _mjs_runTimerCallBack without job ID");
	}
}

function mjs_timedCall(delay,jobid,proc,uref,uref2)
{
	var seq=gMJS_jobid++;

	if(!mjs_valued(jobid))
	{
		jobid="timer_"+seq;
	}

	var jobname=jobid;
	var a=jobid.split("|");
	if(a.length>1)
	{
		jobid=a[0];
	}

	if(mjs_valued(gMJS_timerRegistry[jobid]))
	{
		// Unregister previously armed timer
		_mjs_cancelTimedCall(jobid,"replacing");
	}
	if(!delay)
	{
		// Immediate call
		proc(uref,uref2);
		return null;
	}
	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,...)
//
//	The mjs_lookupChild* variants only consider one level below the given
//	elements.
//
//--------------------------------------------------------------------------------
//
//	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;
}

function mjs_lookupChildTags(el,tagname)
{
	var list=[];
	var l=el.childNodes;

	for(var i=0;i<l.length;i++)
	{
		var child=l[i];
		if((typeof(child)=="object") && (child.tagName==tagname))
		{
			list.push(child);
		}
	}
	return list;
}

function mjs_lookupChildTag(el,tagname)
{
	var l=el.childNodes;

	for(var i=0;i<l.length;i++)
	{
		var child=l[i];
		if((typeof(child)=="object") && (child.tagName==tagname))
		{
			return child;
		}
	}
	return null;
}


//--------------------------------------------------------------------------------
//
//	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;
}

function mjs_form(el)
{
	while(mjs_valued(el) && (el!=document))
	{
		if((typeof(el)=="object") && (el.tagName=="FORM"))
		{
			return el;
		}
		el=el.parentNode;
	}
	return undefined;
}

function mjs_lookupForm(formname)
{
	var list=document.forms;

	for(var i=0;i<list.length;i++)
	{
		if(list[i].name == formname)
		{
			return list[i];
		}
	}
	return undefined;
}


//--------------------------------------------------------------------------------
//
//	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;
}

var gMJS_allocatedIDs=[];

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) || gMJS_allocatedIDs[id])
	{
		// Collision - use index
		var i,nid;
		for(i=2;;i++)
		{
			nid=id+i;
			if(!(document.getElementById(nid) || gMJS_allocatedIDs[nid]))
			{
				gMJS_allocatedIDs[nid]=true;
				el.id=nid;
				return nid;
			}
		}
	}

	// We need to keep that handy because document.getElementById() won't see
	// elements that have not yet been inserted somewhere.

	gMJS_allocatedIDs[id]=true;
	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;
	}
	LOGERROR('mjs_nameOrId')
	return mjs_id(el);
}

function mjs_idOrName(el)
{
	var str;

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

function mjs_lookupid(elid)
{
	return document.getElementById(elid);
}

function mjs_outerHtml(el)
{
	var attrs=el.attributes;
	var str="<"+el.tagName;
	for(var i=0;i<attrs.length;i++)
	{
		str += " "+attrs[i].name+"=\""+attrs[i].value+"\"";
	}
	return str + ">" + el.innerHTML + "</" + el.tagName + ">";
}

//--------------------------------------------------------------------------------
//
//	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_existsAttr(el,attrname)
{
	if(el.getAttribute)
	{
		var str=el.getAttribute(attrname);
		if(mjs_valued(str))
		{
			return true;
		}
	}
	return false;
}

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;
}

//--------------------------------------------------------------------------------
//
//	Drag'n'drop helper
//
//--------------------------------------------------------------------------------

var _mjs_dragUpID,_mjs_dragMoveID,_mjs_dragElement,_mjs_dragOffsetX,_mjs_dragOffsetY;

function _mjs_dragMouseUp()
{
	mjs_cancelEventCallback(_mjs_dragUpID);
	mjs_cancelEventCallback(_mjs_dragMoveID);
	return false;
}

function _mjs_dragMouseMove(el,ev)
{
	var x=mjs_eventMousex(ev);
	var y=mjs_eventMousey(ev);
	_mjs_dragElement._dragCallback(_mjs_dragElement,x-_mjs_dragOffsetX,y-_mjs_dragOffsetY);
	// Important: this prevent the default processing (which in IE trigger text selection)
	return false;
}

function _mjs_startDrag(el,ev)
{
	var elpos=mjs_position(el);
	var mpos=mjs_eventMousePosition(ev);

	_mjs_dragOffsetX=mpos.x-elpos.x;
	_mjs_dragOffsetY=mpos.y-elpos.y;
	_mjs_dragElement=el;

	_mjs_dragUpID=mjs_setEventCallback("mouseup",null,document,_mjs_dragMouseUp);
	_mjs_dragMoveID=mjs_setEventCallback("mousemove",null,document,_mjs_dragMouseMove);
	return false;
}

function mjs_makeDraggable(element,callback)
{
	element._dragCallback=callback;
	mjs_setEventCallback("mousedown",null,element,_mjs_startDrag);
}


//--------------------------------------------------------------------------------
//
//	Create, alter elements
//	======================
//
//	mjs_newElement() creates element of a given type
//
//	mjs_insertElement() inserts an element between the given element and its parent
//
//	mjs_replaceElement() replaces the given element by another one, preserving any
//	existing child item.
//
//	mjs_detachElement() detach the element from its parent
//
//--------------------------------------------------------------------------------

function mjs_newElement(tagName)
{
	return document.createElement(tagName);
}

function mjs_insertElement(el,tagName)
{
	var parent=el.parentNode;
	var newel=mjs_newElement(tagName);

	parent.insertBefore(newel,el);
	parent.removeChild(el);
	newel.appendChild(el);
	return newel;
}

function mjs_replaceElement(el,tagName)
{
	var parent=el.parentNode;
	var newel=mjs_newElement(tagName);

	parent.insertBefore(newel,el);
	while(el.childNodes.length>0)
	{
		var item=el.childNodes[0];
		el.removeChild(item);
		newel.appendChild(item);
	}
	parent.removeChild(el);
	return newel;
}

function mjs_addElement(parent,tagName)
{
	var newel=mjs_newElement(tagName);

	parent.appendChild(newel);
	return newel;
}

function mjs_detachElement(el)
{
	var parent=el.parentNode;
	parent.removeChild(el);
}

function mjs_parseHtmlElement(html)
{
	var el=mjs_newElement("span");
	el.innerHTML=html;
	if(el.childNodes.length>1)
	{
		// More than one item => keep the <span> container
		return el;
	}
	// Only one element -> returns it
	return el.childNodes[0];
}

function mjs_childElementAt(el,index,/* optional */tagName)
{
	var list=el.childNodes;
	var num=0;

	for(var i=0;i<list.length;i++)
	{
		var child=list[i];
		if(child.tagName)
		{
			if(tagName && strcasecmp(child.tagName,tagName))
			{
				LOGERROR("TAG %s != %s",tagName,child.tagName);
				continue;
			}
			if(num==index)
			{
				return child;
			}
			num++;
		}
	}
	return null;
}

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_position(el)
{
	var y=0,x=0;

	if(el.offsetParent)
	{
		for(;el.offsetParent;el=el.offsetParent)
		{
			x += el.offsetLeft;
			y += el.offsetTop;
		}
	}
	return new Point(x,y);
}

function mjs_relPosition(el)
{
	var y=0,x=0;

	if(el.offsetParent)
	{
		return new Point(el.offsetLeft,el.offsetTop);
	}
	return new Point(0,0);
}

function mjs_x(el)
{
	return mjs_position(el).x;
}

function mjs_y(el)
{
	return mjs_position(el).y;
}

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

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

function mjs_size(el)
{
	return new Size(mjs_width(el),mjs_height(el));
}

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;
	}
	var hasx=mjs_valued(x);
	var hasy=mjs_valued(y);
	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
				if(hasx)
				{
					x -= mjs_x(parent);
				}
				if(hasy)
				{
					y -= mjs_y(parent);
				}
				break;
			}
		}
	}
	if(hasx)
	{
		el.style.left=x+"px";
	}
	if(hasy)
	{
		el.style.top=y+"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";
			}
		}
	}
}

function mjs_autoSize(el)
{
	// This restore automatic sizing for the element
	mjs_resize(el,"","");
}

function mjs_setOpacity(el,opacity)
{
	if(mjs_isie)
	{
		el.style.filter		= "alpha(opacity=" + opacity + ")";
	}
	else
	{
    	el.style.opacity	= (opacity/100);
	}
}

//--------------------------------------------------------------------------------
//
//	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;
}

//--------------------------------------------------------------------------------
//
// Cookie & session management
//
//--------------------------------------------------------------------------------

function mjs_saveSessionCookie(name,value)
{
	document.cookie=escape(name)+"="+escape(value)+";path=/";
	return true;
}

function mjs_getCookie(name)
{
	var exp=new RegExp(escape(name)+"=([^;]+)");
	if(exp.test(document.cookie+";"))
	{
		exp.exec(document.cookie+";");
		return unescape(RegExp.$1);
	}
	return null;
}

//--------------------------------------------------------------------------------
//
//	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;
}

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

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

