/* Core JavaScript Library $Id: core.js 232 2007-10-01 20:32:42Z whitaker $ Copyright (c) 2005, Six Apart, Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of "Six Apart" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* stubs */ log = function() {}; log.error = log.warn = log.debug = log; /* utility functions */ defined = function( x ) { return x === undefined ? false : true; } /** * Utility method. * @param x any Any JavaScript value, including undefined. * @return boolean true if the value is not null and is not undefined. */ exists = function( x ) { return (x === undefined || x === null) ? false : true; } finite = function( x ) { return isFinite( x ) ? x : 0; } finiteInt = function( x, base ) { return finite( parseInt( x, base ) ); } finiteFloat = function( x ) { return finite( parseFloat( x ) ); } max = function() { var a = arguments; var n = a[ 0 ]; for( var i = 1; i < a.length; i++ ) if( a[ i ] > n ) n = a[ i ]; return n; } min = function() { var a = arguments; var n = a[ 0 ]; for( var i = 1; i < a.length; i++ ) if( a[ i ] < n ) n = a[ i ]; return n; } /* try block */ Try = { these: function() { for( var i = 0; i < arguments.length; i++ ) { try { return arguments[ i ](); } catch( e ) {} } return undefined; } } /* unique id generator */ Unique = { length: 0, id: function() { return ++this.length; } } /* event methods */ if( !defined( window.Event ) ) Event = {}; Event.stop = function( event ) { event = event || this; if( event === Event ) event = window.event; // w3c if( event.preventDefault ) event.preventDefault(); if( event.stopPropagation ) event.stopPropagation(); // ie try { event.cancelBubble = true; event.returnValue = false; } catch( e ) {} return false; } Event.prep = function( event ) { event = event || window.event; if( !defined( event.stop ) ) event.stop = this.stop; if( !defined( event.target ) ) event.target = event.srcElement; if( !defined( event.relatedTarget ) ) event.relatedTarget = event.toElement; return event; } try { Event.prototype.stop = Event.stop; } catch( e ) {} /* object extensions */ Function.stub = function() {}; if( !Object.prototype.hasOwnProperty ) { Object.prototype.hasOwnProperty = function( p ) { if( !(p in this) ) return false; try { var pr = this.constructor.prototype; while( pr ) { if( pr[ p ] === this[ p ] ) return false; if( pr === pr.constructor.prototype ) break; pr = pr.constructor.prototype; } } catch( e ) {} return true; } } Object.prototype.extend = function() { var a = arguments; for( var i = 0; i < a.length; i++ ) { var o = a[ i ]; for( var p in o ) { try { if( !this[ p ] && (!o.hasOwnProperty || o.hasOwnProperty( p )) ) this[ p ] = o[ p ]; } catch( e ) {} } } return this; } Object.prototype.override = function() { var a = arguments; for( var i = 0; i < a.length; i++ ) { var o = a[ i ]; for( var p in o ) { try { if( !o.hasOwnProperty || o.hasOwnProperty( p ) ) this[ p ] = o[ p ]; } catch( e ) {} } } return this; } Object.prototype.extend( { init: Function.stub, destroy: Function.stub } ); /* function extensions */ Function.prototype.extend( { bind: function( object ) { var method = this; return function() { return method.apply( object, arguments ); }; }, bindEventListener: function( object ) { var method = this; // Use double closure to work around IE 6 memory leak. return function( event ) { try { event = Event.prep( event ); } catch( e ) {} return method.call( object, event ); }; } } ); /* class helpers */ indirectObjects = []; Class = function( superClass ) { // Set the constructor: var constructor = function() { if( arguments.length ) this.init.apply( this, arguments ); }; // -- Accomplish static-inheritance: constructor.override( Class ); // inherit static methods from Class superClass = superClass || Object; constructor.override( superClass ); // inherit static methods from the superClass constructor.superClass = superClass.prototype; // Set the constructor's prototype (accomplish object-inheritance): constructor.prototype = new superClass(); constructor.prototype.constructor = constructor; // rev. 0.7 // -- extend prototype with Class instance methods constructor.prototype.extend( Class.prototype ); // -- override prototype with interface methods for( var i = 1; i < arguments.length; i++ ) constructor.prototype.override( arguments[ i ] ); return constructor; } Class.extend( { initSingleton: function() { if( this.singleton ) return this.singleton; this.singleton = this.singletonConstructor ? new this.singletonConstructor() : new this(); this.singleton.init.apply( this.singleton, arguments ); return this.singleton; } } ); Class.prototype = { destroy: function() { try { if( this.indirectIndex ) indirectObjects[ this.indirectIndex ] = undefined; delete this.indirectIndex; } catch( e ) {} for( var property in this ) { try { if( this.hasOwnProperty( property ) ) delete this[ property ]; } catch( e ) {} } }, getBoundMethod: function( methodName ) { return this[ name ].bind( this ); }, getEventListener: function( methodName ) { return this[ methodName ].bindEventListener( this ); }, getIndirectIndex: function() { if( !defined( this.indirectIndex ) ) { this.indirectIndex = indirectObjects.length; indirectObjects.push( this ); } return this.indirectIndex; }, getIndirectMethod: function( methodName ) { if( !this.indirectMethods ) this.indirectMethods = {}; var method = this[ methodName ]; if( typeof method != "function" ) return undefined; var indirectIndex = this.getIndirectIndex(); if( !this.indirectMethods[ methodName ] ) { this.indirectMethods[ methodName ] = new Function( "var o = indirectObjects[" + indirectIndex + "];" + "return o." + methodName + ".apply( o, arguments );" ); } return this.indirectMethods[ methodName ]; }, getIndirectEventListener: function( methodName ) { if( !this.indirectEventListeners ) this.indirectEventListeners = {}; var method = this[ methodName ]; if( typeof method != "function" ) return undefined; var indirectIndex = this.getIndirectIndex(); if( !this.indirectEventListeners[ methodName ] ) { this.indirectEventListeners[ methodName ] = new Function( "event", "try { event = Event.prep( event ); } catch( e ) {}" + "var o = indirectObjects[" + indirectIndex + "];" + "return o." + methodName + ".call( o, event );" ); } return this.indirectEventListeners[ methodName ]; } } /* string extensions */ String.extend( { escapeJSChar: function( c ) { // try simple escaping switch( c ) { case "\\": return "\\\\"; case "\"": return "\\\""; case "'": return "\\'"; case "\b": return "\\b"; case "\f": return "\\f"; case "\n": return "\\n"; case "\r": return "\\r"; case "\t": return "\\t"; } // return raw bytes now ... should be UTF-8 if( c >= " " ) return c; // try \uXXXX escaping, but shouldn't make it for case 1, 2 c = c.charCodeAt( 0 ).toString( 16 ); switch( c.length ) { case 1: return "\\u000" + c; case 2: return "\\u00" + c; case 3: return "\\u0" + c; case 4: return "\\u" + c; } // should never make it here return ""; }, encodeEntity: function( c ) { switch( c ) { case "<": return "<"; case ">": return ">"; case "&": return "&"; case '"': return """; case "'": return "'"; } return c; }, decodeEntity: function( c ) { switch( c ) { case "amp": return "&"; case "quot": return '"'; case "gt": return ">"; case "lt": return "<"; } var m = c.match( /^#(\d+)$/ ); if( m && defined( m[ 1 ] ) ) return String.fromCharCode( m[ 1 ] ); m = c.match( /^#x([0-9a-f]+)$/i ); if( m && defined( m[ 1 ] ) ) return String.fromCharCode( parseInt( hex, m[ 1 ] ) ); return c; } } ); String.prototype.extend( { escapeJS: function() { return this.replace( /([^ -!#-\[\]-~])/g, function( m, c ) { return String.escapeJSChar( c ); } ) }, escapeJS2: function() { return this.replace( /([\u0000-\u0031'"\\])/g, function( m, c ) { return String.escapeJSChar( c ); } ) }, escapeJS3: function() { return this.replace( /[\u0000-\u0031'"\\]/g, function( m ) { return String.escapeJSChar( m ); } ) }, escapeJS4: function() { return this.replace( /./g, function( m ) { return String.escapeJSChar( m ); } ) }, encodeHTML: function() { return this.replace( /([<>&"])/g, function( m, c ) { return String.encodeEntity( c ) } ); }, decodeHTML: function() { return this.replace( /&(.*?);/g, function( m, c ) { return String.decodeEntity( c ) } ); }, cssToJS: function() { return this.replace( /-([a-z])/g, function( m, c ) { return c.toUpperCase() } ); }, jsToCSS: function() { return this.replace( /([A-Z])/g, function( m, c ) { return "-" + c.toLowerCase() } ); }, firstToLowerCase: function() { return this.replace( /^(.)/, function( m, c ) { return c.toLowerCase() } ); }, rgbToHex: function() { var c = this.match( /(\d+)\D+(\d+)\D+(\d+)/ ); if( !c ) return undefined; return "#" + finiteInt( c[ 1 ] ).toString( 16 ).pad( 2, "0" ) + finiteInt( c[ 2 ] ).toString( 16 ).pad( 2, "0" ) + finiteInt( c[ 3 ] ).toString( 16 ).pad( 2, "0" ); }, pad: function( length, padChar ) { var padding = length - this.length; if( padding <= 0 ) return this; if( !defined( padChar ) ) padChar = " "; var out = []; for( var i = 0; i < padding; i++ ) out.push( padChar ); out.push( this ); return out.join( "" ); }, trim: function() { return this.replace( /^\s+|\s+$/g, "" ); } } ); /* extend array object */ Array.extend( { fromPseudo: function ( args ) { var out = []; for ( var i = 0; i < args.length; i++ ) out.push( args[ i ] ); return out; } }); /* extend array object */ Array.prototype.extend( { copy: function() { var out = []; for( var i = 0; i < this.length; i++ ) out[ i ] = this[ i ]; return out; }, first: function( callback, object ) { var length = this.length; for( var i = 0; i < length; i++ ) { var result = object ? callback.call( object, this[ i ], i, this ) : callback( this[ i ], i, this ); if( result ) return this[ i ]; } return null; }, fitIndex: function( fromIndex, defaultIndex ) { if( !defined( fromIndex ) || fromIndex == null ) fromIndex = defaultIndex; else if( fromIndex < 0 ) { fromIndex = this.length + fromIndex; if( fromIndex < 0 ) fromIndex = 0; } else if( fromIndex >= this.length ) fromIndex = this.length - 1; return fromIndex; }, scramble: function() { for( var i = 0; i < this.length; i++ ) { var j = Math.floor( Math.random() * this.length ); var temp = this[ i ]; this[ i ] = this[ j ]; this[ j ] = temp; } }, add: function() { var a = arguments; for( var i = 0; i < a.length; i++ ) { var index = this.indexOf( a[ i ] ); if( index < 0 ) this.push( arguments[ i ] ); } return this.length; }, remove: function() { var a = arguments; for( var i = 0; i < a.length; i++ ) { var j = this.indexOf( a[ i ] ); if( j >= 0 ) this.splice( j, 1 ); } return this.length; }, /* javascript 1.5 array methods */ /* http://developer-test.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array#Methods */ every: function( callback, object ) { var length = this.length; for( var i = 0; i < length; i++ ) { var result = object ? callback.call( object, this[ i ], i, this ) : callback( this[ i ], i, this ); if( !result ) return false; } return true; }, filter: function( callback, object ) { var out = []; var length = this.length; for( var i = 0; i < length; i++ ) { var result = object ? callback.call( object, this[ i ], i, this ) : callback( this[ i ], i, this ); if( result ) out.push( this[ i ] ); } return out; }, forEach: function( callback, object ) { var length = this.length; for( var i = 0; i < length; i++ ) { object ? callback.call( object, this[ i ], i, this ) : callback( this[ i ], i, this ); } }, indexOf: function( value, fromIndex ) { fromIndex = this.fitIndex( fromIndex, 0 ); for( var i = 0; i < this.length; i++ ) { if( this[ i ] === value ) return i; } return -1; }, lastIndexOf: function( value, fromIndex ) { fromIndex = this.fitIndex( fromIndex, this.length - 1 ); for( var i = fromIndex; i >= 0; i-- ) { if( this[ i ] == value ) return i; } return -1; }, some: function( callback, object ) { var length = this.length; for( var i = 0; i < length; i++ ) { var result = object ? callback.call( object, this[ i ], i, this ) : callback( this[ i ], i, this ); if( result ) return true; } return false; }, /* javascript 1.2 array methods */ concat: function() { var a = arguments; var out = this.copy(); for( i = 0; i < a.length; i++ ) { var b = a[ i ]; for( j = 0; j < b.length; j++ ) out.push( b[ j ] ); } return out; }, push: function() { var a = arguments; for( var i = 0; i < a.length; i++ ) this[ this.length ] = a[ i ]; return this.length; }, pop: function() { if( this.length == 0 ) return undefined; var out = this[ this.length - 1 ]; this.length--; return out; }, unshift: function() { var a = arguments; for( var i = 0; i < a.length; i++ ) { this[ i + a.length ] = this[ i ]; this[ i ] = a[ i ]; } return this.length; }, shift: function() { if( this.length == 0 ) return undefined; var out = this[ 0 ]; for( var i = 1; i < this.length; i++ ) this[ i - 1 ] = this[ i ]; this.length--; return out; } } ); /* date extensions */ Date.extend( { /* iso 8601 date format parser this was fun to write... thanks to: http://www.cl.cam.ac.uk/~mgk25/iso-time.html */ matchISOString: new RegExp( "^([0-9]{4})" + // year "(?:-(?=0[1-9]|1[0-2])|$)(..)?" + // month "(?:-(?=0[1-9]|[12][0-9]|3[01])|$)([0-9]{2})?" + // day of the month "(?:T(?=[01][0-9]|2[0-4])|$)T?([0-9]{2})?" + // hours "(?::(?=[0-5][0-9])|\\+|-|Z|$)([0-9]{2})?" + // minutes "(?::(?=[0-5][0-9]|60$|60[+|-|Z]|60.0+)|\\+|-|Z|$):?([0-9]{2})?" + // seconds "(\.[0-9]+)?" + // fractional seconds "(Z|\\+[01][0-9]|\\+2[0-4]|-[01][0-9]|-2[0-4])?" + // timezone hours ":?([0-5][0-9]|60)?$" // timezone minutes ), fromISOString: function( string ) { var t = this.matchISOString.exec( string ); if( !t ) return undefined; var year = finiteInt( t[ 1 ], 10 ); var month = finiteInt( t[ 2 ], 10 ) - 1; var day = finiteInt( t[ 3 ], 10 ); var hours = finiteInt( t[ 4 ], 10 ); var minutes = finiteInt( t[ 5 ], 10 ); var seconds = finiteInt( t[ 6 ], 10 ); var milliseconds = finiteInt( Math.round( parseFloat( t[ 7 ] ) * 1000 ) ); var tzHours = finiteInt( t[ 8 ], 10 ); var tzMinutes = finiteInt( t[ 9 ], 10 ); var date = new this( 0 ); if( defined( t[ 8 ] ) ) { date.setUTCFullYear( year, month, day ); date.setUTCHours( hours, minutes, seconds, milliseconds ); var offset = (tzHours * 60 + tzMinutes) * 60000; if( offset ) date = new this( date - offset ); } else { date.setFullYear( year, month, day ); date.setHours( hours, minutes, seconds, milliseconds ); } return date; } } ); Date.prototype.extend( { getISOTimezoneOffset: function() { var offset = -this.getTimezoneOffset(); var negative = false; if( offset < 0 ) { negative = true; offset *= -1; } var offsetHours = Math.floor( offset / 60 ).toString().pad( 2, "0" ); var offsetMinutes = Math.floor( offset % 60 ).toString().pad( 2, "0" ); return (negative ? "-" : "+") + offsetHours + ":" + offsetMinutes; }, toISODateString: function() { var year = this.getFullYear(); var month = (this.getMonth() + 1).toString().pad( 2, "0" ); var day = this.getDate().toString().pad( 2, "0" ); return year + "-" + month + "-" + day; }, toUTCISODateString: function() { var year = this.getUTCFullYear(); var month = (this.getUTCMonth() + 1).toString().pad( 2, "0" ); var day = this.getUTCDate().toString().pad( 2, "0" ); return year + "-" + month + "-" + day; }, toISOTimeString: function() { var hours = this.getHours().toString().pad( 2, "0" ); var minutes = this.getMinutes().toString().pad( 2, "0" ); var seconds = this.getSeconds().toString().pad( 2, "0" ); var milliseconds = this.getMilliseconds().toString().pad( 3, "0" ); var timezone = this.getISOTimezoneOffset(); return hours + ":" + minutes + ":" + seconds + "." + milliseconds + timezone; }, toUTCISOTimeString: function() { var hours = this.getUTCHours().toString().pad( 2, "0" ); var minutes = this.getUTCMinutes().toString().pad( 2, "0" ); var seconds = this.getUTCSeconds().toString().pad( 2, "0" ); var milliseconds = this.getUTCMilliseconds().toString().pad( 3, "0" ); return hours + ":" + minutes + ":" + seconds + "." + milliseconds + "Z"; }, toISOString: function() { return this.toISODateString() + "T" + this.toISOTimeString(); }, toUTCISOString: function() { return this.toUTCISODateString() + "T" + this.toUTCISOTimeString(); } } ); /* ajax */ if( !defined( window.XMLHttpRequest ) ) { window.XMLHttpRequest = function() { var types = [ "Microsoft.XMLHTTP", "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP" ]; for( var i = 0; i < types.length; i++ ) { try { return new ActiveXObject( types[ i ] ); } catch( e ) {} } return undefined; } } /* DOM Library - Copyright 2005 Six Apart $Id: dom.js 261 2008-02-26 23:40:50Z janine $ Copyright (c) 2005, Six Apart, Ltd. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of "Six Apart" nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Node class */ if( !defined( window.Node ) ) Node = {}; try { Node.extend( { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_FRAGMENT_NODE: 11 } ); } catch( e ) {} /* DOM class */ if( !defined( window.DOM ) ) DOM = {}; DOM.extend( { getElement: function( e ) { return (typeof e == "string" || typeof e == "number") ? document.getElementById( e ) : e; }, addEventListener: function( e, eventName, func, useCapture ) { try { if( e.addEventListener ) e.addEventListener( eventName, func, useCapture ); else if( e.attachEvent ) e.attachEvent( "on" + eventName, func ); else e[ "on" + eventName ] = func; } catch( e ) {} }, removeEventListener: function( e, eventName, func, useCapture ) { try { if( e.removeEventListener ) e.removeEventListener( eventName, func, useCapture ); else if( e.detachEvent ) e.detachEvent( "on" + eventName, func ); else e[ "on" + eventName ] = undefined; } catch( e ) {} }, focus: function( e ) { try { e = DOM.getElement( e ); e.focus(); } catch( e ) {} }, blur: function( e ) { try { e = DOM.getElement( e ); e.blur(); } catch( e ) {} }, /* style */ getComputedStyle: function( e ) { if( e.currentStyle ) return e.currentStyle; var style = {}; var owner = DOM.getOwnerDocument( e ); if( owner && owner.defaultView && owner.defaultView.getComputedStyle ) { try { style = owner.defaultView.getComputedStyle( e, null ); } catch( e ) {} } return style; }, getStyle: function( e, p ) { var s = DOM.getComputedStyle( e ); return s[ p ]; }, // given a window (or defaulting to current window), returns // object with .x and .y of client's usable area getClientDimensions: function( w ) { if( !w ) w = window; var d = {}; // most browsers if( w.innerHeight ) { d.x = w.innerWidth; d.y = w.innerHeight; return d; } // IE6, strict var de = w.document.documentElement; if( de && de.clientHeight ) { d.x = de.clientWidth; d.y = de.clientHeight; return d; } // IE, misc if( document.body ) { d.x = document.body.clientWidth; d.y = document.body.clientHeight; return d; } return undefined; }, getDimensions: function( e ) { if( !e ) return undefined; var style = DOM.getComputedStyle( e ); return { offsetLeft: e.offsetLeft, offsetTop: e.offsetTop, offsetWidth: e.offsetWidth, offsetHeight: e.offsetHeight, clientWidth: e.clientWidth, clientHeight: e.clientHeight, offsetRight: e.offsetLeft + e.offsetWidth, offsetBottom: e.offsetTop + e.offsetHeight, clientLeft: finiteInt( style.borderLeftWidth ) + finiteInt( style.paddingLeft ), clientTop: finiteInt( style.borderTopWidth ) + finiteInt( style.paddingTop ), clientRight: e.clientLeft + e.clientWidth, clientBottom: e.clientTop + e.clientHeight }; }, getAbsoluteDimensions: function( e ) { var d = DOM.getDimensions( e ); if( !d ) return d; d.absoluteLeft = d.offsetLeft; d.absoluteTop = d.offsetTop; d.absoluteRight = d.offsetRight; d.absoluteBottom = d.offsetBottom; var bork = 0; while( e ) { try { // IE 6 sometimes gives an unwarranted error ("htmlfile: Unspecified error"). e = e.offsetParent; } catch ( err ) { log( "In DOM.getAbsoluteDimensions: " + err.message ); if ( ++bork > 25 ) return null; } if( !e ) return d; d.absoluteLeft += e.offsetLeft; d.absoluteTop += e.offsetTop; d.absoluteRight += e.offsetLeft; d.absoluteBottom += e.offsetTop; } return d; }, getIframeAbsoluteDimensions: function( e ) { var d = DOM.getAbsoluteDimensions( e ); if( !d ) return d; var iframe = DOM.getOwnerIframe( e ); if( !defined( iframe ) ) return d; var d2 = DOM.getIframeAbsoluteDimensions( iframe ); var scroll = DOM.getWindowScroll( iframe.contentWindow ); var left = d2.absoluteLeft - scroll.left; var top = d2.absoluteTop - scroll.top; d.absoluteLeft += left; d.absoluteTop += top; d.absoluteRight += left; d.absoluteBottom += top; return d; }, setLeft: function( e, v ) { e.style.left = finiteInt( v ) + "px"; }, setTop: function( e, v ) { e.style.top = finiteInt( v ) + "px"; }, setRight: function( e, v ) { e.style.right = finiteInt( v ) + "px"; }, setBottom: function( e, v ) { e.style.bottom = finiteInt( v ) + "px"; }, setWidth: function( e, v ) { e.style.width = max( 0, finiteInt( v ) ) + "px"; }, setHeight: function( e, v ) { e.style.height = max( 0, finiteInt( v ) ) + "px"; }, setZIndex: function( e, v ) { e.style.zIndex = finiteInt( v ); }, getWindowScroll: function( w ) { var s = { left: 0, top: 0 }; if (!w) w = window; var d = w.document; var de = d.documentElement; // most browsers if ( defined( w.pageXOffset ) ) { s.left = w.pageXOffset; s.top = w.pageYOffset; } // ie else if( de && defined( de.scrollLeft ) ) { s.left = de.scrollLeft; s.top = de.scrollTop; } // safari else if( defined( w.scrollX ) ) { s.left = w.scrollX; s.top = w.scrollY; } // opera else if( d.body && defined( d.body.scrollLeft ) ) { s.left = d.body.scrollLeft; s.top = d.body.scrollTop; } return s; }, getAbsoluteCursorPosition: function( event ) { event = event || window.event; var s = DOM.getWindowScroll( window ); return { x: s.left + event.clientX, y: s.top + event.clientY }; }, invisibleStyle: { display: "block", position: "absolute", left: 0, top: 0, width: 0, height: 0, margin: 0, border: 0, padding: 0, fontSize: "0.1px", lineHeight: 0, opacity: 0, MozOpacity: 0, filter: "alpha(opacity=0)" }, makeInvisible: function( e ) { for( var p in this.invisibleStyle ) { if( this.invisibleStyle.hasOwnProperty( p ) ) e.style[ p ] = this.invisibleStyle[ p ]; } }, /* text and selection related methods */ mergeTextNodes: function( n ) { var c = 0; while( n ) { if( n.nodeType == Node.TEXT_NODE && n.nextSibling && n.nextSibling.nodeType == Node.TEXT_NODE ) { n.nodeValue += n.nextSibling.nodeValue; n.parentNode.removeChild( n.nextSibling ); c++; } else { if( n.firstChild ) c += DOM.mergeTextNodes( n.firstChild ); n = n.nextSibling; } } return c; }, selectElement: function( e ) { var d = e.ownerDocument; // internet explorer if( d.body.createControlRange ) { var r = d.body.createControlRange(); r.addElement( e ); r.select(); } }, /* dom methods */ isImmutable: function( n ) { try { if( n.getAttribute( "contenteditable" ) == "false" ) return true; } catch( e ) {} return false; }, getImmutable: function( n ) { var immutable = null; while( n ) { if( DOM.isImmutable( n ) ) immutable = n; n = n.parentNode; } return immutable; }, getOwnerDocument: function( n ) { if( !n ) return document; if( n.ownerDocument ) return n.ownerDocument; if( n.getElementById ) return n; return document; }, getOwnerWindow: function( n ) { if( !n ) return window; if( n.parentWindow ) return n.parentWindow; var doc = DOM.getOwnerDocument( n ); if( doc && doc.defaultView ) return doc.defaultView; return window; }, getOwnerIframe: function( n ) { if( !n ) return undefined; var nw = DOM.getOwnerWindow( n ); var nd = DOM.getOwnerDocument( n ); var pw = nw.parent || nw.parentWindow; if( !pw ) return undefined; var parentDocument = pw.document; var es = parentDocument.getElementsByTagName( "iframe" ); for( var i = 0; i < es.length; i++ ) { var e = es[ i ]; try { var d = e.contentDocument || e.contentWindow.document; if( d === nd ) return e; }catch(err) {}; } return undefined; }, filterElementsByClassName: function( es, className ) { var filtered = []; for( var i = 0; i < es.length; i++ ) { var e = es[ i ]; if( DOM.hasClassName( e, className ) ) filtered[ filtered.length ] = e; } return filtered; }, filterElementsByAttribute: function( es, attr ) { if( !es ) return []; if( !defined( attr ) || attr == null || attr == "" ) return es; var filtered = []; for( var i = 0; i < es.length; i++ ) { var element = es[ i ]; if( !element ) continue; if( element.getAttribute && ( element.getAttribute( attr ) ) ) filtered[ filtered.length ] = element; } return filtered; }, filterElementsByTagName: function( es, tagName ) { if( tagName == "*" ) return es; var filtered = []; tagName = tagName.toLowerCase(); for( var i = 0; i < es.length; i++ ) { var e = es[ i ]; if( e.tagName && e.tagName.toLowerCase() == tagName ) filtered[ filtered.length ] = e; } return filtered; }, getElementsByTagAndAttribute: function( root, tagName, attr ) { if( !root ) root = document; var es = root.getElementsByTagName( tagName ); return DOM.filterElementsByAttribute( es, attr ); }, getElementsByAttribute: function( root, attr ) { return DOM.getElementsByTagAndAttribute( root, "*", attr ); }, getElementsByAttributeAndValue: function( root, attr, value ) { var es = DOM.getElementsByTagAndAttribute( root, "*", attr ); var filtered = []; for ( var i = 0; i < es.length; i++ ) if ( es[ i ].getAttribute( attr ) == value ) filtered.push( es[ i ] ); return filtered; }, getElementsByTagAndClassName: function( root, tagName, className ) { if( !root ) root = document; var elements = root.getElementsByTagName( tagName ); return DOM.filterElementsByClassName( elements, className ); }, getElementsByClassName: function( root, className ) { return DOM.getElementsByTagAndClassName( root, "*", className ); }, getAncestors: function( n, includeSelf ) { if( !n ) return []; var as = includeSelf ? [ n ] : []; n = n.parentNode; while( n ) { as.push( n ); n = n.parentNode; } return as; }, getAncestorsByTagName: function( n, tagName, includeSelf ) { var es = DOM.getAncestors( n, includeSelf ); return DOM.filterElementsByTagName( es, tagName ); }, getFirstAncestorByTagName: function( n, tagName, includeSelf ) { return DOM.getAncestorsByTagName( n, tagName, includeSelf )[ 0 ]; }, getAncestorsByClassName: function( n, className, includeSelf ) { var es = DOM.getAncestors( n, includeSelf ); return DOM.filterElementsByClassName( es, className ); }, getFirstAncestorByClassName: function( n, className, includeSelf ) { return DOM.getAncestorsByClassName( n, className, includeSelf )[ 0 ]; }, getAncestorsByTagAndClassName: function( n, tagName, className, includeSelf ) { var es = DOM.getAncestorsByTagName( n, tagName, includeSelf ); return DOM.filterElementsByClassName( es, className ); }, getFirstAncestorByTagAndClassName: function( n, tagName, className, includeSelf ) { return DOM.getAncestorsByTagAndClassName( n, tagName, className, includeSelf )[ 0 ]; }, getPreviousElement: function( n ) { n = n.previousSibling; while( n ) { if( n.nodeType == Node.ELEMENT_NODE ) return n; n = n.previousSibling; } return null; }, getNextElement: function( n ) { n = n.nextSibling; while( n ) { if( n.nodeType == Node.ELEMENT_NODE ) return n; n = n.nextSibling; } return null; }, isInlineNode: function( n ) { // text nodes are inline if( n.nodeType == Node.TEXT_NODE ) return n; // document nodes are non-inline if( n.nodeType == Node.DOCUMENT_NODE ) return false; // all nonelement nodes are inline if( n.nodeType != Node.ELEMENT_NODE ) return n; // br elements are not inline if( n.tagName && n.tagName.toLowerCase() == "br" ) return false; // examine the style property of the inline n var display = DOM.getStyle( n, "display" ); if( display && display.indexOf( "inline" ) >= 0 ) return n; }, isTextNode: function( n ) { if( n.nodeType == Node.TEXT_NODE ) return n; }, isInlineTextNode: function( n ) { if( n.nodeType == Node.TEXT_NODE ) return n; if( !DOM.isInlineNode( n ) ) return null; }, /* this and the following classname functions honor w3c case-sensitive classnames */ getClassNames: function( e ) { if( !e || !e.className ) return []; return e.className.split( /\s+/g ); }, hasClassName: function( e, className ) { if( !e || !e.className ) return false; var cs = DOM.getClassNames( e ); for( var i = 0; i < cs.length; i++ ) { if( cs[ i ] == className ) return true; } return false; }, addClassName: function( e, className ) { if( !e || !className ) return false; var cs = DOM.getClassNames( e ); for( var i = 0; i < cs.length; i++ ) { if( cs[ i ] == className ) return true; } cs.push( className ); e.className = cs.join( " " ); return false; }, removeClassName: function( e, className ) { var r = false; if( !e || !e.className || !className ) return r; var cs = (e.className && e.className.length) ? e.className.split( /\s+/g ) : []; var ncs = []; for( var i = 0; i < cs.length; i++ ) { if( cs[ i ] == className ) { r = true; continue; } ncs.push( cs[ i ] ); } if( r ) e.className = ncs.join( " " ); return r; }, /* tree manipulation methods */ replaceWithChildNodes: function( n ) { var firstChild = n.firstChild; var parentNode = n.parentNode; while( n.firstChild ) parentNode.insertBefore( n.removeChild( n.firstChild ), n ); parentNode.removeChild( n ); return firstChild; }, /* factory methods */ createInvisibleInput: function( d ) { if( !d ) d = window.document; var e = document.createElement( "input" ); e.setAttribute( "autocomplete", "off" ); e.autocomplete = "off"; DOM.makeInvisible( e ); return e; }, getMouseEventAttribute: function( event, a ) { if( !a ) return; var es = DOM.getAncestors( event.target, true ); for( var i = 0; i < es.length; i++ ) { try { var e = es[ i ] var v = e.getAttribute ? e.getAttribute( a ) : null; if( v ) { event.attributeElement = e; event.attribute = v; return v; } } catch( e ) {} } }, setElementAttribute: function( e, a, v ) { /* safari workaround * safari's setAttribute assumes you want to use a namespace * when you have a colon in your attribute */ if ( navigator.userAgent.toLowerCase().match(/webkit/) ) { var at = e.attributes; for ( var i = 0; i < at.length; i++ ) if ( at[ i ].name == a ) return at[ i ].nodeValue = v; } else e.setAttribute( a, v ); }, swapAttributes: function( e, tg, at ) { var ar = e.getAttribute( tg ); if( !ar ) return false; /* clone the node with all children */ if ( e.tagName.toLowerCase() == 'script' ) { /* only clone and replace script tags */ var cl = e.cloneNode( true ); if ( !cl ) return false; DOM.setElementAttribute( cl, at, ar ); cl.removeAttribute( tg ); /* replace new, old */ return e.parentNode.replaceChild( cl, e ); } else { DOM.setElementAttribute( e, at, ar ); e.removeAttribute( tg ); } }, findPosX: function( e ) { var curleft = 0; if (e.offsetParent) { while (1) { curleft += e.offsetLeft; if (!e.offsetParent) { break; } e = e.offsetParent; } } else if (e.x) { curleft += e.x; } return curleft; }, findPosY: function( e ) { var curtop = 0; if (e.offsetParent) { while (1) { curtop += e.offsetTop; if (!e.offsetParent) { break; } e = e.offsetParent; } } else if (e.y) { curtop += e.y; } return curtop; } } ); $ = DOM.getElement; var HTTPReq = new Object; HTTPReq.create = function () { var xtr; var ex; if (typeof(XMLHttpRequest) != "undefined") { xtr = new XMLHttpRequest(); } else { try { xtr = new ActiveXObject("Msxml2.XMLHTTP.4.0"); } catch (ex) { try { xtr = new ActiveXObject("Msxml2.XMLHTTP"); } catch (ex) { } } } // let me explain this. Opera 8 does XMLHttpRequest, but not setRequestHeader. // no problem, we thought: we'll test for setRequestHeader and if it's not present // then fall back to the old behavior (treat it as not working). BUT --- IE6 won't // let you even test for setRequestHeader without throwing an exception (you need // to call .open on the .xtr first or something) try { if (xtr && ! xtr.setRequestHeader) xtr = null; } catch (ex) { } return xtr; }; // opts: // url, onError, onData, method (GET or POST), data // url: where to get/post to // onError: callback on error // onData: callback on data received // method: HTTP method, GET by default // data: what to send to the server (urlencoded) HTTPReq.getJSON = function (opts) { var req = HTTPReq.create(); if (! req) { if (opts.onError) opts.onError("noxmlhttprequest"); return; } var state_callback = function () { if (req.readyState != 4) return; if (req.status != 200) { if (opts.onError) opts.onError(req.status ? "status: " + req.status : "no data"); return; } var resObj; var e; try { eval("resObj = " + req.responseText + ";"); } catch (e) { } if (e || ! resObj) { if (opts.onError) opts.onError("Error parsing response: \"" + req.responseText + "\""); return; } if (opts.onData) opts.onData(resObj); }; req.onreadystatechange = state_callback; var method = opts.method || "GET"; var data = opts.data || null; var url = opts.url; if (opts.method == "GET" && opts.data) { url += url.match(/\?/) ? "&" : "?"; url += opts.data } url += url.match(/\?/) ? "&" : "?"; url += "_rand=" + Math.random(); req.open(method, url, true); // we should send null unless we're in a POST var to_send = null; if (method.toUpperCase() == "POST") { req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); to_send = data; } req.send(to_send); }; HTTPReq.formEncoded = function (vars) { var enc = []; var e; for (var key in vars) { try { if (!vars.hasOwnProperty(key)) continue; enc.push(encodeURIComponent(key) + "=" + encodeURIComponent(vars[key])); } catch( e ) {} } return enc.join("&"); }; // This file contains general-purpose LJ code var LiveJournal = new Object; // The hook mappings LiveJournal.hooks = {}; LiveJournal.register_hook = function (hook, func) { if (! LiveJournal.hooks[hook]) LiveJournal.hooks[hook] = []; LiveJournal.hooks[hook].push(func); }; // args: hook, params to pass to hook LiveJournal.run_hook = function () { var a = arguments; var hookfuncs = LiveJournal.hooks[a[0]]; if (!hookfuncs || !hookfuncs.length) return; var hookargs = []; for (var i = 1; i < a.length; i++) { hookargs.push(a[i]); } var rv = null; hookfuncs.forEach(function (hookfunc) { rv = hookfunc.apply(null, hookargs); }); return rv; }; LiveJournal.pageLoaded = false; LiveJournal.initPage = function () { // only run once if (LiveJournal.pageLoaded) return; LiveJournal.pageLoaded = 1; // set up various handlers for every page LiveJournal.initPlaceholders(); LiveJournal.initLabels(); LiveJournal.initInboxUpdate(); LiveJournal.initPolls(); // run other hooks LiveJournal.run_hook("page_load"); }; // Set up two different ways to test if the page is loaded yet. // The proper way is using DOMContentLoaded, but only Mozilla supports it. { // Others DOM.addEventListener(window, "load", LiveJournal.initPage); // Mozilla DOM.addEventListener(window, "DOMContentLoaded", LiveJournal.initPage); } // Set up a timer to keep the inbox count updated LiveJournal.initInboxUpdate = function () { // Don't run if not logged in or this is disabled if (! Site || ! Site.has_remote || ! Site.inbox_update_poll) return; // Don't run if no inbox count var unread = $("Inbox_Unread_Count"); var unread_menu = $("Inbox_Unread_Count_Menu"); if (! unread && ! unread_menu) return; // Update every five minutes window.setInterval(LiveJournal.updateInbox, 1000 * 60 * 5); }; // Do AJAX request to find the number of unread items in the inbox LiveJournal.updateInbox = function () { var postData = { "action": "get_unread_items" }; var opts = { "data": HTTPReq.formEncoded(postData), "method": "POST", "onData": LiveJournal.gotInboxUpdate }; opts.url = Site.currentJournal ? "/" + Site.currentJournal + "/__rpc_esn_inbox" : "/__rpc_esn_inbox"; HTTPReq.getJSON(opts); }; // We received the number of unread inbox items from the server LiveJournal.gotInboxUpdate = function (resp) { if (! resp || resp.error) return; var unread = $("Inbox_Unread_Count"); var unread_menu = $("Inbox_Unread_Count_Menu"); if (! unread && ! unread_menu) return; var unread_count = resp.unread_count? " (" + resp.unread_count + ")" : ""; if ( unread ) unread.innerHTML = unread_count; if ( unread_menu ) unread_menu.innerHTML = unread_count; }; // Search for placeholders and initialize them LiveJournal.initPlaceholders = function () { var placeholders = DOM.getElementsByTagAndClassName(document, "img", "LJ_Placeholder") || []; Array.prototype.forEach.call(placeholders, function (placeholder) { var parent = DOM.getFirstAncestorByClassName(placeholder, "LJ_Placeholder_Container", false); if (!parent) return; var container = DOM.filterElementsByClassName(parent.getElementsByTagName("div"), "LJ_Container")[0]; if (!container) return; var html = DOM.filterElementsByClassName(parent.getElementsByTagName("div"), "LJ_Placeholder_HTML")[0]; if (!html) return; var placeholder_html = unescape(html.innerHTML); var placeholderClickHandler = function (e) { Event.stop(e); // have to wrap placeholder_html in another block, IE is weird container.innerHTML = "" + placeholder_html + ""; DOM.makeInvisible(placeholder); }; DOM.addEventListener(placeholder, "click", placeholderClickHandler); return false; }); }; // set up labels for Safari LiveJournal.initLabels = function () { // disabled because new webkit has labels that work return; // safari doesn't know what