diff --git a/src/dom/layout.js b/src/dom/layout.js
index 854f566..3152ed6 100644
--- a/src/dom/layout.js
+++ b/src/dom/layout.js
@@ -96,8 +96,115 @@
*
* A set of key/value pairs representing measurements of various
* dimensions of an element.
+ *
+ *
Overview
+ *
+ * The `Element.Layout` class is a specialized way to measure elements.
+ * It helps mitigate:
+ *
+ * * The convoluted steps often needed to get common measurements for
+ * elements.
+ * * The tendency of browsers to report measurements in non-pixel units.
+ * * The quirks that lead some browsers to report inaccurate measurements.
+ * * The difficulty of measuring elements that are hidden.
+ *
+ * Usage
+ *
+ * Instantiate an `Element.Layout` class by passing an element into the
+ * constructor:
+ *
+ * var layout = new Element.Layout(someElement);
+ *
+ * You can also use [[Element.getLayout]], if you prefer.
+ *
+ * Once you have a layout object, retrieve properties using [[Hash]]'s
+ * familiar `get` and `set` syntax.
+ *
+ * layout.get('width'); //-> 400
+ * layout.get('top'); //-> 180
+ *
+ * The following are the CSS-related properties that can be retrieved.
+ * Nearly all of them map directly to their property names in CSS. (The
+ * only exception is for borders — e.g., `border-width` instead of
+ * `border-left-width`.)
+ *
+ * * `height`
+ * * `width`
+ * * `top`
+ * * `left`
+ * * `right`
+ * * `bottom`
+ * * `border-left`
+ * * `border-right`
+ * * `border-top`
+ * * `border-bottom`
+ * * `padding-left`
+ * * `padding-right`
+ * * `padding-top`
+ * * `padding-bottom`
+ * * `margin-top`
+ * * `margin-bottom`
+ * * `margin-left`
+ * * `margin-right`
+ *
+ * In addition, these "composite" properties can be retrieved:
+ *
+ * * `padding-box-width` (width of the content area, from the beginning of
+ * the left padding to the end of the right padding)
+ * * `padding-box-height` (height of the content area, from the beginning
+ * of the top padding to the end of the bottom padding)
+ * * `border-box-width` (width of the content area, from the outer edge of
+ * the left border to the outer edge of the right border)
+ * * `border-box-height` (height of the content area, from the outer edge
+ * of the top border to the outer edge of the bottom border)
+ * * `margin-box-width` (width of the content area, from the beginning of
+ * the left margin to the end of the right margin)
+ * * `margin-box-height` (height of the content area, from the beginning
+ * of the top margin to the end of the bottom margin)
+ *
+ * Caching
+ *
+ * Because these properties can be costly to retrieve, `Element.Layout`
+ * behaves differently from an ordinary [[Hash]].
+ *
+ * First: by default, values are "lazy-loaded" — they aren't computed
+ * until they're retrieved. To measure all properties at once, pass
+ * a second argument into the constructor:
+ *
+ * var layout = new Element.Layout(someElement, true);
+ *
+ * Second: once a particular value is computed, it's cached. Asking for
+ * the same property again will return the original value without
+ * re-computation. This means that **an instance of `Element.Layout`
+ * becomes stale when the element's dimensions change**. When this
+ * happens, obtain a new instance.
+ *
+ * Hidden elements
+ *
+ * Because it's a common case to want the dimensions of a hidden element
+ * (e.g., for animations), it's possible to measure elements that are
+ * hidden with `display: none`.
+ *
+ * However, **it's only possible to measure a hidden element if its parent
+ * is visible**. If its parent (or any other ancestor) is hidden, any
+ * width and height measurements will return `0`, as will measurements for
+ * `top|bottom|left|right`.
+ *
**/
Element.Layout = Class.create(Hash, {
+ /**
+ * new Element.Layout(element[, preCompute])
+ * - element (Element): The element to be measured.
+ * - preCompute (Boolean): Whether to compute all values at once.
+ *
+ * Declare a new layout hash.
+ *
+ * The `preCompute` argument determines whether measurements will be
+ * lazy-loaded or not. If you plan to use many different measurements,
+ * it's often more performant to pre-compute, as it minimizes the
+ * amount of overhead needed to measure. If you need only one or two
+ * measurements, it's probably not worth it.
+ **/
initialize: function($super, element, preCompute) {
$super();
this.element = $(element);
@@ -125,14 +232,25 @@
return Hash.prototype.set.call(this, property, value);
},
+
+ // TODO: Investigate.
set: function(property, value) {
- if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) {
- throw "Cannot set a composite property.";
- }
-
- return this._set(property, toCSSPixels(value));
+ throw "Properties of Element.Layout are read-only.";
+ // if (Element.Layout.COMPOSITE_PROPERTIES.include(property)) {
+ // throw "Cannot set a composite property.";
+ // }
+ //
+ // return this._set(property, toCSSPixels(value));
},
+ /**
+ * Element.Layout#get(property) -> Number
+ * - property (String): One of the properties defined in
+ * [[Element.Layout.PROPERTIES]].
+ *
+ * Retrieve the measurement specified by `property`. Will throw an error
+ * if the property is invalid.
+ **/
get: function($super, property) {
// Try to fetch from the cache.
var value = $super(property);
@@ -228,9 +346,20 @@
Object.extend(Element.Layout, {
// All measurable properties.
+ /**
+ * Element.Layout.PROPERTIES = Array
+ *
+ * A list of all measurable properties.
+ **/
PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'),
- // Sums of other properties. Can be read but not written.
+ /**
+ * Element.Layout.COMPOSITE_PROPERTIES = Array
+ *
+ * A list of all composite properties. Composite properties don't map
+ * directly to CSS properties — they're combinations of other
+ * properties.
+ **/
COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'),
COMPUTATIONS: {
@@ -313,19 +442,37 @@
},
'top': function(element) {
- return getPixelValue(element, 'top');
+ var offset = element.positionedOffset();
+ return offset.top;
},
'bottom': function(element) {
- return getPixelValue(element, 'bottom');
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pHeight = parent.measure('height');
+
+ var mHeight = this.get('border-box-height');
+
+ return pHeight - mHeight - offset.top;
+ //
+ // return getPixelValue(element, 'bottom');
},
'left': function(element) {
- return getPixelValue(element, 'left');
+ var offset = element.positionedOffset();
+ return offset.left;
},
'right': function(element) {
- return getPixelValue(element, 'right');
+ var offset = element.positionedOffset(),
+ parent = element.getOffsetParent(),
+ pWidth = parent.measure('width');
+
+ var mWidth = this.get('border-box-width');
+
+ return pWidth - mWidth - offset.left;
+ //
+ // return getPixelValue(element, 'right');
},
'padding-top': function(element) {
@@ -382,6 +529,27 @@
}
});
+ // An easier way to compute right and bottom offsets.
+ if ('getBoundingClientRect' in document.documentElement) {
+ Object.extend(Element.Layout.COMPUTATIONS, {
+ 'right': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.right - rect.right).round();
+ },
+
+ 'bottom': function(element) {
+ var parent = hasLayout(element.getOffsetParent());
+ var rect = element.getBoundingClientRect(),
+ pRect = parent.getBoundingClientRect();
+
+ return (pRect.bottom - rect.bottom).round();
+ }
+ });
+ }
+
/**
* class Element.Offset
*