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 | |
23 | goog.provide('goog.style'); |
24 | |
25 | |
26 | goog.require('goog.array'); |
27 | goog.require('goog.asserts'); |
28 | goog.require('goog.dom'); |
29 | goog.require('goog.dom.NodeType'); |
30 | goog.require('goog.dom.vendor'); |
31 | goog.require('goog.math.Box'); |
32 | goog.require('goog.math.Coordinate'); |
33 | goog.require('goog.math.Rect'); |
34 | goog.require('goog.math.Size'); |
35 | goog.require('goog.object'); |
36 | goog.require('goog.string'); |
37 | goog.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 | */ |
44 | goog.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 | */ |
63 | goog.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 | */ |
83 | goog.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 | */ |
101 | goog.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 | */ |
126 | goog.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 | */ |
152 | goog.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 | */ |
180 | goog.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 | */ |
203 | goog.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 | */ |
221 | goog.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 | */ |
235 | goog.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 | */ |
247 | goog.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 | */ |
270 | goog.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 | */ |
280 | goog.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 | */ |
290 | goog.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 | */ |
300 | goog.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 | */ |
310 | goog.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 | */ |
320 | goog.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 | */ |
330 | goog.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 | */ |
346 | goog.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 | */ |
374 | goog.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 | */ |
385 | goog.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 | */ |
409 | goog.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 | */ |
436 | goog.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 | */ |
476 | goog.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 | */ |
514 | goog.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 | */ |
574 | goog.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 | */ |
622 | goog.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 | */ |
637 | goog.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 | */ |
665 | goog.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 | */ |
772 | goog.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 | */ |
782 | goog.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 | */ |
798 | goog.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 | */ |
836 | goog.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 | */ |
864 | goog.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 | */ |
878 | goog.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 | */ |
910 | goog.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 | */ |
942 | goog.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 | */ |
976 | goog.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 | */ |
1004 | goog.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 | */ |
1019 | goog.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 | */ |
1030 | goog.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 | */ |
1046 | goog.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 | */ |
1064 | goog.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 | */ |
1094 | goog.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 | */ |
1129 | goog.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 | */ |
1146 | goog.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 | */ |
1159 | goog.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 | */ |
1170 | goog.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 | */ |
1183 | goog.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 | */ |
1206 | goog.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 | */ |
1235 | goog.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 | */ |
1258 | goog.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 | */ |
1291 | goog.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 | */ |
1316 | goog.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 | */ |
1334 | goog.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 | */ |
1347 | goog.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 | */ |
1384 | goog.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 | */ |
1399 | goog.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 | */ |
1425 | goog.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 | */ |
1445 | goog.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 | */ |
1473 | goog.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 | */ |
1485 | goog.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 | */ |
1499 | goog.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 | */ |
1520 | goog.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 | */ |
1552 | goog.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 | */ |
1563 | goog.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 | */ |
1594 | goog.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 | */ |
1628 | goog.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 | */ |
1659 | goog.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 | */ |
1689 | goog.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 | */ |
1716 | goog.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 | */ |
1731 | goog.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 | */ |
1765 | goog.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 | */ |
1775 | goog.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 | */ |
1785 | goog.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 | */ |
1799 | goog.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 | */ |
1816 | goog.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 | */ |
1849 | goog.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 | */ |
1893 | goog.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 | */ |
1901 | goog.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 | */ |
1912 | goog.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 | */ |
1928 | goog.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 | */ |
1939 | goog.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 | */ |
2000 | goog.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 | */ |
2019 | goog.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 | */ |
2033 | goog.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 | */ |
2044 | goog.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 | */ |
2058 | goog.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 | */ |
2087 | goog.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 | */ |
2100 | goog.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 | }; |