/**
* @file fix_ecma.js
*
* Utility file - attempts to bring builtin objects such as Array and Function
* into compliance with ECMAScript edition 3 (JavaScript 1.5).
*
* The main target is support for IE5.0 Windows (JScript 5.0). Even there,
* we do not attempt to compensate for all its missing features (such as lacking
* non-greedy regexps, etc.).
*
* For a good summary of what was fixed by Microsoft in JScript 5.5, see:
*    http://groups.google.com/groups?selm=unx4moEP%24GA.232%40cppssbbsa02.microsoft.com&rnum=1 
*
* We provide implementations of these functions:
* - Function: apply(), call()
* - Array: push(), pop(), shift(), splice(), unshift()
* - Number: toFixed()*, toPrecision()*, toExponential()*
* - RegExp: exec()*
* - String: replace()*, charCodeAt()
* - Error: message*, name*
* - globals: escape*, undefined, encodeURI(), decodeURI(), encodeURIComponent(), decodeURIComponent()
* 
* The functions above marked with "*" are replaced in some cases regardless of whether they
* are already defined. The unmarked ones are defined only if they are not already defined.
* @todo Improve runtime detection of when these fixes are needed. Right now, only "escape" is done well.
*
* This file has no dependencies on any other file.
*
* This file only affects the builtin ECMAScript objects, except that it
* does keep track of the names it has defined in the global Array <var>bu_fixed</var>.
*
* @author Copyright 2003 Mark D. Anderson (mda@discerning.com)
* @author Licensed under the Academic Free License 1.2 http://www.opensource.org/licenses/academic.php
*
* $Id: fix_ecma.js,v 1.9 2004/10/25 07:56:50 mdaoh Exp $
*/

/** Array of names of symbols we've defined */
//:GLVAR Array bu_fixed = []
var bu_fixed = [];

// function to register a fixed name
function bu_fixing(name) {
   // don't use Array.push, as we may not have defined it yet....
   bu_fixed[bu_fixed.length] = name;
}

// Get the version of JScript, or null.
var bu_jscript_version = null;
// This can be done efficiently using conditional compilation:
//     See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/jscript7/html/jsconditionalcompilationvars.asp
// But we don't preserve these right now when stripping comments.
///*@cc_on
//bu_jscript_version = @_jscript_version;
//@*/
// ScriptEngineMajorVersion is defined in JScript 5.0.
if (typeof ScriptEngineMajorVersion == 'function') {
  bu_jscript_version = parseFloat(ScriptEngineMajorVersion() + '.' + ScriptEngineMinorVersion());
}


/****************************************************************
* Function fixes.
****************************************************************/

// Modified from Usenet posts by David Crockford and Yep (y-e.perio@em-lyon.com)
if (!Function.prototype.apply) {
  bu_fixing('Function.apply');
  Function.prototype.apply = function bu_fix_apply(o,a) {
    var r;
    if (!o) o = {}; // in case func.apply(null, arguments).
    o.___apply=this;
    switch((a && a.length) || 0) {
	case 0: r = o.___apply(); break;
	case 1: r = o.___apply(a[0]); break;
	case 2: r = o.___apply(a[0],a[1]); break;
	case 3: r = o.___apply(a[0],a[1],a[2]); break;
	case 4: r = o.___apply(a[0],a[1],a[2],a[3]); break;
	case 5: r = o.___apply(a[0],a[1],a[2],a[3],a[4]); break;
	case 6: r = o.___apply(a[0],a[1],a[2],a[3],a[4],a[5]); break;
	default: 
           for(var i=0, s=""; i<a.length;i++){
             if(i!=0) s += ",";
             s += "a[" + i +"]";
           }
           r = eval("o.___apply(" + s + ")");

    }
    // note that delete is not in JS1.1 and thus won't work in IE5.0 PC.
    // since this is a workaround anyway, we just let it stay.
    //delete o.___apply;
    o.__apply = null;
    return r;
  }
}

if (!Function.prototype.call) {
  bu_fixing('Function.call');
  Function.prototype.call = function bu_fix_call(o) {
    // copy arguments and use apply
    var args = new Array(arguments.length - 1);
    for(var i=1;i<arguments.length;i++) {args[i - 1] = arguments[i];}
    return this.apply(o, args);
  }
}

/****************************************************************
* Array fixes.
****************************************************************/

/*
* There are many re-implementations of these Array functions available, for example:
* - http://www.crockford.com/javascript/remedial.html
* - http://www.webreference.com/dhtml/column33/
* - http://www.technicalpursuit.com/Dev_viewsource.html
* - http://www.informit.com/isapi/product_id~{E40D98B6-703F-4ED9-9B6C-9DD2E731FAD5}/element_id~{26FC4DC0-4520-47D2-B188-EFF1FF8F2E10}/st~{68F792C2-D59B-43BA-A07E-F0B1569A0392}/session_id~{2FF7DAF3-4594-49C3-9928-042852C1BF38}/content/articlex.asp
* - http://cvs.berlios.de/cgi-bin/viewcvs.cgi/jsunit/jsunit/lib/JsUtil.js?rev=1.14&content-type=text/vnd.viewcvs-markup
*
* I have no particular reason to prefer the implementations given here, except that I wrote them.
*/

if (!Array.prototype.push) {
  bu_fixing('Array.push');
  Array.prototype.push = function bu_fix_push() {
    for (var i = 0; i < arguments.length; i++) {this[this.length] = arguments[i];}
    return this.length;
  };
}

if (!Array.prototype.pop) {
  bu_fixing('Array.pop');
  Array.prototype.pop = function bu_fix_pop() {
    if (this.length == 0) return BU_UNDEFINED;
    return this[--this.length];
  }
}

if (!Array.prototype.shift) {
  bu_fixing('Array.shift');
  Array.prototype.shift = function bu_fix_shift() {
     this.reverse();
     var lastv = this.pop();
     this.reverse();
     return lastv;
  }
}

if (!Array.prototype.splice) {
  bu_fixing('Array.splice');
  Array.prototype.splice = function bu_fix_splice(start, deleteCount) {
    var len = parseInt(this.length);

    start = start ? parseInt(start) : 0;
    start = (start < 0) ? Math.max(start+len,0) : Math.min(len,start);

    deleteCount = deleteCount ? parseInt(deleteCount) : 0;
    deleteCount = Math.min(Math.max(parseInt(deleteCount),0), len);

    var deleted = this.slice(start, start+deleteCount);

    var insertCount = Math.max(arguments.length - 2,0);
    // new len, 1 more than last destination index
    var new_len = this.length + insertCount - deleteCount;
    var start_slide = start + insertCount;
    var nslide = len - start_slide; // (this.length - deleteCount) - start
    // slide up
    for(var i=new_len - 1;i>=start_slide;--i) {this[i] = this[i - nslide];}
    // copy inserted elements
    for(i=start;i<start+insertCount;++i) {this[i] = arguments[i-start+2];}
    return deleted;
  }
}

