Created
July 12, 2012 16:42
-
-
Save publickeating/3099233 to your computer and use it in GitHub Desktop.
SC.StackableChildViews Draft
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
This mixin automatically positions the view's child views in a stack either | |
vertically or horizontally and adjusts the View's height or width respectively | |
to fit. It does this by checking the height or width of each child view | |
(depending on the direction of layout) and positioning the following child view | |
accordingly. | |
If the child view's frame changes, then the parent view will re-layout all of | |
the others to fit and re-adjust its width or height to fit as well. | |
A possible usage scenario is a long "form" view made of unique subsection | |
views. If we want to adjust the height of a subsection, to make space for an | |
error message for example, it would be a lot of work to manually reposition | |
all the other sections below it. | |
For example, | |
MyApp.MyView = SC.View.extend(SC.StackableChildViews, { | |
// Laid out from left to right. | |
direction: SC.LAYOUT_HORIZONTAL, | |
// Actual layout will be { left: 10, bottom: 20, top: 20, width: 270 } | |
layout: { left: 10, bottom: 20, top: 20 }, | |
// Keep the child views ordered! | |
childViews: ['sectionA', 'sectionB', 'sectionC'], | |
sectionA: SC.View.design({ | |
// Actual layout will be { left: 0, bottom: 0, top: 0, width: 100 } | |
layout: { width: 100 } | |
}), | |
sectionB: SC.View.design({ | |
// Actual layout will be { left: 100, bottom: 0, top: 0, width: 50 } | |
layout: { width: 50 } | |
}), | |
sectionC: SC.View.design({ | |
// Actual layout will be { left: 150, bottom: 0, top: 0, width: 120 } | |
layout: { width: 120 } | |
}) | |
}); | |
You can also specify values for the padding before the first child view, | |
`paddingBefore`, for the padding after the last child view, `paddingAfter` and | |
for the spacing between each child view, `spacing`. | |
For more control over the spacing between child views, you can provide | |
relevant margin properties on each child view. When the layout direction | |
is SC.LAYOUT_HORIZONTAL, then child views can adjust their automatic | |
position from left to right by providing marginLeft or marginRight. Likewise, | |
when the direction is SC.LAYOUT_VERTICAL, child views can override the | |
default spacing by providing marginTop or marginBottom. | |
For example, | |
MyApp.MyView = SC.View.extend(SC.StackableChildViews, { | |
// Laid out from left to right. | |
direction: SC.LAYOUT_HORIZONTAL, | |
// Actual layout will be { left: 10, bottom: 20, top: 20, width: 570 } | |
layout: { left: 10, bottom: 20, top: 20 }, | |
// Keep the child views ordered! | |
childViews: ['sectionA', 'sectionB', 'sectionC'], | |
sectionA: SC.View.design({ | |
// Actual layout will be { left: 0, bottom: 0, top: 0, width: 100 } | |
layout: { width: 100 }, | |
// Force the following child view to be 200px further to the right. | |
marginRight: 200 | |
}), | |
sectionB: SC.View.design({ | |
// Actual layout will be { left: 200, bottom: 0, top: 0, width: 50 } | |
layout: { width: 50 } | |
}), | |
sectionC: SC.View.design({ | |
// Actual layout will be { left: 450, bottom: 0, top: 0, width: 120 } | |
layout: { width: 120 }, | |
// Force this child view to be 200px further from the previous. | |
marginLeft: 200 | |
}) | |
}); | |
Finally, you can leave a child view out of automatic stacking by explicitly | |
specifying `isStackable` is false on the child view. | |
*/ | |
SC.StackableChildViews = { | |
/** | |
The direction of layout, either SC.LAYOUT_HORIZONTAL or SC.LAYOUT_VERTICAL. | |
@default: SC.LAYOUT_VERTICAL | |
*/ | |
direction: SC.LAYOUT_VERTICAL, | |
/** | |
Ignores changes to child views heights and visibilities when false. | |
If your child views are not going to change height or visibility, you | |
can improve performance by setting this to false in order to prevent the | |
view from observing its child views for changes. | |
@default true | |
*/ | |
liveAdjust: true, | |
/** | |
Padding after the last child view. | |
@default: 0 | |
*/ | |
paddingAfter: 0, | |
/** | |
Padding before the first child view. | |
@default: 0 | |
*/ | |
paddingBefore: 0, | |
/** | |
The spacing between child views. | |
This is essentially the margins between each child view. It can be | |
overridden as needed by setting `marginBottom` and `marginTop` on a | |
child view when using SC.LAYOUT_VERTICAL direction or by setting | |
`marginLeft` and `marginRight` on a child view when using | |
SC.LAYOUT_HORIZONTAL direction. | |
Note that the spacing specified becomes the minimum margin between child | |
views, without explicitly overriding it from both sides. For example, | |
if `spacing` is 25, setting `marginBottom` to 10 on a child view will not | |
result in the next child view being 10px below it, unless the next child | |
view also specified `marginTop` as 10. | |
What this means is that it takes less configuration if you set `spacing` to | |
be the smallest margin you wish to exist and then use the overrides to | |
expand it. For example, if `spacing` is 5, setting `marginBottom` to 10 | |
on a child view will result in the next child view being 10px below it, | |
without having to specify `marginTop` on the next child view. | |
@default: 0 | |
*/ | |
spacing: 0, | |
/** @private Whenever the childViews array changes, we need to change each layout. */ | |
childViewsDidChange: function() { | |
this.layoutChildren(); | |
}, | |
/** @private */ | |
destroyMixin: function() { | |
this.removeObserver('liveAdjust', this, this.liveAdjustDidChange); | |
this.removeObserver('childViews', this, this.childViewsDidChange); | |
}, | |
/** @private */ | |
initMixin: function() { | |
this.addObserver('liveAdjust', this, this.liveAdjustDidChange); | |
this.addObserver('childViews', this, this.childViewsDidChange); | |
this.childViewsDidChange(); | |
}, | |
/** @private */ | |
layoutChildren: function() { | |
var childViews = this.get('childViews'), | |
direction = this.get('direction'), | |
marginBottom, | |
marginRight, | |
paddingAfter = this.get('paddingAfter'), | |
position = 0, | |
spacing = this.get('spacing'); | |
marginBottom = marginRight = this.get('paddingBefore'); | |
for (var i = 0, len = childViews.get('length'); i < len; i++) { | |
var childView = childViews.objectAt(i), | |
isStackable, | |
marginLeft, | |
marginTop; | |
// Ignore child views with isStackable false or that are not visible. | |
isStackable = childView.get('isStackable'); | |
if (!SC.none(isStackable) && !isStackable) continue; | |
if (!childView.get('isVisible')) continue; | |
//@if(debug) | |
// Add some developer support. | |
var layout = childView.get('layout'); | |
if (direction === SC.LAYOUT_VERTICAL && !SC.none(layout.bottom)) { | |
SC.warn('Developer Warning: Views that mix in SC.StackableChildViews for vertical layout may only define childView layouts for height with left and right or height with width and centerX!'); | |
} else if (direction === SC.LAYOUT_HORIZONTAL && !SC.none(layout.right)) { | |
SC.warn('Developer Warning: Views that mix in SC.StackableChildViews for horizontal layout may only define childView layouts for width with top and bottom or width with height and centerY!'); | |
} | |
//@endif | |
// Add observers on frame and isVisible if liveAdjust is set. If | |
// liveAdjust is unset, these observers will be removed in | |
// liveAdjustDidChange(). | |
if (this.get('liveAdjust') && !childView.hasObserverFor('frame')) { | |
childView.addObserver('frame', this, this.layoutChildren); | |
childView.addObserver('isVisible', this, this.layoutChildren); | |
} | |
if (direction === SC.LAYOUT_VERTICAL) { | |
// Determine the top margin. | |
marginTop = childView.get('marginTop') || spacing; | |
position += Math.max(marginBottom, marginTop); | |
childView.adjust('top', position); | |
position += childView.getPath('frame.height'); | |
// Determine the bottom margin. | |
marginBottom = childView.get('marginBottom') || spacing; | |
} else { | |
// Determine the left margin. | |
marginLeft = childView.get('marginLeft') || spacing; | |
position += Math.max(marginRight, marginLeft); | |
childView.adjust('left', position); | |
position += childView.getPath('frame.width'); | |
// Determine the right margin. | |
marginRight = childView.get('marginRight') || spacing; | |
} | |
} | |
// Adjust our frame to fit as well, this ensures that SC.ScrollView works. | |
if (direction === SC.LAYOUT_VERTICAL) { | |
paddingAfter = Math.max(marginBottom, paddingAfter); | |
this.adjust('height', position + paddingAfter); | |
} else { | |
paddingAfter = Math.max(marginRight, paddingAfter); | |
this.adjust('width', position + paddingAfter); | |
} | |
}, | |
/** @private */ | |
liveAdjustDidChange: function() { | |
if (this.get('liveAdjust')) { | |
// If liveAdjust gets set to true, auto adjust child views. | |
this.layoutChildren(); | |
} else { | |
// Else, remove live adjust observers from the child views. | |
var childViews = this.get('childViews'); | |
for (var i = 0, len = childViews.get('length'); i < len; i++) { | |
var childView = childViews.objectAt(i); | |
if (childView.hasObserverFor('frame')) { | |
childView.removeObserver('frame', this, this.layoutChildren); | |
childView.removeObserver('isVisible', this, this.layoutChildren); | |
} | |
} | |
} | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment