// Copyright 2006 Google Inc. // 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. // // 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. /** * @fileoverview Utilities for element styles. * */ goog.provide('goog.style'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.math.Box'); goog.require('goog.math.Coordinate'); goog.require('goog.math.Rect'); goog.require('goog.math.Size'); goog.require('goog.object'); goog.require('goog.userAgent'); goog.require('goog.userAgent.product'); /** * Sets a style value on an element. * @param {Element} element The element to change. * @param {string|Object} style If a string, a style name. If an object, a hash * of style names to style values. * @param {string} opt_value If style was a string, then this should be the * value. */ goog.style.setStyle = function(element, style, opt_value) { if (goog.isString(style)) { goog.style.setStyle_(element, opt_value, style); } else { goog.object.forEach(style, goog.partial(goog.style.setStyle_, element)); } }; /** * Sets a style value on an element, with parameters swapped to work with * {@code goog.object.forEach()}. * @param {Element} element The element to change. * @param {string} value Style value. * @param {string} style Style name. * @private */ goog.style.setStyle_ = function(element, value, style) { element.style[goog.style.toCamelCase(style)] = value; }; /** * Retrieves an explicitly-set style value of a node. This returns '' if there * isn't a style attribute on the element or if this style property has not been * explicitly set in script. * * @param {Element} element Element to get style of. * @param {string} style Property to get, css-style (if you have a camel-case * property, use element.style[style]). * @return {string} Style value. */ goog.style.getStyle = function(element, style) { return element.style[goog.style.toCamelCase(style)]; }; /** * Retrieves a computed style value of a node, or null if the value cannot be * computed (which will be the case in Internet Explorer). * * @param {Element} element Element to get style of. * @param {string} style Property to get (camel-case). * @return {string?} Style value. */ goog.style.getComputedStyle = function(element, style) { var doc = goog.dom.getOwnerDocument(element); if (doc.defaultView && doc.defaultView.getComputedStyle) { var styles = doc.defaultView.getComputedStyle(element, ''); if (styles) { return styles[style]; } } return null; }; /** * Gets the cascaded style value of a node, or null if the value cannot be * computed (only Internet Explorer can do this). * * @param {Element} element Element to get style of. * @param {string} style Property to get (camel-case). * @return {string} Style value. */ goog.style.getCascadedStyle = function(element, style) { return element.currentStyle ? element.currentStyle[style] : null; }; /** * Cross-browser pseudo get computed style. It returns the computed style where * available. If not available it tries the cascaded style value (IE * currentStyle) and in worst case the inline style value. It shouldn't be * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for * discussion. * * @param {Element} element Element to get style of. * @param {string} style Property to get (must be camelCase, not css-style.). * @return {string} Style value. * @private */ goog.style.getStyle_ = function(element, style) { return goog.style.getComputedStyle(element, style) || goog.style.getCascadedStyle(element, style) || element.style[style]; }; /** * Retrieves the computed value of the position CSS attribute. * @param {Element} element The element to get the position of. * @return {string} Position value. */ goog.style.getComputedPosition = function(element) { return goog.style.getStyle_(element, 'position'); }; /** * Retrieves the computed background color string for a given element. The * string returned is suitable for assigning to another element's * background-color, but is not guaranteed to be in any particular string * format. Accessing the color in a numeric form may not be possible in all * browsers or with all input. * * If the background color for the element is defined as a hexadecimal value, * the resulting string can be parsed by goog.color.parse in all supported * browsers. * * Whether named colors like "red" or "lightblue" get translated into a * format which can be parsed is browser dependent. Calling this function on * transparent elements will return "transparent" in most browsers or * "rgba(0, 0, 0, 0)" in Safari. * @param {Element} element The element to get the background color of. * @return {string} The computed string value of the background color. */ goog.style.getBackgroundColor = function(element) { return goog.style.getStyle_(element, 'backgroundColor'); }; /** * Sets the top/left values of an element. If no unit is specified in the * argument then it will add px. * @param {Element} el Element to move. * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate. * @param {string|number} opt_arg2 Top position. */ goog.style.setPosition = function(el, arg1, opt_arg2) { var x, y; var buggyGeckoSubPixelPos = goog.userAgent.GECKO && goog.userAgent.MAC && goog.userAgent.isVersion('1.9'); if (arg1 instanceof goog.math.Coordinate) { x = arg1.x; y = arg1.y; } else { x = arg1; y = opt_arg2; } el.style.left = typeof x == 'number' ? (buggyGeckoSubPixelPos ? Math.round(x) : x) + 'px' : /** @type {string} */ (x); el.style.top = typeof y == 'number' ? (buggyGeckoSubPixelPos ? Math.round(y) : y) + 'px' : /** @type {string} */ (y); }; /** * Gets the offsetLeft and offsetTop properties of an element and returns them * in a Coordinate object * @param {Element} element Element. * @return {goog.math.Coordinate} The position. */ goog.style.getPosition = function(element) { return new goog.math.Coordinate(element.offsetLeft, element.offsetTop); }; /** * Returns the viewport element for a particular document * @param {Node} opt_node DOM node (Document is OK) to get the viewport element * of. * @return {Element} document.documentElement or document.body. */ goog.style.getClientViewportElement = function(opt_node) { var doc; if (opt_node) { if (opt_node.nodeType == goog.dom.NodeType.DOCUMENT) { doc = opt_node; } else { doc = goog.dom.getOwnerDocument(opt_node); } } else { doc = goog.dom.getDocument(); } // In old IE versions the document.body represented the viewport if (goog.userAgent.IE && !goog.dom.getDomHelper(doc).isCss1CompatMode()) { return doc.body; } return doc.documentElement; }; /** * Gets the client rectangle of the DOM element. * * getBoundingClientRect is part of a new CSS object model draft (with a * long-time presence in IE), replacing the error-prone parent offset * computation and the now-deprecated Gecko getBoxObjectFor. * * This utility patches common browser bugs in getClientBoundingRect. It * will fail if getClientBoundingRect is unsupported. * * @param {Element} el The element whose bounding rectangle is being queried. * @return {Object} A native bounding rectangle with numerical left, top, * right, and bottom. Reported by Firefox to be of object type ClientRect. * @private */ goog.style.getBoundingClientRect_ = function(el) { var rect = el.getBoundingClientRect(); // Patch the result in IE only, so that this function can be inlined if // compiled for non-IE. if (goog.userAgent.IE) { // In IE, most of the time, 2 extra pixels are added to the top and left // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and // IE6 standards mode, this border can be overridden by setting the // document element's border to zero -- thus, we cannot rely on the // offset always being 2 pixels. // In quirks mode, the offset can be determined by querying the body's // clientLeft/clientTop, but in standards mode, it is found by querying // the document element's clientLeft/clientTop. Since we already called // getClientBoundingRect we have already forced a reflow, so it is not // too expensive just to query them all. // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx var doc = el.ownerDocument; rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft; rect.top -= doc.documentElement.clientTop + doc.body.clientTop; } return /** @type {Object} */ (rect); }; /** * Returns the first parent that could affect the position of a given element. * @param {Element} element The element to get the offset parent for. * @return {Element?} The first offset parent or null if one cannot be found. */ goog.style.getOffsetParent = function(element) { // element.offsetParent does the right thing in IE, in other browser it // only includes elements with position absolute, relative or fixed, not // elements with overflow set to auto or scroll. if (goog.userAgent.IE) { return element.offsetParent; } var doc = goog.dom.getOwnerDocument(element); var positionStyle = goog.style.getStyle_(element, 'position'); var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; for (var parent = element.parentNode; parent && parent != doc; parent = parent.parentNode) { positionStyle = goog.style.getStyle_(parent, 'position'); skipStatic = skipStatic && positionStyle == 'static' && parent != doc.documentElement && parent != doc.body; if (!skipStatic && (parent.scrollWidth > parent.clientWidth || parent.scrollHeight > parent.clientHeight || positionStyle == 'fixed' || positionStyle == 'absolute')) { return parent; } } return null; }; /** * Calculates and returns the visible rectangle for a given element. Returns a * box describing the visible portion of the nearest scrollable ancestor. * Coordinates are given relative to the document. * * @param {Element} element Element to get the visible rect for. * @return {goog.math.Box} Bounding elementBox describing the visible rect or * null if scrollable ancestor isn't inside the visible viewport. */ goog.style.getVisibleRectForElement = function(element) { var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0); var dom = goog.dom.getDomHelper(element); var scrollEl = dom.getDocumentScrollElement(); var inContainer; // Determine the size of the visible rect by climbing the dom accounting for // all scrollable containers. for (var el = element; el = goog.style.getOffsetParent(el); ) { // clientWidth is zero for inline block elements in IE. if ((!goog.userAgent.IE || el.clientWidth != 0) && (el.scrollWidth != el.clientWidth || el.scrollHeight != el.clientHeight) && goog.style.getStyle_(el, 'overflow') != 'visible') { var pos = goog.style.getPageOffset(el); var client = goog.style.getClientLeftTop(el); pos.x += client.x; pos.y += client.y; visibleRect.top = Math.max(visibleRect.top, pos.y); visibleRect.right = Math.min(visibleRect.right, pos.x + el.clientWidth); visibleRect.bottom = Math.min(visibleRect.bottom, pos.y + el.clientHeight); visibleRect.left = Math.max(visibleRect.left, pos.x); inContainer = inContainer || el != scrollEl; } } // Compensate for document scroll in non webkit browsers. var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop; if (goog.userAgent.WEBKIT) { visibleRect.left += scrollX; visibleRect.top += scrollY; } else { visibleRect.left = Math.max(visibleRect.left, scrollX); visibleRect.top = Math.max(visibleRect.top, scrollY); } if (!inContainer || goog.userAgent.WEBKIT) { visibleRect.right += scrollX; visibleRect.bottom += scrollY; } // Clip by the window's viewport. var winSize = dom.getViewportSize(); visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); return visibleRect.top >= 0 && visibleRect.left >= 0 && visibleRect.bottom > visibleRect.top && visibleRect.right > visibleRect.left ? visibleRect : null; }; /** * Returns clientLeft (width of the left border and, if the directionality is * right to left, the vertical scrollbar) and clientTop as a coordinate object. * * @param {Element} el Element to get clientLeft for. * @return {goog.math.Coordinate} Client left and top. */ goog.style.getClientLeftTop = function(el) { // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see // https://bugzilla.mozilla.org/show_bug.cgi?id=111207 if (goog.userAgent.GECKO && !goog.userAgent.isVersion('1.9')) { var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth')); if (goog.style.isRightToLeft(el)) { var scrollbarWidth = el.offsetWidth - el.clientWidth - left - parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth')); left += scrollbarWidth; } return new goog.math.Coordinate(left, parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth'))); } return new goog.math.Coordinate(el.clientLeft, el.clientTop); }; /** * Returns a Coordinate object relative to the top-left of the HTML document. * Implemented as a single function to save having to do two recursive loops in * opera and safari just to get both coordinates. If you just want one value do * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but * note if you call both those methods the tree will be analysed twice. * * Note: this is based on Yahoo's getXY method. * @see http://developer.yahoo.net/yui/license.txt * * @param {Element} el Element to get the page offset for. * @return {goog.math.Coordinate} The page offset. */ goog.style.getPageOffset = function(el) { var box, doc = goog.dom.getOwnerDocument(el); var positionStyle = goog.style.getStyle_(el, 'position'); // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the // position. When invoked for an element with position absolute and a negative // position though it can be off by one. Therefor the recursive implementation // is used in those (relatively rare) cases. var BUGGY_GECKO_BOX_OBJECT = goog.userAgent.GECKO && doc.getBoxObjectFor && !el.getBoundingClientRect && positionStyle == 'absolute' && (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0); // NOTE(arv): If element is hidden (display none or disconnected or any the // ancestors are hidden) we get (0,0) by default but we still do the // accumulation of scroll position. var pos = new goog.math.Coordinate(0, 0); var viewportElement = goog.style.getClientViewportElement(doc); if (el == viewportElement) { // viewport is always at 0,0 as that defined the coordinate system for this // function - this avoids special case checks in the code below return pos; } // IE and Gecko 1.9+. if (el.getBoundingClientRect) { box = goog.style.getBoundingClientRect_(el); // Must add the scroll coordinates in to get the absolute page offset // of element since getBoundingClientRect returns relative coordinates to // the viewport. var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll(); pos.x = box.left + scrollCoord.x; pos.y = box.top + scrollCoord.y; // Gecko prior to 1.9. } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // Gecko ignores the scroll values for ancestors, up to 1.9. See: // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and // https://bugzilla.mozilla.org/show_bug.cgi?id=330619 box = doc.getBoxObjectFor(el); var vpBox = doc.getBoxObjectFor(viewportElement); pos.x = box.screenX - vpBox.screenX; pos.y = box.screenY - vpBox.screenY; // Safari, Opera and Camino up to 1.0.4. } else { var parent = el; do { pos.x += parent.offsetLeft; pos.y += parent.offsetTop; // For safari/chrome, we need to add parent's clientLeft/Top as well. if (parent != el) { pos.x += parent.clientLeft || 0; pos.y += parent.clientTop || 0; } // In Safari when hit a position fixed element the rest of the offsets // are not correct. if (goog.userAgent.WEBKIT && goog.style.getComputedPosition(parent) == 'fixed') { pos.x += doc.body.scrollLeft; pos.y += doc.body.scrollTop; break; } parent = parent.offsetParent; } while (parent && parent != el) // Opera & (safari absolute) incorrectly account for body offsetTop. if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT && positionStyle == 'absolute')) { pos.y -= doc.body.offsetTop; } for (parent = el; (parent = goog.style.getOffsetParent(parent)) && parent != doc.body; ) { pos.x -= parent.scrollLeft; // Workaround for a bug in Opera 9.2 (and earlier) where table rows may // report an invalid scroll top value. The bug was fixed in Opera 9.5 // however as that version supports getBoundingClientRect it won't // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965 if (!goog.userAgent.OPERA || parent.tagName != 'TR') { pos.y -= parent.scrollTop; } } } return pos; }; /** * Returns the left coordinate of an element relative to the HTML document * @param {Element} el Elements. * @return {number} The left coordinate. */ goog.style.getPageOffsetLeft = function(el) { return goog.style.getPageOffset(el).x; }; /** * Returns the top coordinate of an element relative to the HTML document * @param {Element} el Elements. * @return {number} The top coordinate. */ goog.style.getPageOffsetTop = function(el) { return goog.style.getPageOffset(el).y; }; /** * Returns a Coordinate object relative to the top-left of an HTML document * in an ancestor frame of this element. Used for measuring the position of * an element inside a frame relative to a containing frame. * * @param {Element} el Element to get the page offset for. * @param {Window} relativeWin The window to measure relative to. If relativeWin * is not in the ancestor frame chain of the element, we measure relative to * the top-most window. * @return {goog.math.Coordinate} The page offset. */ goog.style.getFramedPageOffset = function(el, relativeWin) { var position = new goog.math.Coordinate(0, 0); // Iterate up the ancestor frame chain, keeping track of the current window // and the current element in that window. var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el)); var currentEl = el; do { // if we're at the top window, we want to get the page offset. // if we're at an inner frame, we only want to get the window position // so that we can determine the actual page offset in the context of // the outer window. var offset = currentWin == relativeWin ? goog.style.getPageOffset(currentEl) : goog.style.getClientPosition(currentEl); position.x += offset.x; position.y += offset.y; } while (currentWin && currentWin != relativeWin && (currentEl = currentWin.frameElement) && (currentWin = currentWin.parent)); return position; }; /** * Translates the specified rect relative to origBase page, for newBase page. * If origBase and newBase are the same, this function does nothing. * * @param {goog.math.Rect} rect The source rectangle relative to origBase page, * and it will have the translated result. * @param {!goog.dom.DomHelper} origBase The DomHelper for the input rectangle. * @param {!goog.dom.DomHelper} newBase The DomHelper for the resultant * coordinate. This must be a DOM for an ancestor frame of origBase * or the same as origBase. */ goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) { if (origBase.getDocument() != newBase.getDocument()) { var body = origBase.getDocument().body; var pos = goog.style.getFramedPageOffset(body, newBase.getWindow()); // Adjust Body's margin. pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body)); if (goog.userAgent.IE && !origBase.isCss1CompatMode()) { pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll()); } rect.left += pos.x; rect.top += pos.y; } }; /** * Returns the position of an element relative to another element in the * document. A relative to B * @param {Element|Event|goog.events.Event} a Element or mouse event who's * position we're calculating. * @param {Element|Event|goog.events.Event} b Element or mouse event position * is relative to. * @return {goog.math.Coordinate} The relative position. */ goog.style.getRelativePosition = function(a, b) { var ap = goog.style.getClientPosition(a); var bp = goog.style.getClientPosition(b); return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y); }; /** * Returns the position relative to the client viewport. * @param {Element|Event} el Element or a mouse event object. * @return {goog.math.Coordinate} The position. */ goog.style.getClientPosition = function(el) { var pos = new goog.math.Coordinate; if (el.nodeType == goog.dom.NodeType.ELEMENT) { if (el.getBoundingClientRect) { // IE and Gecko 1.9+ var box = goog.style.getBoundingClientRect_(el); pos.x = box.left; pos.y = box.top; } else { var scrollCoord = goog.dom.getDomHelper(/** @type {Element} */ (el)) .getDocumentScroll(); var pageCoord = goog.style.getPageOffset(/** @type {Element} */ (el)); pos.x = pageCoord.x - scrollCoord.x; pos.y = pageCoord.y - scrollCoord.y; } } else { pos.x = el.clientX; pos.y = el.clientY; } return pos; }; /** * Sets the top and left of an element such that it will have a * * @param {Element} el The element to set page offset for. * @param {number|goog.math.Coordinate} x Left position or coordinate obj. * @param {number} opt_y Top position. */ goog.style.setPageOffset = function(el, x, opt_y) { // Get current pageoffset var cur = goog.style.getPageOffset(el); if (x instanceof goog.math.Coordinate) { opt_y = x.y; x = x.x; } // NOTE(arv): We cannot allow strings for x and y. We could but that would // require us to manually transform between different units // Work out deltas var dx = x - cur.x; var dy = opt_y - cur.y; // Set position to current left/top + delta goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy); }; /** * Sets the width/height values of an element. If an argument is numeric, * or a goog.math.Size is passed, it is assumed to be pixels and will add * 'px' after converting it to an integer in string form. (This just sets the * CSS width and height properties so it might set content-box or border-box * size depending on the box model the browser is using.) * * @param {Element} element Element to move. * @param {string|number|goog.math.Size} w Width of the element, or a * size object. * @param {string|number} opt_h Height of the element. Required if w is not a * size object. */ goog.style.setSize = function(element, w, opt_h) { var h; if (w instanceof goog.math.Size) { h = w.height; w = w.width; } else { if (opt_h == undefined) { throw Error('missing height argument'); } h = opt_h; } element.style.width = typeof w == 'number' ? Math.round(w) + 'px' : /** @type {string} */(w); element.style.height = typeof h == 'number' ? Math.round(h) + 'px' : /** @type {string} */(h); }; /** * Gets the height and width of an element, even if its display is none. * Specifically, this returns the height and width of the border box, * irrespective of the box model in effect. * @param {Element} element Element to get width of. * @return {goog.math.Size} Object with width/height properties. */ goog.style.getSize = function(element) { var hasOperaBug = goog.userAgent.OPERA && !goog.userAgent.isVersion('10'); if (goog.style.getStyle_(element, 'display') != 'none') { if (hasOperaBug) { return new goog.math.Size(element.offsetWidth || element.clientWidth, element.offsetHeight || element.clientHeight); } else { return new goog.math.Size(element.offsetWidth, element.offsetHeight); } } var style = element.style; var originalDisplay = style.display; var originalVisibility = style.visibility; var originalPosition = style.position; style.visibility = 'hidden'; style.position = 'absolute'; style.display = 'inline'; var originalWidth, originalHeight; if (hasOperaBug) { originalWidth = element.offsetWidth || element.clientWidth; originalHeight = element.offsetHeight || element.clientHeight; } else { originalWidth = element.offsetWidth; originalHeight = element.offsetHeight; } style.display = originalDisplay; style.position = originalPosition; style.visibility = originalVisibility; return new goog.math.Size(originalWidth, originalHeight); }; /** * Returns a bounding rectangle for a given element in page space. * @param {Element} element Element to get bounds of. * @return {goog.math.Rect} Bounding rectangle for the element. */ goog.style.getBounds = function(element) { var o = goog.style.getPageOffset(element); var s = goog.style.getSize(element); return new goog.math.Rect(o.x, o.y, s.width, s.height); }; /** * A memoized cache for goog.style.toCamelCase. * @type {Object} * @private */ goog.style.toCamelCaseCache_ = {}; /** * Converts a CSS selector in the form style-property to styleProperty * @param {string} selector CSS Selector. * @return {string} Camel case selector. */ goog.style.toCamelCase = function(selector) { return goog.style.toCamelCaseCache_[selector] || (goog.style.toCamelCaseCache_[selector] = String(selector).replace(/\-([a-z])/g, function(all, match) { return match.toUpperCase(); })); }; /** * Converts a CSS selector in the form styleProperty to style-property * @param {string} selector Camel case selector. * @return {string} Selector cased. */ goog.style.toSelectorCase = function(selector) { return selector.replace(/([A-Z])/g, '-$1').toLowerCase(); }; /** * Gets the opacity of a node (x-browser). This gets the inline style opacity * of the node, and does not take into account the cascaded or the computed * style for this node. * @param {Element} el Element whose opacity has to be found. * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''} * if the opacity is not set. */ goog.style.getOpacity = function(el) { var style = el.style; var result = ''; if ('opacity' in style) { result = style.opacity; } else if ('MozOpacity' in style) { result = style.MozOpacity; } else if ('filter' in style) { var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/); if (match) { result = String(match[1] / 100); } } return result == '' ? result : Number(result); }; /** * Sets the opacity of a node (x-browser). * @param {Element} el Elements whose opacity has to be set. * @param {number|string} alpha Opacity between 0 and 1 or an empty string * {@code ''} to clear the opacity. */ goog.style.setOpacity = function(el, alpha) { var style = el.style; if ('opacity' in style) { style.opacity = alpha; } else if ('MozOpacity' in style) { style.MozOpacity = alpha; } else if ('filter' in style) { if (alpha === '') { style.filter = ''; } else { style.filter = 'alpha(opacity=' + alpha * 100 + ')'; } } }; /** * Sets the background of an element to a transparent image in a browser- * independent manner. * * This function does not support repeating backgrounds or alternate background * positions to match the behavior of Internet Explorer. It also does not * support sizingMethods other than crop since they cannot be replicated in * browsers other than Internet Explorer. * * @param {Element} el The element to set background on. * @param {string} src The image source URL. */ goog.style.setTransparentBackgroundImage = function(el, src) { var style = el.style; // It is safe to use the style.filter in IE only. In Safari 'filter' is in // style object but access to style.filter causes it to throw an exception. // Note: IE8 supports images with an alpha channel. if (goog.userAgent.IE && !goog.userAgent.isVersion('8')) { style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' + 'src="' + src + '", sizingMethod="crop")'; } else { // Set style properties individually instead of using background shorthand // to prevent overwriting a pre-existing background color. style.backgroundImage = 'url(' + src + ')'; style.backgroundPosition = 'top left'; style.backgroundRepeat = 'no-repeat'; } }; /** * Clears the background image of an element in a browser independent manner. * @param {Element} el The element to clear background image for. */ goog.style.clearTransparentBackgroundImage = function(el) { var style = el.style; if ('filter' in style) { style.filter = ''; } else { // Set style properties individually instead of using background shorthand // to prevent overwriting a pre-existing background color. style.backgroundImage = 'none'; } }; /** * Shows or hides an element from the page. Hiding the element is done by * setting the display property to "none", removing the element from the * rendering hierarchy so it takes up no space. To show the element, the default * inherited display property is restored (defined either in stylesheets or by * the browser's default style rules.) * * Caveat 1: if the inherited display property for the element is set to "none" * by the stylesheets, that is the property that will be restored by a call to * showElement(), effectively toggling the display between "none" and "none". * * Caveat 2: if the element display style is set inline (by setting either * element.style.display or a style attribute in the HTML), a call to * showElement will clear that setting and defer to the inherited style in the * stylesheet. * @param {Element} el Element to show or hide. * @param {*} display True to render the element in its default style, * false to disable rendering the element. */ goog.style.showElement = function(el, display) { el.style.display = display ? '' : 'none'; }; /** * Test whether the given element has been shown or hidden via a call to * {@link #showElement}. * * Note this is strictly a companion method for a call * to {@link #showElement} and the same caveats apply; in particular, this * method does not guarantee that the return value will be consistent with * whether or not the element is actually visible. * * @param {Element} el The element to test. * @return {boolean} Whether the element has been shown. * @see #showElement */ goog.style.isElementShown = function(el) { return el.style.display != 'none'; }; /** * Installs the styles string into the window that contains opt_element. If * opt_element is null, the main window is used. * @param {string} stylesString The style string to install. * @param {Element} opt_element Element who's parent document should have the * styles installed. * @return {Element} The style element created. */ goog.style.installStyles = function(stylesString, opt_element) { var dh = goog.dom.getDomHelper(opt_element); var styleSheet = null; if (goog.userAgent.IE) { styleSheet = dh.getDocument().createStyleSheet(); } else { var head = dh.$$('head')[0]; // In opera documents are not guaranteed to have a head element, thus we // have to make sure one exists before using it. if (!head) { var body = dh.$$('body')[0]; head = dh.createDom('head'); body.parentNode.insertBefore(head, body); } styleSheet = dh.createDom('style'); dh.appendChild(head, styleSheet); } goog.style.setStyles(styleSheet, stylesString); return styleSheet; }; /** * Sets the content of a style element. The style element can be any valid * style element. This element will have its content completely replaced by * the new stylesString. * @param {Element} element A stylesheet element as returned by installStyles. * @param {string} stylesString The new content of the stylesheet. */ goog.style.setStyles = function(element, stylesString) { if (goog.userAgent.IE) { // Adding the selectors individually caused the browser to hang if the // selector was invalid or there were CSS comments. Setting the cssText of // the style node works fine and ignores CSS that IE doesn't understand element.cssText = stylesString; } else { var propToSet = goog.userAgent.WEBKIT ? 'innerText' : 'innerHTML'; element[propToSet] = stylesString; } }; /** * Sets 'white-space: pre-wrap' for a node (x-browser). * * There are as many ways of specifying pre-wrap as there are browsers. * * CSS3: white-space: pre-wrap; * Mozilla: white-space: -moz-pre-wrap; * Opera: white-space: -o-pre-wrap; * IE6/7: white-space: pre; word-wrap: break-word; * * @param {Element} el Element to enable pre-wrap for. */ goog.style.setPreWrap = function(el) { var style = el.style; if (goog.userAgent.IE) { style.whiteSpace = 'pre'; style.wordWrap = 'break-word'; } else if (goog.userAgent.GECKO) { style.whiteSpace = '-moz-pre-wrap'; } else if (goog.userAgent.OPERA) { style.whiteSpace = '-o-pre-wrap'; } else { style.whiteSpace = 'pre-wrap'; } }; /** * Sets 'display: inline-block' for an element (cross-browser). * @param {Element} el Element to which the inline-block display style is to be * applied. */ goog.style.setInlineBlock = function(el) { var style = el.style; // Without position:relative, weirdness ensues. Just accept it and move on. style.position = 'relative'; if (goog.userAgent.IE && !goog.userAgent.isVersion('8')) { // IE8 supports inline-block so fall through to the else // Zoom:1 forces hasLayout, display:inline gives inline behavior. style.zoom = '1'; style.display = 'inline'; } else if (goog.userAgent.GECKO) { // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box // is close enough. style.display = goog.userAgent.isVersion('1.9a') ? 'inline-block' : '-moz-inline-box'; } else { // Opera, Webkit, and Safari seem to do OK with the standard inline-block // style. style.display = 'inline-block'; } }; /** * Determines whether the given content element needs to be placed in a wrapper * div before being appended to an inline-block element. Because Firefox2 * does not properly support inline-block, some elements need this so that RTL * characters are preserved. * @param {string|Node|NodeList|Array.} content Content element in the * form of a child argument to {@link goog.dom.createDom}. * @return {boolean} Whether a wrapper is needed. */ goog.style.needsInlineBlockWrapper = function(content) { return goog.style.browserNeedsInlineBlockWrapper_ && goog.style.hasUnguardedText_(content); }; /** * Determines whether the given content element has text nodes that are not * wrapped in a div. * @param {string|Node|Array.} content Content element in the form * of a child argument to {@link goog.dom.createDom}. * @return {boolean} Whether there are text nodes. * @private */ goog.style.hasUnguardedText_ = function(content) { if (!content) { return false; } else if (goog.isString(content) || content.nodeType == goog.dom.NodeType.TEXT) { return true; } else { // content must be an array. return goog.array.some(content, goog.style.hasUnguardedText_); } }; /** * On FF2, text nodes cannot be placed directly under an inline-block element. * If they are, FF2 will change all RTL characters to LTR characters. * @type {boolean} * @private */ goog.style.browserNeedsInlineBlockWrapper_ = goog.userAgent.GECKO && !goog.userAgent.isVersion('1.9'); /** * Returns true if the element is using right to left (rtl) direction. * @param {Element} el The element to test. * @return {boolean} True for right to left, false for left to right. */ goog.style.isRightToLeft = function(el) { return 'rtl' == goog.style.getStyle_(el, 'direction'); }; /** * The CSS style property corresponding to an element being * unselectable on the current browser platform (null if none). * Opera and IE instead use a DOM attribute 'unselectable'. * @type {string?} * @private */ goog.style.unselectableStyle_ = goog.userAgent.GECKO ? 'MozUserSelect' : goog.userAgent.WEBKIT ? 'WebkitUserSelect' : null; /** * Returns true if the element is set to be unselectable, false otherwise. * Note that on some platforms (e.g. Mozilla), even if an element isn't set * to be unselectable, it will behave as such if any of its ancestors is * unselectable. * @param {Element} el Element to check. * @return {boolean} Whether the element is set to be unselectable. */ goog.style.isUnselectable = function(el) { if (goog.style.unselectableStyle_) { return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none'; } else if (goog.userAgent.IE || goog.userAgent.OPERA) { return el.getAttribute('unselectable') == 'on'; } return false; }; /** * Makes the element and its descendants selectable or unselectable. Note * that on some platforms (e.g. Mozilla), even if an element isn't set to * be unselectable, it will behave as such if any of its ancestors is * unselectable. * @param {Element} el The element to alter. * @param {boolean} unselectable Whether the element and its descendants * should be made unselectable. * @param {boolean} opt_noRecurse Whether to only alter the element's own * selectable state, and leave its descendants alone; defaults to false. */ goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) { var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null; var name = goog.style.unselectableStyle_; if (name) { // Add/remove the appropriate CSS style to/from the element and its // descendants. var value = unselectable ? 'none' : ''; el.style[name] = value; if (descendants) { for (var i = 0, descendant; descendant = descendants[i]; i++) { descendant.style[name] = value; } } } else if (goog.userAgent.IE || goog.userAgent.OPERA) { // Toggle the 'unselectable' attribute on the element and its descendants. var value = unselectable ? 'on' : ''; el.setAttribute('unselectable', value); if (descendants) { for (var i = 0, descendant; descendant = descendants[i]; i++) { descendant.setAttribute('unselectable', value); } } } }; /** * Gets the border box size for an element. * @param {Element} element The element to get the size for. * @return {goog.math.Size} The border box size. */ goog.style.getBorderBoxSize = function(element) { return new goog.math.Size(element.offsetWidth, element.offsetHeight); }; /** * Sets the border box size of an element. This is potentially expensive in IE * if the document is CSS1Compat mode * @param {Element} element The element to set the size on. * @param {goog.math.Size} size The new size. */ goog.style.setBorderBoxSize = function(element, size) { var doc = goog.dom.getOwnerDocument(element); var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); if (goog.userAgent.IE && (!isCss1CompatMode || !goog.userAgent.isVersion('8'))) { var style = element.style; if (isCss1CompatMode) { var paddingBox = goog.style.getPaddingBox(element); var borderBox = goog.style.getBorderBox(element); style.pixelWidth = size.width - borderBox.left - paddingBox.left - paddingBox.right - borderBox.right; style.pixelHeight = size.height - borderBox.top - paddingBox.top - paddingBox.bottom - borderBox.bottom; } else { style.pixelWidth = size.width; style.pixelHeight = size.height; } } else { goog.style.setBoxSizingSize_(element, size, 'border-box'); } }; /** * Gets the content box size for an element. This is potentially expensive in * all browsers. * @param {Element} element The element to get the size for. * @return {goog.math.Size} The content box size. */ goog.style.getContentBoxSize = function(element) { var doc = goog.dom.getOwnerDocument(element); var ieCurrentStyle = goog.userAgent.IE && element.currentStyle; if (ieCurrentStyle && goog.dom.getDomHelper(doc).isCss1CompatMode() && ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' && !ieCurrentStyle.boxSizing) { // If IE in CSS1Compat mode than just use the width and height. // If we have a boxSizing then fall back on measuring the borders etc. var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width, 'width', 'pixelWidth'); var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height, 'height', 'pixelHeight'); return new goog.math.Size(width, height); } else { var borderBoxSize = goog.style.getBorderBoxSize(element); var paddingBox = goog.style.getPaddingBox(element); var borderBox = goog.style.getBorderBox(element); return new goog.math.Size(borderBoxSize.width - borderBox.left - paddingBox.left - paddingBox.right - borderBox.right, borderBoxSize.height - borderBox.top - paddingBox.top - paddingBox.bottom - borderBox.bottom); } }; /** * Sets the content box size of an element. This is potentially expensive in IE * if the document is BackCompat mode. * @param {Element} element The element to set the size on. * @param {goog.math.Size} size The new size. */ goog.style.setContentBoxSize = function(element, size) { var doc = goog.dom.getOwnerDocument(element); var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode(); if (goog.userAgent.IE && (!isCss1CompatMode || !goog.userAgent.isVersion('8'))) { var style = element.style; if (isCss1CompatMode) { style.pixelWidth = size.width; style.pixelHeight = size.height; } else { var paddingBox = goog.style.getPaddingBox(element); var borderBox = goog.style.getBorderBox(element); style.pixelWidth = size.width + borderBox.left + paddingBox.left + paddingBox.right + borderBox.right; style.pixelHeight = size.height + borderBox.top + paddingBox.top + paddingBox.bottom + borderBox.bottom; } } else { goog.style.setBoxSizingSize_(element, size, 'content-box'); } }; /** * Helper function that sets the box sizing as well as the width and height * @param {Element} element The element to set the size on. * @param {goog.math.Size} size The new size to set. * @param {string} boxSizing The box-sizing value. * @private */ goog.style.setBoxSizingSize_ = function(element, size, boxSizing) { var style = element.style; if (goog.userAgent.GECKO) { style.MozBoxSizing = boxSizing; } else if (goog.userAgent.WEBKIT) { style.WebkitBoxSizing = boxSizing; } else if (goog.userAgent.OPERA && !goog.userAgent.isVersion('9.50')) { // Opera pre-9.5 does not have CSSStyleDeclaration::boxSizing, but // box-sizing can still be set via CSSStyleDeclaration::setProperty. if (boxSizing) { style.setProperty('box-sizing', boxSizing); } else { style.removeProperty('box-sizing'); } } else { // Includes IE8 style.boxSizing = boxSizing; } style.width = size.width + 'px'; style.height = size.height + 'px'; }; /** * IE specific function that converts a non pixel unit to pixels. * @param {Element} element The element to convert the value for. * @param {string} value The current value as a string. The value must not be * ''. * @param {string} name The CSS property name to use for the converstion. This * should be 'left', 'top', 'width' or 'height'. * @param {string} pixelName The CSS pixel property name to use to get the * value in pixels. * @return {number} The value in pixels. * @private */ goog.style.getIePixelValue_ = function(element, value, name, pixelName) { // Try if we already have a pixel value. IE does not do half pixels so we // only check if it matches a number followed by 'px'. if (/^\d+px?$/.test(value)) { return parseInt(value, 10); } else { var oldStyleValue = element.style[name]; var oldRuntimeValue = element.runtimeStyle[name]; // set runtime style to prevent changes element.runtimeStyle[name] = element.currentStyle[name]; element.style[name] = value; var pixelValue = element.style[pixelName]; // restore element.style[name] = oldStyleValue; element.runtimeStyle[name] = oldRuntimeValue; return pixelValue; } }; /** * Helper function for getting the pixel padding or margin for IE. * @param {Element} element The element to get the padding for. * @param {string} propName The property name. * @return {number} The pixel padding. * @private */ goog.style.getIePixelDistance_ = function(element, propName) { return goog.style.getIePixelValue_(element, goog.style.getCascadedStyle(element, propName), 'left', 'pixelLeft'); }; /** * Gets the computed paddings or margins (on all sides) in pixels. * @param {Element} element The element to get the padding for. * @param {string} stylePrefix Pass 'padding' to retrieve the padding box, * or 'margin' to retrieve the margin box. * @return {goog.math.Box} The computed paddings or margins. * @private */ goog.style.getBox_ = function(element, stylePrefix) { if (goog.userAgent.IE) { var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left'); var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right'); var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top'); var bottom = goog.style.getIePixelDistance_( element, stylePrefix + 'Bottom'); return new goog.math.Box(top, right, bottom, left); } else { // On non-IE browsers, getComputedStyle is always non-null. var left = /** @type {string} */ ( goog.style.getComputedStyle(element, stylePrefix + 'Left')); var right = /** @type {string} */ ( goog.style.getComputedStyle(element, stylePrefix + 'Right')); var top = /** @type {string} */ ( goog.style.getComputedStyle(element, stylePrefix + 'Top')); var bottom = /** @type {string} */ ( goog.style.getComputedStyle(element, stylePrefix + 'Bottom')); // NOTE(arv): Gecko can return floating point numbers for the computed // style values. return new goog.math.Box(parseFloat(top), parseFloat(right), parseFloat(bottom), parseFloat(left)); } }; /** * Gets the computed paddings (on all sides) in pixels. * @param {Element} element The element to get the padding for. * @return {goog.math.Box} The computed paddings. */ goog.style.getPaddingBox = function(element) { return goog.style.getBox_(element, 'padding'); }; /** * Gets the computed margins (on all sides) in pixels. * @param {Element} element The element to get the margins for. * @return {goog.math.Box} The computed margins. */ goog.style.getMarginBox = function(element) { return goog.style.getBox_(element, 'margin'); }; /** * A map used to map the border width keywords to a pixel width. * @type {Object} * @private */ goog.style.ieBorderWidthKeywords_ = { 'thin': 2, 'medium': 4, 'thick': 6 }; /** * Helper function for IE to get the pixel border. * @param {Element} element The element to get the pixel border for. * @param {string} prop The part of the property name. * @return {number} The value in pixels. * @private */ goog.style.getIePixelBorder_ = function(element, prop) { if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') { return 0; } var width = goog.style.getCascadedStyle(element, prop + 'Width'); if (width in goog.style.ieBorderWidthKeywords_) { return goog.style.ieBorderWidthKeywords_[width]; } return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft'); }; /** * Gets the computed border widths (on all sides) in pixels * @param {Element} element The element to get the border widths for. * @return {goog.math.Box} The computed border widths. */ goog.style.getBorderBox = function(element) { if (goog.userAgent.IE) { var left = goog.style.getIePixelBorder_(element, 'borderLeft'); var right = goog.style.getIePixelBorder_(element, 'borderRight'); var top = goog.style.getIePixelBorder_(element, 'borderTop'); var bottom = goog.style.getIePixelBorder_(element, 'borderBottom'); return new goog.math.Box(top, right, bottom, left); } else { // On non-IE browsers, getComputedStyle is always non-null. var left = /** @type {string} */ ( goog.style.getComputedStyle(element, 'borderLeftWidth')); var right = /** @type {string} */ ( goog.style.getComputedStyle(element, 'borderRightWidth')); var top = /** @type {string} */ ( goog.style.getComputedStyle(element, 'borderTopWidth')); var bottom = /** @type {string} */ ( goog.style.getComputedStyle(element, 'borderBottomWidth')); return new goog.math.Box(parseFloat(top), parseFloat(right), parseFloat(bottom), parseFloat(left)); } }; /** * Returns the font face applied to a given node. Opera and IE should return * the font actually displayed. Firefox returns the author's most-preferred * font (whether the browser is capable of displaying it or not.) * @param {Element} el The element whose font family is returned. * @return {string} The font family applied to el. */ goog.style.getFontFamily = function(el) { var doc = goog.dom.getOwnerDocument(el); var font = ''; if (doc.createTextRange) { var range = doc.body.createTextRange(); range.moveToElementText(el); font = range.queryCommandValue('FontName'); } if (!font) { // Note if for some reason IE can't derive FontName with a TextRange, we // fallback to using currentStyle font = goog.style.getStyle_(el, 'fontFamily'); // Opera on Linux provides the font vendor's name in square-brackets. if (goog.userAgent.OPERA && goog.userAgent.LINUX) { font = font.replace(/ \[[^\]]*\]/, ''); } } // Firefox returns the applied font-family string (author's list of // preferred fonts.) We want to return the most-preferred font, in lieu of // the *actually* applied font. var fontsArray = font.split(','); if (fontsArray.length > 1) font = fontsArray[0]; // Sanitize for x-browser consistency: // Strip quotes because browsers aren't consistent with how they're // applied; Opera always encloses, Firefox sometimes, and IE never. return goog.string.stripQuotes(font, '"\''); }; /** * Returns the units used for a CSS length measurement. * @param {string} value A CSS length quantity. * @return {string?} The units of measurement. */ goog.style.getLengthUnits = function(value) { var units = value.match(/[^\d]+$/); return units && units[0] || null; }; /** * Map of absolute CSS length units * @type {Object} * @private */ goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = { 'cm' : 1, 'in' : 1, 'mm' : 1, 'pc' : 1, 'pt' : 1 }; /** * Map of relative CSS length units that can be accurately converted to px * font-size values using getIePixelValue_. Only units that are defined in * relation to a font size are convertible (%, small, etc. are not). * @type {Object} * @private */ goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = { 'em' : 1, 'ex' : 1 }; /** * Returns the font size, in pixels, of text in an element. * @param {Element} el The element whose font size is returned. * @return {number} The font size (in pixels). */ goog.style.getFontSize = function(el) { var fontSize = goog.style.getStyle_(el, 'fontSize'); var sizeUnits = goog.style.getLengthUnits(fontSize); if (fontSize && 'px' == sizeUnits) { // NOTE(nathanl): This could be parseFloat instead, but IE doesn't return // decimal fractions in getStyle_ and Firefox reports the fractions, but // ignores them when rendering. Interestingly enough, when we force the // issue and size something to e.g., 50% of 25px, the browsers round in // opposite directions with Firefox reporting 12px and IE 13px. I punt. return parseInt(fontSize, 10); } // In IE, we can convert absolute length units to a px value using // goog.style.getIePixelValue_. Units defined in relation to a font size // (em, ex) are applied relative to the element's parentNode and can also // be converted. if (goog.userAgent.IE) { if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) { return goog.style.getIePixelValue_(el, fontSize, 'left', 'pixelLeft'); } else if (el.parentNode && sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) { return goog.style.getIePixelValue_(el.parentNode, fontSize, 'left', 'pixelLeft'); } } // Sometimes we can't cleanly find the font size (some units relative to a // node's parent's font size are difficult: %, smaller et al), so we create // an invisible, absolutely-positioned span sized to be the height of an 'M' // rendered in its parent's (i.e., our target element's) font size. This is // the definition of CSS's font size attribute. var sizeElement = goog.dom.createDom( 'span', {'style': 'visibility:hidden;position:absolute;' + 'line-height:0;padding:0;margin:0;border:0;height:1em;'}); goog.dom.appendChild(el, sizeElement); fontSize = sizeElement.offsetHeight; goog.dom.removeNode(sizeElement); return fontSize; }; /** * Parses a style attribute value. Converts CSS property names to camel case. * @param {string} value The style attribute value. * @return {!Object} Map of CSS properties to string values. */ goog.style.parseStyleAttribute = function(value) { var result = {}; goog.array.forEach(value.split(/\s*;\s*/), function(pair) { var keyValue = pair.split(/\s*:\s*/); if (keyValue.length == 2) { result[goog.style.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1]; } }); return result; }; /** * Reverse of parseStyleAttribute; that is, takes a style object and returns the * corresponding attribute value. Converts camel case property names to proper * CSS selector names. * @param {Object} obj Map of CSS properties to values. * @return {string} The style attribute value. */ goog.style.toStyleAttribute = function(obj) { var buffer = []; goog.object.forEach(obj, function(value, key) { buffer.push(goog.style.toSelectorCase(key), ':', value, ';'); }); return buffer.join(''); }; /** * Sets CSS float property on an element. * @param {Element} el The element to set float property on. * @param {string} value The value of float CSS property to set on this element. */ goog.style.setFloat = function(el, value) { el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value; }; /** * Gets value of explicitly-set float CSS property on an element. * @param {Element} el The element to get float property of. * @return {string} The value of explicitly-set float CSS property on this * element. */ goog.style.getFloat = function(el) { return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || ''; };