if (!Array.prototype.unshift) {
  bu_fixing('Array.unshift');
  Array.prototype.unshift = function bu_fix_unshift() {
     // prepare for a call to splice
     var a = [0,0];
     for(var i=0;i<arguments.length;i++) {a.push(arguments[i]);}
     var ret = this.splice.apply(a);
     return this.length;
  }
/*
  Array.prototype.unshift2 = function bu_fix_unshift2() {
     var len = arguments.length;
     var a = new Array(len);
     for(var i=0;i<len;i++) {a[i] = arguments[i];}
     this.reverse();
     a.reverse();
     this.push(a);
     this.reverse();
     return this.length;
  }
*/
}

/****************************************************************
* Number fixes.
****************************************************************/
/*
Safari1 and IE5.0 don't implement Number.toFixed, toPrecision, toExponential.
IE5.5 and IE6 implement them, but have rounding problems with Number.toFixed,
for example (.51).toFixed(0) == '0' instead of '1'. Ditto for .94.

Note that you can't do something like
    s = String(Math.round(f * Math.pow(10, prec)) / Math.pow(10, prec));
Because the division sometimes gives the wrong result, at least on IE
For example, for f=.51 and prec=1 we might get 0.5000000000000001 instead of 0.5

My implementations below are grotesque, but keep in mind these are only for
deficient browsers.

See also:
    http://jibbering.com/faq/#FAQ4_6
    http://www.merlyn.demon.co.uk/js-round.htm#TNM
    http://www.faqts.com/knowledge_base/view.phtml/aid/1157/fid/209/lang/
*/
if (!Number.prototype.toFixed || bu_jscript_version) {
  function bu_nz(n) {return n <= 0 ? '' : '0000000000000000000000000'.substring(25 - n)}

  bu_fixing('Number.toFixed');
  if (Number.prototype.toFixed) Number.prototype.$$toFixed$$ = Number.prototype.toFixed;
  Number.prototype.toFixed = function(fracDigits) {
    var f = this;
    if (typeof fracDigits == 'undefined') fracDigits = 0;
    if (fracDigits < 0) throw Error("negative fracDigits " + fracDigits);
    var n = Math.round(Math.abs(f) * Math.pow(10, fracDigits));
    var s;
    if (isNaN(n) || n == 2147483647) { // Safari braindamage
        s = String(f);
        var dec = s.indexOf('.');
	if (dec == -1) return fracDigits > 0 ? s + '.' + bu_nz(fracDigits) : s;
        var res = s.substring(0,dec+1);
        var fraction = s.substring(dec+1);
        if (fraction.length >= fracDigits) return res + fraction.substring(0,fracDigits);
        res = res + fraction + bu_nz(fracDigits - fraction.length); 
//alert("got nan, returning " + res);
        return res; 
    }
    s = String(n);
//alert("f=" + f + " fracDigits=" + fracDigits + " s=" + s);
    // stick a decimal point at the appropriate point, if fracDigits > 0
    if (fracDigits > 0) {
      // larger than 1 
      if (s.length > fracDigits)
        s = s.substring(0, s.length - fracDigits) + '.' + s.substring(s.length - fracDigits);
      // a fraction, have to put some zeros after decimal as shift down.
      else {
        s = '0.' + bu_nz(fracDigits - s.length) + s;
      }
    }
    if (f < 0) s = '-' + s;
    return s;
  }
}

if (!Number.prototype.toPrecision || bu_jscript_version) {
  bu_fixing('Number.toPrecision');
  if (Number.prototype.toPrecision) Number.prototype.$$toPrecision$$ = Number.prototype.toPrecision;
  Number.prototype.toPrecision = function(prec) {
    var f = this;
    if (typeof prec == 'undefined') return String(f);
    if (prec < 0) throw Error("negative precision " + prec);

    // For IE5.5 and IE6, there is a native toPrecision which will only be potentially wrong if
    // it returns fixed format.
    if (Number.prototype.$$toPrecision$$ /*&& bu_jscript_version*/) {
      var nat = Number.prototype.$$toPrecision$$.call(this, prec);
      if (/e/i.test(nat)) return nat;
    }

    // the exponent part
    var exp = Math.floor(Math.log(Math.abs(f))/Math.LN10 + 0.000001);
    // if exponent >= precision, want exponential notation with prec - 1 fracDigits
    if (exp >= prec) {
	s = this.toExponential(prec - 1);
    }
    // otherwise want fixed format with precision significant digits (note not same as fracDigits)
    else {
	//s = this.toFixed(prec);

        // we know exp < prec.
        // make it an integer with desired number of significant digits, by making prec digits left of period.
	var n = Math.round(Math.abs(f) * Math.pow(10, prec - exp - 1)); 
        // Now we have to effectively multiply by Math.pow(10, exp + 1 - prec), which means digit shift
	s = String(n);
	var nshift = prec - exp - 1;
	if (nshift == 0) {
	}
	else if (nshift < s.length) {
	    s = s.substring(0, s.length - nshift) + '.' + s.substring(s.length - nshift);
	}
	else {
	    s = '0.' + bu_nz(nshift - s.length) + s;
        }
	// alert("f=" + f + " p=" + prec + " n=" + n + " s=" + s + " nshift=" + nshift);
    }
    return s;
  }
}

if (!Number.prototype.toExponential) {
  bu_fixing('Number.toExponential');
  Number.prototype.toExponential = function(fracDigits) {
    var f = this;
    // the exponent part
    // sigh, Math.log(100000000)/Math.LN10 = 7.999999999999998
    var exp = Math.floor(Math.log(Math.abs(f))/Math.LN10 + 0.000001);
    var n; 
    // if fracDigits is undefined, then use as many digits as need be.
    if (typeof fracDigits == 'undefined') {
       n = Math.abs(f) * Math.pow(10, 0 - exp - 1);
    }
    else {
       // make a number with 1 more than fracDigits digits to left of decimal point, and round it.
       n = Math.round(Math.abs(f) * Math.pow(10, fracDigits - exp));
    }
    // convert to string, put period after first digit
    var s = String(n).replace(/(\d)/, "$1.");

    // alert("f=" + f + " fracDigits=" + fracDigits + " exp=" + exp + " n=" + n + " s=" + s + " exp exact=" + Math.log(Math.abs(f))/Math.LN10);

    // add exponent part
/*
// C ensures at least 2 digits in exponent. But ECMAScript says just append the exp
    s += 'e' + (exp > 10 ? '+' + exp : 
                (exp >= 0 ? '+0' + exp :
                 (exp > -10 ? '-0' + Math.abs(exp) : exp)));
*/
    s += (exp >= 0 ? 'e+' : 'e') + exp;
    // put sign in front
    if (f < 0) s = '-' + s;
    return s;
  }
}

