1 // ========================================================================== 2 // Project: SproutCore - JavaScript Application Framework 3 // Copyright: ©2006-2011 Strobe Inc. and contributors. 4 // Portions ©2008-2011 Apple Inc. All rights reserved. 5 // License: Licensed under MIT license (see license.js) 6 // ========================================================================== 7 sc_require('views/scroll_view'); 8 9 10 /** @class 11 12 Implements a scroll view for menus. This class extends SC.ScrollView for 13 menus. 14 15 The main difference with SC.ScrollView is that there is only vertical 16 scrolling. Value Syncing between SC.MenuScrollView and SC.MenuScrollerView 17 is done using valueBinding. 18 19 @extends SC.ScrollView 20 @since SproutCore 1.0 21 */ 22 SC.MenuScrollView = SC.ScrollView.extend( 23 /** @scope SC.MenuScrollView.prototype */{ 24 25 // --------------------------------------------------------------------------------------------- 26 // Properties 27 // 28 29 /** 30 The bottom scroller view class. This will be replaced with a view instance when the 31 MenuScrollView is created unless hasVerticalScroller is false. 32 33 @type SC.View 34 @default SC.MenuScrollerView 35 */ 36 bottomScrollerView: SC.MenuScrollerView, 37 38 /** 39 Returns true if the view has both a vertical scroller and the scroller is visible. 40 41 @field 42 @type Boolean 43 @readonly 44 @see SC.ScrollView 45 */ 46 canScrollVertical: function () { 47 return !!(this.get('hasVerticalScroller') && // This property isn't bindable. 48 this.get('bottomScrollerView') && // This property isn't bindable. 49 this.get('topScrollerView') && // This property isn't bindable. 50 this.get('isVerticalScrollerVisible')); 51 }.property('isVerticalScrollerVisible').cacheable(), 52 53 /** SC.View.prototype 54 @type Array 55 @default ['sc-menu-scroll-view'] 56 @see SC.View#classNames 57 */ 58 classNames: ['sc-menu-scroll-view'], 59 60 /** 61 Control Size for Menu content: change verticalLineScroll 62 63 @type String 64 @default SC.REGULAR_CONTROL_SIZE 65 @see SC.Control 66 */ 67 controlSize: SC.REGULAR_CONTROL_SIZE, 68 69 /** 70 YES if the view should maintain a horizontal scroller. This property must be set when the view 71 is created. 72 73 @type Boolean 74 @default false 75 @see SC.ScrollView 76 */ 77 hasHorizontalScroller: false, 78 79 /** 80 The top scroller view class. This will be replaced with a view instance when the MenuScrollView 81 is created unless hasVerticalScroller is false. 82 83 @type SC.View 84 @default SC.MenuScrollerView 85 */ 86 topScrollerView: SC.MenuScrollerView, 87 88 // --------------------------------------------------------------------------------------------- 89 // Methods 90 // 91 92 /** @private @see SC.ScrollView. Check frame changes for size changes. */ 93 _sc_contentViewFrameDidChange: function () { 94 sc_super(); 95 96 // Unlike a normal SC.ScrollView, the visibility of the top & bottom scrollers changes as the 97 // scrolling commences. For example, once the user scrolls a tiny bit, we need to show the 98 // top scroller. 99 this._sc_repositionScrollers(); 100 }, 101 102 /** @private @see SC.ScrollView. When the content view's size changes, we need to update our scroll offset properties. */ 103 _sc_repositionContentViewUnfiltered: function () { 104 var hasVerticalScroller = this.get('hasVerticalScroller'), 105 // UNUSED. minimumVerticalScrollOffset = this.get('minimumVerticalScrollOffset'), 106 maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset'); 107 108 if (hasVerticalScroller) { 109 var bottomScrollerView = this.get('bottomScrollerView'), 110 topScrollerView = this.get('topScrollerView'); 111 112 topScrollerView.set('maximum', maximumVerticalScrollOffset); 113 bottomScrollerView.set('maximum', maximumVerticalScrollOffset); 114 115 // Update if the visibility of the scrollers has changed now. 116 var containerHeight = this._sc_containerHeight, 117 contentHeight = this._sc_contentHeight; 118 119 if (this.get('autohidesVerticalScroller')) { 120 this.setIfChanged('isVerticalScrollerVisible', contentHeight > containerHeight); 121 } 122 } 123 124 sc_super(); 125 }, 126 127 /** @private @see SC.ScrollView. Re-position the scrollers and content depending on the need to scroll or not. */ 128 _sc_repositionScrollersUnfiltered: function () { 129 var hasScroller = this.get('hasVerticalScroller'), 130 containerView = this.get('containerView'); 131 132 if (hasScroller && this.get('autohidesVerticalScroller')) { 133 var bottomScrollerView = this.get('bottomScrollerView'), 134 bottomScrollerThickness = bottomScrollerView.get('scrollerThickness'), 135 maximumVerticalScrollOffset = this.get('maximumVerticalScrollOffset'), 136 topScrollerView = this.get('topScrollerView'), 137 topScrollerThickness = topScrollerView.get('scrollerThickness'), 138 verticalOffset = this.get('verticalScrollOffset'), 139 isBottomScrollerVisible = bottomScrollerView.get('isVisible'), 140 isTopScrollerVisible = topScrollerView.get('isVisible'); 141 142 // This asymetric update moves the container view out of the way of the scrollers (essentially 143 // so that the scroller views can be transparent). What's important is that as the container 144 // view is adjusted, the vertical scroll offset is adjusted properly so that the content view 145 // position doesn't jump around. 146 if (isTopScrollerVisible) { 147 if (verticalOffset <= topScrollerThickness) { 148 topScrollerView.set('isVisible', false); 149 containerView.adjust('top', 0); 150 this.decrementProperty('verticalScrollOffset', topScrollerThickness); 151 } 152 } else if (verticalOffset > 0) { 153 topScrollerView.set('isVisible', true); 154 containerView.adjust('top', topScrollerThickness); 155 this.incrementProperty('verticalScrollOffset', topScrollerThickness); 156 } 157 158 if (isBottomScrollerVisible) { 159 if (verticalOffset >= maximumVerticalScrollOffset - bottomScrollerThickness) { 160 bottomScrollerView.set('isVisible', false); 161 containerView.adjust('bottom', 0); 162 this.incrementProperty('verticalScrollOffset', bottomScrollerThickness); 163 } 164 } else if (verticalOffset < maximumVerticalScrollOffset) { 165 bottomScrollerView.set('isVisible', true); 166 containerView.adjust('bottom', bottomScrollerThickness); 167 this.decrementProperty('verticalScrollOffset', bottomScrollerThickness); 168 } 169 } 170 }, 171 172 /** @private 173 Instantiate scrollers & container views as needed. Replace their classes 174 in the regular properties. 175 */ 176 createChildViews: function () { 177 var childViews = [], 178 autohidesVerticalScroller = this.get('autohidesVerticalScroller'); 179 180 // Set up the container view. 181 var containerView = this.get('containerView'); 182 183 //@if(debug) 184 // Provide some debug-mode only developer support to prevent problems. 185 if (!containerView) { 186 throw new Error("Developer Error: SC.ScrollView must have a containerView class set before it is instantiated."); 187 } 188 //@endif 189 190 containerView = this.containerView = this.createChildView(containerView, { 191 contentView: this.contentView // Initialize the view with the currently set container view. 192 }); 193 this.contentView = containerView.get('contentView'); // Replace our content view with the instantiated version. 194 childViews.push(containerView); 195 196 // Set up the scrollers. 197 if (!this.get('hasVerticalScroller')) { 198 // Remove the class entirely. 199 this.topScrollerView = null; 200 this.bottomScrollerView = null; 201 } else { 202 var controlSize = this.get('controlSize'), 203 topScrollerView = this.get('topScrollerView'); 204 205 // Use a default scroller view. 206 /* jshint eqnull:true */ 207 if (topScrollerView == null) { 208 topScrollerView = SC.MenuScrollerView; 209 } 210 211 // Replace the class property with an instance. 212 topScrollerView = this.topScrollerView = this.createChildView(topScrollerView, { 213 controlSize: controlSize, 214 scrollDown: false, 215 isVisible: !autohidesVerticalScroller, 216 layout: { height: 0 }, 217 218 value: this.get('verticalScrollOffset'), 219 valueBinding: '.parentView.verticalScrollOffset', // Bind the value of the scroller to our vertical offset. 220 minimum: this.get('minimumVerticalScrollOffset'), 221 maximum: this.get('maximumVerticalScrollOffset') 222 }); 223 224 var topScrollerThickness = topScrollerView.get('scrollerThickness'); 225 topScrollerView.adjust('height', topScrollerThickness); 226 227 // Add the scroller view to the child views array. 228 childViews.push(topScrollerView); 229 230 231 var bottomScrollerView = this.get('bottomScrollerView'); 232 233 // Use a default scroller view. 234 /* jshint eqnull:true */ 235 if (bottomScrollerView == null) { 236 bottomScrollerView = SC.MenuScrollerView; 237 } 238 239 // Replace the class property with an instance. 240 bottomScrollerView = this.bottomScrollerView = this.createChildView(bottomScrollerView, { 241 controlSize: controlSize, 242 scrollDown: true, 243 isVisible: !autohidesVerticalScroller, 244 layout: { bottom: 0, height: 0 }, 245 246 value: this.get('verticalScrollOffset'), 247 valueBinding: '.parentView.verticalScrollOffset', // Bind the value of the scroller to our vertical offset. 248 minimum: this.get('minimumVerticalScrollOffset'), 249 maximum: this.get('maximumVerticalScrollOffset') 250 }); 251 252 var bottomScrollerThickness = bottomScrollerView.get('scrollerThickness'); 253 bottomScrollerView.adjust('height', bottomScrollerThickness); 254 255 // Add the scroller view to the child views array. 256 childViews.push(bottomScrollerView); 257 258 // If the scrollers aren't initially hidden, adjust the container. 259 if (!autohidesVerticalScroller) { 260 containerView.adjust('top', topScrollerThickness); 261 containerView.adjust('bottom', bottomScrollerThickness); 262 } 263 } 264 265 // Set the childViews array. 266 this.childViews = childViews; 267 } 268 269 }); 270