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