/****************************************************************
* RegExp fixes.
****************************************************************/
/*
Fix RegExp.exec on IE5.0 PC

RegExp instances do not have the flags re.global, re.multiline, re.ignoreCase.
RegExp instances do not have re.lastIndex.
The RegExp.exec return value does not have .input or .index.
RegExp.exec ignores re.lastIndex (which doesn't exist) and RegExp.lastIndex (which is
set, but does not behave the same).
So RegExp.exec loops will generally loop forever if there is a match.

See Peter Torr:
  http://groups.google.com/groups?selm=OSyFsf18%24GA.240%40cppssbbsa05
  http://groups.google.com/groups?selm=%23BQZ1ge4%24GA.1576%40cpmsftngp04
  http://groups.google.com/groups?selm=unx4moEP%24GA.232%40cppssbbsa02.microsoft.com&rnum=1

Note that String.match will loop properly in 5.0, but won't do captures, so that doesn't help.

TODO: if not re.global, and we detect we are called multiple times, what should we do?

Note that these properties only exist in IE5.5+:
  RegExp.lastMatch
  RegExp.leftContext
  RegExp.rightContext
  RegExp.lastParen
But these are not standard EcmaScript 262 attributes.
*/
if (bu_jscript_version && (bu_jscript_version < 5.5)) {
    // fix instance properties
    // local regexp literals seem to be created each time a function is called, so this is not 
    // necessary within passes of a loop, but is necessary again for each function call using the re.
    function bu_fix_re_flags(re) {
	var s = re.toString();
	var flags = s.substring(s.lastIndexOf('/'));
	re.global = flags.indexOf('g') != -1;
	re.multiline = flags.indexOf('m') != -1;
	re.ignoreCase = flags.indexOf('i') != -1;
	//alert("fixed flags, global=" + re.global + " flags=" + flags);
    }

    bu_fixing('RegExp.exec');
    RegExp.prototype.$$exec$$ = RegExp.prototype.exec;
    RegExp.prototype.exec = function(s) {
	if (typeof this.global == 'undefined') bu_fix_re_flags(this);

	// RegExp.lastIndex starts out -1. but doesn't always seem to set back to -1 for a failed match.
	// And it is not overwritable either, so we use this.lastIndex within the loop.
	// It should be set anyway, for standards compliance.
	var lastInd;
	if (typeof this.lastIndex == 'undefined') {
	    lastInd = this.lastIndex = 0;
	    // if (RegExp.lastIndex > 0) alert("ignoring RegExp.lastIndex on new match");
	}
	else {
	    lastInd = this.lastIndex;
	}
	//alert("lastInd = " + lastInd + " RegExp.lastIndex=" + RegExp.lastIndex);

	var res = this.$$exec$$(s.substring(lastInd));
	if (!res) {
	    // does nothing:
	    //    if (RegExp.lastIndex > 0) {alert("setting RegExp.lastIndex=-1"); RegExp.lastIndex = -1;}
	    this.lastIndex = 0;
	    // alert("exec of " + this + " on '" + s + "' returning " + res + " RegExp.lastIndex=" + RegExp.lastIndex);
	    return res;
	}

	// now RegExp.lastIndex is end of match within substring, so make it from start of original.
	this.lastIndex = (RegExp.lastIndex + lastInd);

	// index of start of match within whole input
	res.index = this.lastIndex - res[0].length;

	// make up a new property to hold whole original input
	if (lastInd == 0) RegExp.$$lastInput$$ = s;
	res.input = RegExp.$$lastInput$$;

	// alert("exec of " + this + " on '" + s + "' returning with res=" + res.join('|') + " res.input=" + res.input + " res.index=" + res.index + " RegExp.lastIndex=" + RegExp.lastIndex + " lastInd=" + lastInd);
	return res;
    }
}


/****************************************************************
* String fixes.
****************************************************************/
/*
Fix or implement String.prototype.replace(searchValue, replaceValue) when replaceValue is a function

When replaceValue is a function, it should be called with arguments (matching_str, capture1, ..., captureN, offset, input).
 
Mozilla and IE5.5 both do this correctly.
Neither sets the re.lastIndex. IE5.5 does set the nonstandard RegExp.lastIndex to be the same as offset.

Opera7 attempts to implement it, but does the last two arguments incorrectly.
It sets offset=0 always, and input same as matching_str.
It sets re.lastIndex apparently to the length of matching_str, which isn't helpful.

Rhino (and ICE browser) throws a java NullPointerException with some regexp's and String.replace:
   http://groups.google.com/groups?selm=b950119f.0308251338.59f7b40f%40posting.google.com
   http://bugzilla.mozilla.org/show_bug.cgi?id=217379

Safari1 and IE5.0 don't implement the functionality at all (they try to treat replaceValue as a string
even when it is a function).

We have not implemented the unusual case where replaceValue is a Function but
searchValue is a String instead of a RegExp.

This implementation relies on RegExp.exec being fixed for looping on IE 5.0 (done in this file).

Note that String.prototype.match(re) with global=true supposedly works in IE 5.0,
although it doesn't do captures (just returning an array of matches from
each call to re.exec). 

@todo verify that the above claims about String.replace support are still true, particularly in Opera 7.5

*/
// easier to characterize where it does work: Moz, IE5.5, SpiderMonkey
if ((typeof navigator != 'undefined' && (navigator.userAgent.indexOf('rv:') != -1)) || 
    (bu_jscript_version && (bu_jscript_version >= 5.5)) ||
    (typeof line2pc !== 'undefined')) {
}
else {
    bu_fixing('String.replace');
    String.prototype.$$replace$$ = String.prototype.replace;
    String.prototype.replace = function(searchValue, replaceValue) {
	if (typeof replaceValue != 'function') {
	   var v = this.$$replace$$(searchValue, replaceValue);
	   //print("(fix_ecma.js) typeof replaceValue=" + (typeof replaceValue) + " returning: '" + v + "'"); 
	   return v;
	}
	if (!searchValue.exec) throw Error("unimplemented: searchValue not a RegExp: " + searchValue);
	var re = searchValue;
	var a;
	var instr = this;
	var out_parts = [];
	// RegExp.exec returns an array which is is the match followed by the captures.
	// this is just what replaceValue as a function expects, but it also expects two more arguments
	// at the end: offset and whole input
	var npasses = 0;
	var prev_end = 0;
	while( (a = re.exec(instr)) ) {
	    npasses++;
	    a.push(a.index);
	    a.push(a.input);
	    var res = replaceValue.apply(null, a);
	    // push on previous part of string prior to match
	    out_parts.push(this.substring(prev_end, a.index));
            // push on replacement
	    out_parts.push(res);

	    prev_end = a.index + a[0].length;

	    // only do one match if not global
	    if (!re.global) break;
	}
	// no matches replaced; return input
	if (npasses == 0) {
	   //print("(fix_ecma.js) npasses=0, so returning: '" + instr + "' typeof=" + (typeof instr) + " instr==null? " + (instr == null) + " uneval=" + burst.Lang.uneval(instr));
	   //return '' + instr;
	   return instr;
	}

	// push on rest of string and return it.
	out_parts.push(this.substring(prev_end));
	var s = out_parts.join('');
	//print("(fix_ecma.js) npasses=" + npasses + "; returning: '" + s + "' typeof=" + (typeof s));
	return s;
    }
}

