// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // // See scriptaculous.js for full license. Object.debug = function(obj) { var info = []; if(typeof obj in ["string","number"]) { return obj; } else { for(property in obj) if(typeof obj[property]!="function") info.push(property + ' => ' + (typeof obj[property] == "string" ? '"' + obj[property] + '"' : obj[property])); } return ("'" + obj + "' #" + typeof obj + ": {" + info.join(", ") + "}"); } String.prototype.toArray = function() { var results = []; for (var i = 0; i < this.length; i++) results.push(this.charAt(i)); return results; } /*--------------------------------------------------------------------------*/ var Builder = { NODEMAP: { AREA: 'map', CAPTION: 'table', COL: 'table', COLGROUP: 'table', LEGEND: 'fieldset', OPTGROUP: 'select', OPTION: 'select', PARAM: 'object', TBODY: 'table', TD: 'table', TFOOT: 'table', TH: 'table', THEAD: 'table', TR: 'table' }, // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken, // due to a Firefox bug node: function(elementName) { elementName = elementName.toUpperCase(); // try innerHTML approach var parentTag = this.NODEMAP[elementName] || 'div'; var parentElement = document.createElement(parentTag); parentElement.innerHTML = "<" + elementName + ">"; var element = parentElement.firstChild || null; // see if browser added wrapping tags if(element && (element.tagName != elementName)) element = element.getElementsByTagName(elementName)[0]; // fallback to createElement approach if(!element) element = document.createElement(elementName); // abort if nothing could be created if(!element) return; // attributes (or text) if(arguments[1]) if(this._isStringOrNumber(arguments[1]) || (arguments[1] instanceof Array)) { this._children(element, arguments[1]); } else { var attrs = this._attributes(arguments[1]); if(attrs.length) { parentElement.innerHTML = "<" +elementName + " " + attrs + ">"; element = parentElement.firstChild || null; // workaround firefox 1.0.X bug if(!element) { element = document.createElement(elementName); for(attr in arguments[1]) element[attr == 'class' ? 'className' : attr] = arguments[1][attr]; } if(element.tagName != elementName) element = parentElement.getElementsByTagName(elementName)[0]; } } // text, or array of children if(arguments[2]) this._children(element, arguments[2]); return element; }, _text: function(text) { return document.createTextNode(text); }, _attributes: function(attributes) { var attrs = []; for(attribute in attributes) attrs.push((attribute=='className' ? 'class' : attribute) + '="' + attributes[attribute].toString().escapeHTML() + '"'); return attrs.join(" "); }, _children: function(element, children) { if(typeof children=='object') { // array can hold nodes and text children.flatten().each( function(e) { if(typeof e=='object') element.appendChild(e) else if(Builder._isStringOrNumber(e)) element.appendChild(Builder._text(e)); }); } else if(Builder._isStringOrNumber(children)) element.appendChild(Builder._text(children)); }, _isStringOrNumber: function(param) { return(typeof param=='string' || typeof param=='number'); } } /* ------------- element ext -------------- */ // adapted from http://dhtmlkitchen.com/learn/js/setstyle/index4.jsp // note: Safari return null on elements with display:none; see http://bugzilla.opendarwin.org/show_bug.cgi?id=4125 // instead of "auto" values returns null so it's easier to use with || constructs String.prototype.camelize = function() { var oStringList = this.split('-'); if(oStringList.length == 1) return oStringList[0]; var ret = this.indexOf("-") == 0 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0]; for(var i = 1, len = oStringList.length; i < len; i++){ var s = oStringList[i]; ret += s.charAt(0).toUpperCase() + s.substring(1) } return ret; } Element.getStyle = function(element, style) { element = $(element); var value = element.style[style.camelize()]; if(!value) if(document.defaultView && document.defaultView.getComputedStyle) { var css = document.defaultView.getComputedStyle(element, null); value = (css!=null) ? css.getPropertyValue(style) : null; } else if(element.currentStyle) { value = element.currentStyle[style.camelize()]; } // If top, left, bottom, or right values have been queried, return "auto" for consistency resaons // if position is "static", as Opera (and others?) returns the pixel values relative to root element // (or positioning context?) if (window.opera && (style == "left" || style == "top" || style == "right" || style == "bottom")) if (Element.getStyle(element, "position") == "static") value = "auto"; if(value=='auto') value = null; return value; } // converts rgb() and #xxx to #xxxxxx format, // returns self (or first argument) if not convertable String.prototype.parseColor = function() { color = "#"; if(this.slice(0,4) == "rgb(") { var cols = this.slice(4,this.length-1).split(','); var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); } else { if(this.slice(0,1) == '#') { if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); if(this.length==7) color = this.toLowerCase(); } } return(color.length==7 ? color : (arguments[0] || this)); } Element.makePositioned = function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if(pos =='static' || !pos) { element._madePositioned = true; element.style.position = "relative"; // Opera returns the offset relative to the positioning context, when an element is position relative // but top and left have not been defined if (window.opera){ element.style.top = 0; element.style.left = 0; } } } Element.undoPositioned = function(element) { element = $(element); if(typeof element._madePositioned != "undefined"){ element._madePositioned = undefined; element.style.position = ""; element.style.top = ""; element.style.left = ""; element.style.bottom = ""; element.style.right = ""; } } Element.makeClipping = function(element) { element = $(element); if (typeof element._overflow != 'undefined') return; element._overflow = element.style.overflow; if((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden'; } Element.undoClipping = function(element) { element = $(element); if (typeof element._overflow == 'undefined') return; element.style.overflow = element._overflow; element._overflow = undefined; } Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { var children = $(element).childNodes; var text = ""; var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); for (var i = 0; i < children.length; i++) { if(children[i].nodeType==3) { text+=children[i].nodeValue; } else { if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); } } return text; } Element.setContentZoom = function(element, percent) { element = $(element); element.style.fontSize = (percent/100) + "em"; if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); } Element.getOpacity = function(element){ var opacity; if (opacity = Element.getStyle(element, "opacity")) return parseFloat(opacity); if (opacity = (Element.getStyle(element, "filter") || '').match(/alpha\(opacity=(.*)\)/)) if(opacity[1]) return parseFloat(opacity[1]) / 100; return 1.0; } Element.setOpacity = function(element, value){ element= $(element); var els = element.style; if (value == 1){ els.opacity = '0.999999'; if(/MSIE/.test(navigator.userAgent)) els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,''); } else { if(value < 0.00001) value = 0; els.opacity = value; if(/MSIE/.test(navigator.userAgent)) els.filter = Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + "alpha(opacity="+value*100+")"; } } Element.getInlineOpacity = function(element){ element= $(element); var op; op = element.style.opacity; if (typeof op != "undefined" && op != "") return op; return ""; } Element.setInlineOpacity = function(element, value){ element= $(element); var els = element.style; els.opacity = value; } Element.getDimensions = function(element){ element = $(element); // All *Width and *Height properties give 0 on elements with display "none", // so enable the element temporarily if (Element.getStyle(element,'display') == "none"){ var els = element.style; var originalVisibility = els.visibility; var originalPosition = els.position; els.visibility = "hidden"; els.position = "absolute"; els.display = ""; var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; els.display = "none"; els.position = originalPosition; els.visibility = originalVisibility; return {width: originalWidth, height: originalHeight}; } return {width: element.offsetWidth, height: element.offsetHeight}; } /*--------------------------------------------------------------------------*/ Position.positionedOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { p = Element.getStyle(element,'position'); if(p == 'relative' || p == 'absolute') break; } } while (element); return [valueL, valueT]; } // Safari returns margins on body which is incorrect if the child is absolutely positioned. // for performance reasons, we create a specialized version of Position.cumulativeOffset for // KHTML/WebKit only if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { Position.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent==document.body) if (Element.getStyle(element,'position')=='absolute') break; element = element.offsetParent; } while (element); return [valueL, valueT]; } } Position.page = function(forElement) { var valueT = 0, valueL = 0; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; // Safari fix if (element.offsetParent==document.body) if (Element.getStyle(element,'position')=='absolute') break; } while (element = element.offsetParent); element = forElement; do { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } while (element = element.parentNode); return [valueL, valueT]; } // elements with display:none don't return an offsetParent, // fall back to manual calculation Position.offsetParent = function(element) { if(element.offsetParent) return element.offsetParent; if(element == document.body) return element; while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element,'position')!='static') return element; return document.body; } Position.clone = function(source, target) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || {}) // find page position of source source = $(source); var p = Position.page(source); // find coordinate system to use target = $(target); var delta = [0, 0]; var parent = null; // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(target,'position') == 'absolute') { parent = Position.offsetParent(target); delta = Position.page(parent); } // correct by body offsets (fixes Safari) if (parent==document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } // set position if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + "px"; if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + "px"; if(options.setWidth) target.style.width = source.offsetWidth + "px"; if(options.setHeight) target.style.height = source.offsetHeight + "px"; } Position.absolutize = function(element) { element = $(element); if(element.style.position=='absolute') return; Position.prepare(); var offsets = Position.positionedOffset(element); var top = offsets[1]; var left = offsets[0]; var width = element.clientWidth; var height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px';; element.style.left = left + 'px';; element.style.width = width + 'px';; element.style.height = height + 'px';; } Position.relativize = function(element) { element = $(element); if(element.style.position=='relative') return; Position.prepare(); element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; } /*--------------------------------------------------------------------------*/ Element.Class = { // Element.toggleClass(element, className) toggles the class being on/off // Element.toggleClass(element, className1, className2) toggles between both classes, // defaulting to className1 if neither exist toggle: function(element, className) { if(Element.Class.has(element, className)) { Element.Class.remove(element, className); if(arguments.length == 3) Element.Class.add(element, arguments[2]); } else { Element.Class.add(element, className); if(arguments.length == 3) Element.Class.remove(element, arguments[2]); } }, // gets space-delimited classnames of an element as an array get: function(element) { return $(element).className.split(' '); }, // functions adapted from original functions by Gavin Kistner remove: function(element) { element = $(element); var removeClasses = arguments; $R(1,arguments.length-1).each( function(index) { element.className = element.className.split(' ').reject( function(klass) { return (klass == removeClasses[index]) } ).join(' '); }); }, add: function(element) { element = $(element); for(var i = 1; i < arguments.length; i++) { Element.Class.remove(element, arguments[i]); element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; } }, // returns true if all given classes exist in said element has: function(element) { element = $(element); if(!element || !element.className) return false; var regEx; for(var i = 1; i < arguments.length; i++) { if((typeof arguments[i] == 'object') && (arguments[i].constructor == Array)) { for(var j = 0; j < arguments[i].length; j++) { regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); if(!regEx.test(element.className)) return false; } } else { regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); if(!regEx.test(element.className)) return false; } } return true; }, // expects arrays of strings and/or strings as optional paramters // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') has_any: function(element) { element = $(element); if(!element || !element.className) return false; var regEx; for(var i = 1; i < arguments.length; i++) { if((typeof arguments[i] == 'object') && (arguments[i].constructor == Array)) { for(var j = 0; j < arguments[i].length; j++) { regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)"); if(regEx.test(element.className)) return true; } } else { regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)"); if(regEx.test(element.className)) return true; } } return false; }, childrenWith: function(element, className) { var children = $(element).getElementsByTagName('*'); var elements = new Array(); for (var i = 0; i < children.length; i++) if (Element.Class.has(children[i], className)) elements.push(children[i]); return elements; } }