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