if (!String.prototype.charCodeAt) {
  bu_fixing('String.charCodeAt');
  var BU_ASCII = "\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027" +
        "\030\031\032\033\034\035\036\037\040\041\042\043\044\045\046\047\050\051\052\053\054\055\056\057" +
        "\060\061\062\063\064\065\066\067\070\071\072\073\074\075\076\077\100\101\102\103\104\105\106\107" +
        "\120\121\122\123\124\125\126\127\130\131\132\133\134\135\136\137\140\141\142\143\144\145\146\147" +
        "\150\151\152\153\154\155\156\157\160\161\162\163\164\165\166\167\170\171\172\173\174\175\176\177" +
	"\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227" +
        "\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257" +
        "\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307" +
        "\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347" +
        "\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377";
  String.prototype.charCodeAt = function(index) {
    var c = this.charAt(index);
    // charAt and charCodeAt have similar behavior about non-numeric or out of range argument,
    // but return value differs.
    if (c == '') return NaN;
    var code = BU_ASCII.indexOf(c);
    if (code != -1) return code;
    // charCodeAt is defined to return a number < 2^16.
    // we could i suppose iterate over eval("\\0" + i.toNumber(8)) for i = 256; i< 
    throw "character at position " + index + " is not [0, 255]: " + c;
  }
}



/****************************************************************
* Error fixes.
****************************************************************/
/*
ECMAScript edition 3 says that the Error object has properties "name" and "message".

IE5 (JS.0) does not have "name" or "message", just "description" (same as "message") and "number".

The IE5 number is: errorObject.number & 0xffff, where facility code and error code are 16-bits each.

IE5.5 supports both "message" and "name".

Note that this doesn't help much regarding native Error objects thrown in IE5.0.

@todo Consider improving Error.toString whenever the default implementation useless (showing "[object]" or something like that).
*/
if (typeof Error == 'undefined' || (bu_jscript_version && (bu_jscript_version < 5.5))) {
  bu_fixing('Error');
  Error = function(msg) { 
    if (!(this instanceof Error)) return new Error(msg);
    this.message = msg || '';
    return this; // just to silence warnings
  };
  Error.prototype = new Object();
  Error.prototype.name = 'Error';
  Error.prototype.toString = function() {return this.name + ': ' + this.message};
}

/****************************************************************
* global variable fixes
****************************************************************/
// There is no global "undefined" in IE5.0 PC. 
// Since there is no "in" operator in IE5.0 PC, and typeof window.undefined == 'undefined' doesn't help,
// we just test for jscript version here.
// We could also use the cc_on trick....
// Note that this doesn't do a whole lot, because IE5.0 may still complain if you do "return undefined", for example.
if (bu_jscript_version && (bu_jscript_version < 5.5)) {/*alert("defining undefined");*/ eval("var undefined");}

/****************************************************************
* global function fixes
****************************************************************/
/*
encodeURI, decodeURI, encodeURIComponent and decodeURIComponent.
These are missing in IE5.0PC, IE5Mac, and Safari1.

The ECMAScript edition 1 'escape' and 'unescape' are really not URI-compatible, but are
better than nothing. 

escape does not encode:
   alphanums
   @*_+-./

encodeURI does not encode:
  alphanum
  the URI reserved characters ;/?:@&=+$,
  the mark characters -_.!~*'()
  the character #

encodeURIComponent does not encode:
  alphanum
  the mark characters -_.!~*'()

Note that decodeURI is supposed to pass through '%XX' unchanged if it decodes as a character
that is any of the URI reserved characters or #.
So decodeURI('z%3Bz') == 'z%3Bz', while unescape('z%3Bz') == decodeURIComponent('z%3Bz') == 'z;z'.

In 262-3, SpiderMonkey, Rhino, and IE, escape('@') == '@'. In Mozilla, escape('@') == '%40'. See:
   http://bugzilla.mozilla.org/show_bug.cgi?id=192148
*/

// our only fix for Mozilla!
if (escape('@') == '%40') {
  //alert('fixing escape');
  bu_fixing('escape');
  var $$escape$$ = escape;
  //IE5.0 doesn't like this way of defining, even if it isn't going to get run.
  //function escape(s) {
  window.escape = function(s) {
    //alert("in wrapper escape");
    var esc = $$escape$$(s);
    return esc.replace(/\%40/g, '@');
  }
}

