|
9 | 9 | return (Number(match[1]) / 100);
|
10 | 10 | }
|
11 | 11 |
|
| 12 | + // A bare-bones version of Element.getStyle. Needed because getStyle is |
| 13 | + // public-facing and too user-friendly for our tastes. We need raw, |
| 14 | + // non-normalized values. |
| 15 | + // |
| 16 | + // Camel-cased property names only. |
| 17 | + function getRawStyle(element, style) { |
| 18 | + element = $(element); |
| 19 | + |
| 20 | + // Try inline styles first. |
| 21 | + var value = element.style[style]; |
| 22 | + if (!value || value === 'auto') { |
| 23 | + // Reluctantly retrieve the computed style. |
| 24 | + var css = document.defaultView.getComputedStyle(element, null); |
| 25 | + value = css ? css[style] : null; |
| 26 | + } |
| 27 | + |
| 28 | + if (style === 'opacity') return value ? parseFloat(value) : 1.0; |
| 29 | + return value === 'auto' ? null : value; |
| 30 | + } |
| 31 | + |
| 32 | + function getRawStyle_IE(element, style) { |
| 33 | + // Try inline styles first. |
| 34 | + var value = element.style[style]; |
| 35 | + if (!value && element.currentStyle) { |
| 36 | + // Reluctantly retrieve the current style. |
| 37 | + value = element.currentStyle[style]; |
| 38 | + } |
| 39 | + return value; |
| 40 | + } |
| 41 | + |
| 42 | + // Quickly figures out the content width of an element. Used instead of |
| 43 | + // `element.measure('width')` in several places below; we don't want to |
| 44 | + // call back into layout code recursively if we don't have to. |
| 45 | + // |
| 46 | + // But this means it doesn't handle edge cases. Use it when you know the |
| 47 | + // element in question is visible and will give accurate measurements. |
| 48 | + function getContentWidth(element, context) { |
| 49 | + var boxWidth = element.offsetWidth; |
| 50 | + |
| 51 | + var bl = getPixelValue(element, 'borderLeftWidth', context) || 0; |
| 52 | + var br = getPixelValue(element, 'borderRightWidth', context) || 0; |
| 53 | + var pl = getPixelValue(element, 'paddingLeft', context) || 0; |
| 54 | + var pr = getPixelValue(element, 'paddingRight', context) || 0; |
| 55 | + |
| 56 | + return boxWidth - bl - br - pl - pr; |
| 57 | + } |
| 58 | + |
| 59 | + if ('currentStyle' in document.documentElement) { |
| 60 | + getRawStyle = getRawStyle_IE; |
| 61 | + } |
| 62 | + |
| 63 | + |
12 | 64 | // Can be called like this:
|
13 | 65 | // getPixelValue("11px");
|
14 | 66 | // Or like this:
|
|
17 | 69 | var element = null;
|
18 | 70 | if (Object.isElement(value)) {
|
19 | 71 | element = value;
|
20 |
| - value = element.getStyle(property); |
| 72 | + value = getRawStyle(element, property); |
21 | 73 | }
|
22 | 74 |
|
23 |
| - if (value === null) { |
| 75 | + if (value === null || Object.isUndefined(value)) { |
24 | 76 | return null;
|
25 | 77 | }
|
26 | 78 |
|
|
30 | 82 | if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) {
|
31 | 83 | return window.parseFloat(value);
|
32 | 84 | }
|
33 |
| - |
34 |
| - var isPercentage = value.include('%'), isViewport = (context === document.viewport); |
| 85 | + |
| 86 | + var isPercentage = value.include('%'), isViewport = (context === document.viewport); |
35 | 87 |
|
36 | 88 | // When IE gives us something other than a pixel value, this technique
|
37 | 89 | // (invented by Dean Edwards) will convert it to pixels.
|
|
48 | 100 |
|
49 | 101 | return value;
|
50 | 102 | }
|
51 |
| - |
| 103 | + |
52 | 104 | // For other browsers, we have to do a bit of work.
|
53 | 105 | // (At this point, only percentages should be left; all other CSS units
|
54 | 106 | // are converted to pixels by getComputedStyle.)
|
55 | 107 | if (element && isPercentage) {
|
| 108 | + // The `context` argument comes into play for percentage units; it's |
| 109 | + // the thing that the unit represents a percentage of. When an |
| 110 | + // absolutely-positioned element has a width of 50%, we know that's |
| 111 | + // 50% of its offset parent. If it's `position: fixed` instead, we know |
| 112 | + // it's 50% of the viewport. And so on. |
56 | 113 | context = context || element.parentNode;
|
57 | 114 | var decimal = toDecimal(value);
|
58 | 115 | var whole = null;
|
|
61 | 118 | var isHorizontal = property.include('left') || property.include('right') ||
|
62 | 119 | property.include('width');
|
63 | 120 |
|
64 |
| - var isVertical = property.include('top') || property.include('bottom') || |
| 121 | + var isVertical = property.include('top') || property.include('bottom') || |
65 | 122 | property.include('height');
|
66 | 123 |
|
67 | 124 | if (context === document.viewport) {
|
|
87 | 144 |
|
88 | 145 | // Turns plain numbers into pixel measurements.
|
89 | 146 | function toCSSPixels(number) {
|
90 |
| - if (Object.isString(number) && number.endsWith('px')) { |
| 147 | + if (Object.isString(number) && number.endsWith('px')) |
91 | 148 | return number;
|
92 |
| - } |
93 | 149 | return number + 'px';
|
94 | 150 | }
|
95 | 151 |
|
| 152 | + // Shortcut for figuring out if an element is `display: none` or not. |
96 | 153 | function isDisplayed(element) {
|
97 | 154 | var originalElement = element;
|
98 | 155 | while (element && element.parentNode) {
|
|
105 | 162 | return true;
|
106 | 163 | }
|
107 | 164 |
|
| 165 | + // In IE6-7, positioned elements often need hasLayout triggered before they |
| 166 | + // report accurate measurements. |
108 | 167 | var hasLayout = Prototype.K;
|
109 | 168 | if ('currentStyle' in document.documentElement) {
|
110 | 169 | hasLayout = function(element) {
|
|
282 | 341 | **/
|
283 | 342 | get: function($super, property) {
|
284 | 343 | // Try to fetch from the cache.
|
285 |
| - var value = $super(property); |
| 344 | + var value = $super(property); |
286 | 345 | return value === null ? this._compute(property) : value;
|
287 | 346 | },
|
288 | 347 |
|
|
291 | 350 | // when hidden), elements need a "preparation" phase that ensures
|
292 | 351 | // accuracy of measurements.
|
293 | 352 | _begin: function() {
|
294 |
| - if (this._prepared) return; |
295 |
| - |
| 353 | + if (this._isPrepared()) return; |
| 354 | + |
296 | 355 | var element = this.element;
|
297 | 356 | if (isDisplayed(element)) {
|
298 |
| - this._prepared = true; |
| 357 | + this._setPrepared(true); |
299 | 358 | return;
|
300 | 359 | }
|
301 | 360 |
|
| 361 | + // If we get this far, it means this element is hidden. To get usable |
| 362 | + // measurements, we must remove `display: none`, but in a manner that |
| 363 | + // isn't noticeable to the user. That means we also set |
| 364 | + // `visibility: hidden` to make it invisible, and `position: absolute` |
| 365 | + // so that it won't alter the document flow when displayed. |
| 366 | + // |
| 367 | + // Once we do this, the element is "prepared," and we can make our |
| 368 | + // measurements. When we're done, the `_end` method cleans up our |
| 369 | + // changes. |
| 370 | + |
302 | 371 | // Remember the original values for some styles we're going to alter.
|
303 | 372 | var originalStyles = {
|
304 | 373 | position: element.style.position || '',
|
|
307 | 376 | display: element.style.display || ''
|
308 | 377 | };
|
309 | 378 |
|
310 |
| - // We store them so that the `_end` function can retrieve them later. |
| 379 | + // We store them so that the `_end` method can retrieve them later. |
311 | 380 | element.store('prototype_original_styles', originalStyles);
|
312 | 381 |
|
313 |
| - var position = element.getStyle('position'), |
314 |
| - width = element.getStyle('width'); |
315 |
| - |
316 |
| - if (width === "0px" || width === null) { |
317 |
| - // Opera won't report the true width of the element through |
| 382 | + var position = getRawStyle(element, 'position'), width = element.offsetWidth; |
| 383 | + |
| 384 | + if (width === 0 || width === null) { |
| 385 | + // Opera/IE won't report the true width of the element through |
318 | 386 | // `getComputedStyle` if it's hidden. If we got a nonsensical value,
|
319 | 387 | // we need to show the element and try again.
|
320 | 388 | element.style.display = 'block';
|
321 |
| - width = element.getStyle('width'); |
| 389 | + width = element.offsetWidth; |
322 | 390 | }
|
323 | 391 |
|
324 | 392 | // Preserve the context in case we get a percentage value.
|
325 | 393 | var context = (position === 'fixed') ? document.viewport :
|
326 | 394 | element.parentNode;
|
327 | 395 |
|
328 |
| - element.setStyle({ |
329 |
| - position: 'absolute', |
| 396 | + var tempStyles = { |
330 | 397 | visibility: 'hidden',
|
331 | 398 | display: 'block'
|
332 |
| - }); |
| 399 | + }; |
333 | 400 |
|
334 |
| - var positionedWidth = element.getStyle('width'); |
| 401 | + // If the element's `position: fixed`, it's already out of the document |
| 402 | + // flow, so it's both unnecessary and inaccurate to set |
| 403 | + // `position: absolute`. |
| 404 | + if (position !== 'fixed') tempStyles.position = 'absolute'; |
| 405 | + |
| 406 | + element.setStyle(tempStyles); |
335 | 407 |
|
336 |
| - var newWidth; |
| 408 | + var positionedWidth = element.offsetWidth, newWidth; |
337 | 409 | if (width && (positionedWidth === width)) {
|
338 | 410 | // If the element's width is the same both before and after
|
339 | 411 | // we set absolute positioning, that means:
|
340 | 412 | // (a) it was already absolutely-positioned; or
|
341 | 413 | // (b) it has an explicitly-set width, instead of width: auto.
|
342 | 414 | // Either way, it means the element is the width it needs to be
|
343 | 415 | // in order to report an accurate height.
|
344 |
| - newWidth = getPixelValue(element, 'width', context); |
| 416 | + newWidth = getContentWidth(element, context); |
345 | 417 | } else if (position === 'absolute' || position === 'fixed') {
|
346 | 418 | // Absolute- and fixed-position elements' dimensions don't depend
|
347 | 419 | // upon those of their parents.
|
348 |
| - newWidth = getPixelValue(element, 'width', context); |
| 420 | + newWidth = getContentWidth(element, context); |
349 | 421 | } else {
|
350 | 422 | // Otherwise, the element's width depends upon the width of its
|
351 | 423 | // parent.
|
|
360 | 432 | this.get('margin-right');
|
361 | 433 | }
|
362 | 434 |
|
| 435 | + // Whatever the case, we've now figured out the correct `width` value |
| 436 | + // for the element. |
363 | 437 | element.setStyle({ width: newWidth + 'px' });
|
364 | 438 |
|
365 | 439 | // The element is now ready for measuring.
|
366 |
| - this._prepared = true; |
| 440 | + this._setPrepared(true); |
367 | 441 | },
|
368 | 442 |
|
369 | 443 | _end: function() {
|
370 | 444 | var element = this.element;
|
371 | 445 | var originalStyles = element.retrieve('prototype_original_styles');
|
372 |
| - element.store('prototype_original_styles', null); |
| 446 | + element.store('prototype_original_styles', null); |
373 | 447 | element.setStyle(originalStyles);
|
374 |
| - this._prepared = false; |
| 448 | + this._setPrepared(false); |
375 | 449 | },
|
376 | 450 |
|
377 | 451 | _compute: function(property) {
|
|
383 | 457 | return this._set(property, COMPUTATIONS[property].call(this, this.element));
|
384 | 458 | },
|
385 | 459 |
|
| 460 | + _isPrepared: function() { |
| 461 | + return this.element.retrieve('prototype_element_layout_prepared', false); |
| 462 | + }, |
| 463 | + |
| 464 | + _setPrepared: function(bool) { |
| 465 | + return this.element.store('prototype_element_layout_prepared', bool); |
| 466 | + }, |
| 467 | + |
386 | 468 | /**
|
387 | 469 | * Element.Layout#toObject([keys...]) -> Object
|
388 | 470 | * - keys (String): A space-separated list of keys to include.
|
|
459 | 541 | if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return;
|
460 | 542 |
|
461 | 543 | var value = this.get(key);
|
462 |
| - // Unless the value is null, add 'px' to the end and add it to the |
463 |
| - // returned object. |
464 | 544 | if (value != null) css[cssNameFor(key)] = value + 'px';
|
465 | 545 | }, this);
|
466 | 546 | return css;
|
|
523 | 603 |
|
524 | 604 | var pLeft = this.get('padding-left'),
|
525 | 605 | pRight = this.get('padding-right');
|
526 |
| - |
| 606 | + |
527 | 607 | if (!this._preComputing) this._end();
|
528 |
| - |
529 | 608 | return bWidth - bLeft - bRight - pLeft - pRight;
|
530 | 609 | },
|
531 | 610 |
|
|
766 | 845 | * `Element.getLayout` again only when the values in an existing
|
767 | 846 | * `Element.Layout` object have become outdated.
|
768 | 847 | *
|
769 |
| - * Remember that instances of `Element.Layout` compute values the first |
770 |
| - * time they're asked for and remember those values for later retrieval. |
771 |
| - * If you want to compute all an element's measurements at once, pass |
772 |
| - * |
| 848 | + * If the `preCompute` argument is `true`, all properties will be measured |
| 849 | + * when the layout object is instantiated. If you plan to measure several |
| 850 | + * properties of an element's dimensions, it's probably worth it to get a |
| 851 | + * pre-computed hash. |
| 852 | + * |
773 | 853 | * ##### Examples
|
774 | 854 | *
|
775 | 855 | * var layout = $('troz').getLayout();
|
|
1512 | 1592 | do {
|
1513 | 1593 | valueT += element.offsetTop || 0;
|
1514 | 1594 | valueL += element.offsetLeft || 0;
|
1515 |
| - if (element.offsetParent == document.body) |
| 1595 | + if (element.offsetParent == document.body) { |
1516 | 1596 | if (Element.getStyle(element, 'position') == 'absolute') break;
|
| 1597 | + } |
1517 | 1598 |
|
1518 | 1599 | element = element.offsetParent;
|
1519 | 1600 | } while (element);
|
|
0 commit comments