lib/goog/style/style.js

1// Copyright 2006 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/**
16 * @fileoverview Utilities for element styles.
17 *
18 * @see ../demos/inline_block_quirks.html
19 * @see ../demos/inline_block_standards.html
20 * @see ../demos/style_viewport.html
21 */
22
23goog.provide('goog.style');
24
25
26goog.require('goog.array');
27goog.require('goog.asserts');
28goog.require('goog.dom');
29goog.require('goog.dom.NodeType');
30goog.require('goog.dom.vendor');
31goog.require('goog.math.Box');
32goog.require('goog.math.Coordinate');
33goog.require('goog.math.Rect');
34goog.require('goog.math.Size');
35goog.require('goog.object');
36goog.require('goog.string');
37goog.require('goog.userAgent');
38
39
40/**
41 * @define {boolean} Whether we know at compile time that
42 * getBoundingClientRect() is present and bug-free on the browser.
43 */
44goog.define('goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS', false);
45
46
47/**
48 * Sets a style value on an element.
49 *
50 * This function is not indended to patch issues in the browser's style
51 * handling, but to allow easy programmatic access to setting dash-separated
52 * style properties. An example is setting a batch of properties from a data
53 * object without overwriting old styles. When possible, use native APIs:
54 * elem.style.propertyKey = 'value' or (if obliterating old styles is fine)
55 * elem.style.cssText = 'property1: value1; property2: value2'.
56 *
57 * @param {Element} element The element to change.
58 * @param {string|Object} style If a string, a style name. If an object, a hash
59 * of style names to style values.
60 * @param {string|number|boolean=} opt_value If style was a string, then this
61 * should be the value.
62 */
63goog.style.setStyle = function(element, style, opt_value) {
64 if (goog.isString(style)) {
65 goog.style.setStyle_(element, opt_value, style);
66 } else {
67 for (var key in style) {
68 goog.style.setStyle_(element, style[key], key);
69 }
70 }
71};
72
73
74/**
75 * Sets a style value on an element, with parameters swapped to work with
76 * {@code goog.object.forEach()}. Prepends a vendor-specific prefix when
77 * necessary.
78 * @param {Element} element The element to change.
79 * @param {string|number|boolean|undefined} value Style value.
80 * @param {string} style Style name.
81 * @private
82 */
83goog.style.setStyle_ = function(element, value, style) {
84 var propertyName = goog.style.getVendorJsStyleName_(element, style);
85
86 if (propertyName) {
87 element.style[propertyName] = value;
88 }
89};
90
91
92/**
93 * Returns the style property name in camel-case. If it does not exist and a
94 * vendor-specific version of the property does exist, then return the vendor-
95 * specific property name instead.
96 * @param {Element} element The element to change.
97 * @param {string} style Style name.
98 * @return {string} Vendor-specific style.
99 * @private
100 */
101goog.style.getVendorJsStyleName_ = function(element, style) {
102 var camelStyle = goog.string.toCamelCase(style);
103
104 if (element.style[camelStyle] === undefined) {
105 var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
106 goog.string.toTitleCase(camelStyle);
107
108 if (element.style[prefixedStyle] !== undefined) {
109 return prefixedStyle;
110 }
111 }
112
113 return camelStyle;
114};
115
116
117/**
118 * Returns the style property name in CSS notation. If it does not exist and a
119 * vendor-specific version of the property does exist, then return the vendor-
120 * specific property name instead.
121 * @param {Element} element The element to change.
122 * @param {string} style Style name.
123 * @return {string} Vendor-specific style.
124 * @private
125 */
126goog.style.getVendorStyleName_ = function(element, style) {
127 var camelStyle = goog.string.toCamelCase(style);
128
129 if (element.style[camelStyle] === undefined) {
130 var prefixedStyle = goog.dom.vendor.getVendorJsPrefix() +
131 goog.string.toTitleCase(camelStyle);
132
133 if (element.style[prefixedStyle] !== undefined) {
134 return goog.dom.vendor.getVendorPrefix() + '-' + style;
135 }
136 }
137
138 return style;
139};
140
141
142/**
143 * Retrieves an explicitly-set style value of a node. This returns '' if there
144 * isn't a style attribute on the element or if this style property has not been
145 * explicitly set in script.
146 *
147 * @param {Element} element Element to get style of.
148 * @param {string} property Property to get, css-style (if you have a camel-case
149 * property, use element.style[style]).
150 * @return {string} Style value.
151 */
152goog.style.getStyle = function(element, property) {
153 // element.style is '' for well-known properties which are unset.
154 // For for browser specific styles as 'filter' is undefined
155 // so we need to return '' explicitly to make it consistent across
156 // browsers.
157 var styleValue = element.style[goog.string.toCamelCase(property)];
158
159 // Using typeof here because of a bug in Safari 5.1, where this value
160 // was undefined, but === undefined returned false.
161 if (typeof(styleValue) !== 'undefined') {
162 return styleValue;
163 }
164
165 return element.style[goog.style.getVendorJsStyleName_(element, property)] ||
166 '';
167};
168
169
170/**
171 * Retrieves a computed style value of a node. It returns empty string if the
172 * value cannot be computed (which will be the case in Internet Explorer) or
173 * "none" if the property requested is an SVG one and it has not been
174 * explicitly set (firefox and webkit).
175 *
176 * @param {Element} element Element to get style of.
177 * @param {string} property Property to get (camel-case).
178 * @return {string} Style value.
179 */
180goog.style.getComputedStyle = function(element, property) {
181 var doc = goog.dom.getOwnerDocument(element);
182 if (doc.defaultView && doc.defaultView.getComputedStyle) {
183 var styles = doc.defaultView.getComputedStyle(element, null);
184 if (styles) {
185 // element.style[..] is undefined for browser specific styles
186 // as 'filter'.
187 return styles[property] || styles.getPropertyValue(property) || '';
188 }
189 }
190
191 return '';
192};
193
194
195/**
196 * Gets the cascaded style value of a node, or null if the value cannot be
197 * computed (only Internet Explorer can do this).
198 *
199 * @param {Element} element Element to get style of.
200 * @param {string} style Property to get (camel-case).
201 * @return {string} Style value.
202 */
203goog.style.getCascadedStyle = function(element, style) {
204 // TODO(nicksantos): This should be documented to return null. #fixTypes
205 return element.currentStyle ? element.currentStyle[style] : null;
206};
207
208
209/**
210 * Cross-browser pseudo get computed style. It returns the computed style where
211 * available. If not available it tries the cascaded style value (IE
212 * currentStyle) and in worst case the inline style value. It shouldn't be
213 * called directly, see http://wiki/Main/ComputedStyleVsCascadedStyle for
214 * discussion.
215 *
216 * @param {Element} element Element to get style of.
217 * @param {string} style Property to get (must be camelCase, not css-style.).
218 * @return {string} Style value.
219 * @private
220 */
221goog.style.getStyle_ = function(element, style) {
222 return goog.style.getComputedStyle(element, style) ||
223 goog.style.getCascadedStyle(element, style) ||
224 (element.style && element.style[style]);
225};
226
227
228/**
229 * Retrieves the computed value of the box-sizing CSS attribute.
230 * Browser support: http://caniuse.com/css3-boxsizing.
231 * @param {!Element} element The element whose box-sizing to get.
232 * @return {?string} 'content-box', 'border-box' or 'padding-box'. null if
233 * box-sizing is not supported (IE7 and below).
234 */
235goog.style.getComputedBoxSizing = function(element) {
236 return goog.style.getStyle_(element, 'boxSizing') ||
237 goog.style.getStyle_(element, 'MozBoxSizing') ||
238 goog.style.getStyle_(element, 'WebkitBoxSizing') || null;
239};
240
241
242/**
243 * Retrieves the computed value of the position CSS attribute.
244 * @param {Element} element The element to get the position of.
245 * @return {string} Position value.
246 */
247goog.style.getComputedPosition = function(element) {
248 return goog.style.getStyle_(element, 'position');
249};
250
251
252/**
253 * Retrieves the computed background color string for a given element. The
254 * string returned is suitable for assigning to another element's
255 * background-color, but is not guaranteed to be in any particular string
256 * format. Accessing the color in a numeric form may not be possible in all
257 * browsers or with all input.
258 *
259 * If the background color for the element is defined as a hexadecimal value,
260 * the resulting string can be parsed by goog.color.parse in all supported
261 * browsers.
262 *
263 * Whether named colors like "red" or "lightblue" get translated into a
264 * format which can be parsed is browser dependent. Calling this function on
265 * transparent elements will return "transparent" in most browsers or
266 * "rgba(0, 0, 0, 0)" in WebKit.
267 * @param {Element} element The element to get the background color of.
268 * @return {string} The computed string value of the background color.
269 */
270goog.style.getBackgroundColor = function(element) {
271 return goog.style.getStyle_(element, 'backgroundColor');
272};
273
274
275/**
276 * Retrieves the computed value of the overflow-x CSS attribute.
277 * @param {Element} element The element to get the overflow-x of.
278 * @return {string} The computed string value of the overflow-x attribute.
279 */
280goog.style.getComputedOverflowX = function(element) {
281 return goog.style.getStyle_(element, 'overflowX');
282};
283
284
285/**
286 * Retrieves the computed value of the overflow-y CSS attribute.
287 * @param {Element} element The element to get the overflow-y of.
288 * @return {string} The computed string value of the overflow-y attribute.
289 */
290goog.style.getComputedOverflowY = function(element) {
291 return goog.style.getStyle_(element, 'overflowY');
292};
293
294
295/**
296 * Retrieves the computed value of the z-index CSS attribute.
297 * @param {Element} element The element to get the z-index of.
298 * @return {string|number} The computed value of the z-index attribute.
299 */
300goog.style.getComputedZIndex = function(element) {
301 return goog.style.getStyle_(element, 'zIndex');
302};
303
304
305/**
306 * Retrieves the computed value of the text-align CSS attribute.
307 * @param {Element} element The element to get the text-align of.
308 * @return {string} The computed string value of the text-align attribute.
309 */
310goog.style.getComputedTextAlign = function(element) {
311 return goog.style.getStyle_(element, 'textAlign');
312};
313
314
315/**
316 * Retrieves the computed value of the cursor CSS attribute.
317 * @param {Element} element The element to get the cursor of.
318 * @return {string} The computed string value of the cursor attribute.
319 */
320goog.style.getComputedCursor = function(element) {
321 return goog.style.getStyle_(element, 'cursor');
322};
323
324
325/**
326 * Retrieves the computed value of the CSS transform attribute.
327 * @param {Element} element The element to get the transform of.
328 * @return {string} The computed string representation of the transform matrix.
329 */
330goog.style.getComputedTransform = function(element) {
331 var property = goog.style.getVendorStyleName_(element, 'transform');
332 return goog.style.getStyle_(element, property) ||
333 goog.style.getStyle_(element, 'transform');
334};
335
336
337/**
338 * Sets the top/left values of an element. If no unit is specified in the
339 * argument then it will add px. The second argument is required if the first
340 * argument is a string or number and is ignored if the first argument
341 * is a coordinate.
342 * @param {Element} el Element to move.
343 * @param {string|number|goog.math.Coordinate} arg1 Left position or coordinate.
344 * @param {string|number=} opt_arg2 Top position.
345 */
346goog.style.setPosition = function(el, arg1, opt_arg2) {
347 var x, y;
348 var buggyGeckoSubPixelPos = goog.userAgent.GECKO &&
349 (goog.userAgent.MAC || goog.userAgent.X11) &&
350 goog.userAgent.isVersionOrHigher('1.9');
351
352 if (arg1 instanceof goog.math.Coordinate) {
353 x = arg1.x;
354 y = arg1.y;
355 } else {
356 x = arg1;
357 y = opt_arg2;
358 }
359
360 // Round to the nearest pixel for buggy sub-pixel support.
361 el.style.left = goog.style.getPixelStyleValue_(
362 /** @type {number|string} */ (x), buggyGeckoSubPixelPos);
363 el.style.top = goog.style.getPixelStyleValue_(
364 /** @type {number|string} */ (y), buggyGeckoSubPixelPos);
365};
366
367
368/**
369 * Gets the offsetLeft and offsetTop properties of an element and returns them
370 * in a Coordinate object
371 * @param {Element} element Element.
372 * @return {!goog.math.Coordinate} The position.
373 */
374goog.style.getPosition = function(element) {
375 return new goog.math.Coordinate(element.offsetLeft, element.offsetTop);
376};
377
378
379/**
380 * Returns the viewport element for a particular document
381 * @param {Node=} opt_node DOM node (Document is OK) to get the viewport element
382 * of.
383 * @return {Element} document.documentElement or document.body.
384 */
385goog.style.getClientViewportElement = function(opt_node) {
386 var doc;
387 if (opt_node) {
388 doc = goog.dom.getOwnerDocument(opt_node);
389 } else {
390 doc = goog.dom.getDocument();
391 }
392
393 // In old IE versions the document.body represented the viewport
394 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
395 !goog.dom.getDomHelper(doc).isCss1CompatMode()) {
396 return doc.body;
397 }
398 return doc.documentElement;
399};
400
401
402/**
403 * Calculates the viewport coordinates relative to the page/document
404 * containing the node. The viewport may be the browser viewport for
405 * non-iframe document, or the iframe container for iframe'd document.
406 * @param {!Document} doc The document to use as the reference point.
407 * @return {!goog.math.Coordinate} The page offset of the viewport.
408 */
409goog.style.getViewportPageOffset = function(doc) {
410 var body = doc.body;
411 var documentElement = doc.documentElement;
412 var scrollLeft = body.scrollLeft || documentElement.scrollLeft;
413 var scrollTop = body.scrollTop || documentElement.scrollTop;
414 return new goog.math.Coordinate(scrollLeft, scrollTop);
415};
416
417
418/**
419 * Gets the client rectangle of the DOM element.
420 *
421 * getBoundingClientRect is part of a new CSS object model draft (with a
422 * long-time presence in IE), replacing the error-prone parent offset
423 * computation and the now-deprecated Gecko getBoxObjectFor.
424 *
425 * This utility patches common browser bugs in getBoundingClientRect. It
426 * will fail if getBoundingClientRect is unsupported.
427 *
428 * If the element is not in the DOM, the result is undefined, and an error may
429 * be thrown depending on user agent.
430 *
431 * @param {!Element} el The element whose bounding rectangle is being queried.
432 * @return {Object} A native bounding rectangle with numerical left, top,
433 * right, and bottom. Reported by Firefox to be of object type ClientRect.
434 * @private
435 */
436goog.style.getBoundingClientRect_ = function(el) {
437 var rect;
438 try {
439 rect = el.getBoundingClientRect();
440 } catch (e) {
441 // In IE < 9, calling getBoundingClientRect on an orphan element raises an
442 // "Unspecified Error". All other browsers return zeros.
443 return {'left': 0, 'top': 0, 'right': 0, 'bottom': 0};
444 }
445
446 // Patch the result in IE only, so that this function can be inlined if
447 // compiled for non-IE.
448 if (goog.userAgent.IE && el.ownerDocument.body) {
449
450 // In IE, most of the time, 2 extra pixels are added to the top and left
451 // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and
452 // IE6 standards mode, this border can be overridden by setting the
453 // document element's border to zero -- thus, we cannot rely on the
454 // offset always being 2 pixels.
455
456 // In quirks mode, the offset can be determined by querying the body's
457 // clientLeft/clientTop, but in standards mode, it is found by querying
458 // the document element's clientLeft/clientTop. Since we already called
459 // getBoundingClientRect we have already forced a reflow, so it is not
460 // too expensive just to query them all.
461
462 // See: http://msdn.microsoft.com/en-us/library/ms536433(VS.85).aspx
463 var doc = el.ownerDocument;
464 rect.left -= doc.documentElement.clientLeft + doc.body.clientLeft;
465 rect.top -= doc.documentElement.clientTop + doc.body.clientTop;
466 }
467 return /** @type {Object} */ (rect);
468};
469
470
471/**
472 * Returns the first parent that could affect the position of a given element.
473 * @param {Element} element The element to get the offset parent for.
474 * @return {Element} The first offset parent or null if one cannot be found.
475 */
476goog.style.getOffsetParent = function(element) {
477 // element.offsetParent does the right thing in IE7 and below. In other
478 // browsers it only includes elements with position absolute, relative or
479 // fixed, not elements with overflow set to auto or scroll.
480 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(8)) {
481 return element.offsetParent;
482 }
483
484 var doc = goog.dom.getOwnerDocument(element);
485 var positionStyle = goog.style.getStyle_(element, 'position');
486 var skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute';
487 for (var parent = element.parentNode; parent && parent != doc;
488 parent = parent.parentNode) {
489 positionStyle =
490 goog.style.getStyle_(/** @type {!Element} */ (parent), 'position');
491 skipStatic = skipStatic && positionStyle == 'static' &&
492 parent != doc.documentElement && parent != doc.body;
493 if (!skipStatic && (parent.scrollWidth > parent.clientWidth ||
494 parent.scrollHeight > parent.clientHeight ||
495 positionStyle == 'fixed' ||
496 positionStyle == 'absolute' ||
497 positionStyle == 'relative')) {
498 return /** @type {!Element} */ (parent);
499 }
500 }
501 return null;
502};
503
504
505/**
506 * Calculates and returns the visible rectangle for a given element. Returns a
507 * box describing the visible portion of the nearest scrollable offset ancestor.
508 * Coordinates are given relative to the document.
509 *
510 * @param {Element} element Element to get the visible rect for.
511 * @return {goog.math.Box} Bounding elementBox describing the visible rect or
512 * null if scrollable ancestor isn't inside the visible viewport.
513 */
514goog.style.getVisibleRectForElement = function(element) {
515 var visibleRect = new goog.math.Box(0, Infinity, Infinity, 0);
516 var dom = goog.dom.getDomHelper(element);
517 var body = dom.getDocument().body;
518 var documentElement = dom.getDocument().documentElement;
519 var scrollEl = dom.getDocumentScrollElement();
520
521 // Determine the size of the visible rect by climbing the dom accounting for
522 // all scrollable containers.
523 for (var el = element; el = goog.style.getOffsetParent(el); ) {
524 // clientWidth is zero for inline block elements in IE.
525 // on WEBKIT, body element can have clientHeight = 0 and scrollHeight > 0
526 if ((!goog.userAgent.IE || el.clientWidth != 0) &&
527 (!goog.userAgent.WEBKIT || el.clientHeight != 0 || el != body) &&
528 // body may have overflow set on it, yet we still get the entire
529 // viewport. In some browsers, el.offsetParent may be
530 // document.documentElement, so check for that too.
531 (el != body && el != documentElement &&
532 goog.style.getStyle_(el, 'overflow') != 'visible')) {
533 var pos = goog.style.getPageOffset(el);
534 var client = goog.style.getClientLeftTop(el);
535 pos.x += client.x;
536 pos.y += client.y;
537
538 visibleRect.top = Math.max(visibleRect.top, pos.y);
539 visibleRect.right = Math.min(visibleRect.right,
540 pos.x + el.clientWidth);
541 visibleRect.bottom = Math.min(visibleRect.bottom,
542 pos.y + el.clientHeight);
543 visibleRect.left = Math.max(visibleRect.left, pos.x);
544 }
545 }
546
547 // Clip by window's viewport.
548 var scrollX = scrollEl.scrollLeft, scrollY = scrollEl.scrollTop;
549 visibleRect.left = Math.max(visibleRect.left, scrollX);
550 visibleRect.top = Math.max(visibleRect.top, scrollY);
551 var winSize = dom.getViewportSize();
552 visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width);
553 visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height);
554 return visibleRect.top >= 0 && visibleRect.left >= 0 &&
555 visibleRect.bottom > visibleRect.top &&
556 visibleRect.right > visibleRect.left ?
557 visibleRect : null;
558};
559
560
561/**
562 * Calculate the scroll position of {@code container} with the minimum amount so
563 * that the content and the borders of the given {@code element} become visible.
564 * If the element is bigger than the container, its top left corner will be
565 * aligned as close to the container's top left corner as possible.
566 *
567 * @param {Element} element The element to make visible.
568 * @param {Element} container The container to scroll.
569 * @param {boolean=} opt_center Whether to center the element in the container.
570 * Defaults to false.
571 * @return {!goog.math.Coordinate} The new scroll position of the container,
572 * in form of goog.math.Coordinate(scrollLeft, scrollTop).
573 */
574goog.style.getContainerOffsetToScrollInto =
575 function(element, container, opt_center) {
576 // Absolute position of the element's border's top left corner.
577 var elementPos = goog.style.getPageOffset(element);
578 // Absolute position of the container's border's top left corner.
579 var containerPos = goog.style.getPageOffset(container);
580 var containerBorder = goog.style.getBorderBox(container);
581 // Relative pos. of the element's border box to the container's content box.
582 var relX = elementPos.x - containerPos.x - containerBorder.left;
583 var relY = elementPos.y - containerPos.y - containerBorder.top;
584 // How much the element can move in the container, i.e. the difference between
585 // the element's bottom-right-most and top-left-most position where it's
586 // fully visible.
587 var spaceX = container.clientWidth - element.offsetWidth;
588 var spaceY = container.clientHeight - element.offsetHeight;
589
590 var scrollLeft = container.scrollLeft;
591 var scrollTop = container.scrollTop;
592 if (opt_center) {
593 // All browsers round non-integer scroll positions down.
594 scrollLeft += relX - spaceX / 2;
595 scrollTop += relY - spaceY / 2;
596 } else {
597 // This formula was designed to give the correct scroll values in the
598 // following cases:
599 // - element is higher than container (spaceY < 0) => scroll down by relY
600 // - element is not higher that container (spaceY >= 0):
601 // - it is above container (relY < 0) => scroll up by abs(relY)
602 // - it is below container (relY > spaceY) => scroll down by relY - spaceY
603 // - it is in the container => don't scroll
604 scrollLeft += Math.min(relX, Math.max(relX - spaceX, 0));
605 scrollTop += Math.min(relY, Math.max(relY - spaceY, 0));
606 }
607 return new goog.math.Coordinate(scrollLeft, scrollTop);
608};
609
610
611/**
612 * Changes the scroll position of {@code container} with the minimum amount so
613 * that the content and the borders of the given {@code element} become visible.
614 * If the element is bigger than the container, its top left corner will be
615 * aligned as close to the container's top left corner as possible.
616 *
617 * @param {Element} element The element to make visible.
618 * @param {Element} container The container to scroll.
619 * @param {boolean=} opt_center Whether to center the element in the container.
620 * Defaults to false.
621 */
622goog.style.scrollIntoContainerView = function(element, container, opt_center) {
623 var offset =
624 goog.style.getContainerOffsetToScrollInto(element, container, opt_center);
625 container.scrollLeft = offset.x;
626 container.scrollTop = offset.y;
627};
628
629
630/**
631 * Returns clientLeft (width of the left border and, if the directionality is
632 * right to left, the vertical scrollbar) and clientTop as a coordinate object.
633 *
634 * @param {Element} el Element to get clientLeft for.
635 * @return {!goog.math.Coordinate} Client left and top.
636 */
637goog.style.getClientLeftTop = function(el) {
638 // NOTE(eae): Gecko prior to 1.9 doesn't support clientTop/Left, see
639 // https://bugzilla.mozilla.org/show_bug.cgi?id=111207
640 if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher('1.9')) {
641 var left = parseFloat(goog.style.getComputedStyle(el, 'borderLeftWidth'));
642 if (goog.style.isRightToLeft(el)) {
643 var scrollbarWidth = el.offsetWidth - el.clientWidth - left -
644 parseFloat(goog.style.getComputedStyle(el, 'borderRightWidth'));
645 left += scrollbarWidth;
646 }
647 return new goog.math.Coordinate(left,
648 parseFloat(goog.style.getComputedStyle(el, 'borderTopWidth')));
649 }
650
651 return new goog.math.Coordinate(el.clientLeft, el.clientTop);
652};
653
654
655/**
656 * Returns a Coordinate object relative to the top-left of the HTML document.
657 * Implemented as a single function to save having to do two recursive loops in
658 * opera and safari just to get both coordinates. If you just want one value do
659 * use goog.style.getPageOffsetLeft() and goog.style.getPageOffsetTop(), but
660 * note if you call both those methods the tree will be analysed twice.
661 *
662 * @param {Element} el Element to get the page offset for.
663 * @return {!goog.math.Coordinate} The page offset.
664 */
665goog.style.getPageOffset = function(el) {
666 var box, doc = goog.dom.getOwnerDocument(el);
667 var positionStyle = goog.style.getStyle_(el, 'position');
668 // TODO(gboyer): Update the jsdoc in a way that doesn't break the universe.
669 goog.asserts.assertObject(el, 'Parameter is required');
670
671 // NOTE(eae): Gecko pre 1.9 normally use getBoxObjectFor to calculate the
672 // position. When invoked for an element with position absolute and a negative
673 // position though it can be off by one. Therefor the recursive implementation
674 // is used in those (relatively rare) cases.
675 var BUGGY_GECKO_BOX_OBJECT =
676 !goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS &&
677 goog.userAgent.GECKO && doc.getBoxObjectFor &&
678 !el.getBoundingClientRect && positionStyle == 'absolute' &&
679 (box = doc.getBoxObjectFor(el)) && (box.screenX < 0 || box.screenY < 0);
680
681 // NOTE(arv): If element is hidden (display none or disconnected or any the
682 // ancestors are hidden) we get (0,0) by default but we still do the
683 // accumulation of scroll position.
684
685 // TODO(arv): Should we check if the node is disconnected and in that case
686 // return (0,0)?
687
688 var pos = new goog.math.Coordinate(0, 0);
689 var viewportElement = goog.style.getClientViewportElement(doc);
690 if (el == viewportElement) {
691 // viewport is always at 0,0 as that defined the coordinate system for this
692 // function - this avoids special case checks in the code below
693 return pos;
694 }
695
696 // IE, Gecko 1.9+, and most modern WebKit.
697 if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
698 el.getBoundingClientRect) {
699 box = goog.style.getBoundingClientRect_(el);
700 // Must add the scroll coordinates in to get the absolute page offset
701 // of element since getBoundingClientRect returns relative coordinates to
702 // the viewport.
703 var scrollCoord = goog.dom.getDomHelper(doc).getDocumentScroll();
704 pos.x = box.left + scrollCoord.x;
705 pos.y = box.top + scrollCoord.y;
706
707 // Gecko prior to 1.9.
708 } else if (doc.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) {
709 // Gecko ignores the scroll values for ancestors, up to 1.9. See:
710 // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
711 // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
712
713 box = doc.getBoxObjectFor(el);
714 // TODO(user): Fix the off-by-one error when window is scrolled down
715 // or right more than 1 pixel. The viewport offset does not move in lock
716 // step with the window scroll; it moves in increments of 2px and at
717 // somewhat random intervals.
718 var vpBox = doc.getBoxObjectFor(viewportElement);
719 pos.x = box.screenX - vpBox.screenX;
720 pos.y = box.screenY - vpBox.screenY;
721
722 // Safari, Opera and Camino up to 1.0.4.
723 } else {
724 var parent = el;
725 do {
726 pos.x += parent.offsetLeft;
727 pos.y += parent.offsetTop;
728 // For safari/chrome, we need to add parent's clientLeft/Top as well.
729 if (parent != el) {
730 pos.x += parent.clientLeft || 0;
731 pos.y += parent.clientTop || 0;
732 }
733 // In Safari when hit a position fixed element the rest of the offsets
734 // are not correct.
735 if (goog.userAgent.WEBKIT &&
736 goog.style.getComputedPosition(parent) == 'fixed') {
737 pos.x += doc.body.scrollLeft;
738 pos.y += doc.body.scrollTop;
739 break;
740 }
741 parent = parent.offsetParent;
742 } while (parent && parent != el);
743
744 // Opera & (safari absolute) incorrectly account for body offsetTop.
745 if (goog.userAgent.OPERA || (goog.userAgent.WEBKIT &&
746 positionStyle == 'absolute')) {
747 pos.y -= doc.body.offsetTop;
748 }
749
750 for (parent = el; (parent = goog.style.getOffsetParent(parent)) &&
751 parent != doc.body && parent != viewportElement; ) {
752 pos.x -= parent.scrollLeft;
753 // Workaround for a bug in Opera 9.2 (and earlier) where table rows may
754 // report an invalid scroll top value. The bug was fixed in Opera 9.5
755 // however as that version supports getBoundingClientRect it won't
756 // trigger this code path. https://bugs.opera.com/show_bug.cgi?id=249965
757 if (!goog.userAgent.OPERA || parent.tagName != 'TR') {
758 pos.y -= parent.scrollTop;
759 }
760 }
761 }
762
763 return pos;
764};
765
766
767/**
768 * Returns the left coordinate of an element relative to the HTML document
769 * @param {Element} el Elements.
770 * @return {number} The left coordinate.
771 */
772goog.style.getPageOffsetLeft = function(el) {
773 return goog.style.getPageOffset(el).x;
774};
775
776
777/**
778 * Returns the top coordinate of an element relative to the HTML document
779 * @param {Element} el Elements.
780 * @return {number} The top coordinate.
781 */
782goog.style.getPageOffsetTop = function(el) {
783 return goog.style.getPageOffset(el).y;
784};
785
786
787/**
788 * Returns a Coordinate object relative to the top-left of an HTML document
789 * in an ancestor frame of this element. Used for measuring the position of
790 * an element inside a frame relative to a containing frame.
791 *
792 * @param {Element} el Element to get the page offset for.
793 * @param {Window} relativeWin The window to measure relative to. If relativeWin
794 * is not in the ancestor frame chain of the element, we measure relative to
795 * the top-most window.
796 * @return {!goog.math.Coordinate} The page offset.
797 */
798goog.style.getFramedPageOffset = function(el, relativeWin) {
799 var position = new goog.math.Coordinate(0, 0);
800
801 // Iterate up the ancestor frame chain, keeping track of the current window
802 // and the current element in that window.
803 var currentWin = goog.dom.getWindow(goog.dom.getOwnerDocument(el));
804 var currentEl = el;
805 do {
806 // if we're at the top window, we want to get the page offset.
807 // if we're at an inner frame, we only want to get the window position
808 // so that we can determine the actual page offset in the context of
809 // the outer window.
810 var offset = currentWin == relativeWin ?
811 goog.style.getPageOffset(currentEl) :
812 goog.style.getClientPositionForElement_(
813 goog.asserts.assert(currentEl));
814
815 position.x += offset.x;
816 position.y += offset.y;
817 } while (currentWin && currentWin != relativeWin &&
818 (currentEl = currentWin.frameElement) &&
819 (currentWin = currentWin.parent));
820
821 return position;
822};
823
824
825/**
826 * Translates the specified rect relative to origBase page, for newBase page.
827 * If origBase and newBase are the same, this function does nothing.
828 *
829 * @param {goog.math.Rect} rect The source rectangle relative to origBase page,
830 * and it will have the translated result.
831 * @param {goog.dom.DomHelper} origBase The DomHelper for the input rectangle.
832 * @param {goog.dom.DomHelper} newBase The DomHelper for the resultant
833 * coordinate. This must be a DOM for an ancestor frame of origBase
834 * or the same as origBase.
835 */
836goog.style.translateRectForAnotherFrame = function(rect, origBase, newBase) {
837 if (origBase.getDocument() != newBase.getDocument()) {
838 var body = origBase.getDocument().body;
839 var pos = goog.style.getFramedPageOffset(body, newBase.getWindow());
840
841 // Adjust Body's margin.
842 pos = goog.math.Coordinate.difference(pos, goog.style.getPageOffset(body));
843
844 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9) &&
845 !origBase.isCss1CompatMode()) {
846 pos = goog.math.Coordinate.difference(pos, origBase.getDocumentScroll());
847 }
848
849 rect.left += pos.x;
850 rect.top += pos.y;
851 }
852};
853
854
855/**
856 * Returns the position of an element relative to another element in the
857 * document. A relative to B
858 * @param {Element|Event|goog.events.Event} a Element or mouse event whose
859 * position we're calculating.
860 * @param {Element|Event|goog.events.Event} b Element or mouse event position
861 * is relative to.
862 * @return {!goog.math.Coordinate} The relative position.
863 */
864goog.style.getRelativePosition = function(a, b) {
865 var ap = goog.style.getClientPosition(a);
866 var bp = goog.style.getClientPosition(b);
867 return new goog.math.Coordinate(ap.x - bp.x, ap.y - bp.y);
868};
869
870
871/**
872 * Returns the position of the event or the element's border box relative to
873 * the client viewport.
874 * @param {!Element} el Element whose position to get.
875 * @return {!goog.math.Coordinate} The position.
876 * @private
877 */
878goog.style.getClientPositionForElement_ = function(el) {
879 var pos;
880 if (goog.style.GET_BOUNDING_CLIENT_RECT_ALWAYS_EXISTS ||
881 el.getBoundingClientRect) {
882 // IE, Gecko 1.9+, and most modern WebKit
883 var box = goog.style.getBoundingClientRect_(el);
884 pos = new goog.math.Coordinate(box.left, box.top);
885 } else {
886 var scrollCoord = goog.dom.getDomHelper(el).getDocumentScroll();
887 var pageCoord = goog.style.getPageOffset(el);
888 pos = new goog.math.Coordinate(
889 pageCoord.x - scrollCoord.x,
890 pageCoord.y - scrollCoord.y);
891 }
892
893 // Gecko below version 12 doesn't add CSS translation to the client position
894 // (using either getBoundingClientRect or getBoxOffsetFor) so we need to do
895 // so manually.
896 if (goog.userAgent.GECKO && !goog.userAgent.isVersionOrHigher(12)) {
897 return goog.math.Coordinate.sum(pos, goog.style.getCssTranslation(el));
898 } else {
899 return pos;
900 }
901};
902
903
904/**
905 * Returns the position of the event or the element's border box relative to
906 * the client viewport.
907 * @param {Element|Event|goog.events.Event} el Element or a mouse / touch event.
908 * @return {!goog.math.Coordinate} The position.
909 */
910goog.style.getClientPosition = function(el) {
911 goog.asserts.assert(el);
912 if (el.nodeType == goog.dom.NodeType.ELEMENT) {
913 return goog.style.getClientPositionForElement_(
914 /** @type {!Element} */ (el));
915 } else {
916 var isAbstractedEvent = goog.isFunction(el.getBrowserEvent);
917 var be = /** @type {!goog.events.BrowserEvent} */ (el);
918 var targetEvent = el;
919
920 if (el.targetTouches && el.targetTouches.length) {
921 targetEvent = el.targetTouches[0];
922 } else if (isAbstractedEvent && be.getBrowserEvent().targetTouches &&
923 be.getBrowserEvent().targetTouches.length) {
924 targetEvent = be.getBrowserEvent().targetTouches[0];
925 }
926
927 return new goog.math.Coordinate(
928 targetEvent.clientX,
929 targetEvent.clientY);
930 }
931};
932
933
934/**
935 * Moves an element to the given coordinates relative to the client viewport.
936 * @param {Element} el Absolutely positioned element to set page offset for.
937 * It must be in the document.
938 * @param {number|goog.math.Coordinate} x Left position of the element's margin
939 * box or a coordinate object.
940 * @param {number=} opt_y Top position of the element's margin box.
941 */
942goog.style.setPageOffset = function(el, x, opt_y) {
943 // Get current pageoffset
944 var cur = goog.style.getPageOffset(el);
945
946 if (x instanceof goog.math.Coordinate) {
947 opt_y = x.y;
948 x = x.x;
949 }
950
951 // NOTE(arv): We cannot allow strings for x and y. We could but that would
952 // require us to manually transform between different units
953
954 // Work out deltas
955 var dx = x - cur.x;
956 var dy = opt_y - cur.y;
957
958 // Set position to current left/top + delta
959 goog.style.setPosition(el, el.offsetLeft + dx, el.offsetTop + dy);
960};
961
962
963/**
964 * Sets the width/height values of an element. If an argument is numeric,
965 * or a goog.math.Size is passed, it is assumed to be pixels and will add
966 * 'px' after converting it to an integer in string form. (This just sets the
967 * CSS width and height properties so it might set content-box or border-box
968 * size depending on the box model the browser is using.)
969 *
970 * @param {Element} element Element to set the size of.
971 * @param {string|number|goog.math.Size} w Width of the element, or a
972 * size object.
973 * @param {string|number=} opt_h Height of the element. Required if w is not a
974 * size object.
975 */
976goog.style.setSize = function(element, w, opt_h) {
977 var h;
978 if (w instanceof goog.math.Size) {
979 h = w.height;
980 w = w.width;
981 } else {
982 if (opt_h == undefined) {
983 throw Error('missing height argument');
984 }
985 h = opt_h;
986 }
987
988 goog.style.setWidth(element, /** @type {string|number} */ (w));
989 goog.style.setHeight(element, /** @type {string|number} */ (h));
990};
991
992
993/**
994 * Helper function to create a string to be set into a pixel-value style
995 * property of an element. Can round to the nearest integer value.
996 *
997 * @param {string|number} value The style value to be used. If a number,
998 * 'px' will be appended, otherwise the value will be applied directly.
999 * @param {boolean} round Whether to round the nearest integer (if property
1000 * is a number).
1001 * @return {string} The string value for the property.
1002 * @private
1003 */
1004goog.style.getPixelStyleValue_ = function(value, round) {
1005 if (typeof value == 'number') {
1006 value = (round ? Math.round(value) : value) + 'px';
1007 }
1008
1009 return value;
1010};
1011
1012
1013/**
1014 * Set the height of an element. Sets the element's style property.
1015 * @param {Element} element Element to set the height of.
1016 * @param {string|number} height The height value to set. If a number, 'px'
1017 * will be appended, otherwise the value will be applied directly.
1018 */
1019goog.style.setHeight = function(element, height) {
1020 element.style.height = goog.style.getPixelStyleValue_(height, true);
1021};
1022
1023
1024/**
1025 * Set the width of an element. Sets the element's style property.
1026 * @param {Element} element Element to set the width of.
1027 * @param {string|number} width The width value to set. If a number, 'px'
1028 * will be appended, otherwise the value will be applied directly.
1029 */
1030goog.style.setWidth = function(element, width) {
1031 element.style.width = goog.style.getPixelStyleValue_(width, true);
1032};
1033
1034
1035/**
1036 * Gets the height and width of an element, even if its display is none.
1037 *
1038 * Specifically, this returns the height and width of the border box,
1039 * irrespective of the box model in effect.
1040 *
1041 * Note that this function does not take CSS transforms into account. Please see
1042 * {@code goog.style.getTransformedSize}.
1043 * @param {Element} element Element to get size of.
1044 * @return {!goog.math.Size} Object with width/height properties.
1045 */
1046goog.style.getSize = function(element) {
1047 return goog.style.evaluateWithTemporaryDisplay_(
1048 goog.style.getSizeWithDisplay_, /** @type {!Element} */ (element));
1049};
1050
1051
1052/**
1053 * Call {@code fn} on {@code element} such that {@code element}'s dimensions are
1054 * accurate when it's passed to {@code fn}.
1055 * @param {function(!Element): T} fn Function to call with {@code element} as
1056 * an argument after temporarily changing {@code element}'s display such
1057 * that its dimensions are accurate.
1058 * @param {!Element} element Element (which may have display none) to use as
1059 * argument to {@code fn}.
1060 * @return {T} Value returned by calling {@code fn} with {@code element}.
1061 * @template T
1062 * @private
1063 */
1064goog.style.evaluateWithTemporaryDisplay_ = function(fn, element) {
1065 if (goog.style.getStyle_(element, 'display') != 'none') {
1066 return fn(element);
1067 }
1068
1069 var style = element.style;
1070 var originalDisplay = style.display;
1071 var originalVisibility = style.visibility;
1072 var originalPosition = style.position;
1073
1074 style.visibility = 'hidden';
1075 style.position = 'absolute';
1076 style.display = 'inline';
1077
1078 var retVal = fn(element);
1079
1080 style.display = originalDisplay;
1081 style.position = originalPosition;
1082 style.visibility = originalVisibility;
1083
1084 return retVal;
1085};
1086
1087
1088/**
1089 * Gets the height and width of an element when the display is not none.
1090 * @param {Element} element Element to get size of.
1091 * @return {!goog.math.Size} Object with width/height properties.
1092 * @private
1093 */
1094goog.style.getSizeWithDisplay_ = function(element) {
1095 var offsetWidth = element.offsetWidth;
1096 var offsetHeight = element.offsetHeight;
1097 var webkitOffsetsZero =
1098 goog.userAgent.WEBKIT && !offsetWidth && !offsetHeight;
1099 if ((!goog.isDef(offsetWidth) || webkitOffsetsZero) &&
1100 element.getBoundingClientRect) {
1101 // Fall back to calling getBoundingClientRect when offsetWidth or
1102 // offsetHeight are not defined, or when they are zero in WebKit browsers.
1103 // This makes sure that we return for the correct size for SVG elements, but
1104 // will still return 0 on Webkit prior to 534.8, see
1105 // http://trac.webkit.org/changeset/67252.
1106 var clientRect = goog.style.getBoundingClientRect_(element);
1107 return new goog.math.Size(clientRect.right - clientRect.left,
1108 clientRect.bottom - clientRect.top);
1109 }
1110 return new goog.math.Size(offsetWidth, offsetHeight);
1111};
1112
1113
1114/**
1115 * Gets the height and width of an element, post transform, even if its display
1116 * is none.
1117 *
1118 * This is like {@code goog.style.getSize}, except:
1119 * <ol>
1120 * <li>Takes webkitTransforms such as rotate and scale into account.
1121 * <li>Will return null if {@code element} doesn't respond to
1122 * {@code getBoundingClientRect}.
1123 * <li>Currently doesn't make sense on non-WebKit browsers which don't support
1124 * webkitTransforms.
1125 * </ol>
1126 * @param {!Element} element Element to get size of.
1127 * @return {goog.math.Size} Object with width/height properties.
1128 */
1129goog.style.getTransformedSize = function(element) {
1130 if (!element.getBoundingClientRect) {
1131 return null;
1132 }
1133
1134 var clientRect = goog.style.evaluateWithTemporaryDisplay_(
1135 goog.style.getBoundingClientRect_, element);
1136 return new goog.math.Size(clientRect.right - clientRect.left,
1137 clientRect.bottom - clientRect.top);
1138};
1139
1140
1141/**
1142 * Returns a bounding rectangle for a given element in page space.
1143 * @param {Element} element Element to get bounds of. Must not be display none.
1144 * @return {!goog.math.Rect} Bounding rectangle for the element.
1145 */
1146goog.style.getBounds = function(element) {
1147 var o = goog.style.getPageOffset(element);
1148 var s = goog.style.getSize(element);
1149 return new goog.math.Rect(o.x, o.y, s.width, s.height);
1150};
1151
1152
1153/**
1154 * Converts a CSS selector in the form style-property to styleProperty.
1155 * @param {*} selector CSS Selector.
1156 * @return {string} Camel case selector.
1157 * @deprecated Use goog.string.toCamelCase instead.
1158 */
1159goog.style.toCamelCase = function(selector) {
1160 return goog.string.toCamelCase(String(selector));
1161};
1162
1163
1164/**
1165 * Converts a CSS selector in the form styleProperty to style-property.
1166 * @param {string} selector Camel case selector.
1167 * @return {string} Selector cased.
1168 * @deprecated Use goog.string.toSelectorCase instead.
1169 */
1170goog.style.toSelectorCase = function(selector) {
1171 return goog.string.toSelectorCase(selector);
1172};
1173
1174
1175/**
1176 * Gets the opacity of a node (x-browser). This gets the inline style opacity
1177 * of the node, and does not take into account the cascaded or the computed
1178 * style for this node.
1179 * @param {Element} el Element whose opacity has to be found.
1180 * @return {number|string} Opacity between 0 and 1 or an empty string {@code ''}
1181 * if the opacity is not set.
1182 */
1183goog.style.getOpacity = function(el) {
1184 var style = el.style;
1185 var result = '';
1186 if ('opacity' in style) {
1187 result = style.opacity;
1188 } else if ('MozOpacity' in style) {
1189 result = style.MozOpacity;
1190 } else if ('filter' in style) {
1191 var match = style.filter.match(/alpha\(opacity=([\d.]+)\)/);
1192 if (match) {
1193 result = String(match[1] / 100);
1194 }
1195 }
1196 return result == '' ? result : Number(result);
1197};
1198
1199
1200/**
1201 * Sets the opacity of a node (x-browser).
1202 * @param {Element} el Elements whose opacity has to be set.
1203 * @param {number|string} alpha Opacity between 0 and 1 or an empty string
1204 * {@code ''} to clear the opacity.
1205 */
1206goog.style.setOpacity = function(el, alpha) {
1207 var style = el.style;
1208 if ('opacity' in style) {
1209 style.opacity = alpha;
1210 } else if ('MozOpacity' in style) {
1211 style.MozOpacity = alpha;
1212 } else if ('filter' in style) {
1213 // TODO(arv): Overwriting the filter might have undesired side effects.
1214 if (alpha === '') {
1215 style.filter = '';
1216 } else {
1217 style.filter = 'alpha(opacity=' + alpha * 100 + ')';
1218 }
1219 }
1220};
1221
1222
1223/**
1224 * Sets the background of an element to a transparent image in a browser-
1225 * independent manner.
1226 *
1227 * This function does not support repeating backgrounds or alternate background
1228 * positions to match the behavior of Internet Explorer. It also does not
1229 * support sizingMethods other than crop since they cannot be replicated in
1230 * browsers other than Internet Explorer.
1231 *
1232 * @param {Element} el The element to set background on.
1233 * @param {string} src The image source URL.
1234 */
1235goog.style.setTransparentBackgroundImage = function(el, src) {
1236 var style = el.style;
1237 // It is safe to use the style.filter in IE only. In Safari 'filter' is in
1238 // style object but access to style.filter causes it to throw an exception.
1239 // Note: IE8 supports images with an alpha channel.
1240 if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
1241 // See TODO in setOpacity.
1242 style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
1243 'src="' + src + '", sizingMethod="crop")';
1244 } else {
1245 // Set style properties individually instead of using background shorthand
1246 // to prevent overwriting a pre-existing background color.
1247 style.backgroundImage = 'url(' + src + ')';
1248 style.backgroundPosition = 'top left';
1249 style.backgroundRepeat = 'no-repeat';
1250 }
1251};
1252
1253
1254/**
1255 * Clears the background image of an element in a browser independent manner.
1256 * @param {Element} el The element to clear background image for.
1257 */
1258goog.style.clearTransparentBackgroundImage = function(el) {
1259 var style = el.style;
1260 if ('filter' in style) {
1261 // See TODO in setOpacity.
1262 style.filter = '';
1263 } else {
1264 // Set style properties individually instead of using background shorthand
1265 // to prevent overwriting a pre-existing background color.
1266 style.backgroundImage = 'none';
1267 }
1268};
1269
1270
1271/**
1272 * Shows or hides an element from the page. Hiding the element is done by
1273 * setting the display property to "none", removing the element from the
1274 * rendering hierarchy so it takes up no space. To show the element, the default
1275 * inherited display property is restored (defined either in stylesheets or by
1276 * the browser's default style rules.)
1277 *
1278 * Caveat 1: if the inherited display property for the element is set to "none"
1279 * by the stylesheets, that is the property that will be restored by a call to
1280 * showElement(), effectively toggling the display between "none" and "none".
1281 *
1282 * Caveat 2: if the element display style is set inline (by setting either
1283 * element.style.display or a style attribute in the HTML), a call to
1284 * showElement will clear that setting and defer to the inherited style in the
1285 * stylesheet.
1286 * @param {Element} el Element to show or hide.
1287 * @param {*} display True to render the element in its default style,
1288 * false to disable rendering the element.
1289 * @deprecated Use goog.style.setElementShown instead.
1290 */
1291goog.style.showElement = function(el, display) {
1292 goog.style.setElementShown(el, display);
1293};
1294
1295
1296/**
1297 * Shows or hides an element from the page. Hiding the element is done by
1298 * setting the display property to "none", removing the element from the
1299 * rendering hierarchy so it takes up no space. To show the element, the default
1300 * inherited display property is restored (defined either in stylesheets or by
1301 * the browser's default style rules).
1302 *
1303 * Caveat 1: if the inherited display property for the element is set to "none"
1304 * by the stylesheets, that is the property that will be restored by a call to
1305 * setElementShown(), effectively toggling the display between "none" and
1306 * "none".
1307 *
1308 * Caveat 2: if the element display style is set inline (by setting either
1309 * element.style.display or a style attribute in the HTML), a call to
1310 * setElementShown will clear that setting and defer to the inherited style in
1311 * the stylesheet.
1312 * @param {Element} el Element to show or hide.
1313 * @param {*} isShown True to render the element in its default style,
1314 * false to disable rendering the element.
1315 */
1316goog.style.setElementShown = function(el, isShown) {
1317 el.style.display = isShown ? '' : 'none';
1318};
1319
1320
1321/**
1322 * Test whether the given element has been shown or hidden via a call to
1323 * {@link #setElementShown}.
1324 *
1325 * Note this is strictly a companion method for a call
1326 * to {@link #setElementShown} and the same caveats apply; in particular, this
1327 * method does not guarantee that the return value will be consistent with
1328 * whether or not the element is actually visible.
1329 *
1330 * @param {Element} el The element to test.
1331 * @return {boolean} Whether the element has been shown.
1332 * @see #setElementShown
1333 */
1334goog.style.isElementShown = function(el) {
1335 return el.style.display != 'none';
1336};
1337
1338
1339/**
1340 * Installs the styles string into the window that contains opt_element. If
1341 * opt_element is null, the main window is used.
1342 * @param {string} stylesString The style string to install.
1343 * @param {Node=} opt_node Node whose parent document should have the
1344 * styles installed.
1345 * @return {Element|StyleSheet} The style element created.
1346 */
1347goog.style.installStyles = function(stylesString, opt_node) {
1348 var dh = goog.dom.getDomHelper(opt_node);
1349 var styleSheet = null;
1350
1351 // IE < 11 requires createStyleSheet. Note that doc.createStyleSheet will be
1352 // undefined as of IE 11.
1353 var doc = dh.getDocument();
1354 if (goog.userAgent.IE && doc.createStyleSheet) {
1355 styleSheet = doc.createStyleSheet();
1356 goog.style.setStyles(styleSheet, stylesString);
1357 } else {
1358 var head = dh.getElementsByTagNameAndClass('head')[0];
1359
1360 // In opera documents are not guaranteed to have a head element, thus we
1361 // have to make sure one exists before using it.
1362 if (!head) {
1363 var body = dh.getElementsByTagNameAndClass('body')[0];
1364 head = dh.createDom('head');
1365 body.parentNode.insertBefore(head, body);
1366 }
1367 styleSheet = dh.createDom('style');
1368 // NOTE(user): Setting styles after the style element has been appended
1369 // to the head results in a nasty Webkit bug in certain scenarios. Please
1370 // refer to https://bugs.webkit.org/show_bug.cgi?id=26307 for additional
1371 // details.
1372 goog.style.setStyles(styleSheet, stylesString);
1373 dh.appendChild(head, styleSheet);
1374 }
1375 return styleSheet;
1376};
1377
1378
1379/**
1380 * Removes the styles added by {@link #installStyles}.
1381 * @param {Element|StyleSheet} styleSheet The value returned by
1382 * {@link #installStyles}.
1383 */
1384goog.style.uninstallStyles = function(styleSheet) {
1385 var node = styleSheet.ownerNode || styleSheet.owningElement ||
1386 /** @type {Element} */ (styleSheet);
1387 goog.dom.removeNode(node);
1388};
1389
1390
1391/**
1392 * Sets the content of a style element. The style element can be any valid
1393 * style element. This element will have its content completely replaced by
1394 * the new stylesString.
1395 * @param {Element|StyleSheet} element A stylesheet element as returned by
1396 * installStyles.
1397 * @param {string} stylesString The new content of the stylesheet.
1398 */
1399goog.style.setStyles = function(element, stylesString) {
1400 if (goog.userAgent.IE && goog.isDef(element.cssText)) {
1401 // Adding the selectors individually caused the browser to hang if the
1402 // selector was invalid or there were CSS comments. Setting the cssText of
1403 // the style node works fine and ignores CSS that IE doesn't understand.
1404 // However IE >= 11 doesn't support cssText any more, so we make sure that
1405 // cssText is a defined property and otherwise fall back to innerHTML.
1406 element.cssText = stylesString;
1407 } else {
1408 element.innerHTML = stylesString;
1409 }
1410};
1411
1412
1413/**
1414 * Sets 'white-space: pre-wrap' for a node (x-browser).
1415 *
1416 * There are as many ways of specifying pre-wrap as there are browsers.
1417 *
1418 * CSS3/IE8: white-space: pre-wrap;
1419 * Mozilla: white-space: -moz-pre-wrap;
1420 * Opera: white-space: -o-pre-wrap;
1421 * IE6/7: white-space: pre; word-wrap: break-word;
1422 *
1423 * @param {Element} el Element to enable pre-wrap for.
1424 */
1425goog.style.setPreWrap = function(el) {
1426 var style = el.style;
1427 if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
1428 style.whiteSpace = 'pre';
1429 style.wordWrap = 'break-word';
1430 } else if (goog.userAgent.GECKO) {
1431 style.whiteSpace = '-moz-pre-wrap';
1432 } else {
1433 style.whiteSpace = 'pre-wrap';
1434 }
1435};
1436
1437
1438/**
1439 * Sets 'display: inline-block' for an element (cross-browser).
1440 * @param {Element} el Element to which the inline-block display style is to be
1441 * applied.
1442 * @see ../demos/inline_block_quirks.html
1443 * @see ../demos/inline_block_standards.html
1444 */
1445goog.style.setInlineBlock = function(el) {
1446 var style = el.style;
1447 // Without position:relative, weirdness ensues. Just accept it and move on.
1448 style.position = 'relative';
1449
1450 if (goog.userAgent.IE && !goog.userAgent.isVersionOrHigher('8')) {
1451 // IE8 supports inline-block so fall through to the else
1452 // Zoom:1 forces hasLayout, display:inline gives inline behavior.
1453 style.zoom = '1';
1454 style.display = 'inline';
1455 } else if (goog.userAgent.GECKO) {
1456 // Pre-Firefox 3, Gecko doesn't support inline-block, but -moz-inline-box
1457 // is close enough.
1458 style.display = goog.userAgent.isVersionOrHigher('1.9a') ? 'inline-block' :
1459 '-moz-inline-box';
1460 } else {
1461 // Opera, Webkit, and Safari seem to do OK with the standard inline-block
1462 // style.
1463 style.display = 'inline-block';
1464 }
1465};
1466
1467
1468/**
1469 * Returns true if the element is using right to left (rtl) direction.
1470 * @param {Element} el The element to test.
1471 * @return {boolean} True for right to left, false for left to right.
1472 */
1473goog.style.isRightToLeft = function(el) {
1474 return 'rtl' == goog.style.getStyle_(el, 'direction');
1475};
1476
1477
1478/**
1479 * The CSS style property corresponding to an element being
1480 * unselectable on the current browser platform (null if none).
1481 * Opera and IE instead use a DOM attribute 'unselectable'.
1482 * @type {?string}
1483 * @private
1484 */
1485goog.style.unselectableStyle_ =
1486 goog.userAgent.GECKO ? 'MozUserSelect' :
1487 goog.userAgent.WEBKIT ? 'WebkitUserSelect' :
1488 null;
1489
1490
1491/**
1492 * Returns true if the element is set to be unselectable, false otherwise.
1493 * Note that on some platforms (e.g. Mozilla), even if an element isn't set
1494 * to be unselectable, it will behave as such if any of its ancestors is
1495 * unselectable.
1496 * @param {Element} el Element to check.
1497 * @return {boolean} Whether the element is set to be unselectable.
1498 */
1499goog.style.isUnselectable = function(el) {
1500 if (goog.style.unselectableStyle_) {
1501 return el.style[goog.style.unselectableStyle_].toLowerCase() == 'none';
1502 } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
1503 return el.getAttribute('unselectable') == 'on';
1504 }
1505 return false;
1506};
1507
1508
1509/**
1510 * Makes the element and its descendants selectable or unselectable. Note
1511 * that on some platforms (e.g. Mozilla), even if an element isn't set to
1512 * be unselectable, it will behave as such if any of its ancestors is
1513 * unselectable.
1514 * @param {Element} el The element to alter.
1515 * @param {boolean} unselectable Whether the element and its descendants
1516 * should be made unselectable.
1517 * @param {boolean=} opt_noRecurse Whether to only alter the element's own
1518 * selectable state, and leave its descendants alone; defaults to false.
1519 */
1520goog.style.setUnselectable = function(el, unselectable, opt_noRecurse) {
1521 // TODO(attila): Do we need all of TR_DomUtil.makeUnselectable() in Closure?
1522 var descendants = !opt_noRecurse ? el.getElementsByTagName('*') : null;
1523 var name = goog.style.unselectableStyle_;
1524 if (name) {
1525 // Add/remove the appropriate CSS style to/from the element and its
1526 // descendants.
1527 var value = unselectable ? 'none' : '';
1528 el.style[name] = value;
1529 if (descendants) {
1530 for (var i = 0, descendant; descendant = descendants[i]; i++) {
1531 descendant.style[name] = value;
1532 }
1533 }
1534 } else if (goog.userAgent.IE || goog.userAgent.OPERA) {
1535 // Toggle the 'unselectable' attribute on the element and its descendants.
1536 var value = unselectable ? 'on' : '';
1537 el.setAttribute('unselectable', value);
1538 if (descendants) {
1539 for (var i = 0, descendant; descendant = descendants[i]; i++) {
1540 descendant.setAttribute('unselectable', value);
1541 }
1542 }
1543 }
1544};
1545
1546
1547/**
1548 * Gets the border box size for an element.
1549 * @param {Element} element The element to get the size for.
1550 * @return {!goog.math.Size} The border box size.
1551 */
1552goog.style.getBorderBoxSize = function(element) {
1553 return new goog.math.Size(element.offsetWidth, element.offsetHeight);
1554};
1555
1556
1557/**
1558 * Sets the border box size of an element. This is potentially expensive in IE
1559 * if the document is CSS1Compat mode
1560 * @param {Element} element The element to set the size on.
1561 * @param {goog.math.Size} size The new size.
1562 */
1563goog.style.setBorderBoxSize = function(element, size) {
1564 var doc = goog.dom.getOwnerDocument(element);
1565 var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
1566
1567 if (goog.userAgent.IE &&
1568 !goog.userAgent.isVersionOrHigher('10') &&
1569 (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
1570 var style = element.style;
1571 if (isCss1CompatMode) {
1572 var paddingBox = goog.style.getPaddingBox(element);
1573 var borderBox = goog.style.getBorderBox(element);
1574 style.pixelWidth = size.width - borderBox.left - paddingBox.left -
1575 paddingBox.right - borderBox.right;
1576 style.pixelHeight = size.height - borderBox.top - paddingBox.top -
1577 paddingBox.bottom - borderBox.bottom;
1578 } else {
1579 style.pixelWidth = size.width;
1580 style.pixelHeight = size.height;
1581 }
1582 } else {
1583 goog.style.setBoxSizingSize_(element, size, 'border-box');
1584 }
1585};
1586
1587
1588/**
1589 * Gets the content box size for an element. This is potentially expensive in
1590 * all browsers.
1591 * @param {Element} element The element to get the size for.
1592 * @return {!goog.math.Size} The content box size.
1593 */
1594goog.style.getContentBoxSize = function(element) {
1595 var doc = goog.dom.getOwnerDocument(element);
1596 var ieCurrentStyle = goog.userAgent.IE && element.currentStyle;
1597 if (ieCurrentStyle &&
1598 goog.dom.getDomHelper(doc).isCss1CompatMode() &&
1599 ieCurrentStyle.width != 'auto' && ieCurrentStyle.height != 'auto' &&
1600 !ieCurrentStyle.boxSizing) {
1601 // If IE in CSS1Compat mode than just use the width and height.
1602 // If we have a boxSizing then fall back on measuring the borders etc.
1603 var width = goog.style.getIePixelValue_(element, ieCurrentStyle.width,
1604 'width', 'pixelWidth');
1605 var height = goog.style.getIePixelValue_(element, ieCurrentStyle.height,
1606 'height', 'pixelHeight');
1607 return new goog.math.Size(width, height);
1608 } else {
1609 var borderBoxSize = goog.style.getBorderBoxSize(element);
1610 var paddingBox = goog.style.getPaddingBox(element);
1611 var borderBox = goog.style.getBorderBox(element);
1612 return new goog.math.Size(borderBoxSize.width -
1613 borderBox.left - paddingBox.left -
1614 paddingBox.right - borderBox.right,
1615 borderBoxSize.height -
1616 borderBox.top - paddingBox.top -
1617 paddingBox.bottom - borderBox.bottom);
1618 }
1619};
1620
1621
1622/**
1623 * Sets the content box size of an element. This is potentially expensive in IE
1624 * if the document is BackCompat mode.
1625 * @param {Element} element The element to set the size on.
1626 * @param {goog.math.Size} size The new size.
1627 */
1628goog.style.setContentBoxSize = function(element, size) {
1629 var doc = goog.dom.getOwnerDocument(element);
1630 var isCss1CompatMode = goog.dom.getDomHelper(doc).isCss1CompatMode();
1631 if (goog.userAgent.IE &&
1632 !goog.userAgent.isVersionOrHigher('10') &&
1633 (!isCss1CompatMode || !goog.userAgent.isVersionOrHigher('8'))) {
1634 var style = element.style;
1635 if (isCss1CompatMode) {
1636 style.pixelWidth = size.width;
1637 style.pixelHeight = size.height;
1638 } else {
1639 var paddingBox = goog.style.getPaddingBox(element);
1640 var borderBox = goog.style.getBorderBox(element);
1641 style.pixelWidth = size.width + borderBox.left + paddingBox.left +
1642 paddingBox.right + borderBox.right;
1643 style.pixelHeight = size.height + borderBox.top + paddingBox.top +
1644 paddingBox.bottom + borderBox.bottom;
1645 }
1646 } else {
1647 goog.style.setBoxSizingSize_(element, size, 'content-box');
1648 }
1649};
1650
1651
1652/**
1653 * Helper function that sets the box sizing as well as the width and height
1654 * @param {Element} element The element to set the size on.
1655 * @param {goog.math.Size} size The new size to set.
1656 * @param {string} boxSizing The box-sizing value.
1657 * @private
1658 */
1659goog.style.setBoxSizingSize_ = function(element, size, boxSizing) {
1660 var style = element.style;
1661 if (goog.userAgent.GECKO) {
1662 style.MozBoxSizing = boxSizing;
1663 } else if (goog.userAgent.WEBKIT) {
1664 style.WebkitBoxSizing = boxSizing;
1665 } else {
1666 // Includes IE8 and Opera 9.50+
1667 style.boxSizing = boxSizing;
1668 }
1669
1670 // Setting this to a negative value will throw an exception on IE
1671 // (and doesn't do anything different than setting it to 0).
1672 style.width = Math.max(size.width, 0) + 'px';
1673 style.height = Math.max(size.height, 0) + 'px';
1674};
1675
1676
1677/**
1678 * IE specific function that converts a non pixel unit to pixels.
1679 * @param {Element} element The element to convert the value for.
1680 * @param {string} value The current value as a string. The value must not be
1681 * ''.
1682 * @param {string} name The CSS property name to use for the converstion. This
1683 * should be 'left', 'top', 'width' or 'height'.
1684 * @param {string} pixelName The CSS pixel property name to use to get the
1685 * value in pixels.
1686 * @return {number} The value in pixels.
1687 * @private
1688 */
1689goog.style.getIePixelValue_ = function(element, value, name, pixelName) {
1690 // Try if we already have a pixel value. IE does not do half pixels so we
1691 // only check if it matches a number followed by 'px'.
1692 if (/^\d+px?$/.test(value)) {
1693 return parseInt(value, 10);
1694 } else {
1695 var oldStyleValue = element.style[name];
1696 var oldRuntimeValue = element.runtimeStyle[name];
1697 // set runtime style to prevent changes
1698 element.runtimeStyle[name] = element.currentStyle[name];
1699 element.style[name] = value;
1700 var pixelValue = element.style[pixelName];
1701 // restore
1702 element.style[name] = oldStyleValue;
1703 element.runtimeStyle[name] = oldRuntimeValue;
1704 return pixelValue;
1705 }
1706};
1707
1708
1709/**
1710 * Helper function for getting the pixel padding or margin for IE.
1711 * @param {Element} element The element to get the padding for.
1712 * @param {string} propName The property name.
1713 * @return {number} The pixel padding.
1714 * @private
1715 */
1716goog.style.getIePixelDistance_ = function(element, propName) {
1717 var value = goog.style.getCascadedStyle(element, propName);
1718 return value ?
1719 goog.style.getIePixelValue_(element, value, 'left', 'pixelLeft') : 0;
1720};
1721
1722
1723/**
1724 * Gets the computed paddings or margins (on all sides) in pixels.
1725 * @param {Element} element The element to get the padding for.
1726 * @param {string} stylePrefix Pass 'padding' to retrieve the padding box,
1727 * or 'margin' to retrieve the margin box.
1728 * @return {!goog.math.Box} The computed paddings or margins.
1729 * @private
1730 */
1731goog.style.getBox_ = function(element, stylePrefix) {
1732 if (goog.userAgent.IE) {
1733 var left = goog.style.getIePixelDistance_(element, stylePrefix + 'Left');
1734 var right = goog.style.getIePixelDistance_(element, stylePrefix + 'Right');
1735 var top = goog.style.getIePixelDistance_(element, stylePrefix + 'Top');
1736 var bottom = goog.style.getIePixelDistance_(
1737 element, stylePrefix + 'Bottom');
1738 return new goog.math.Box(top, right, bottom, left);
1739 } else {
1740 // On non-IE browsers, getComputedStyle is always non-null.
1741 var left = /** @type {string} */ (
1742 goog.style.getComputedStyle(element, stylePrefix + 'Left'));
1743 var right = /** @type {string} */ (
1744 goog.style.getComputedStyle(element, stylePrefix + 'Right'));
1745 var top = /** @type {string} */ (
1746 goog.style.getComputedStyle(element, stylePrefix + 'Top'));
1747 var bottom = /** @type {string} */ (
1748 goog.style.getComputedStyle(element, stylePrefix + 'Bottom'));
1749
1750 // NOTE(arv): Gecko can return floating point numbers for the computed
1751 // style values.
1752 return new goog.math.Box(parseFloat(top),
1753 parseFloat(right),
1754 parseFloat(bottom),
1755 parseFloat(left));
1756 }
1757};
1758
1759
1760/**
1761 * Gets the computed paddings (on all sides) in pixels.
1762 * @param {Element} element The element to get the padding for.
1763 * @return {!goog.math.Box} The computed paddings.
1764 */
1765goog.style.getPaddingBox = function(element) {
1766 return goog.style.getBox_(element, 'padding');
1767};
1768
1769
1770/**
1771 * Gets the computed margins (on all sides) in pixels.
1772 * @param {Element} element The element to get the margins for.
1773 * @return {!goog.math.Box} The computed margins.
1774 */
1775goog.style.getMarginBox = function(element) {
1776 return goog.style.getBox_(element, 'margin');
1777};
1778
1779
1780/**
1781 * A map used to map the border width keywords to a pixel width.
1782 * @type {Object}
1783 * @private
1784 */
1785goog.style.ieBorderWidthKeywords_ = {
1786 'thin': 2,
1787 'medium': 4,
1788 'thick': 6
1789};
1790
1791
1792/**
1793 * Helper function for IE to get the pixel border.
1794 * @param {Element} element The element to get the pixel border for.
1795 * @param {string} prop The part of the property name.
1796 * @return {number} The value in pixels.
1797 * @private
1798 */
1799goog.style.getIePixelBorder_ = function(element, prop) {
1800 if (goog.style.getCascadedStyle(element, prop + 'Style') == 'none') {
1801 return 0;
1802 }
1803 var width = goog.style.getCascadedStyle(element, prop + 'Width');
1804 if (width in goog.style.ieBorderWidthKeywords_) {
1805 return goog.style.ieBorderWidthKeywords_[width];
1806 }
1807 return goog.style.getIePixelValue_(element, width, 'left', 'pixelLeft');
1808};
1809
1810
1811/**
1812 * Gets the computed border widths (on all sides) in pixels
1813 * @param {Element} element The element to get the border widths for.
1814 * @return {!goog.math.Box} The computed border widths.
1815 */
1816goog.style.getBorderBox = function(element) {
1817 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(9)) {
1818 var left = goog.style.getIePixelBorder_(element, 'borderLeft');
1819 var right = goog.style.getIePixelBorder_(element, 'borderRight');
1820 var top = goog.style.getIePixelBorder_(element, 'borderTop');
1821 var bottom = goog.style.getIePixelBorder_(element, 'borderBottom');
1822 return new goog.math.Box(top, right, bottom, left);
1823 } else {
1824 // On non-IE browsers, getComputedStyle is always non-null.
1825 var left = /** @type {string} */ (
1826 goog.style.getComputedStyle(element, 'borderLeftWidth'));
1827 var right = /** @type {string} */ (
1828 goog.style.getComputedStyle(element, 'borderRightWidth'));
1829 var top = /** @type {string} */ (
1830 goog.style.getComputedStyle(element, 'borderTopWidth'));
1831 var bottom = /** @type {string} */ (
1832 goog.style.getComputedStyle(element, 'borderBottomWidth'));
1833
1834 return new goog.math.Box(parseFloat(top),
1835 parseFloat(right),
1836 parseFloat(bottom),
1837 parseFloat(left));
1838 }
1839};
1840
1841
1842/**
1843 * Returns the font face applied to a given node. Opera and IE should return
1844 * the font actually displayed. Firefox returns the author's most-preferred
1845 * font (whether the browser is capable of displaying it or not.)
1846 * @param {Element} el The element whose font family is returned.
1847 * @return {string} The font family applied to el.
1848 */
1849goog.style.getFontFamily = function(el) {
1850 var doc = goog.dom.getOwnerDocument(el);
1851 var font = '';
1852 // The moveToElementText method from the TextRange only works if the element
1853 // is attached to the owner document.
1854 if (doc.body.createTextRange && goog.dom.contains(doc, el)) {
1855 var range = doc.body.createTextRange();
1856 range.moveToElementText(el);
1857 /** @preserveTry */
1858 try {
1859 font = range.queryCommandValue('FontName');
1860 } catch (e) {
1861 // This is a workaround for a awkward exception.
1862 // On some IE, there is an exception coming from it.
1863 // The error description from this exception is:
1864 // This window has already been registered as a drop target
1865 // This is bogus description, likely due to a bug in ie.
1866 font = '';
1867 }
1868 }
1869 if (!font) {
1870 // Note if for some reason IE can't derive FontName with a TextRange, we
1871 // fallback to using currentStyle
1872 font = goog.style.getStyle_(el, 'fontFamily');
1873 }
1874
1875 // Firefox returns the applied font-family string (author's list of
1876 // preferred fonts.) We want to return the most-preferred font, in lieu of
1877 // the *actually* applied font.
1878 var fontsArray = font.split(',');
1879 if (fontsArray.length > 1) font = fontsArray[0];
1880
1881 // Sanitize for x-browser consistency:
1882 // Strip quotes because browsers aren't consistent with how they're
1883 // applied; Opera always encloses, Firefox sometimes, and IE never.
1884 return goog.string.stripQuotes(font, '"\'');
1885};
1886
1887
1888/**
1889 * Regular expression used for getLengthUnits.
1890 * @type {RegExp}
1891 * @private
1892 */
1893goog.style.lengthUnitRegex_ = /[^\d]+$/;
1894
1895
1896/**
1897 * Returns the units used for a CSS length measurement.
1898 * @param {string} value A CSS length quantity.
1899 * @return {?string} The units of measurement.
1900 */
1901goog.style.getLengthUnits = function(value) {
1902 var units = value.match(goog.style.lengthUnitRegex_);
1903 return units && units[0] || null;
1904};
1905
1906
1907/**
1908 * Map of absolute CSS length units
1909 * @type {Object}
1910 * @private
1911 */
1912goog.style.ABSOLUTE_CSS_LENGTH_UNITS_ = {
1913 'cm' : 1,
1914 'in' : 1,
1915 'mm' : 1,
1916 'pc' : 1,
1917 'pt' : 1
1918};
1919
1920
1921/**
1922 * Map of relative CSS length units that can be accurately converted to px
1923 * font-size values using getIePixelValue_. Only units that are defined in
1924 * relation to a font size are convertible (%, small, etc. are not).
1925 * @type {Object}
1926 * @private
1927 */
1928goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_ = {
1929 'em' : 1,
1930 'ex' : 1
1931};
1932
1933
1934/**
1935 * Returns the font size, in pixels, of text in an element.
1936 * @param {Element} el The element whose font size is returned.
1937 * @return {number} The font size (in pixels).
1938 */
1939goog.style.getFontSize = function(el) {
1940 var fontSize = goog.style.getStyle_(el, 'fontSize');
1941 var sizeUnits = goog.style.getLengthUnits(fontSize);
1942 if (fontSize && 'px' == sizeUnits) {
1943 // NOTE(user): This could be parseFloat instead, but IE doesn't return
1944 // decimal fractions in getStyle_ and Firefox reports the fractions, but
1945 // ignores them when rendering. Interestingly enough, when we force the
1946 // issue and size something to e.g., 50% of 25px, the browsers round in
1947 // opposite directions with Firefox reporting 12px and IE 13px. I punt.
1948 return parseInt(fontSize, 10);
1949 }
1950
1951 // In IE, we can convert absolute length units to a px value using
1952 // goog.style.getIePixelValue_. Units defined in relation to a font size
1953 // (em, ex) are applied relative to the element's parentNode and can also
1954 // be converted.
1955 if (goog.userAgent.IE) {
1956 if (sizeUnits in goog.style.ABSOLUTE_CSS_LENGTH_UNITS_) {
1957 return goog.style.getIePixelValue_(el,
1958 fontSize,
1959 'left',
1960 'pixelLeft');
1961 } else if (el.parentNode &&
1962 el.parentNode.nodeType == goog.dom.NodeType.ELEMENT &&
1963 sizeUnits in goog.style.CONVERTIBLE_RELATIVE_CSS_UNITS_) {
1964 // Check the parent size - if it is the same it means the relative size
1965 // value is inherited and we therefore don't want to count it twice. If
1966 // it is different, this element either has explicit style or has a CSS
1967 // rule applying to it.
1968 var parentElement = /** @type {Element} */ (el.parentNode);
1969 var parentSize = goog.style.getStyle_(parentElement, 'fontSize');
1970 return goog.style.getIePixelValue_(parentElement,
1971 fontSize == parentSize ?
1972 '1em' : fontSize,
1973 'left',
1974 'pixelLeft');
1975 }
1976 }
1977
1978 // Sometimes we can't cleanly find the font size (some units relative to a
1979 // node's parent's font size are difficult: %, smaller et al), so we create
1980 // an invisible, absolutely-positioned span sized to be the height of an 'M'
1981 // rendered in its parent's (i.e., our target element's) font size. This is
1982 // the definition of CSS's font size attribute.
1983 var sizeElement = goog.dom.createDom(
1984 'span',
1985 {'style': 'visibility:hidden;position:absolute;' +
1986 'line-height:0;padding:0;margin:0;border:0;height:1em;'});
1987 goog.dom.appendChild(el, sizeElement);
1988 fontSize = sizeElement.offsetHeight;
1989 goog.dom.removeNode(sizeElement);
1990
1991 return fontSize;
1992};
1993
1994
1995/**
1996 * Parses a style attribute value. Converts CSS property names to camel case.
1997 * @param {string} value The style attribute value.
1998 * @return {!Object} Map of CSS properties to string values.
1999 */
2000goog.style.parseStyleAttribute = function(value) {
2001 var result = {};
2002 goog.array.forEach(value.split(/\s*;\s*/), function(pair) {
2003 var keyValue = pair.split(/\s*:\s*/);
2004 if (keyValue.length == 2) {
2005 result[goog.string.toCamelCase(keyValue[0].toLowerCase())] = keyValue[1];
2006 }
2007 });
2008 return result;
2009};
2010
2011
2012/**
2013 * Reverse of parseStyleAttribute; that is, takes a style object and returns the
2014 * corresponding attribute value. Converts camel case property names to proper
2015 * CSS selector names.
2016 * @param {Object} obj Map of CSS properties to values.
2017 * @return {string} The style attribute value.
2018 */
2019goog.style.toStyleAttribute = function(obj) {
2020 var buffer = [];
2021 goog.object.forEach(obj, function(value, key) {
2022 buffer.push(goog.string.toSelectorCase(key), ':', value, ';');
2023 });
2024 return buffer.join('');
2025};
2026
2027
2028/**
2029 * Sets CSS float property on an element.
2030 * @param {Element} el The element to set float property on.
2031 * @param {string} value The value of float CSS property to set on this element.
2032 */
2033goog.style.setFloat = function(el, value) {
2034 el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] = value;
2035};
2036
2037
2038/**
2039 * Gets value of explicitly-set float CSS property on an element.
2040 * @param {Element} el The element to get float property of.
2041 * @return {string} The value of explicitly-set float CSS property on this
2042 * element.
2043 */
2044goog.style.getFloat = function(el) {
2045 return el.style[goog.userAgent.IE ? 'styleFloat' : 'cssFloat'] || '';
2046};
2047
2048
2049/**
2050 * Returns the scroll bar width (represents the width of both horizontal
2051 * and vertical scroll).
2052 *
2053 * @param {string=} opt_className An optional class name (or names) to apply
2054 * to the invisible div created to measure the scrollbar. This is necessary
2055 * if some scrollbars are styled differently than others.
2056 * @return {number} The scroll bar width in px.
2057 */
2058goog.style.getScrollbarWidth = function(opt_className) {
2059 // Add two hidden divs. The child div is larger than the parent and
2060 // forces scrollbars to appear on it.
2061 // Using overflow:scroll does not work consistently with scrollbars that
2062 // are styled with ::-webkit-scrollbar.
2063 var outerDiv = goog.dom.createElement('div');
2064 if (opt_className) {
2065 outerDiv.className = opt_className;
2066 }
2067 outerDiv.style.cssText = 'overflow:auto;' +
2068 'position:absolute;top:0;width:100px;height:100px';
2069 var innerDiv = goog.dom.createElement('div');
2070 goog.style.setSize(innerDiv, '200px', '200px');
2071 outerDiv.appendChild(innerDiv);
2072 goog.dom.appendChild(goog.dom.getDocument().body, outerDiv);
2073 var width = outerDiv.offsetWidth - outerDiv.clientWidth;
2074 goog.dom.removeNode(outerDiv);
2075 return width;
2076};
2077
2078
2079/**
2080 * Regular expression to extract x and y translation components from a CSS
2081 * transform Matrix representation.
2082 *
2083 * @type {!RegExp}
2084 * @const
2085 * @private
2086 */
2087goog.style.MATRIX_TRANSLATION_REGEX_ =
2088 new RegExp('matrix\\([0-9\\.\\-]+, [0-9\\.\\-]+, ' +
2089 '[0-9\\.\\-]+, [0-9\\.\\-]+, ' +
2090 '([0-9\\.\\-]+)p?x?, ([0-9\\.\\-]+)p?x?\\)');
2091
2092
2093/**
2094 * Returns the x,y translation component of any CSS transforms applied to the
2095 * element, in pixels.
2096 *
2097 * @param {!Element} element The element to get the translation of.
2098 * @return {!goog.math.Coordinate} The CSS translation of the element in px.
2099 */
2100goog.style.getCssTranslation = function(element) {
2101 var transform = goog.style.getComputedTransform(element);
2102 if (!transform) {
2103 return new goog.math.Coordinate(0, 0);
2104 }
2105 var matches = transform.match(goog.style.MATRIX_TRANSLATION_REGEX_);
2106 if (!matches) {
2107 return new goog.math.Coordinate(0, 0);
2108 }
2109 return new goog.math.Coordinate(parseFloat(matches[1]),
2110 parseFloat(matches[2]));
2111};