if (typeof decodeURIComponent == 'undefined') {
  bu_fixing('encodeURI');
  var BU_UNESCAPE_ENCODE = escape(';?:&=$,!~\'()#');
  function encodeURI(s) {
    var esc = escape(s);
    // now need to unescape ;?:&=$, and !~'() and #
    return esc.replace(/\%../g, function(match) {
      return BU_UNESCAPE_ENCODE.indexOf(match) == -1 ? match : unescape(match);
    });
  }

  bu_fixing('encodeURIComponent');
  var BU_UNESCAPE_ENCODE_COMP = escape('!~\'()');
  function encodeURIComponent(s) {
    var esc = escape(s);
    // now need to unescape !~'() and escape @+/
    var unesc = esc.replace(/\%../g, function(match) {
		// POR COMPATIBLIDAD CON GMAPS
		if (!BU_UNESCAPE_ENCODE_COMP){var BU_UNESCAPE_ENCODE_COMP = escape('!~\'()');}
      return BU_UNESCAPE_ENCODE_COMP.indexOf(match) == -1 ? match : unescape(match);
    });
    return unesc.replace(/\@/g, '%40').replace(/\+/g,'%2B').replace(/\//g,'%2F');
  }

  bu_fixing('decodeURI');
  var BU_PASSTHRU_DECODE = escape(';/?:@&=+$,#');
  function decodeURI(enc) {
    // need to pass through the encoded versions of ;/?:@&=+$,#
    var s = enc.replace(/\%../g, function(match) {
       return BU_PASSTHRU_DECODE.indexOf(match) == -1 ? unescape(match) : match;
    });
    return s;
  }

  bu_fixing('decodeURIComponent');
  var decodeURIComponent = unescape;
}

/****************************************************************
* operator fixes (not actually possible to fix).
****************************************************************/
/*
* The boolean operator "in" is available in IE starting only with JScript 5.5 (not IE5.0PC or IE5Mac).
* [This is the operator used in "if ('a' in obj)"; the "for" loop is fine.]
*
* The expression "mem in obj" is roughly equivalent to "typeof obj[mem] != 'undefined'".
* The exception case is when obj[mem] exists but happens to be undefined.
*
* The boolean "in" is used both to determine membership, and in some cases to avoid
* strict warnings upon returning obj[mem]. 
* We define bu_in elsewhere for these purposes.
*/

/*
The operator "instanceof" is in Netscape JavaScript1.4 and JScript5 Win, but not IE5Mac.

It is basically impossible to properly implement "instanceof" even as a function
using other functionality.
This is one of the reasons why IE5Mac is broken beyond repair.

It became a reserved word in ECMAScript edition 2.
Its semantics was defined in edition 3.

It is possible to emulate instanceof with a function if __proto__ is available.

The __proto__ member is Nestcape-specific. It connects and object with its prototype:
    object.__proto__ === ctor.prototype

// depends on __proto__
function bu_instanceof_netscape(object, constructor) { 
   while (object != null) { 
      if (object == constructor.prototype) 
         return true; 
      object = object.__proto__; 
   }  return false; 
}

Without __proto__, we are just left with MyClass.prototype (must be explicitly set and defaults to null),
and object.constructor, which is a Function. 
The object.constructor member is inherited.
If object.prototype.constructor are overridden, then all bets are off, but
otherwise object.constructor is the function that made the object's prototype.
It is equal to MyClass.prototype.constructor if there is a prototype,
and otherwise is MyClass.

If inheritance is done simply with just:
    SubClass.prototype = new BaseClass();
then we have:
    var base = new BaseClass();
    var sub = new SubClass();
    var test_bcb = base.constructor === BaseClass; // undefined behavior. true in IE5Mac, false in safari
    var test_scs = sub.constructor === SubClass;  // false always.
    var test_scb = sub.constructor === BaseClass;  // undefined behavior. true in IE5Mac, false in safari.

If inheritance is done with:
    SubClass.prototype = new BaseClass();
    SubClass.prototype.constructor = SubClass;
then 
    var base = new BaseClass();
    var sub = new SubClass();
    var test_bcb = base.constructor === BaseClass; // undefined behavior. true in IE5Mac, false in Safari
    var test_scs = sub.constructor === SubClass;  // true always.
    var test_scb = sub.constructor === BaseClass;  // false always.

If more involved conventions are used then it could be done. For example,
Explicitly setting a .__proto__ member in the constructor function body,
or setting 
    SubClass.prototype.super = BaseClass;
among many other approaches.
But those approaches require consistent cooperation by the programmer.

Something that will work as long as inheritance isn't used:

var bu_global_this = this;
function bu_instanceof_partial(object, ctor) {
  if (!object || object === bu_global_this) return false;
  if (object.constructor === ctor) return true;
  throw "beat's me";
}
*/


/****************************************************************
* optional hooks for notifying library that this is done.
****************************************************************/
// for Burst
if (typeof bu_loaded != 'undefined') bu_loaded('fix_ecma.js');
// for NetWindows
if (typeof __scripts__ != 'undefined') {
	__scripts__.provide(__config__.corePath+"fix_ecma.js");
	__scripts__.finalize(__config__.corePath+"fix_ecma.js");
}
// for bootstrap.js
//if (typeof dj_throw != 'undefined' && typeof window != 'undefined') window.alert("loaded fix_ecma.js, fixed: " + bu_fixed.join(','));
//if (window != 'undefined') window.alert("loaded fix_ecma.js, fixed: " + bu_fixed.join(','));

/*  Prototype JavaScript framework, version 1.4.0
 *  (c) 2005 Sam Stephenson <sam@conio.net>
 *
 *  THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
 *  against the source tree, available from the Prototype darcs repository.
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *
 *  For details, see the Prototype web site: http://prototype.conio.net/
 *
/*--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.4.0',
  ScriptFragment: '',

  emptyFunction: function() {},
  K: function(x) {return x}
}

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

var Abstract = new Object();

Object.extend = function(destination, source) {
  for (property in source) {
    destination[property] = source[property];
  }
  return destination;
}

Object.inspect = function(object) {
  try {
    if (object == undefined) return 'undefined';
    if (object == null) return 'null';
    return object.inspect ? object.inspect() : object.toString();
  } catch (e) {
    if (e instanceof RangeError) return '...';
    throw e;
  }
}

Function.prototype.bind = function() {
  var __method = this, args = $A(arguments), object = args.shift();
  return function() {
    return __method.apply(object, args.concat($A(arguments)));
  }
}

Function.prototype.bindAsEventListener = function(object) {
  var __method = this;
  return function(event) {
    return __method.call(object, event || window.event);
  }
}

Object.extend(Number.prototype, {
/*  toColorPart: function() {
    var digits = this.toString(16);
    if (this < 16) return '0' + digits;
    return digits;
  },*/

  succ: function() {
    return this + 1;
  },

  times: function(iterator) {
    $R(0, this, true).each(iterator);
    return this;
  }
});

Number.prototype.toColorPart = function() {
  var digits = this.toString(16);
  if (this < 16) return '0' + digits;
  return digits;
}

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0; i < arguments.length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
}

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create();
PeriodicalExecuter.prototype = {
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.callback();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
}

/*--------------------------------------------------------------------------*/

function $() {
  var elements = new Array();

  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];
    if (typeof element == 'string')
      element = document.getElementById(element);

    if (arguments.length == 1)
      return element;

    elements.push(element);
  }

  return elements;
}
Object.extend(String.prototype, {
  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'ig'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'ig');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'i');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(eval);
  },

  escapeHTML: function() {
    var div = document.createElement('div');
    var text = document.createTextNode(this);
    div.appendChild(text);
    return div.innerHTML;
  },

  unescapeHTML: function() {
    var div = document.createElement('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? div.childNodes[0].nodeValue : '';
  },

  toQueryParams: function() {
    var pairs = this.match(/^\??(.*)$/)[1].split('&');
    return pairs.inject({}, function(params, pairString) {
      var pair = pairString.split('=');
      params[pair[0]] = pair[1];
      return params;
    });
  },

  toArray: function() {
    return this.split('');
  },

  camelize: function() {
    var oStringList = this.split('-');
    if (oStringList.length == 1) return oStringList[0];

    var camelizedString = this.indexOf('-') == 0
      ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
      : oStringList[0];

    for (var i = 1, len = oStringList.length; i < len; i++) {
      var s = oStringList[i];
      camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
    }

    return camelizedString;
  },

  inspect: function() {
    return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'";
  }
});

String.prototype.parseQuery = String.prototype.toQueryParams;

var $break    = new Object();
var $continue = new Object();

