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