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