var Enumerable = {
  each: function(iterator) {
    var index = 0;
    try {
      this._each(function(value) {
        try {
          iterator(value, index++);
        } catch (e) {
          if (e != $continue) throw e;
        }
      });
    } catch (e) {
      if (e != $break) throw e;
    }
  },

  all: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      result = result && !!(iterator || Prototype.K)(value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator) {
    var result = true;
    this.each(function(value, index) {
      if (result = !!(iterator || Prototype.K)(value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      results.push(iterator(value, index));
    });
    return results;
  },

  detect: function (iterator) {
    var result;
    this.each(function(value, index) {
      if (iterator(value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (iterator(value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(pattern, iterator) {
    var results = [];
    this.each(function(value, index) {
      var stringValue = value.toString();
      if (stringValue.match(pattern))
        results.push((iterator || Prototype.K)(value, index));
    })
    return results;
  },

  include: function(object) {
    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inject: function(memo, iterator) {
    this.each(function(value, index) {
      memo = iterator(memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.collect(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (value >= (result || value))
        result = value;
    });
    return result;
  },

  min: function(iterator) {
    var result;
    this.each(function(value, index) {
      value = (iterator || Prototype.K)(value, index);
      if (value <= (result || value))
        result = value;
    });
    return result;
  },

  partition: function(iterator) {
    var trues = [], falses = [];
    this.each(function(value, index) {
      ((iterator || Prototype.K)(value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value, index) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator(value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator) {
    return this.collect(function(value, index) {
      return {value: value, criteria: iterator(value, index)};
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.collect(Prototype.K);
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (typeof args.last() == 'function')
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      iterator(value = collections.pluck(index));
      return value;
    });
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
}

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray
});
var $A = Array.from = function(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) {
    return iterable.toArray();
  } else {
    var results = [];
    for (var i = 0; i < iterable.length; i++)
      results.push(iterable[i]);
    return results;
  }
}

Object.extend(Array.prototype, Enumerable);

Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0; i < this.length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != undefined || value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(value.constructor == Array ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  indexOf: function(object) {
    for (var i = 0; i < this.length; i++)
      if (this[i] == object) return i;
    return -1;
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  shift: function() {
    var result = this[0];
    for (var i = 0; i < this.length - 1; i++)
      this[i] = this[i + 1];
    this.length--;
    return result;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  }
});
var Hash = {
  _each: function(iterator) {
    for (key in this) {
      var value = this[key];
      if (typeof value == 'function') continue;

      var pair = [key, value];
      pair.key = key;
      pair.value = value;
      iterator(pair);
    }
  },

  keys: function() {
    return this.pluck('key');
  },

  values: function() {
    return this.pluck('value');
  },

  merge: function(hash) {
    return $H(hash).inject($H(this), function(mergedHash, pair) {
      mergedHash[pair.key] = pair.value;
      return mergedHash;
    });
  },

  toQueryString: function() {
    return this.map(function(pair) {
      return pair.map(encodeURIComponent).join('=');
    }).join('&');
  },

  inspect: function() {
    return '#<Hash:{' + this.map(function(pair) {
      return pair.map(Object.inspect).join(': ');
    }).join(', ') + '}>';
  }
}

function $H(object) {
  var hash = Object.extend({}, object || {});
  Object.extend(hash, Enumerable);
  Object.extend(hash, Hash);
  return hash;
}
ObjectRange = Class.create();
Object.extend(ObjectRange.prototype, Enumerable);
Object.extend(ObjectRange.prototype, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    do {
      iterator(value);
      value = value.succ();
    } while (this.include(value));
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
}

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')},
      function() {return new XMLHttpRequest()}
    ) || false;
  },

  activeRequestCount: 0
}

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responderToAdd) {
    if (!this.include(responderToAdd))
      this.responders.push(responderToAdd);
  },

  unregister: function(responderToRemove) {
    this.responders = this.responders.without(responderToRemove);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (responder[callback] && typeof responder[callback] == 'function') {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) {}
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate: function() {
    Ajax.activeRequestCount++;
  },

  onComplete: function() {
    Ajax.activeRequestCount--;
  }
});

Ajax.Base = function() {};
Ajax.Base.prototype = {
  setOptions: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      parameters:   ''
    }
    Object.extend(this.options, options || {});
  },

  responseIsSuccess: function() {
    return this.transport.status == undefined
        || this.transport.status == 0
        || (this.transport.status >= 200 && this.transport.status < 300);
  },

  responseIsFailure: function() {
    return !this.responseIsSuccess();
  }
}

Ajax.Request = Class.create();
Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(url, options) {
    this.transport = Ajax.getTransport();
    this.setOptions(options);
    this.request(url);
  },

  request: function(url) {
    var parameters = this.options.parameters || '';
    if (parameters.length > 0) parameters += '&_=';

    try {
      this.url = url;
      if (this.options.method == 'get' && parameters.length > 0)
        this.url += (this.url.match(/\?/) ? '&' : '?') + parameters;

      Ajax.Responders.dispatch('onCreate', this, this.transport);

      this.transport.open(this.options.method, this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) {
        this.transport.onreadystatechange = this.onStateChange.bind(this);
        setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10);
      }

      this.setRequestHeaders();

      var body = this.options.postBody ? this.options.postBody : parameters;
      this.transport.send(this.options.method == 'post' ? body : null);

    } catch (e) {
      this.dispatchException(e);
    }
  },

  setRequestHeaders: function() {
    var requestHeaders =
      ['X-Requested-With', 'XMLHttpRequest',
       'X-Prototype-Version', Prototype.Version];

    if (this.options.method == 'post') {
      requestHeaders.push('Content-type',
        'application/x-www-form-urlencoded');

      /* Force "Connection: close" for Mozilla browsers to work around
       * a bug where XMLHttpReqeuest sends an incorrect Content-length
       * header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType)
        requestHeaders.push('Connection', 'close');
    }

    if (this.options.requestHeaders)
      requestHeaders.push.apply(requestHeaders, this.options.requestHeaders);

    for (var i = 0; i < requestHeaders.length; i += 2)
      this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]);
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState != 1)
      this.respondToReadyState(this.transport.readyState);
  },

  header: function(name) {
    try {
      return this.transport.getResponseHeader(name);
    } catch (e) {}
  },

  evalJSON: function() {
    try {
      return eval(this.header('X-JSON'));
    } catch (e) {}
  },

  evalResponse: function() {
    try {
      return eval(this.transport.responseText);
    } catch (e) {
      this.dispatchException(e);
    }
  },

  respondToReadyState: function(readyState) {
    var event = Ajax.Request.Events[readyState];
    var transport = this.transport, json = this.evalJSON();

    if (event == 'Complete') {
      try {
        (this.options['on' + this.transport.status]
         || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(transport, json);
      } catch (e) {
        this.dispatchException(e);
      }

      if ((this.header('Content-type') || '').match(/^text\/javascript/i))
        this.evalResponse();
    }

    try {
      (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
      Ajax.Responders.dispatch('on' + event, this, transport, json);
    } catch (e) {
      this.dispatchException(e);
    }

    /* Avoid memory leak in MSIE: clean up the oncomplete event handler */
    if (event == 'Complete')
      this.transport.onreadystatechange = Prototype.emptyFunction;
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Updater = Class.create();

Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
  initialize: function(container, url, options) {
    this.containers = {
      success: container.success ? $(container.success) : $(container),
      failure: container.failure ? $(container.failure) :
        (container.success ? null : $(container))
    }

    this.transport = Ajax.getTransport();
    this.setOptions(options);

    var onComplete = this.options.onComplete || Prototype.emptyFunction;
    this.options.onComplete = (function(transport, object) {
      this.updateContent();
      onComplete(transport, object);
    }).bind(this);

    this.request(url);
  },

  updateContent: function() {
    var receiver = this.responseIsSuccess() ?
      this.containers.success : this.containers.failure;
    var response = this.transport.responseText;

    if (!this.options.evalScripts)
      response = response.stripScripts();

    if (receiver) {
      if (this.options.insertion) {
        new this.options.insertion(receiver, response);
      } else {
        Element.update(receiver, response);
      }
    }

    if (this.responseIsSuccess()) {
      if (this.onComplete)
        setTimeout(this.onComplete.bind(this), 10);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create();
Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
  initialize: function(container, url, options) {
    this.setOptions(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = {};
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(request) {
    if (this.options.decay) {
      this.decay = (request.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = request.responseText;
    }
    this.timer = setTimeout(this.onTimerEvent.bind(this),
      this.decay * this.frequency * 1000);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
document.getElementsByClassName = function(className, parentElement) {
  var children = ($(parentElement) || document.body).getElementsByTagName('*');
  return $A(children).inject([], function(elements, child) {
    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
      elements.push(child);
    return elements;
  });
}

/*--------------------------------------------------------------------------*/

if (!window.Element) {
  var Element = new Object();
}

Object.extend(Element, {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      Element[Element.visible(element) ? 'hide' : 'show'](element);
    }
  },

  hide: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = 'none';
    }
  },

  show: function() {
    for (var i = 0; i < arguments.length; i++) {
      var element = $(arguments[i]);
      element.style.display = '';
    }
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
  },

  update: function(element, html) {
    $(element).innerHTML = html.stripScripts();
    setTimeout(function() {html.evalScripts()}, 10);
  },

  getHeight: function(element) {
    element = $(element);
    return element.offsetHeight;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).include(className);
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).add(className);
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    return Element.classNames(element).remove(className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    for (var i = 0; i < element.childNodes.length; i++) {
      var node = element.childNodes[i];
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        Element.remove(node);
    }
  },

  empty: function(element) {
    return $(element).innerHTML.match(/^\s*$/);
  },

  scrollTo: function(element) {
    element = $(element);
    var x = element.x ? element.x : element.offsetLeft,
        y = element.y ? element.y : element.offsetTop;
    window.scrollTo(x, y);
  },

  getStyle: function(element, style) {
    element = $(element);
    var value = element.style[style.camelize()];
    if (!value) {
      if (document.defaultView && document.defaultView.getComputedStyle) {
        var css = document.defaultView.getComputedStyle(element, null);
        value = css ? css.getPropertyValue(style) : null;
      } else if (element.currentStyle) {
        value = element.currentStyle[style.camelize()];
      }
    }

    if (window.opera && ['left', 'top', 'right', 'bottom'].include(style))
      if (Element.getStyle(element, 'position') == 'static') value = 'auto';

    return value == 'auto' ? null : value;
  },

  setStyle: function(element, style) {
    element = $(element);
    for (name in style)
      element.style[name.camelize()] = style[name];
  },

  getDimensions: function(element) {
    element = $(element);
    if (Element.getStyle(element, 'display') != 'none')
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = element.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = '';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = 'none';
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (window.opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return;
    element._overflow = element.style.overflow;
    if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden')
      element.style.overflow = 'hidden';
  },

  undoClipping: function(element) {
    element = $(element);
    if (element._overflow) return;
    element.style.overflow = element._overflow;
    element._overflow = undefined;
  }
});

var Toggle = new Object();
Toggle.display = Element.toggle;

/*--------------------------------------------------------------------------*/

Abstract.Insertion = function(adjacency) {
  this.adjacency = adjacency;
}

Abstract.Insertion.prototype = {
  initialize: function(element, content) {
    this.element = $(element);
    this.content = content.stripScripts();

    if (this.adjacency && this.element.insertAdjacentHTML) {
      try {
        this.element.insertAdjacentHTML(this.adjacency, this.content);
      } catch (e) {
//		Changeset 4150
//		Support Insertion.Before/Insertion.After for <tr> elements in IE. Closes #3925. [rails-venkatp@sneakemail.com]
//		http://dev.rubyonrails.org/changeset/4150
//        if (this.element.tagName.toLowerCase() == 'tbody') {
		var tagName = this.element.tagName.toLowerCase();
		if (tagName == 'tbody' || tagName == 'tr') {
          this.insertContent(this.contentFromAnonymousTable());
        } else {
          throw e;
        }
      }
    } else {
      this.range = this.element.ownerDocument.createRange();
      if (this.initializeRange) this.initializeRange();
      this.insertContent([this.range.createContextualFragment(this.content)]);
    }

    setTimeout(function() {content.evalScripts()}, 10);
  },

  contentFromAnonymousTable: function() {
    var div = document.createElement('div');
    div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
    return $A(div.childNodes[0].childNodes[0].childNodes);
  }
}

var Insertion = new Object();

Insertion.Before = Class.create();
Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
  initializeRange: function() {
    this.range.setStartBefore(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment, this.element);
    }).bind(this));
  }
});

Insertion.Top = Class.create();
Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(true);
  },

  insertContent: function(fragments) {
    fragments.reverse(false).each((function(fragment) {
      this.element.insertBefore(fragment, this.element.firstChild);
    }).bind(this));
  }
});

Insertion.Bottom = Class.create();
Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
  initializeRange: function() {
    this.range.selectNodeContents(this.element);
    this.range.collapse(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.appendChild(fragment);
    }).bind(this));
  }
});

