1 /**
  2  * @fileoverview
  3  * <code>APE</code> provides core features, including namespacing and object creational aspects.
  4  * 
  5  * <h3>APE JavaScript Library</h3>
  6  * <p>
  7  * Released under Academic Free Licence 3.0.
  8  * </p>
  9  * 
 10  * @author Garrett Smith 
 11  */
 12 
 13 /** @name APE
 14  * @namespace */
 15 if(APE !== undefined) throw Error("APE is already defined.");
 16 var APE = {
 17     
 18     /**
 19      * @memberOf APE
 20      * @description Prototype inheritance.
 21      * @param {Object} subclass 
 22      * @param {Object} superclass
 23      * @param {Object} mixin If present, <var>mixin</var>'s own properties are copied to receiver 
 24      * using APE.mixin(subclass.prototoype, superclass.prototype).
 25      */
 26     extend : function(subclass, superclass, mixin) {
 27         if(arguments.length === 0) return;
 28         var f = arguments.callee, subp;
 29         f.prototype = superclass.prototype;
 30         subclass.prototype = subp = new f;
 31         if(typeof mixin == "object") 
 32             APE.mixin(subp, mixin);
 33         subp.constructor = subclass;
 34         return subclass;
 35     },
 36 
 37     /**
 38      * Shallow copy of properties; does not look up prototype chain. 
 39      * Copies all properties in s to r, using hasOwnProperty.
 40      * @param {Object} r the receiver of properties.
 41      * @param {Object} s the supplier of properties.
 42      * Accounts for JScript DontEnum bug for valueOf and toString.
 43      * @return {Object} r the receiver.
 44      */
 45     mixin : function(r, s) {
 46         var jscriptSkips = ['toString', 'valueOf'],
 47             prop,
 48             i = 0,
 49             skipped;
 50         for(prop in s) {
 51             if(s.hasOwnProperty(prop))
 52                 r[prop] = s[prop];
 53         }
 54         // JScript DontEnum bug.
 55         for( ; i < jscriptSkips.length; i++) {
 56             skipped = jscriptSkips[i];
 57             if(s.hasOwnProperty(skipped))
 58                 r[skipped] = s[skipped];
 59         }
 60         return r;
 61     },
 62 
 63     toString : function() { return "[APE JavaScript Library]"; },
 64 
 65     /** Creational method meant for being cross-cut.
 66      * Uses APE.newApply to create 
 67      * @param {HTMLElement} el An element. If el does not have 
 68      * an ID, then an ID will be automatically generated, based on the 
 69      * constructor's (this) identifier, or, If this is anonymous, "APE".
 70      * @requires {Object} an object to be attached to as a property.
 71      * @aspect 
 72      * @scope {Function} that accepts an HTMLElement for 
 73      * its first argument. 
 74      * APE.getByNode is intended to be bouund to a constructor function.
 75      * @return <code>{new this(el [,args...])}</code>
 76      */
 77     getByNode : function(el) {
 78         var id = el.id,
 79             fName;        
 80         if(!id) {
 81             if(!APE.getByNode._i) APE.getByNode._i = 0;
 82             fName = APE.getFunctionName(this);
 83             if(!fName) fName = "APE";
 84             id = el.id = fName+"_" + (APE.getByNode._i++);
 85         }
 86         if(!this.hasOwnProperty("instances")) this.instances = {};
 87         return this.instances[id] || (this.instances[id] = APE.newApply(this, arguments));       
 88     },
 89 
 90     /** Tries to get a name of a function object, returns "" if anonymous. 
 91      */
 92     getFunctionName : function(fun) {
 93         if(typeof fun.name == "string") return fun.name;
 94         var name = Function.prototype.toString.call(fun).match(/\s([a-z]+)\(/i);
 95         return name && name[1]||"";
 96     },
 97 
 98     /** Creational method meant for being cross-cut.
 99      * @param {HTMLElement} el An element that has an id.
100      * @requires {Object} an object to bind to.
101      * @aspect 
102      * @description <code>getById</code> must be assigned to a function constructor
103      * that accepts an HTMLElement's <code>id</code> for 
104      * its first argument. 
105      * @example <pre>
106      * function Slider(el, config){ }
107      * Slider.getById = APE.getById;
108      * </pre>
109      * This allows for implementations to use a factory method with the constructor.
110      * <pre>
111      * Slider.getById( "weight", 1 );
112      * </pre>
113      * Subsequent calls to:
114      * <pre>
115      * Slider.getById( "weight" );
116      * </pre>
117      * will return the same Slider instance.
118      * An <code>instances</code> property is added to the constructor object
119      * that <code>getById</code> is assigned to.
120      * @return <pre>new this(id [,args...])</pre>
121      */
122     getById : function(id) {
123         if(!this.hasOwnProperty("instances")) this.instances = {};
124         return this.instances[id] || (this.instances[id] = APE.newApply(this, arguments));       
125     },
126     
127     /** 
128      * @param {Function} fun constructor to be invoked.
129      * @param {Array} args arguments to pass to the constructor.
130      * Instantiates a constructor and uses apply().
131      */
132     newApply : function(fun, args) {
133         if(arguments.length === 0) return;
134         var f = arguments.callee, i;
135 
136         f.prototype = fun.prototype;// Copy prototype.
137         f.prototype.constructor = fun;
138 
139         i = new f;
140         fun.apply(i, args); // Apply the original constructor.
141         return i;
142     },
143 
144     /** Throws the error in a setTimeout 1ms.
145      *  Deferred errors are useful for Event Notification systems,
146      * Animation, and testing.
147      * @param {Error} error that occurred.
148      */
149     deferError : function(error) {        
150         setTimeout(function(){throw error;},1);
151     }
152 };
153 
154 (function(){
155 
156     APE.namespace = namespace;
157 
158     /** 
159      * @memberOf APE
160      * @description creates a namespace split on "."
161      * does <em>not</em> automatically add APE to the front of the chain, as YUI does.
162      * @param {String} s the namespace. "foo.bar" would create a namespace foo.bar, but only
163      * if that namespace did not exist.
164      * @return {Package} the namespace.
165      */
166     function namespace(s) {
167         var packages = s.split("."),
168             pkg = window,
169             hasOwnProperty = Object.prototype.hasOwnProperty,
170             qName = pkg.qualifiedName,
171             i = 0,
172             len = packages.length;
173             name;
174         for (; i < len; i++) {
175             name = packages[i];
176 
177             // Internet Explorer does not support 
178             // hasOwnProperty on things like window, so call Object.prototype.hasOwnProperty.
179             // Opera does not support the global object or [[Put]] properly (see below)
180             if(!hasOwnProperty.call(pkg, name)) {
181                 pkg[name] = new Package((qName||"APE")+"."+name);
182             }
183             pkg = pkg[name];
184         }
185 
186         return pkg;
187     }
188 
189     Package.prototype.toString = function(){
190         return"["+this.qualifiedName+"]";
191     };
192     
193     /* constructor Package
194      */
195     function Package(qualifiedName) {
196         this.qualifiedName = qualifiedName;
197     }
198 })();
199 
200 (function(){
201 /**@class 
202  * A safe patch to the Object object. This patch addresses a bug that only affects Opera.
203  * <strong>It does <em>not</em> affect any for-in loops in any browser</strong> (see tests). 
204  */
205 Object=window.Object;
206 
207 var O = Object.prototype, hasOwnProperty = O.hasOwnProperty;
208 if(typeof window != "undefined" && hasOwnProperty && !hasOwnProperty.call(window, "Object")) {
209 /** 
210  * @overrides Object.prototype.hasOwnProperty
211  * @method
212  * This is a conditional patch that affects some versions of Opera.
213  * It is perfectly safe to do this and does not affect enumeration.
214  */
215     Object.prototype.hasOwnProperty = function(p) {
216         if(this === window) return (p in this) && (O[p] !== this[p]);
217         return hasOwnProperty.call(this, p);
218     };
219 }
220 })();/** 
221  * @fileoverview 
222  * EventPublisher
223  *
224  * Released under Academic Free Licence 3.0.
225  * @author Garrett Smith
226  * @class 
227  * <code>APE.EventPublisher</code> can be used for native browser events or custom events.
228  *
229  * <p> For native browser events, use <code>APE.EventPublisher</code>
230  * steals the event handler off native elements and creates a callStack. 
231  * that fires in its place.
232  * </p>
233  * <p>
234  * There are two ways to create custom events.
235  * </p>
236  * <ol>
237  * <li>Create a function on the object that fires the "event", then call that function 
238  * when the event fires (this happens automatically with native events).
239  * </li>
240  * <li>
241  * Instantiate an <code>EventPublisher</code> using the constructor, then call <code>fire</code>
242  * when the callbacks should be run.
243  * </li>
244  * </ol>
245  * <p>
246  * An <code>EventPublisher</code> itself publishes <code>beforeFire</code> and <code>afterFire</code>.
247  * This makes it possible to add AOP before advice to the callStack.
248  * </p><p>
249  * adding before-before advice is possible, but will impair performance.
250  * Instead, add multiple beforeAdvice with: 
251  * <code>publisher.addBefore(fp, thisArg).add(fp2, thisArg);</code>
252  * </p><p>
253  * There are no <code>beforeEach</code> and <code>afterEach</code> methods; to create advice 
254  * for each callback would require modification 
255  * to the registry (see comments below). I have not yet found a real need for this.
256  * </p>
257  */
258 /**
259  * @constructor
260  * @description creates an <code>EventPublisher</code> with methods <code>add()</code>,
261  * <code>fire</code>, et c.
262  */
263 APE.EventPublisher = function(src, type) {
264     this.src = src;
265     // Really could use a List of bound methods here. 
266     this._callStack = [];
267     this.type = type;
268 };
269 
270 APE.EventPublisher.prototype = {
271 
272 /**  
273  *  @param {Function} fp the callback function that gets called when src[sEvent] is called.
274  *  @param {Object} thisArg the context that the function executes in.
275  *  @return {EventPublisher} this;
276  */
277     add : function(fp, thisArg) {
278         this._callStack.push([fp, thisArg||this.src]);
279         return this;
280     },
281 /**  Adds beforeAdvice to the callStack. This fires before the callstack. 
282  *  @param {Function:boolean} fp the callback function that gets called when src[sEvent] is called.
283  *  function's returnValue proceed false stops the callstack and returns false to the original call.
284  *  @param {Object} thisArg the context that the function executes in.
285  *  @return {EventPublisher} this;
286  */
287     addBefore : function(f, thisArg) {
288         return APE.EventPublisher.add(this, "beforeFire", f, thisArg); 
289     },
290     
291 /**  Adds afterAdvice to the callStack. This fires after the callstack. 
292  *  @param {Function:boolean} fp the callback function that gets called when src[sEvent] is called.
293  *  function's returnValue of false returns false to the original call.
294  *  @param {Object} thisArg the context that the function executes in.
295  *  @return {EventPublisher} this;
296  */
297     addAfter : function(f, thisArg) {
298         return APE.EventPublisher.add(this, "afterFire", f, thisArg); 
299     },
300 
301     /** 
302      * @param {String} "beforeFire", "afterFire" conveneince.
303      * @return {EventPublisher} this;
304      */
305     getEvent : function(type) {
306         return APE.EventPublisher.get(this, type);
307     },
308 
309 /**  Removes fp from callstack.
310  *  @param {Function:boolean} fp the callback function to remove.
311  *  @param {Object} [thisArg] the context that the function executes in.
312  *  @return {Function} the function that was passed in, or null if not found;
313  */
314     remove : function(fp, thisArg) {
315         var cs = this._callStack, i = 0, len, call;
316         if(!thisArg) thisArg = this.src;
317         for(len = cs.length; i < len; i++) {
318             call = cs[i];
319             if(call[0] === fp && call[1] === thisArg) {
320                 return cs.splice(i, 1);
321             }
322         }
323         return null;
324     },
325 
326 /**  Removes fp from callstack's beforeFire.
327  *  @param {Function:boolean} fp the callback function to remove.
328  *  @param {Object} [thisArg] the context that the function executes in.
329  *  @return {Function} the function that was passed in, or null if not found (uses remove());
330  */
331     removeBefore : function(fp, thisArg) {
332         return this.getEvent("beforeFire").remove(fp, thisArg);
333     },
334 
335 
336 /**  Removes fp from callstack's afterFire.
337  *  @param {Function:boolean} fp the callback function to remove.
338  *  @param {Object} [thisArg] the context that the function executes in.
339  *  @return {Function} the function that was passed in, or null if not found (uses remove());
340  */
341     removeAfter : function(fp, thisArg) {
342         return this.getEvent("afterFire").remove(fp, thisArg);
343     },
344 
345 /** Fires the event. */
346     fire : function(payload) {
347         return APE.EventPublisher.fire(this)(payload);
348     },
349 
350 /** helpful debugging info */
351     toString : function() {
352         return  "APE.EventPublisher: {src=" + this.src + ", type=" + this.type +
353              ", length="+this._callStack.length+"}";
354     }
355 };
356 
357 /** 
358  *  @static
359  *  @param {Object} src the object which calls the function
360  *  @param {String} sEvent the function that gets called.
361  *  @param {Function} fp the callback function that gets called when src[sEvent] is called.
362  *  @param {Object} thisArg the context that the function executes in.
363  */
364 APE.EventPublisher.add = function(src, sEvent, fp, thisArg) {
365     return APE.EventPublisher.get(src, sEvent).add(fp, thisArg);
366 };
367 
368 /** 
369  * @static
370  * @private
371  * @memberOf {APE.EventPublisher}
372  * @return {boolean} false if any one of callStack's methods return false.
373  */
374 APE.EventPublisher.fire = function(publisher) {
375     // This closure sucks. We should have partial/bind in ES.
376     // If we did, this could more reasonably be a prototype method.
377     
378     // return function w/identifier doesn't work in Safari 2.
379     return fireEvent; 
380     function fireEvent(e) {
381         var preventDefault = false,
382             i = 0, len,
383             cs = publisher._callStack, csi;
384 
385         // beforeFire can affect return value.
386         if(typeof publisher.beforeFire == "function") {
387             try {
388                 if(publisher.beforeFire(e) == false)
389                     preventDefault = true;
390             } catch(ex){APE.deferError(ex);}
391         }
392 
393         for(len = cs.length; i < len; i++) {
394             csi = cs[i]; 
395             // If an error occurs, continue the event fire,
396             // but still throw the error.
397             try {
398                 // TODO: beforeEach to prevent or advise each call.
399                 if(csi[0].call(csi[1], e) == false)
400                     preventDefault = true; // continue main callstack and return false afterwards.
401                 // TODO: afterEach
402             }
403             catch(ex) {
404                 APE.deferError(ex);
405             }
406         }
407         // afterFire can prevent default.
408         if(typeof publisher.afterFire == "function") {
409             if(publisher.afterFire(e) == false)
410                 preventDefault = true;
411         }
412         return !preventDefault;
413     }
414 };
415 
416 /** 
417  * @static
418  * @param {Object} src the object which calls the function
419  * @param {String} sEvent the function that gets called.
420  * @memberOf {APE.EventPublisher}
421  * Looks for an APE.EventPublisher in the Registry.
422  * If none found, creates and adds one to the Registry.
423  */
424 APE.EventPublisher.get = function(src, sEvent) {
425 
426     var publisherList = this.Registry.hasOwnProperty(sEvent) && this.Registry[sEvent] || 
427         (this.Registry[sEvent] = []),
428         i = 0, len = publisherList.length,
429         publisher;
430     
431     for(; i < len; i++)
432         if(publisherList[i].src === src)
433             return publisherList[i];
434     
435     // not found.
436     publisher = new APE.EventPublisher(src, sEvent);
437     // Steal. 
438     if(src[sEvent])
439         publisher.add(src[sEvent], src);
440     src[sEvent] = this.fire(publisher);
441     publisherList[publisherList.length] = publisher;
442     return publisher;
443 };
444 
445 /** 
446  * Map of [APE.EventPublisher], keyed by type.
447  * @private
448  * @static
449  * @memberOf {APE.EventPublisher}
450  */
451 APE.EventPublisher.Registry = {};
452 
453 /**
454  * @static
455  * @memberOf {APE.EventPublisher}
456  * called onunload, automatically onunload. 
457  * This is only called for if window.CollectGarbage is 
458  * supported. IE has memory leak problems; other browsers have fast forward/back,
459  * but that won't work if there's an onunload handler.
460  */
461 APE.EventPublisher.cleanUp = function() {
462     var type, publisherList, publisher, i, len;
463     for(type in this.Registry) {
464         publisherList = this.Registry[type];
465         for(i = 0, len = publisherList.length; i < len; i++) {
466             publisher = publisherList[i];
467             publisher.src[publisher.type] = null;
468         }
469     }
470 };
471 if(window.CollectGarbage)
472     APE.EventPublisher.get( window, "onunload" ).addAfter( APE.EventPublisher.cleanUp, APE.EventPublisher );/** @fileoverview
473  * Element style functions
474  *
475  * @author Garrett Smith
476  */
477 
478 /**@name APE.dom 
479  * @namespace*/
480 APE.namespace("APE.dom");
481 (function(){
482 
483     APE.mixin(APE.dom, /** @scope APE.dom */{
484         /** @function */ getStyle : _getComputedStyle,
485         getCascadedStyle : getCascadedStyle,
486         setOpacity : setOpacity,
487         getFilterOpacity : getFilterOpacity,
488         getStyleUnit : getStyleUnit,
489         findInheritedStyle : findInheritedStyle,
490         getContainingBlock : getContainingBlock,
491         getPixelCoords : getPixelCoords
492     });
493     
494     var getCS = "getComputedStyle",
495         IS_COMPUTED_STYLE_SUPPORTED = document.defaultView 
496             && typeof document.defaultView[getCS] == "function",
497         currentStyle = "currentStyle",
498         style = "style";
499 
500     /** findInheritedStyle tries to find a cascaded style value for the element.
501      * If the value is inherit|transparent, it looks up the tree, recursively.
502      * @memberOf APE.dom
503      * 
504      * @param {Element} el - element's style you want.
505      * @param prop {String} style property, such as backgroundColor.
506      * @param {String} [units] optional unit to search for. Example: "em".
507      * @return {String} computed style or an empty string.
508      */
509     function findInheritedStyle(el, prop, units) {
510         var value = "", n = el;
511         for( ; value = getCascadedStyle(n, prop, units); n = n.parentNode) 
512             if(value && !noValueExp.test(value)) break;
513         return value;
514     }
515 
516     var noValueExp = /^(?:inher|trans|(?:rgba\((?=(0,\s))(?:\1\1\1)0\)))/;
517     
518     /** 
519      * Special method for a browser that supports el.filters and not style.opacity.
520      * @memberOf APE.dom
521      * @param {HTMLElement} el the element to find opacity on.
522      * @return {ufloat} [0-1] amount of opacity.
523      * calling this method on a browser that does not support filters
524      * results in 1 being returned.  Use dom.getStyle or dom.getCascadedStyle instead
525      */
526      function getFilterOpacity(el) {
527         var filters = el.filters;
528         if(!filters) return"";
529         try { // Will throw error if no DXImageTransform.
530             return filters['DXImageTransform.Microsoft.Alpha'].opacity/100;
531 
532         } catch(e) {
533             try { 
534                 return filters('alpha').opacity/100;
535             } catch(e) {
536                 return 1;
537             }
538         }
539     }
540 
541     /** 
542      * Cross-browser adapter method for style.filters vs style.opacity.
543      * @memberOf APE.dom
544      * @param {HTMLElement} el the element to set opacity on.
545      * @param {ufloat} i [0-1] the amount of opacity.
546      * @return {ufloat} [0-1] amount of opacity.
547      */
548      function setOpacity(el, i) {
549         var s = el[style], cs;
550         if("opacity"in s) {
551             s.opacity = i;
552         }
553         else if("filter"in s) {
554             cs = el[currentStyle];
555             s.filter = 'alpha(opacity=' + (i * 100) + ')';
556             if(cs && ("hasLayout"in cs) && !cs.hasLayout) {
557                 style.zoom = 1;
558             }
559         }
560     }
561 
562     /** 
563      * @memberOf APE.dom
564      * @name getStyle
565      * 
566      * @function
567      * @description returns the computed style of property <code>p</code> of <code>el</code>.
568      * Returns different results in IE, so user beware! If your 
569      * styleSheet has units like "em" or "in", this method does 
570      * not attempt to convert those to px.
571      *
572      * Use "cssFloat" for getting an element's float and special 
573      * "filters" treatment for "opacity".
574      * 
575      * @param {HTMLElement} el the element to set opacity on.
576      * @param {String} p the property to retrieve.
577      * @return {String} the computed style value or the empty string if no value was found.
578      */
579     function _getComputedStyle(el, p) {
580         var value = "", cs, matches, splitVal, i, len, doc = el.ownerDocument, 
581             defaultView = doc.defaultView;
582         if(IS_COMPUTED_STYLE_SUPPORTED) {
583             cs = defaultView[getCS](el, "");
584             if(p == "borderRadius" && !("borderRadius"in cs)) {
585                 p = "MozBorderRadius"in cs ? "MozBorderRadius" : 
586                     "WebkitBorderRadius"in cs ? "WebkitBorderRadius" : "";
587             }
588 
589             if(!(p in cs)) return "";
590             value = cs[p];
591             if(value === "") {
592                 // would try to get a rect, but Webkit doesn't support that.
593                 value = (tryGetShorthandValues(cs, p)).join(" ");
594             }
595         }
596         else if(currentStyle in el) {
597             cs = el[currentStyle];
598             if(p == "opacity" && !("opacity"in el[currentStyle]))
599                 value = getFilterOpacity(el);
600             else {
601                 if(p == "cssFloat")
602                     p = "styleFloat";
603                 value = cs[p];
604 
605                 if(p == "clip" && !value && ("clipTop"in cs)) {
606                     value = getCurrentStyleClipValues(el, cs);
607                 }
608                 else if(value == "auto") 
609                     value = getCurrentStyleValueFromAuto(el, p);
610                 else if(!(p in cs)) return "";
611             }
612             matches = nonPixelExp.exec(value);
613             if(matches) {
614                 splitVal = value.split(" ");
615                 splitVal[0] = convertNonPixelToPixel( el, matches);
616                 for(i = 1, len = splitVal.length; i < len; i++) {
617                     matches = nonPixelExp.exec(splitVal[i]);
618                     splitVal[i] = convertNonPixelToPixel( el, matches);
619                 }
620                 value = splitVal.join(" ");
621             }
622         }
623         return value;
624     }
625 
626     function getCurrentStyleClipValues(el, cs) {
627         var values = [], i = 0, prop;
628         for( ;i < 4; i++){
629             prop = props[i];
630             clipValue = cs['clip'+prop];
631             if(clipValue == "auto") {
632                 clipValue = (prop == "Left" || prop == "Top" ? "0px" : prop == "Right" ? 
633                     el.offsetWidth + px : el.offsetHeight + px);
634             }
635             values.push(clipValue);
636         }
637         return {
638             top:values[0], right:values[1], bottom:values[2], left:values[3],
639             toString : function() {return 'rect(' + values.join(' ')+')';}
640         };
641     }
642 
643     var sty = document.documentElement[style],
644         floatProp = 'cssFloat'in sty ? 'cssFloat': 'styleFloat',
645         props = ["Top", "Right", "Bottom", "Left"],
646         cornerProps = ["Topright", "Bottomright", "Bottomleft", "Topleft"];
647         docEl = sty = null;
648     /** 
649      * @memberOf APE.dom
650      *
651      * @description Cross-browser adapter method for reading style. 
652      * Adapts for filters from opacity and styleFloat from cssFloat.
653      * For browsers that support computed styles, but not <code>currentStyle</code>
654      * (Firefox/Safari) pass in a <code>desiredUnit</code> of either <code>em<code> or 
655      * <code>ex</code>
656      * @memberOf APE.dom
657      *
658      * Tries to get a style of the attribute el[p]. If not found, uses 
659      * getComputedStyle or currentStyle. getComputedStyle returns a pixel unit, but 
660      * we have a patch to do the math.
661      * 
662      * <p>
663      * Performance tip: performance-critical operations can reference the 
664      * element's style first: <code>var val = el.style[p] || getComputedStyle(el);</code>
665      * </p>
666      * 
667      * @param {HTMLElement} el the element to set opacity on.
668      * @param {String} p the property to retrieve.
669      * @param {String} desiredUnit one of: ["em", ] the type of unit to retrieve/compute - 
670      * this is not really supported in IE - it is mostly for browsers that don't support 
671      * currentStyle.
672      * @return {String} the cascaded style value or the empty string if no value was found.
673      */
674     function getCascadedStyle(el, p, desiredUnit) {
675         
676         var s = el[style],
677             value = s[p]||"";
678         if(value && multiLengthPropExp.test(p)) {
679             value = normalizeValue(value);
680         }
681 
682         // IE provides "medium" as default inline-style border value for all border props.
683         // This bogus value should be ignored.
684 
685         if(!value || (desiredUnit && value.indexOf(desiredUnit) === -1) 
686             || p.indexOf("border") === 0 && borderWidthExp.test(value)) {
687 
688             if(currentStyle in el) {
689                 value = getCascadedFromCurrent(el, p, desiredUnit);
690             }
691 
692             else {
693                 if(borderRadiusExp.test(p))
694                     p = borderRadiusExp.exec(p)[0];
695                 value = getCascadedFromComputed(el, p, desiredUnit);
696             }
697         }
698         return value;
699     }
700 
701     function getCascadedFromCurrent(el, p, desiredUnit) {
702 
703         var curSty = el[currentStyle], value = "", unitAdapter, doc = el.ownerDocument, 
704             defaultView = doc.defaultView;
705         
706         if(p == "opacity") {
707             
708             // currentStyle is pretty fucked in Opera.
709             // returns "1". So go for getComputedStyle first.
710             if(IS_COMPUTED_STYLE_SUPPORTED)
711                 value = defaultView[getCS](el,'').opacity;
712             else if(!("opacity"in curSty)) {
713                 value = getFilterOpacity(el);
714             }
715         }
716         else if(p == 'clip' && !curSty[p] && 'clipTop'in curSty) {
717             value = getCurrentStyleClipValues(el, curSty);
718         }
719         else {
720 
721             // We've tried clip and opacity now, so it seems that the property 
722             // does not exist, ala "WebkitBorderRadius" in IE.
723             if(!(p in curSty)) return"";
724             if(floatExp.test(p))
725                 p = floatProp;
726             value = el[style][p] || curSty[p];
727 
728             if(value == "auto") 
729                 value = getCurrentStyleValueFromAuto(el, p) || value;
730 
731         }
732 
733         if(desiredUnit && value.indexOf(desiredUnit) == -1) {
734             // Opera 9.2 royally fucked up currentStyle.
735             // calls floor() on some values
736             // If we ended up here, we have something like "0em",
737             // so we pretend to have "0px" and then use a UnitAdapter.
738             if(isCurrentStyleFloored && unitExp.test(value) && IS_COMPUTED_STYLE_SUPPORTED) {
739                 var cs = defaultView[getCS](el, p);
740                 unitAdapter = getAdapterFor(el, p, desiredUnit);
741                 if(unitAdapter) {
742                     value = unitAdapter.fromPx(el.parentNode, p, cs[p], cs);
743                 }
744             }
745             else if(value == 0 && desiredUnit)
746                 value = "0" + desiredUnit;
747             else {
748                 unitAdapter = getAdapterFor(el, p, desiredUnit);
749                 value = unitAdapter.fromPx(el.parentNode, p);
750             }
751 
752         }
753         return value;
754     }
755 
756     function getCurrentStyleValueFromAuto(el, p) {
757         
758         var s = el[style], v, borderWidth, doc = el.ownerDocument;
759         if("pixelWidth"in s && pixelDimensionExp.test(p)) {
760             var pp = "pixel" + (p.charAt(0).toUpperCase()) + p.substring(1);
761             v = s[pp];
762             if(v === 0) {
763                 if(p == "width") {
764                     borderWidth = parseFloat(_getComputedStyle(el, "borderRightWidth"))||0;
765                     paddingWidth = parseFloat(_getComputedStyle(el, "paddingLeft"))||0
766                         + parseFloat(_getComputedStyle(el, "paddingRight"))||0;
767 
768                     return el.offsetWidth - el.clientLeft - borderWidth - paddingWidth + "px";
769                 } 
770                 else if(p == "height") {
771                     borderWidth = parseFloat(_getComputedStyle(el, "borderBottomWidth"))||0;
772                     paddingWidth = parseFloat(_getComputedStyle(el, "paddingTop"))||0
773                         + parseFloat(_getComputedStyle(el, "paddingBottom"))||0;
774                     return el.offsetHeight - el.clientTop - borderWidth + "px";
775                 }
776             }
777             return s[pp] + "px";
778         }
779         if(p == "margin" && el[currentStyle].position != "absolute" && 
780           doc.compatMode != "BackCompat") {
781             v = parseFloat(_getComputedStyle(el.parentNode, 'width')) - el.offsetWidth;
782             if(v == 0) return "0px";
783             v = "0px " + v;
784             return v + " " + v;
785         }
786         
787         // Can't get borderWidth because we only have clientTop and clientLeft.
788     }
789 
790     function getCascadedFromComputed(el, p, desiredUnit) {
791 
792         if(IS_COMPUTED_STYLE_SUPPORTED) {
793             var defaultView = el.ownerDocument.defaultView,
794                 cs = defaultView[getCS](el,''),
795                 value = cs[p],
796                 valueSplit,
797                 i = 0,
798                 len,
799                 parentNode,
800                 unitAdapter,
801                 valuei;
802 
803             // Always return a string. Even for bogus properties.
804             if(!(p in cs)) return "";
805 
806             if(value === "") {
807                 valueSplit = tryGetShorthandValues(cs, p);
808             }
809             else if(parseFloat(value) == 0 && desiredUnit) {
810                 return "0" + desiredUnit;
811             }
812 
813             // The desiredUnit won't match computedStyle, but might match the 
814             // element's HTML style Attribute.
815             if(desiredUnit) { 
816                 if(!valueSplit)
817                     valueSplit = [value];
818                 unitAdapter = getAdapterFor(el, p, desiredUnit);
819 
820                 if(unitAdapter) {
821                     parentNode = el.parentNode;
822                     for(len = valueSplit.length; i < len; i++) {
823                         valuei = valueSplit[i];
824                         if(valuei == "0") valueSplit[i] = "0" + desiredUnit;
825                         else if(!unitAdapter.exp.test(valuei))
826                             valueSplit[i] = unitAdapter.fromPx(parentNode, p, valuei, cs);
827                     }
828                 }
829             }
830             if(valueSplit)
831                 value = normalizeValue(valueSplit.join(" "));
832             return value;
833         }
834     }
835 
836     /** 
837      * takes a "1px 1px 1px 1px" string and tries to reduce to 
838      * 1px. This makes calculating some values easier.
839      */
840     function normalizeValue(value) {
841         var values = value.split(" "),
842             i = 1,
843             len,
844             allEqual = true,
845             p0 = values[0];
846         for(len = values.length-1; i < len; i++) {
847             if(!allEqual) break;
848             allEqual = values[i] == values[i+1];
849         }
850         if(allEqual) 
851             value = p0;
852         return value;
853     }
854 
855     /** 
856      * Tries to get a shorthand value for margin|padding|borderWidth. 
857      * @return  {[string]} Either 4 values or, if all four values are equal,
858      * then one collapsed value (in an array).
859      */
860     function tryGetShorthandValues(cs, p) {
861         var multiMatch = multiLengthPropExp.exec(p),
862             prefix, suffix, 
863             prevValue, nextValue, 
864             values,
865             allEqual = true, 
866             propertyList,
867             i = 1;
868         
869         if(multiMatch && multiMatch[0]) {
870             propertyList = props;
871             prefix = multiMatch[1]||multiMatch[0];
872             suffix = multiMatch[2] || ""; // ["borderWidth", "border", "Width"]
873         }
874         else if(borderRadiusExp.test(p)) {
875            propertyList = cornerProps;
876             prefix = borderRadiusExp.exec(p)[0];
877             suffix = ""; 
878         }
879         else return [""];
880 
881         prevValue = cs[prefix + propertyList[0] + suffix ];
882         values = [prevValue];
883 
884         while(i < 4) {
885             nextValue = cs[prefix + propertyList[i] + suffix];
886             allEqual = allEqual && nextValue == prevValue;
887             prevValue = nextValue;
888             values[i++] = nextValue;
889         }
890         if(allEqual)
891             return [prevValue];
892         return values;
893     }
894 
895     
896     /** UnitAdapter to convert from px (default) to em/ex.
897      */
898     // The ugliness of this adapter is that it has conditional logic
899     // based on support of gcs.
900     function UnitFontAdapter(unit, fontSizeMultiplier, el, prop) {
901         this.exp = new RegExp("\\d" + unit + "$");
902         this.unit = unit;
903         this.fontSizeMultiplier = fontSizeMultiplier;
904 
905         if(el && el[currentStyle]) {
906             this.val = el[currentStyle][prop];
907             if(nonPixelExp.test(this.val))
908                 this.val = convertNonPixelToPixel(el, nonPixelExp.exec(this.val));
909             if(prop == "fontSize") el = el.parentNode; 
910             this.fontSize = _getComputedStyle(el, "fontSize");
911         }
912     }
913 
914     UnitFontAdapter.prototype = {
915         fromPx : convertPixelToEmEx
916     };
917     
918     function getAdapterFor(el, prop, desiredUnit) {
919         if(desiredUnit == 'em') {
920             if(IS_COMPUTED_STYLE_SUPPORTED)
921                 return UnitFontAdapter.em || (UnitFontAdapter.em = new UnitFontAdapter( "em", 1 ));
922             return new UnitFontAdapter( "em", 1, el, prop);
923         }
924         if(desiredUnit == 'ex') {
925             if(IS_COMPUTED_STYLE_SUPPORTED)
926                 return UnitFontAdapter.ex || (UnitFontAdapter.ex = new UnitFontAdapter( "ex", .5 ));
927             return new UnitFontAdapter( "ex", 1, el, prop);
928         }
929 
930         if(desiredUnit == "%") {
931             if(percentFromContainingBlock.test(prop))
932                 return new CbPercentageAdapter(el, prop);
933             return new ParentPercentageAdapter(el, prop);
934         }
935     }
936 
937     var pxExp = /\dpx$/, 
938         borderWidthExp = /^thi|med/,
939         nonPixelExp = /(-?\d+|(?:-?\d*\.\d+))(?:em|ex|pt|pc|in|cm|mm\s*)/,
940         unitExp = /(-?\d+|(?:-?\d*\.\d+))(px|em|ex|pt|pc|in|cm|mm|%)\s*/,
941         floatExp = /loat$/,
942 	    positiveLengthExp = /(?:width|height|padding|fontSize)$/ig, 
943         percentFromContainingBlock = /^width|height|margin|padding|textIndent/,
944         inherFromParExp = /^(?:font|text|letter)/,
945         pixelDimensionExp = /width|height|top|left/,
946         px = "px",
947 
948         // Capture (border)(Width) because we need to put "Top" in the middle.
949         // CSS 2.1 should be borderWidthTop, to be consistent with paddingTop ~ it is backwards.
950         multiLengthPropExp = /^(?:margin|(border)(Width)|padding)$/,
951         
952         borderRadiusExp = /^[a-zA-Z]*[bB]orderRadius$/; 
953 
954     /**
955      * @requires nonPixelExp
956      * @param {HTMLElement} el
957      * @param {Array} String[] of matches from nonPixelExp.exec( val ).
958      */
959     function convertNonPixelToPixel(el, matches) {
960 
961         if(el.runtimeStyle) {
962 
963             // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
964             // If we're not dealing with a regular pixel number
965             // but a number that has a weird ending, we need to convert it to pixels.
966 
967             var val = matches[0]; // grab the -1.2em or whatever.
968             if(parseFloat(val) == 0) {
969                 return "0px";
970             }
971 
972             var s = el[style],
973                 sLeft = s.left,
974                 rs = el.runtimeStyle,
975                 rsLeft = rs.left;
976 
977             rs.left = el[currentStyle].left;
978             s.left = (val || 0);
979 
980             // The element does not need to have position: to get values.
981             // IE's math is a little off with converting em to px; IE rounds to 
982             // the nearest pixel.
983             val = s.pixelLeft + px;
984             // put it back.
985             s.left = sLeft;
986             rs.left = rsLeft;
987             return val;
988         }
989     }
990 
991     // Calculates a value based on the containing block.
992     // 8.1 Box dimensions.
993     //
994     // This needs to have at least a few instance variables: el, p.
995     // This change, adding 
996     // Refactor this.
997 
998 
999 
1000     var PercentageAdapter_prototype = {
1001 
1002         fromPx : function(containingBlockNode/*ignore*/, prop, val) {
1003             
1004             containingBlockNode = this.parent;
1005 
1006             var defaultView = containingBlockNode.ownerDocument.defaultView;
1007                 containingBlockValue = defaultView[getCS](containingBlockNode,'').width,
1008                 containingBlockPx = parseFloat(containingBlockValue),
1009                 thisPx = parseFloat(val),
1010 
1011             // toPrecision of 2 decimal places.
1012                 thisPercent = Math.ceil(thisPx/containingBlockPx * 10000)/100;
1013 
1014             // textIndent can be negative, but none of the other props 
1015             // that we care about can (width, padding, margin).
1016             if(positiveLengthExp.test(prop))
1017                 if(thisPercent < 0) thisPercent = 0;
1018             return thisPercent + "%";
1019         },
1020         
1021         exp : inherFromParExp
1022     };
1023 
1024     function ParentPercentageAdapter(el, p) {
1025         this.p = p;
1026         this.parent = getContainingBlock(el);
1027     }
1028 
1029     function CbPercentageAdapter(el, p) {
1030         this.p = p;
1031         this.parent = el.parentNode;
1032     }
1033 
1034     ParentPercentageAdapter.prototype = CbPercentageAdapter.prototype = PercentageAdapter_prototype;
1035     CbPercentageAdapter.prototype.exp = percentFromContainingBlock;
1036 
1037     /** 
1038      * @memberOf APE.dom
1039      * @param {String} value a string value of a measurement. Example: 5em
1040      * @return {String} The unit portion of the string. If no matching unit is found,
1041      * then the empty string is returned.
1042      */
1043     function getStyleUnit( value ) {
1044         var unit = unitExp.exec(value);
1045         return unit && unit[2] || "";
1046     }
1047 
1048     /** 
1049      * Used for converting getComputedStyle(el,"") 
1050      * value to an em value. If the property is fontSize, 
1051      * it is necessary to get the parent's computed fontSize.
1052      * @memberOf APE.dom
1053      * @param {HTMLElement} parentNode
1054      * @param {String} prop style property to work with.
1055      * @param {String} val the value, such as "12.3px".
1056      * @param {ComputedCSSStyleDeclaration} computedStyle object to work with.
1057      */
1058     function convertPixelToEmEx(parentNode, prop, val, computedStyle) {
1059         val = this.val || val;
1060         var match = pxExp.exec(val),
1061             defaultView = parentNode.ownerDocument.defaultView;
1062         if(match) {
1063             if(match[0]) {
1064                 var fontSize,
1065                     d = parseFloat(val);
1066 
1067                 if(!this.fontSize) {
1068                     // If we're trying to get fontSize
1069                     if(prop == "fontSize") {
1070                         fontSize = defaultView[getCS](parentNode,'').fontSize;
1071                     }
1072                     else
1073                         fontSize = computedStyle.fontSize;
1074                 }
1075                 fontSize = parseFloat(fontSize||this.fontSize);
1076                 if(isFinite(d)) {
1077                     return d/fontSize * this.fontSizeMultiplier + this.unit;
1078                 }
1079             }
1080         }
1081         if(!val) return "";
1082         else if(isFinite(val)) return val + this.unit;
1083         return val; // return input.
1084     }
1085 
1086     var isCurrentStyleFloored = (function(){
1087         var head = document.getElementsByTagName("head")[0],
1088             fs, isFloored, s;
1089         if(!head[currentStyle]) return false;
1090         s = head[style];
1091         fs = s.fontSize;
1092         s.fontSize = ".4em";
1093         isFloored = head[currentStyle].fontSize == "0em";
1094         s.fontSize = fs;
1095         return isFloored;
1096     })();
1097 
1098     /** 
1099      * Finds the containing block of el, as per CSS 2.1 sec 10.1
1100      * @memberOf APE.dom 
1101      * @param {HTMLElement} el
1102      * @return {HTMLElement} el's containing block.
1103      */
1104     function getContainingBlock(el) {
1105         var elPosition = _getComputedStyle(el, "position"), 
1106             docEl = el.ownerDocument.documentElement,
1107             parent = el.parentNode;
1108         if(/^(?:r|s)/.test(elPosition) || !elPosition) return parent;
1109         if(elPosition == "fixed") return null;
1110         while(parent && parent != docEl) {
1111             if(_getComputedStyle(parent, "position") != "static")
1112                 return parent;
1113             parent = parent.parentNode;
1114         }
1115         return docEl;
1116     }
1117     
1118     /** @function()
1119      * @return {Object} {x: Number, y:Number}
1120      */
1121     function getPixelCoords(el){
1122         var f = (IS_COMPUTED_STYLE_SUPPORTED ? function(el) {
1123             var cs = el.ownerDocument.defaultView[getCS](el, "");
1124             return {
1125                 x : parseInt(cs.left)||0,
1126                 y : parseInt(cs.top)||0
1127             };
1128         } : function(el){
1129             var style = el.style;
1130             return {
1131                 // pixelLeft will return 0 when the element does not have 
1132                 // left: in the style attribute.
1133                 x : style.pixelLeft || parseInt(_getComputedStyle(el,"left"))||0,
1134                 y : style.pixelTop || parseInt(_getComputedStyle(el,"top"))||0
1135             };
1136         });
1137         this.getPixelCoords = f;
1138         return f(el);
1139     }
1140 })();/**
1141  * @author Garret Smith
1142  */
1143 
1144 
1145 (function() {
1146 
1147     // Public exports.
1148     APE.mixin(APE.dom, {
1149         getScrollOffsets : getScrollOffsets,
1150         getViewportDimensions : getViewportDimensions
1151     });
1152 
1153     var docEl = document.documentElement,
1154         IS_BODY_ACTING_ROOT = docEl && docEl.clientWidth === 0;
1155     docEl = null;
1156 
1157     /** @memberOf APE.dom 
1158      * @name getScrollOffsets
1159      * @function
1160      * @return an object with <code>width</code> and <code>height</code>.
1161      * This will exhibit a bug in Mozilla, which is often 5-7 pixels off.
1162      */
1163     function getScrollOffsets() {
1164         var f;
1165         if("pageXOffset"in window)
1166             f = function() {
1167                 return{ left:window.pageXOffset, top: window.pageYOffset};
1168             };
1169         else if(IS_BODY_ACTING_ROOT)
1170             f = function() {
1171                 var body = document.body;
1172                 return{ left: body.scrollLeft, top : body.scrollTop};
1173             };
1174         else f = function() {
1175             var docEl = document.documentElement;
1176             return{ left : docEl.scrollLeft, top : docEl.scrollTop };
1177         };
1178         this.getScrollOffsets = f;
1179         return f();   
1180     }
1181 
1182     /** @memberOf APE.dom 
1183      * @name getViewportDimensions
1184      * @function
1185      * @return an object with <code>width</code> and <code>height</code>.
1186      * This will exhibit a bug in Mozilla, which is often 5-7 pixels off.
1187      */
1188     function getViewportDimensions() {
1189         var f;
1190 
1191         if(typeof document.width == "number") {
1192             f = function(){
1193                 var d = document, docEl = d.documentElement;
1194                 return{ width : Math.min(d.width, docEl.clientWidth), 
1195                     height : Math.min(d.height, docEl.clientHeight)};
1196             };
1197         } else if("innerWidth"in window) {
1198             f = function() {
1199                 return { width: window.innerWidth, height: window.innerHeight };
1200             };
1201         }
1202         else if(IS_BODY_ACTING_ROOT) {
1203             f = function() {
1204                 var body = document.body;
1205                 return { width: body.clientWidth, height: body.clientHeight};
1206             };
1207         }
1208         else f = function(){
1209             return{width:docEl.clientWidth, height:docEl.clientHeight};
1210         };
1211         this.getViewportDimensions = f;
1212 
1213         // Call in correct global context (for iframe).
1214         return f(); 
1215     }
1216 })();/**
1217  * @fileoverview
1218  * @static
1219  * @author Garrett Smith
1220  * APE.dom package functions for calculating element position properties.
1221  */
1222 /** @name APE.dom */
1223 
1224 (function() {
1225     APE.mixin(
1226         APE.dom, 
1227             /** @scope APE.dom */ {
1228             getOffsetCoords : getOffsetCoords,
1229             isAboveElement : isAboveElement,
1230             isBelowElement : isBelowElement,
1231             isInsideElement: isInsideElement
1232     });
1233     
1234     var doc = this.document,
1235         documentElement = doc.documentElement,
1236         round = Math.round, max = Math.max,
1237 
1238     // Load-time constants.
1239         IS_BODY_ACTING_ROOT = documentElement && documentElement.clientWidth === 0,
1240 
1241     // IE, Safari, and Opera support clientTop. FF 2 doesn't
1242         IS_CLIENT_TOP_SUPPORTED = 'clientTop'in documentElement,
1243 
1244         TABLE = /^h/.test(documentElement.tagName) ? "table" : "TABLE",
1245 
1246         IS_CURRENT_STYLE_SUPPORTED = 'currentStyle'in documentElement,
1247 
1248     // XXX Opera <= 9.2 - parent border widths are included in offsetTop.
1249         IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET,
1250 
1251     // XXX Opera <= 9.2 - body offsetTop is inherited to children's offsetTop
1252     // when body position is not static.
1253     // opera will inherit the offsetTop/offsetLeft of body for relative offsetParents.
1254 
1255         IS_BODY_MARGIN_INHERITED,
1256         IS_BODY_TOP_INHERITED,
1257         IS_BODY_OFFSET_EXCLUDING_MARGIN,
1258 
1259     // XXX Mozilla includes a table border in the TD's offsetLeft.
1260     // There is 1 exception: 
1261     //   When the TR has position: relative and the TD has block level content.
1262     //   In that case, the TD does not include the TABLE's border in it's offsetLeft.
1263     // We do not account for this peculiar bug. 
1264         IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET,
1265         IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH,
1266 
1267         IS_BODY_OFFSET_IGNORED_WHEN_BODY_RELATIVE_AND_LAST_CHILD_POSITIONED,
1268 
1269         IS_CONTAINER_BODY_STATIC_INCLUDING_HTML_PADDING,
1270         IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD,
1271         IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_ABS_CHILD,
1272         IS_CONTAINER_BODY_INCLUDING_HTML_MARGIN,
1273 
1274         // In Safari 2.0.4, BODY can have offsetTop when offsetParent is null.
1275         // but offsetParent will be HTML (root) when HTML has position. 
1276         // IS_BODY_OFFSET_TOP_NO_OFFSETPARENT,
1277         
1278         IS_COMPUTED_STYLE_SUPPORTED = doc.defaultView 
1279             && typeof doc.defaultView.getComputedStyle != "undefined",
1280         getBoundingClientRect = "getBoundingClientRect",
1281         relative = "relative",
1282         borderTopWidth = "borderTopWidth",
1283         borderLeftWidth = "borderLeftWidth",
1284         positionedExp = /^(?:r|a)/,
1285         absoluteExp = /^(?:a|f)/;
1286 
1287     /**
1288      * @memberOf APE.dom
1289      * @param {HTMLElement} el you want coords of.
1290      * @param {HTMLElement} positionedContainer container to look up to. The container must have 
1291      * position: (relative|absolute|fixed);
1292      *
1293      * @param {x:Number, y:Number} coords object to pass in.
1294      * @return {x:Number, y:Number} coords of el from container.
1295      * 
1296      * Passing in a container will improve performance in browsers that don't support 
1297      * getBoundingClientRect, but those that do will have a recursive call. Test accordingly.
1298      * <p>
1299      * Container is sometimes irrelevant. Container is irrelevant when comparing positions 
1300      * of objects who do not share a common ancestor. In this case, pass in document.
1301      * </p>
1302      *<p>
1303      * Passing in re-used coords can improve performance in all browsers.
1304      * There is a side effect to passing in coords:
1305      * For drag drop operations, reuse coords:
1306      *</p>
1307      * <pre>
1308      * // Update our coords:
1309      * dom.getOffsetCoords(el, container, this.coords);
1310      * </pre>
1311      * Where <code>this.coords = {};</code>
1312      */
1313     function getOffsetCoords(el, container, coords) {
1314 
1315         var doc = el.ownerDocument,
1316             documentElement = doc.documentElement,
1317             body = doc.body;
1318 
1319         if(!container)
1320             container = doc;
1321 
1322         if(!coords) 
1323             coords = {x:0, y:0};
1324 
1325         if(el === container) {
1326             coords.x = coords.y = 0;
1327             return coords;
1328         }
1329         if(el[getBoundingClientRect]) {
1330             
1331             // In BackCompat mode, body's border goes to the window. BODY is ICB.
1332             var rootBorderEl = IS_BODY_ACTING_ROOT ? body : documentElement,
1333                 box = el[getBoundingClientRect](),
1334                 x = box.left + max( documentElement.scrollLeft, body.scrollLeft ),
1335                 y = box.top + max( documentElement.scrollTop, body.scrollTop ),
1336                 bodyCurrentStyle,
1337                 borderTop = rootBorderEl.clientTop,
1338                 borderLeft = rootBorderEl.clientLeft;
1339 
1340             if(IS_CLIENT_TOP_SUPPORTED) {
1341                 x -= borderLeft;
1342                 y -= borderTop;
1343             }
1344             if(container !== doc) {
1345                 box = getOffsetCoords(container, null);
1346                 x -= box.x;
1347                 y -= box.y;
1348                 if(IS_BODY_ACTING_ROOT && container === body && IS_CLIENT_TOP_SUPPORTED) {
1349                     x -= borderLeft;
1350                     y -= borderTop;
1351                 }
1352             }
1353 
1354             if(IS_BODY_ACTING_ROOT && IS_CURRENT_STYLE_SUPPORTED 
1355                 && container != doc && container !== body) {
1356                 bodyCurrentStyle = body.currentStyle;
1357                 x += parseFloat(bodyCurrentStyle.marginLeft)||0 + 
1358                     parseFloat(bodyCurrentStyle.left)||0;
1359                 y += parseFloat(bodyCurrentStyle.marginTop)||0 +
1360                      parseFloat(bodyCurrentStyle.top)||0;
1361             }
1362             coords.x = x;
1363             coords.y = y;
1364 
1365             return coords;
1366         }
1367 
1368     // Crawling up the tree. 
1369         else if(IS_COMPUTED_STYLE_SUPPORTED){
1370             
1371             var offsetLeft = el.offsetLeft, 
1372                 offsetTop = el.offsetTop,
1373                 defaultView = doc.defaultView,
1374                 cs = defaultView.getComputedStyle(el, '');
1375             if(cs.position == "fixed") {
1376                 coords.x = offsetLeft + documentElement.scrollLeft;
1377                 coords.y = offsetTop + documentElement.scrollTop;
1378                 return coords;
1379             }
1380             var bcs = defaultView.getComputedStyle(body,''),
1381                 isBodyStatic = !positionedExp.test(bcs.position),
1382                 lastOffsetParent = el, 
1383                 parent = el.parentNode,
1384                 offsetParent = el.offsetParent;
1385             
1386         // Main loop -----------------------------------------------------------------------
1387             // Loop up, gathering scroll offsets on parentNodes.
1388             // when we get to a parent that's an offsetParent, update 
1389             // the current offsetParent marker.
1390             for( ; parent && parent !== container; parent = parent.parentNode) { 
1391                 if(parent !== body && parent !== documentElement) {
1392                     offsetLeft -= parent.scrollLeft;
1393                     offsetTop -= parent.scrollTop;
1394                 }
1395                 if(parent === offsetParent) {
1396                     // If we get to BODY and have static position, skip it.
1397                     if(parent === body && isBodyStatic); 
1398                     else {
1399 
1400                         // XXX Mozilla; Exclude static body; if static, it's offsetTop will be wrong.
1401                         // Include parent border widths. This matches behavior of clientRect approach.
1402                         // XXX Opera <= 9.2 includes parent border widths. 
1403                         // See IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET below. 
1404                         if( !IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET && 
1405                             ! (parent.tagName === TABLE && IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET)) {
1406                                 var pcs = defaultView.getComputedStyle(parent, "");
1407                                 // Mozilla doesn't support clientTop. Add borderWidth to the sum. 
1408                                 offsetLeft += parseFloat(pcs[borderLeftWidth])||0;
1409                                 offsetTop += parseFloat(pcs[borderTopWidth])||0;
1410                         }
1411                         if(parent !== body) {
1412                             offsetLeft += offsetParent.offsetLeft;
1413                             offsetTop += offsetParent.offsetTop;
1414                             lastOffsetParent = offsetParent;
1415                             offsetParent = parent.offsetParent; // next marker to check for offsetParent.
1416                         }
1417                     }
1418                 }
1419             }
1420         
1421             //--------Post - loop, body adjustments----------------------------------------------
1422             // Complications due to CSSOM Views - the browsers try to implement a contradictory
1423             // spec: http://www.w3.org/TR/cssom-view/#offset-attributes
1424 
1425             // XXX Mozilla, Safari 3, Opera: body margin is never
1426             // included in body offsetLeft/offsetTop.
1427             // This is wrong. Body's offsetTop should work like any other element.
1428             // In Safari 2.0.4, BODY can have offsetParent, and even 
1429             // if it doesn't, it can still have offsetTop.
1430             // But Safari 2.0.4 doubles offsetTop for relatively positioned elements 
1431             // and this script does not account for that.
1432 
1433             // XXX Mozilla: When body has a border, body's offsetTop === negative borderWidth;
1434             // Don't use body.offsetTop.
1435             var bodyOffsetLeft = 0,
1436                 bodyOffsetTop = 0,
1437                 isLastElementAbsolute,
1438                 isLastOffsetElementPositioned,
1439                 isContainerDocOrDocEl = container === doc || container === documentElement,
1440                 dcs,
1441                 lastOffsetPosition;
1442 
1443             // If the lastOffsetParent is document, 
1444             // it is not positioned (and hence, not absolute).
1445             if(lastOffsetParent != doc) {
1446                 lastOffsetPosition = defaultView.getComputedStyle(lastOffsetParent,'').position;
1447                 isLastElementAbsolute = absoluteExp.test(lastOffsetPosition);
1448                 isLastOffsetElementPositioned = isLastElementAbsolute || 
1449                     positionedExp.test(lastOffsetPosition);
1450             }
1451 
1452             // do we need to add margin? 
1453             if( 
1454                 (lastOffsetParent === el && el.offsetParent === body && !IS_BODY_MARGIN_INHERITED
1455                 && container !== body && !(isBodyStatic && IS_BODY_OFFSET_EXCLUDING_MARGIN))
1456                 || (IS_BODY_MARGIN_INHERITED && lastOffsetParent === el && !isLastOffsetElementPositioned)
1457                 || !isBodyStatic 
1458                 && isLastOffsetElementPositioned
1459                 && IS_BODY_OFFSET_IGNORED_WHEN_BODY_RELATIVE_AND_LAST_CHILD_POSITIONED
1460                 && isContainerDocOrDocEl) {                    
1461                     bodyOffsetTop += parseFloat(bcs.marginTop)||0;
1462                     bodyOffsetLeft += parseFloat(bcs.marginLeft)||0;
1463             }
1464 
1465             // Case for padding on documentElement.
1466             if(container === body) {
1467                 dcs = defaultView.getComputedStyle(documentElement,'');
1468                 if( 
1469                     (!isBodyStatic && 
1470                         ((IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD && !isLastElementAbsolute)
1471                         || 
1472                         (IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_ABS_CHILD && isLastElementAbsolute))
1473                     )
1474                     || isBodyStatic && IS_CONTAINER_BODY_STATIC_INCLUDING_HTML_PADDING
1475                     ) {
1476                     bodyOffsetTop -= parseFloat(dcs.paddingTop)||0;
1477                     bodyOffsetLeft -= parseFloat(dcs.paddingLeft)||0;
1478                 }
1479 
1480                 if(IS_CONTAINER_BODY_INCLUDING_HTML_MARGIN){
1481                     if(!isLastOffsetElementPositioned 
1482                         || isLastOffsetElementPositioned && !isBodyStatic)
1483                     bodyOffsetTop -= parseFloat(dcs.marginTop)||0;
1484                     bodyOffsetLeft -= parseFloat(dcs.marginLeft)||0;
1485                 }
1486             }
1487             if(isBodyStatic) { 
1488                 // XXX Safari subtracts border width of body from element's offsetTop (opera does it, too)
1489                 if(IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH
1490                 // XXX: Safari will use HTML for containing block (CSS),
1491                 // but will subtract the body's border from the body's absolutely positioned 
1492                 // child.offsetTop. Safari reports the child's offsetParent is BODY, but 
1493                 // doesn't treat it that way (Safari bug).
1494                     || (!isLastElementAbsolute && !IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET
1495                         && isContainerDocOrDocEl)) {
1496                            bodyOffsetTop += parseFloat(bcs[borderTopWidth]);
1497                            bodyOffsetLeft += parseFloat(bcs[borderLeftWidth]);
1498                 }
1499             }
1500 
1501             // body is positioned, and if it excludes margin,
1502             // it's probably partly using the AVK-CSSOM disaster.
1503             else if(IS_BODY_OFFSET_EXCLUDING_MARGIN) {
1504                 if(isContainerDocOrDocEl) {
1505                     if(!IS_BODY_TOP_INHERITED) {
1506 
1507                         // If the body is positioned, add its left and top value.
1508                          bodyOffsetTop += parseFloat(bcs.top)||0;
1509                          bodyOffsetLeft += parseFloat(bcs.left)||0;
1510                         
1511                         // XXX: Opera normally include the parentBorder in offsetTop.
1512                         // We have a preventative measure in the loop above.
1513                         if(isLastElementAbsolute && IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET) {
1514                             bodyOffsetTop += parseFloat(bcs[borderTopWidth]);
1515                             bodyOffsetLeft += parseFloat(bcs[borderLeftWidth]);
1516                         }
1517                     }
1518 
1519                     // Padding on documentElement is not included,
1520                     // but in this case, we're searching to documentElement, so we 
1521                     // have to add it back in.
1522                     if(container === doc && !isBodyStatic 
1523                         && !IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD) {
1524                         if(!dcs) dcs = defaultView.getComputedStyle(documentElement,'');
1525                         bodyOffsetTop += parseFloat(dcs.paddingTop)||0;
1526                         bodyOffsetLeft += parseFloat(dcs.paddingLeft)||0;
1527                     }
1528                 }
1529                 else if(IS_BODY_TOP_INHERITED) {
1530                     bodyOffsetTop -= parseFloat(bcs.top);
1531                     bodyOffsetLeft -= parseFloat(bcs.left);
1532                 }
1533                 if(IS_BODY_MARGIN_INHERITED && (!isLastOffsetElementPositioned || container === body)) {
1534                     bodyOffsetTop -= parseFloat(bcs.marginTop)||0;
1535                     bodyOffsetLeft -= parseFloat(bcs.marginLeft)||0;
1536                 }
1537             }
1538             coords.x = round(offsetLeft + bodyOffsetLeft);
1539             coords.y = round(offsetTop + bodyOffsetTop);
1540 
1541             return coords;
1542         }
1543     }
1544 
1545 // A closure for initializing load time constants.
1546     if(!(getBoundingClientRect in documentElement) && IS_COMPUTED_STYLE_SUPPORTED)
1547     (function(){
1548     var waitForBodyTimer = setInterval(function() {
1549         var body = doc.body;
1550         if(!body) return;
1551         clearInterval(waitForBodyTimer);
1552         var marginTop = "marginTop", position = "position", padding = "padding", 
1553             stat = "static",
1554             border = "border", s = body.style, 
1555             bCssText = s.cssText,
1556             bv = '1px solid transparent',
1557             z = "0",
1558             one = "1px",
1559             offsetTop = "offsetTop",
1560             ds = documentElement.style,
1561             dCssText = ds.cssText,
1562             x = doc.createElement('div'),
1563             xs = x.style,
1564             table = doc.createElement(TABLE);
1565 
1566         s[padding] = s[marginTop] = s.top = z;
1567         ds.position = stat;
1568 
1569         s[border] = bv;
1570 
1571         xs.margin = z;
1572         xs[position] = stat;
1573 
1574         // insertBefore - to avoid environment conditions with bottom script
1575         // where appendChild would fail.
1576         x = body.insertBefore(x, body.firstChild);
1577         IS_PARENT_BODY_BORDER_INCLUDED_IN_OFFSET = (x[offsetTop] === 1);
1578         
1579         s[border] = z;
1580 
1581         // Table test.
1582         table.innerHTML = "<tbody><tr><td>x</td></tr></tbody>";
1583         table.style[border] = "7px solid";
1584         table.cellSpacing = table.cellPadding = z;
1585 
1586         body.insertBefore(table, body.firstChild);
1587         IS_TABLE_BORDER_INCLUDED_IN_TD_OFFSET = table.getElementsByTagName("td")[0].offsetLeft === 7;
1588 
1589         body.removeChild(table);
1590 
1591         // Now add margin to determine if body offsetTop is inherited.
1592         s[marginTop] = one;
1593         s[position] = relative;
1594         IS_BODY_MARGIN_INHERITED = (x[offsetTop] === 1);
1595         
1596         //IS_BODY_OFFSET_TOP_NO_OFFSETPARENT = body.offsetTop && !body.offsetParent;
1597 
1598         IS_BODY_OFFSET_EXCLUDING_MARGIN = body[offsetTop] === 0;
1599         s[marginTop] = z;
1600         s.top = one;
1601         IS_BODY_TOP_INHERITED = x[offsetTop] === 1;
1602 
1603         s.top = z;
1604         s[marginTop] = one;
1605         s[position] = xs[position] = relative;
1606         IS_BODY_OFFSET_IGNORED_WHEN_BODY_RELATIVE_AND_LAST_CHILD_POSITIONED = x[offsetTop] === 0;
1607 
1608         xs[position] = "absolute";
1609         s[position] = stat;
1610         if(x.offsetParent === body) {
1611             s[border] = bv;
1612             xs.top = "2px";
1613             // XXX Safari gets offsetParent wrong (says 'body' when body is static,
1614             // but then positions element from ICB and then subtracts body's clientWidth.
1615             // Safari is half wrong.
1616             //
1617             // XXX Mozilla says body is offsetParent but does NOT subtract EL's offsetLeft/Top.
1618             // Mozilla is completely wrong.
1619             IS_STATIC_BODY_OFFSET_PARENT_BUT_ABSOLUTE_CHILD_SUBTRACTS_BODY_BORDER_WIDTH = x[offsetTop] === 1;
1620             s[border] = z;
1621             
1622             xs[position] = relative;
1623             ds[padding] = one;
1624             s[marginTop] = z;
1625 
1626             IS_CONTAINER_BODY_STATIC_INCLUDING_HTML_PADDING = x[offsetTop] === 3;
1627 
1628             // Opera does not respect position: relative on BODY.
1629             s[position] = relative;
1630             IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_REL_CHILD = x[offsetTop] === 3;
1631 
1632             xs[position] = "absolute";
1633             IS_CONTAINER_BODY_RELATIVE_INCLUDING_HTML_PADDING_ABS_CHILD = x[offsetTop] === 3;
1634             
1635             ds[padding] = z;
1636             ds[marginTop] = one;
1637 
1638             // Opera inherits HTML margin when body is relative and child is relative or absolute.
1639             IS_CONTAINER_BODY_INCLUDING_HTML_MARGIN = x[offsetTop] === 3;
1640         }
1641 
1642         // xs.position = "fixed";
1643         // FIXED_HAS_OFFSETPARENT = x.offsetParent != null;
1644 
1645         body.removeChild(x);
1646         s.cssText = bCssText||"";
1647         ds.cssText = dCssText||"";
1648     }, 60);
1649     })();
1650 
1651 
1652   // TODO: add an optional commonAncestor parameter to the below.
1653     /** 
1654      * @memberOf APE.dom
1655      * @return {boolean} true if a is vertically within b's content area (and does not overlap, 
1656      * top nor bottom).
1657      */
1658     function isInsideElement(a, b) {
1659         var aTop = getOffsetCoords(a).y,
1660             bTop = getOffsetCoords(b).y;
1661         return aTop + a.offsetHeight <= bTop + b.offsetHeight && aTop >= bTop;
1662     }
1663 
1664     /** 
1665      * @memberOf APE.dom
1666      * @return {boolean} true if a overlaps the top of b's content area.
1667      */
1668     function isAboveElement(a, b) {
1669         return (getOffsetCoords(a).y <= getOffsetCoords(b).y);
1670     }
1671 
1672     /** 
1673      * @memberOf APE.dom
1674      * @return {boolean} true if a overlaps the bottom of b's content area.
1675      */
1676     function isBelowElement(a, b) {
1677         return (getOffsetCoords(a).y + a.offsetHeight >= getOffsetCoords(b).y + b.offsetHeight);
1678     }
1679     
1680     // release from closure.
1681     isInsideElement = isAboveElement = isBelowElement = null;
1682 })();/** 
1683  * @fileoverview dom ClassName Functions.
1684  * @namespace APE.dom
1685  * @author Garrett Smith
1686  * <p>
1687  * ClassName functions are added to APE.dom.
1688  * </p>
1689  */
1690 
1691 
1692 (function() {
1693     APE.mixin(APE.dom, 
1694         /** @scope APE.dom */{
1695         hasToken : hasToken,
1696         removeClass : removeClass,
1697         addClass : addClass,
1698         getElementsByClassName : getElementsByClassName,
1699         findAncestorWithClass : findAncestorWithClass
1700     });
1701 
1702     /** @param {String} s string to search
1703      * @param {String} token white-space delimited token the delimiter of the token.
1704      * This is generally used with element className: 
1705      * @example if(dom.hasToken(el.className, "menu")) // element has class "menu".
1706      */
1707     function hasToken (s, token) {
1708         return getTokenizedExp(token,"").test(s);
1709     }
1710 
1711     /** @param {HTMLElement} el  
1712      * @param {String} klass className token(s) to be removed. 
1713      * @description removes all occurances of <code>klass</code> from element's className.
1714      */
1715     function removeClass(el, klass) {
1716         el.className = normalizeString(el.className.replace(getTokenizedExp(klass, "g")," "));
1717     }
1718     /** @param {HTMLElement} el  
1719      * @param {String} klass className token(s) to be added. 
1720      * @description adds <code>klass</code> to the element's class attribute, if it does not 
1721      * exist.
1722      */
1723     function addClass(el, klass) {
1724         if(!el.className) el.className = klass;
1725         if(!getTokenizedExp(klass).test(el.className)) el.className += " " + klass;
1726     }
1727     
1728     var Exps = { };
1729     function getTokenizedExp(token, flag){
1730         var p = token + "$" + flag; 
1731         return (Exps[p] || (Exps[p] = RegExp("(?:^|\\s)"+token+"(?:$|\\s)", flag)));
1732     }
1733 
1734     /** @param {HTMLElement} el  
1735      * @param {String} tagName tagName to be searched. Use "*" for any tag.
1736      * @param {String} klass className token(s) to be added. 
1737      * @return {Array} Elements with the specified tagName and className.
1738      * Searches will generally be faster with a smaller HTMLCollection
1739      * and shorter tree.
1740      */
1741     function getElementsByClassName(el, tagName, klass){
1742         var ret = Array(len);
1743 
1744         if(el.getElementsByClassName && (tagName === "*" || !tagName)) {
1745             // Native performance boost.
1746             return ret.slice.call(el.getElementsByClassName(klass));
1747         }
1748         var exp = getTokenizedExp(klass,""),
1749             collection = el.getElementsByTagName(tagName),
1750             len = collection.length,
1751             counter = 0;
1752         for(i = 0; i < len; i++){
1753             if(exp.test(collection[i].className))
1754                 ret[counter++] = collection[i];
1755         }
1756         ret.length = counter; // trim array.
1757         return ret;
1758     }
1759 
1760     /** Finds an ancestor with specified className
1761      */
1762     function findAncestorWithClass(el, klass) {
1763         if(el == null)
1764             return null;
1765         var exp = getTokenizedExp(klass,"");
1766         for(var parent = el.parentNode;parent != null;){
1767             if( exp.test(parent.className) )
1768                 return parent;
1769             parent = parent.parentNode;
1770         }
1771         return null;
1772     }
1773 
1774 var STRING_TRIM_EXP = /^\s+|\s+$/g,
1775     WS_MULT_EXP = /\s\s+/g;
1776 function normalizeString(s) { return s.replace(STRING_TRIM_EXP,'').replace(WS_MULT_EXP, " "); }
1777 
1778 if(!document.getElementsByClassName) nativeGEBCN = null;
1779 })();
1780 (function(){
1781 
1782     var docEl = document.documentElement;
1783 
1784     APE.mixin(
1785         APE.dom, {
1786         contains : getContains(),
1787         findAncestorWithAttribute : findAncestorWithAttribute,
1788         findAncestorWithTagName : findAncestorWithTagName,
1789         findNextSiblingElement : findNextSiblingElement,
1790         findPreviousSiblingElement : findPreviousSiblingElement,
1791         getChildElements : getChildElements
1792     });
1793 
1794     /** 
1795      * @memberOf APE.dom
1796      * @return {boolean} true if a contains b.
1797      * Internet Explorer's native contains() is different. It will return true for:
1798      * code body.contains(body); 
1799      * Whereas APE.dom.contains will return false.
1800      */
1801     function getContains(){
1802         if('compareDocumentPosition'in docEl)
1803             return function(el, b) {
1804                 return (el.compareDocumentPosition(b) & 16) !== 0;
1805         };
1806         else if(docEl.contains)
1807             return function(el, b) {
1808                 return el !== b && el.contains(b);
1809         };
1810         return function(el, b) {
1811             if(el === b) return false;
1812             while(el != b && (b = b.parentNode) != null);
1813             return el === b;
1814         };
1815     }
1816     /** 
1817      * @memberOf APE.dom
1818      * @param {HTMLElement} el the element to start from.
1819      * @param {String} attName the name of the attribute.
1820      * @param {String} [value] the value of the attribute. If omitted, then only the 
1821      * presence of attribute is checked and the value is anything.
1822      * @return {HTMLElement} closest ancestor with <code>attName</code> matching value.
1823      * Returns null if not found.
1824      */
1825     function findAncestorWithAttribute(el, attName, value) {
1826         for(var map, parent = el.parentNode;parent != null;){
1827             map = parent.attributes;
1828             if(!map) return null;
1829             var att = map[attName];
1830             if(att && att.specified)
1831                 if(att.value === value || (value === undefined))
1832                     return parent;            
1833             parent = parent.parentNode;
1834         }
1835         return null;
1836     }
1837 
1838     var isUpperCase = /^H/.test(docEl.tagName);
1839     function findAncestorWithTagName(el, tagName) {
1840         tagName = (isUpperCase ? tagName.toUpperCase() : tagName.toLowerCase());
1841         for(var parent = el.parentNode;parent != null; ){
1842             if( parent.tagName === tagName )
1843                 return parent;
1844             parent = parent.parentNode;
1845         }
1846         return null;
1847     }
1848 
1849     function findNextSiblingElement(el) {
1850         for(var ns = el.nextSibling; ns != null; ns = ns.nextSibling)
1851             if(ns.tagName) 
1852                 return ns;
1853             return null;
1854     }
1855 
1856     function findPreviousSiblingElement(el) {
1857         for(var ps = el.previousSibling; ps != null; ps = ps.previousSibling)
1858             if(ps.tagName) 
1859                 return ps;
1860             return null;
1861     }
1862 
1863     function getChildElements(el) {    
1864         var i = 0, ret = [],
1865             cn = el.childNodes;
1866         for(var len = cn.length; i < len; i++) {
1867             if("tagName"in cn[i])
1868                 ret[ret.length] = cn[i];
1869         }
1870         return ret;
1871     }
1872 })();/**
1873  * @requires APE.dom.Viewport
1874  */
1875 /** @namespace APE.dom */
1876 
1877 
1878 //-------- Needs Test --------------------------------------------
1879 (function(){
1880 var dom = APE.dom;
1881 APE.mixin(
1882     dom.Event = {},{
1883         getTarget : getTarget,
1884         getCoords : getCoords
1885 });
1886 
1887 function getTarget(e) { return e && e.target || event.srcElement; }
1888 function getCoords(e) {
1889     var f;
1890     if("pageX"in e) {
1891         f = function(e) {
1892             return {
1893                 x : e.pageX,
1894                 y : e.pageY
1895             };
1896         };
1897     }
1898     else {
1899         f = function(e){
1900             var scrollOffsets = dom.getScrollOffsets(), e = e||event;
1901             return {
1902                 x : e.clientX + scrollOffsets.left,
1903                 y : e.clientY + scrollOffsets.top
1904             }
1905         };
1906     }
1907     return(dom.Event.getCoords = f)(e);
1908 }
1909 })();/**
1910  * XXX: IE Fix for getElementById returning elements by name.
1911  */
1912 (function(){
1913     var d = document, x = d.body, c,
1914         g = 'getElementById',
1915         orig = document[g];
1916 
1917     if(!x) return setTimeout(arguments.callee,50);
1918 
1919     try {
1920         c = d.createElement("<A NAME=0>");
1921         x.insertBefore(c, x.firstChild);
1922         if(d[g]('0')){
1923             x.removeChild(c);
1924             d[g] = getElementById;
1925         }
1926     } catch(x){}
1927     function getElementById(id) {
1928         var el = Function.prototype.call.call(orig, this, id), els, i;
1929 
1930         if(el.id == id) return el;
1931         els = this.getElementsByName(id);
1932 
1933         for(i = 0; i < els.length; i++)
1934             if(els[i].id === id) return els[i];
1935         return null;
1936     };
1937 })();
1938