Insertion.After = Class.create();
Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
  initializeRange: function() {
    this.range.setStartAfter(this.element);
  },

  insertContent: function(fragments) {
    fragments.each((function(fragment) {
      this.element.parentNode.insertBefore(fragment,
        this.element.nextSibling);
    }).bind(this));
  }
});

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set(this.toArray().concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set(this.select(function(className) {
      return className != classNameToRemove;
    }).join(' '));
  },

  toString: function() {
    return this.toArray().join(' ');
  }
}

Object.extend(Element.ClassNames.prototype, Enumerable);
var Field = {
  clear: function() {
    for (var i = 0; i < arguments.length; i++)
      $(arguments[i]).value = '';
  },

  focus: function(element) {
    $(element).focus();
  },

  present: function() {
    for (var i = 0; i < arguments.length; i++)
      if ($(arguments[i]).value == '') return false;
    return true;
  },

  select: function(element) {
    $(element).select();
  },

  activate: function(element) {
    element = $(element);
    element.focus();
    if (element.select)
      element.select();
  }
}

/*--------------------------------------------------------------------------*/

var Form = {
  serialize: function(form) {
    var elements = Form.getElements($(form));
    var queryComponents = new Array();

    for (var i = 0; i < elements.length; i++) {
      var queryComponent = Form.Element.serialize(elements[i]);
      if (queryComponent)
        queryComponents.push(queryComponent);
    }

    return queryComponents.join('&');
  },

  getElements: function(form) {
    form = $(form);
    var elements = new Array();

    for (tagName in Form.Element.Serializers) {
      var tagElements = form.getElementsByTagName(tagName);
      for (var j = 0; j < tagElements.length; j++)
        elements.push(tagElements[j]);
    }
    return elements;
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name)
      return inputs;

    var matchingInputs = new Array();
    for (var i = 0; i < inputs.length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) ||
          (name && input.name != name))
        continue;
      matchingInputs.push(input);
    }

    return matchingInputs;
  },

  disable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.blur();
      element.disabled = 'true';
    }
  },

  enable: function(form) {
    var elements = Form.getElements(form);
    for (var i = 0; i < elements.length; i++) {
      var element = elements[i];
      element.disabled = '';
    }
  },

  findFirstElement: function(form) {
    return Form.getElements(form).find(function(element) {
      return element.type != 'hidden' && !element.disabled &&
        ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    Field.activate(Form.findFirstElement(form));
  },

  reset: function(form) {
    $(form).reset();
  }
}

Form.Element = {
  serialize: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);

    if (parameter) {
      var key = encodeURIComponent(parameter[0]);
      if (key.length == 0) return;

      if (parameter[1].constructor != Array)
        parameter[1] = [parameter[1]];

      return parameter[1].map(function(value) {
        return key + '=' + encodeURIComponent(value);
      }).join('&');
    }
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    var parameter = Form.Element.Serializers[method](element);

    if (parameter)
      return parameter[1];
  }
}

Form.Element.Serializers = {
  input: function(element) {
    switch (element.type.toLowerCase()) {
      case 'submit':
      case 'hidden':
      case 'password':
      case 'text':
        return Form.Element.Serializers.textarea(element);
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element);
    }
    return false;
  },

  inputSelector: function(element) {
    if (element.checked)
      return [element.name, element.value];
  },

  textarea: function(element) {
    return [element.name, element.value];
  },

  select: function(element) {
    var value = '';
    if (element.type == 'select-one') {
      var index = element.selectedIndex;
      if (index >= 0)
        value = element.options[index].value || element.options[index].text;
    } else {
      value = new Array();
      for (var i = 0; i < element.length; i++) {
        var opt = element.options[i];
        if (opt.selected)
          value.push(opt.value || opt.text);

/*    return Form.Element.Serializers[element.type == 'select-one' ?
      'selectOne' : 'selectMany'](element);
  },

  selectOne: function(element) {
    var value = '', opt, index = element.selectedIndex;
    if (index >= 0) {
      opt = element.options[index];
      value = opt.value;
      if (!value && !('value' in opt))
        value = opt.text;
    }
    return [element.name, value];
  },

  selectMany: function(element) {
    var value = new Array();
    for (var i = 0; i < element.length; i++) {
      var opt = element.options[i];
      if (opt.selected) {
        var optValue = opt.value;
        if (!optValue && !('value' in opt))
          optValue = opt.text;
        value.push(optValue);*/
      }
    }
    return [element.name, value];
  }
}

/*--------------------------------------------------------------------------*/

var $F = Form.Element.getValue;

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = function() {}
Abstract.TimedObserver.prototype = {
  initialize: function(element, frequency, callback) {
    this.frequency = frequency;
    this.element   = $(element);
    this.callback  = callback;

    this.lastValue = this.getValue();
    this.registerCallback();
  },

  registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  onTimerEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
}

Form.Element.Observer = Class.create();
Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create();
Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = function() {}
Abstract.EventObserver.prototype = {
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    var elements = Form.getElements(this.element);
    for (var i = 0; i < elements.length; i++)
      this.registerCallback(elements[i]);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        case 'password':
        case 'text':
        case 'textarea':
        case 'select-one':
        case 'select-multiple':
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
}

Form.Element.EventObserver = Class.create();
Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create();
Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) {
  var Event = new Object();
}

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,

  element: function(event) {
    return event.target || event.srcElement;
  },

  isLeftClick: function(event) {
    return (((event.which) && (event.which == 1)) ||
            ((event.button) && (event.button == 1)));
  },

  pointerX: function(event) {
    return event.pageX || (event.clientX +
      (document.documentElement.scrollLeft || document.body.scrollLeft));
  },

  pointerY: function(event) {
    return event.pageY || (event.clientY +
      (document.documentElement.scrollTop || document.body.scrollTop));
  },

  stop: function(event) {
    if (event.preventDefault) {
      event.preventDefault();
      event.stopPropagation();
    } else {
      event.returnValue = false;
      event.cancelBubble = true;
    }
  },

  // find the first node with the given tagName, starting from the
  // node the event was triggered on; traverses the DOM upwards
  findElement: function(event, tagName) {
    var element = Event.element(event);
    while (element.parentNode && (!element.tagName ||
        (element.tagName.toUpperCase() != tagName.toUpperCase())))
      element = element.parentNode;
    return element;
  },

  observers: false,

  _observeAndCache: function(element, name, observer, useCapture) {
    if (!this.observers) this.observers = [];
    if (element.addEventListener) {
      this.observers.push([element, name, observer, useCapture]);
      element.addEventListener(name, observer, useCapture);
    } else if (element.attachEvent) {
      this.observers.push([element, name, observer, useCapture]);
      element.attachEvent('on' + name, observer);
    }
  },

  unloadCache: function() {
    if (!Event.observers) return;
    for (var i = 0; i < Event.observers.length; i++) {
      Event.stopObserving.apply(this, Event.observers[i]);
      Event.observers[i][0] = null;
    }
    Event.observers = false;
  },

  observe: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.attachEvent))
      name = 'keydown';

    this._observeAndCache(element, name, observer, useCapture);
  },

  stopObserving: function(element, name, observer, useCapture) {
    var element = $(element);
    useCapture = useCapture || false;

    if (name == 'keypress' &&
        (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
        || element.detachEvent))
      name = 'keydown';

    if (element.removeEventListener) {
      element.removeEventListener(name, observer, useCapture);
    } else if (element.detachEvent) {
      element.detachEvent('on' + name, observer);
    }
  }
});

/* prevent memory leaks in IE */
Event.observe(window, 'unload', Event.unloadCache, false);
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  realOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return [valueL, valueT];
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
    } while (element);
    return [valueL, valueT];
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        p = Element.getStyle(element, 'position');
        if (p == 'relative' || p == 'absolute') break;
      }
    } while (element);
    return [valueL, valueT];
  },

  offsetParent: function(element) {
    if (element.offsetParent) return element.offsetParent;
    if (element == document.body) return element;

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return element;

    return document.body;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = this.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = this.realOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = this.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  clone: function(source, target) {
    source = $(source);
    target = $(target);
    target.style.position = 'absolute';
    var offsets = this.cumulativeOffset(source);
    target.style.top    = offsets[1] + 'px';
    target.style.left   = offsets[0] + 'px';
    target.style.width  = source.offsetWidth + 'px';
    target.style.height = source.offsetHeight + 'px';
  },

  page: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent==document.body)
        if (Element.getStyle(element,'position')=='absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      valueT -= element.scrollTop  || 0;
      valueL -= element.scrollLeft || 0;
    } while (element = element.parentNode);

    return [valueL, valueT];
  },

  clone: function(source, target) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || {})

    // find page position of source
    source = $(source);
    var p = Position.page(source);

    // find coordinate system to use
    target = $(target);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(target,'position') == 'absolute') {
      parent = Position.offsetParent(target);
      delta = Position.page(parent);
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if(options.setLeft)   target.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if(options.setTop)    target.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if(options.setWidth)  target.style.width = source.offsetWidth + 'px';
    if(options.setHeight) target.style.height = source.offsetHeight + 'px';
  },

  absolutize: function(element) {
    element = $(element);
    if (element.style.position == 'absolute') return;
    Position.prepare();

    var offsets = Position.positionedOffset(element);
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';;
    element.style.left   = left + 'px';;
    element.style.width  = width + 'px';;
    element.style.height = height + 'px';;
  },

  relativize: function(element) {
    element = $(element);
    if (element.style.position == 'relative') return;
    Position.prepare();

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
  }
}

// Safari returns margins on body which is incorrect if the child is absolutely
// positioned.  For performance reasons, redefine Position.cumulativeOffset for
// KHTML/WebKit only.
if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
  Position.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return [valueL, valueT];
  }
}