sproutcore 1.6.0.1-java → 1.7.1.beta-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. data/CHANGELOG +21 -0
  2. data/Gemfile +5 -0
  3. data/Rakefile +26 -13
  4. data/VERSION.yml +2 -2
  5. data/lib/Buildfile +43 -4
  6. data/lib/buildtasks/build.rake +10 -0
  7. data/lib/buildtasks/helpers/file_rule.rb +22 -0
  8. data/lib/buildtasks/helpers/file_rule_list.rb +137 -0
  9. data/lib/buildtasks/manifest.rake +133 -122
  10. data/lib/frameworks/sproutcore/CHANGELOG.md +69 -2
  11. data/lib/frameworks/sproutcore/apps/tests/english.lproj/strings.js +1 -0
  12. data/lib/frameworks/sproutcore/frameworks/bootstrap/system/browser.js +28 -22
  13. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/array.js +9 -5
  14. data/lib/frameworks/sproutcore/frameworks/core_foundation/controllers/controller.js +1 -1
  15. data/lib/frameworks/sproutcore/frameworks/core_foundation/controls/button.js +18 -13
  16. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/bind.js +5 -3
  17. data/lib/frameworks/sproutcore/frameworks/core_foundation/ext/handlebars/collection.js +2 -0
  18. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/action_support.js +80 -0
  19. data/lib/frameworks/sproutcore/frameworks/core_foundation/mixins/template_helpers/text_field_support.js +84 -116
  20. data/lib/frameworks/sproutcore/frameworks/core_foundation/panes/pane.js +8 -5
  21. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/event.js +157 -157
  22. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/platform.js +5 -3
  23. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/root_responder.js +6 -6
  24. data/lib/frameworks/sproutcore/frameworks/core_foundation/system/sparse_array.js +10 -7
  25. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/mixins/action_support.js +106 -0
  26. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/collection.js +18 -0
  27. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/template/handlebars.js +71 -1
  28. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/attribute_bindings_test.js +38 -0
  29. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/class_name_bindings_test.js +47 -0
  30. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutChildViews.js +18 -18
  31. data/lib/frameworks/sproutcore/frameworks/core_foundation/tests/views/view/layoutStyle.js +42 -10
  32. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view.js +158 -1
  33. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/keyboard.js +26 -1
  34. data/lib/frameworks/sproutcore/frameworks/core_foundation/views/view/layout_style.js +14 -8
  35. data/lib/frameworks/sproutcore/frameworks/datastore/models/record.js +15 -2
  36. data/lib/frameworks/sproutcore/frameworks/datastore/models/record_attribute.js +108 -108
  37. data/lib/frameworks/sproutcore/frameworks/datastore/system/query.js +1 -1
  38. data/lib/frameworks/sproutcore/frameworks/datastore/system/record_array.js +2 -4
  39. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/record/error_methods.js +2 -2
  40. data/lib/frameworks/sproutcore/frameworks/datastore/tests/models/single_attribute.js +26 -0
  41. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/query/builders.js +7 -0
  42. data/lib/frameworks/sproutcore/frameworks/datastore/tests/system/record_array/error_methods.js +1 -1
  43. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/system/datetime.js +4 -1
  44. data/lib/frameworks/sproutcore/frameworks/datetime/frameworks/core/tests/system/datetime.js +6 -0
  45. data/lib/frameworks/sproutcore/frameworks/desktop/panes/menu.js +26 -5
  46. data/lib/frameworks/sproutcore/frameworks/desktop/panes/picker.js +97 -96
  47. data/lib/frameworks/sproutcore/frameworks/desktop/system/drag.js +4 -3
  48. data/lib/frameworks/sproutcore/frameworks/desktop/tests/panes/menu/ui.js +17 -4
  49. data/lib/frameworks/sproutcore/frameworks/desktop/views/collection.js +7 -7
  50. data/lib/frameworks/sproutcore/frameworks/desktop/views/menu_item.js +7 -5
  51. data/lib/frameworks/sproutcore/frameworks/desktop/views/scroll.js +12 -3
  52. data/lib/frameworks/sproutcore/frameworks/desktop/views/web.js +23 -14
  53. data/lib/frameworks/sproutcore/frameworks/experimental/Buildfile +5 -1
  54. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/render_delegates/menu_scroller.js +28 -0
  55. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/tests/menu/scroll.js +235 -0
  56. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroll.js +363 -0
  57. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/menu/views/menu/scroller.js +250 -0
  58. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/desktop_scroller.js +92 -0
  59. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/native_scroll.js +25 -0
  60. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/scroll.js +33 -0
  61. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/render_delegates/touch_scroller.js +76 -0
  62. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/integration.js +50 -0
  63. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/methods.js +143 -0
  64. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/tests/scroll/ui.js +258 -0
  65. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroll.js +1164 -0
  66. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/core_scroller.js +332 -0
  67. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroll.js +236 -0
  68. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/desktop/scroller.js +347 -0
  69. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroll.js +15 -0
  70. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/scroller.js +10 -0
  71. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroll.js +804 -0
  72. data/lib/frameworks/sproutcore/frameworks/experimental/frameworks/scroll_view/views/touch/scroller.js +133 -0
  73. data/lib/frameworks/sproutcore/frameworks/foundation/resources/text_field.css +3 -3
  74. data/lib/frameworks/sproutcore/frameworks/foundation/validators/number.js +3 -1
  75. data/lib/frameworks/sproutcore/frameworks/foundation/views/text_field.js +3 -3
  76. data/lib/frameworks/sproutcore/frameworks/media/views/audio.js +2 -1
  77. data/lib/frameworks/sproutcore/frameworks/media/views/controls.js +2 -1
  78. data/lib/frameworks/sproutcore/frameworks/media/views/media_slider.js +2 -4
  79. data/lib/frameworks/sproutcore/frameworks/media/views/mini_controls.js +2 -4
  80. data/lib/frameworks/sproutcore/frameworks/media/views/simple_controls.js +2 -4
  81. data/lib/frameworks/sproutcore/frameworks/media/views/video.js +2 -2
  82. data/lib/frameworks/sproutcore/frameworks/routing/system/routes.js +29 -3
  83. data/lib/frameworks/sproutcore/frameworks/runtime/core.js +2 -2
  84. data/lib/frameworks/sproutcore/frameworks/runtime/debug/test_suites/array/replace.js +1 -1
  85. data/lib/frameworks/sproutcore/frameworks/runtime/private/property_chain.js +2 -1
  86. data/lib/frameworks/sproutcore/frameworks/runtime/system/binding.js +3 -3
  87. data/lib/frameworks/sproutcore/frameworks/runtime/system/index_set.js +2 -2
  88. data/lib/frameworks/sproutcore/frameworks/runtime/system/object.js +1 -1
  89. data/lib/frameworks/sproutcore/themes/ace/resources/collection/normal/list_item.css +2 -2
  90. data/lib/frameworks/sproutcore/themes/legacy_theme/english.lproj/segmented.css +1 -1
  91. data/lib/gen/app/templates/apps/@target_name@/Buildfile +3 -5
  92. data/lib/gen/app/templates/apps/@target_name@/resources/_theme.css +18 -0
  93. data/lib/gen/project/templates/@filename@/Buildfile +2 -2
  94. data/lib/sproutcore.rb +30 -5
  95. data/lib/sproutcore/builders.rb +1 -0
  96. data/lib/sproutcore/builders/chance_file.rb +9 -16
  97. data/lib/sproutcore/builders/html.rb +2 -1
  98. data/lib/sproutcore/builders/minify.rb +4 -35
  99. data/lib/sproutcore/builders/module.rb +38 -1
  100. data/lib/sproutcore/builders/split.rb +63 -0
  101. data/lib/sproutcore/builders/strings.rb +7 -1
  102. data/lib/sproutcore/helpers.rb +1 -1
  103. data/lib/sproutcore/helpers/css_split.rb +190 -0
  104. data/lib/sproutcore/helpers/entry_sorter.rb +2 -0
  105. data/lib/sproutcore/helpers/minifier.rb +40 -16
  106. data/lib/sproutcore/helpers/static_helper.rb +35 -17
  107. data/lib/sproutcore/models/manifest.rb +26 -0
  108. data/lib/sproutcore/models/target.rb +12 -1
  109. data/lib/sproutcore/rack.rb +1 -0
  110. data/lib/sproutcore/rack/proxy.rb +244 -225
  111. data/lib/sproutcore/rack/restrict_ip.rb +67 -0
  112. data/lib/sproutcore/rack/service.rb +8 -2
  113. data/lib/sproutcore/tools.rb +102 -46
  114. data/lib/sproutcore/tools/build.rb +91 -43
  115. data/lib/sproutcore/tools/gen.rb +2 -3
  116. data/lib/sproutcore/tools/manifest.rb +22 -16
  117. data/lib/sproutcore/tools/server.rb +21 -0
  118. data/spec/buildtasks/helpers/accept_list +22 -0
  119. data/spec/buildtasks/helpers/accept_list.rb +128 -0
  120. data/spec/buildtasks/helpers/list.json +11 -0
  121. data/spec/buildtasks/manifest/prepare_build_tasks/chance_2x_spec.rb +1 -39
  122. data/spec/buildtasks/manifest/prepare_build_tasks/chance_spec.rb +0 -38
  123. data/spec/buildtasks/manifest/prepare_build_tasks/combine_spec.rb +4 -4
  124. data/spec/buildtasks/manifest/prepare_build_tasks/module_spec.rb +2 -2
  125. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_indirect_spec.rb +7 -16
  126. data/spec/buildtasks/manifest/prepare_build_tasks/packed_2x_spec.rb +7 -17
  127. data/spec/buildtasks/manifest/prepare_build_tasks/packed_spec.rb +11 -6
  128. data/spec/fixtures/builder_tests/Buildfile +2 -1
  129. data/spec/fixtures/builder_tests/apps/module_test/modules/required_module/core.js +0 -0
  130. data/spec/lib/builders/module_spec.rb +1 -1
  131. data/spec/spec_helper.rb +1 -0
  132. data/sproutcore.gemspec +4 -9
  133. data/vendor/chance/lib/chance.rb +25 -6
  134. data/vendor/chance/lib/chance/factory.rb +45 -0
  135. data/vendor/chance/lib/chance/instance.rb +173 -28
  136. data/vendor/chance/lib/chance/instance/data_url.rb +0 -29
  137. data/vendor/chance/lib/chance/instance/slicing.rb +57 -4
  138. data/vendor/chance/lib/chance/instance/spriting.rb +112 -21
  139. data/vendor/chance/lib/chance/parser.rb +80 -52
  140. data/vendor/sproutcore/SCCompiler.jar +0 -0
  141. data/vendor/sproutcore/lib/args4j-2.0.12.jar +0 -0
  142. data/vendor/sproutcore/lib/yuicompressor-2.4.2.jar +0 -0
  143. metadata +84 -25
@@ -0,0 +1,1164 @@
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
+
8
+ sc_require('views/scroller');
9
+
10
+ /** @class
11
+ The core scroll view component.
12
+
13
+ This class is an input-agnostic scroll view implementation.
14
+
15
+ Important Events:
16
+
17
+ - contentView frame size changes (to autoshow/hide scrollbar - adjust scrollbar size)
18
+ - horizontalScrollOffset change
19
+ - verticalScrollOffsetChanges
20
+
21
+ @extends SC.View
22
+ @since SproutCore 1.6
23
+ */
24
+ SC.CoreScrollView = SC.View.extend(
25
+ /** @scope SC.ScrollView.prototype */{
26
+
27
+ /**
28
+ @type Array
29
+ @default ['sc-scroll-view']
30
+ @see SC.View#classNames
31
+ */
32
+ classNames: ['sc-scroll-view'],
33
+
34
+ // ..........................................................
35
+ // PROPERTIES
36
+ //
37
+
38
+ /**
39
+ Walk like a duck
40
+
41
+ @type Boolean
42
+ @default YES
43
+ @readOnly
44
+ */
45
+ isScrollable: YES,
46
+
47
+ /**
48
+ The content view you want the scroll view to manage.
49
+ This will be assigned to the `contentView` of the `clipView` also.
50
+
51
+ @type SC.View
52
+ @default null
53
+ */
54
+ contentView: null,
55
+
56
+ /**
57
+ The horizontal alignment for non-filling content inside of the `ScrollView`.
58
+ Possible values are:
59
+
60
+ - `SC.ALIGN_LEFT`
61
+ - `SC.ALIGN_RIGHT`
62
+ - `SC.ALIGN_CENTER`
63
+
64
+ @type String
65
+ @default SC.ALIGN_LEFT
66
+ */
67
+ horizontalAlign: SC.ALIGN_LEFT,
68
+
69
+ /**
70
+ The vertical alignment for non-filling content inside of the `ScrollView`.
71
+ Possible values are:
72
+
73
+ - `SC.ALIGN_TOP`
74
+ - `SC.ALIGN_BOTTOM`
75
+ - `SC.ALIGN_MIDDLE`
76
+
77
+ @type String
78
+ @default SC.ALIGN_TOP
79
+ */
80
+ verticalAlign: SC.ALIGN_TOP,
81
+
82
+ /**
83
+ The current horizontal scroll offset. Changing this value will update both
84
+ the `contentView` and the horizontal scroller, if there is one.
85
+
86
+ @field
87
+ @type Number
88
+ @default 0
89
+ */
90
+ horizontalScrollOffset: function (key, value) {
91
+ if (typeof value !== "undefined") {
92
+ var minOffset = this.minimumHorizontalScrollOffset(),
93
+ maxOffset = this.get('maximumHorizontalScrollOffset');
94
+ this._scroll_horizontalScrollOffset = Math.max(minOffset, Math.min(maxOffset, value));
95
+ }
96
+
97
+ return this._scroll_horizontalScrollOffset || 0;
98
+ }.property().cacheable(),
99
+
100
+ /**
101
+ The current vertical scroll offset. Changing this value will update both
102
+ the `contentView` and the vertical scroller, if there is one.
103
+
104
+ @field
105
+ @type Number
106
+ @default 0
107
+ */
108
+ verticalScrollOffset: function (key, value) {
109
+ if (typeof value !== "undefined") {
110
+ var minOffset = this.get('minimumVerticalScrollOffset'),
111
+ maxOffset = this.get('maximumVerticalScrollOffset');
112
+ this._scroll_verticalScrollOffset = Math.max(minOffset, Math.min(maxOffset, value));
113
+ }
114
+
115
+ return this._scroll_verticalScrollOffset || 0;
116
+ }.property().cacheable(),
117
+
118
+ /** @private
119
+ Calculates the maximum offset given content and container sizes, and the
120
+ alignment.
121
+ */
122
+ maximumScrollOffset: function (contentSize, containerSize, align) {
123
+ // If the content is larger than the container, the maximum offset is
124
+ // the location of the `contentView` when scrolled all the way down.
125
+ var delta = contentSize - containerSize;
126
+ if (contentSize >= containerSize) return delta;
127
+
128
+ return (align === SC.ALIGN_LEFT || align === SC.ALIGN_TOP) ? 0 : // top-left aligned
129
+ (align === SC.ALIGN_MIDDLE || align === SC.ALIGN_CENTER) ?
130
+ Math.round(delta / 2) : // center aligned
131
+ delta; // right aligned
132
+ },
133
+
134
+ /** @private
135
+ Calculates the minimum offset given content and container sizes, and the
136
+ alignment.
137
+ */
138
+ minimumScrollOffset: function (contentSize, containerSize, align) {
139
+ var delta = contentSize - containerSize;
140
+
141
+ return (contentSize > containerSize || align === SC.ALIGN_LEFT || align === SC.ALIGN_TOP) ?
142
+ 0 : // top-left aligned or larger than the container.
143
+ (align === SC.ALIGN_MIDDLE || align === SC.ALIGN_CENTER) ?
144
+ Math.round(delta / 2) : // center aligned
145
+ delta; // right aligned
146
+ },
147
+
148
+ /**
149
+ The maximum horizontal scroll offset allowed given the current contentView
150
+ size and the size of the scroll view. If horizontal scrolling is
151
+ disabled, this will always return 0.
152
+
153
+ @field
154
+ @type Number
155
+ @default 0
156
+ */
157
+ maximumHorizontalScrollOffset: function () {
158
+ var view = this.get('contentView'),
159
+ contentWidth = view ? view.get('frame').width : 0,
160
+ calculatedWidth = view ? view.get('calculatedWidth') : 0,
161
+ containerWidth = this.get('containerView').get('frame').width;
162
+
163
+ // The following code checks if there is a calculatedWidth (collections)
164
+ // to avoid looking at the incorrect value calculated by frame.
165
+ if (calculatedWidth) {
166
+ contentWidth = view.calculatedWidth;
167
+ }
168
+ contentWidth *= this._scale;
169
+
170
+ // we still must go through minimumScrollOffset even if we can't scroll
171
+ // because we need to adjust for alignment. So, just make sure it won't allow scrolling.
172
+ if (!this.get('canScrollHorizontal')) contentWidth = Math.min(contentWidth, containerWidth);
173
+ return this.maximumScrollOffset(contentWidth, containerWidth, this.get("horizontalAlign"));
174
+ }.property(),
175
+
176
+ /**
177
+ The maximum vertical scroll offset allowed given the current contentView
178
+ size and the size of the scroll view. If vertical scrolling is disabled,
179
+ this will always return 0 (or whatever alignment dictates).
180
+
181
+ @field
182
+ @type Number
183
+ @default 0
184
+ */
185
+ maximumVerticalScrollOffset: function () {
186
+ var view = this.get('contentView'),
187
+ contentHeight = (view && view.get('frame')) ? view.get('frame').height : 0,
188
+ calculatedHeight = view ? view.get('calculatedHeight') : 0,
189
+ containerHeight = this.get('containerView').get('frame').height;
190
+
191
+ // The following code checks if there is a calculatedWidth (collections)
192
+ // to avoid looking at the incorrect value calculated by frame.
193
+ if (calculatedHeight) {
194
+ contentHeight = calculatedHeight;
195
+ }
196
+ contentHeight *= this._scale;
197
+
198
+ // we still must go through minimumScrollOffset even if we can't scroll
199
+ // because we need to adjust for alignment. So, just make sure it won't allow scrolling.
200
+ if (!this.get('canScrollVertical')) contentHeight = Math.min(contentHeight, containerHeight);
201
+ return this.maximumScrollOffset(contentHeight, containerHeight, this.get("verticalAlign"));
202
+ }.property(),
203
+
204
+
205
+ /**
206
+ The minimum horizontal scroll offset allowed given the current contentView
207
+ size and the size of the scroll view. If horizontal scrolling is
208
+ disabled, this will always return 0 (or whatever alignment dictates).
209
+
210
+ @field
211
+ @type Number
212
+ @default 0
213
+ */
214
+ minimumHorizontalScrollOffset: function () {
215
+ var view = this.get('contentView'),
216
+ contentWidth = view ? view.get('frame').width : 0,
217
+ calculatedWidth = view ? view.get('calculatedWidth') : 0,
218
+ containerWidth = this.get('containerView').get('frame').width;
219
+
220
+ // The following code checks if there is a calculatedWidth (collections)
221
+ // to avoid looking at the incorrect value calculated by frame.
222
+ if (calculatedWidth) {
223
+ contentWidth = calculatedWidth;
224
+ }
225
+ contentWidth *= this._scale;
226
+
227
+ // we still must go through minimumScrollOffset even if we can't scroll
228
+ // because we need to adjust for alignment. So, just make sure it won't allow scrolling.
229
+ if (!this.get('canScrollHorizontal')) contentWidth = Math.min(contentWidth, containerWidth);
230
+ return this.minimumScrollOffset(contentWidth, containerWidth, this.get("horizontalAlign"));
231
+ }.property(),
232
+
233
+ /**
234
+ The minimum vertical scroll offset allowed given the current contentView
235
+ size and the size of the scroll view. If vertical scrolling is disabled,
236
+ this will always return 0 (or whatever alignment dictates).
237
+
238
+ @field
239
+ @type Number
240
+ @default 0
241
+ */
242
+ minimumVerticalScrollOffset: function () {
243
+ var view = this.get('contentView'),
244
+ contentHeight = (view && view.get('frame')) ? view.get('frame').height : 0,
245
+ calculatedHeight = view ? view.get('calculatedHeight') : 0,
246
+ containerHeight = this.get('containerView').get('frame').height;
247
+
248
+ // The following code checks if there is a calculatedWidth (collections)
249
+ // to avoid looking at the incorrect value calculated by frame.
250
+ if (calculatedHeight) {
251
+ contentHeight = view.calculatedHeight;
252
+ }
253
+ contentHeight *= this._scale;
254
+
255
+ // we still must go through minimumScrollOffset even if we can't scroll
256
+ // because we need to adjust for alignment. So, just make sure it won't allow scrolling.
257
+ if (!this.get('canScrollVertical')) contentHeight = Math.min(contentHeight, containerHeight);
258
+ return this.minimumScrollOffset(contentHeight, containerHeight, this.get("verticalAlign"));
259
+ }.property(),
260
+
261
+
262
+ /**
263
+ Amount to scroll one vertical line.
264
+
265
+ Used by the default implementation of scrollDownLine() and scrollUpLine().
266
+
267
+ @type Number
268
+ @default 20
269
+ */
270
+ verticalLineScroll: 20,
271
+
272
+ /**
273
+ Amount to scroll one horizontal line.
274
+
275
+ Used by the default implementation of scrollLeftLine() and
276
+ scrollRightLine().
277
+
278
+ @type Number
279
+ @default 20
280
+ */
281
+ horizontalLineScroll: 20,
282
+
283
+ /**
284
+ Amount to scroll one vertical page.
285
+
286
+ Used by the default implementation of scrollUpPage() and scrollDownPage().
287
+
288
+ @field
289
+ @type Number
290
+ @default value of frame.height
291
+ @observes frame
292
+ */
293
+ verticalPageScroll: function () {
294
+ return this.get('frame').height;
295
+ }.property('frame'),
296
+
297
+ /**
298
+ Amount to scroll one horizontal page.
299
+
300
+ Used by the default implementation of scrollLeftPage() and
301
+ scrollRightPage().
302
+
303
+ @field
304
+ @type Number
305
+ @default value of frame.width
306
+ @observes frame
307
+ */
308
+ horizontalPageScroll: function () {
309
+ return this.get('frame').width;
310
+ }.property('frame'),
311
+
312
+ /**
313
+ Whether or not native scrollbars are wanted.
314
+
315
+ @type Boolean
316
+ @default NO
317
+ */
318
+ wantsNativeScrollbars: NO,
319
+
320
+ // ..........................................................
321
+ // SCROLLERS
322
+ //
323
+
324
+ /**
325
+ YES if the view should maintain a horizontal scroller.
326
+ This property must be set when the view is created.
327
+
328
+ @type Boolean
329
+ @default YES
330
+ */
331
+ hasHorizontalScroller: YES,
332
+
333
+ /**
334
+ The horizontal scroller view class.
335
+
336
+ This will be replaced with a view instance when the
337
+ ScrollView is created unless hasHorizontalScroller is NO.
338
+
339
+ @type SC.View
340
+ @default null
341
+ */
342
+ horizontalScrollerView: null,
343
+
344
+ /**
345
+ YES if the horizontal scroller should be visible. You can change this
346
+ property value anytime to show or hide the horizontal scroller. If you
347
+ do not want to use a horizontal scroller at all, you should instead set
348
+ hasHorizontalScroller to NO to avoid creating a scroller view in the
349
+ first place.
350
+
351
+ @type Boolean
352
+ @default YES
353
+ */
354
+ isHorizontalScrollerVisible: YES,
355
+
356
+ /**
357
+ Returns YES if the view both has a horizontal scroller, the scroller is
358
+ visible.
359
+
360
+ @field
361
+ @type Boolean
362
+ @default YES
363
+ */
364
+ canScrollHorizontal: function () {
365
+ return !!(this.get('hasHorizontalScroller') &&
366
+ this.get('horizontalScrollerView') &&
367
+ this.get('isHorizontalScrollerVisible'));
368
+ }.property('isHorizontalScrollerVisible', 'horizontalScrollerView').cacheable(),
369
+
370
+ /**
371
+ If YES, the horizontal scroller will autohide if the contentView is
372
+ smaller than the visible area. You must set hasHorizontalScroller to YES
373
+ for this property to have any effect.
374
+
375
+ @type Boolean
376
+ @default YES
377
+ */
378
+ autohidesHorizontalScroller: YES,
379
+
380
+ /**
381
+ YES if the view shuld maintain a vertical scroller. This property must
382
+ be set when the view is created.
383
+
384
+ @type Boolean
385
+ @default YES
386
+ */
387
+ hasVerticalScroller: YES,
388
+
389
+ /**
390
+ The vertical scroller view class. This will be replaced with a view
391
+ instance when the ScrollView is created unless hasVerticalScroller is NO.
392
+
393
+ @type SC.View
394
+ @default null
395
+ */
396
+ verticalScrollerView: null,
397
+
398
+ /**
399
+ YES if the vertical scroller should be visible. You can change this
400
+ property value anytime to show or hide the vertical scroller. If you do
401
+ not want to use a vertical scroller at all, you should instead set
402
+ hasVerticalScroller to NO to avoid creating a scroller view in the first
403
+ place.
404
+
405
+ @type Boolean
406
+ @default YES
407
+ */
408
+ isVerticalScrollerVisible: YES,
409
+
410
+ /**
411
+ Returns YES if the view both has a horizontal scroller, the scroller is
412
+ visible.
413
+
414
+ @field
415
+ @type Boolean
416
+ @default YES
417
+ */
418
+ canScrollVertical: function () {
419
+ return !!(this.get('hasVerticalScroller') &&
420
+ this.get('verticalScrollerView') &&
421
+ this.get('isVerticalScrollerVisible'));
422
+ }.property('isVerticalScrollerVisible', 'verticalScrollerView').cacheable(),
423
+
424
+ /**
425
+ If YES, the vertical scroller will autohide if the contentView is
426
+ smaller than the visible area. You must set hasVerticalScroller to YES
427
+ for this property to have any effect.
428
+
429
+ @type Boolean
430
+ @default YES
431
+ */
432
+ autohidesVerticalScroller: YES,
433
+
434
+ /**
435
+ Use this property to set the 'bottom' offset of your vertical scroller,
436
+ to make room for a thumb view or other accessory view. Default is 0.
437
+
438
+ @type Number
439
+ @default 0
440
+ */
441
+ verticalScrollerBottom: 0,
442
+
443
+ /**
444
+ Use this to overlay the vertical scroller.
445
+
446
+ This ensures that the container frame will not resize to accomodate the
447
+ vertical scroller, hence overlaying the scroller on top of
448
+ the container.
449
+
450
+ @type Boolean
451
+ @default NO
452
+ */
453
+ verticalOverlay: NO,
454
+
455
+ /**
456
+ Use this to overlay the horizontal scroller.
457
+
458
+ This ensures that the container frame will not resize to accomodate the
459
+ horizontal scroller, hence overlaying the scroller on top of
460
+ the container
461
+
462
+ @type Boolean
463
+ @default NO
464
+ */
465
+ horizontalOverlay: NO,
466
+
467
+ /**
468
+ Use to control the positioning of the vertical scroller. If you do not
469
+ set 'verticalOverlay' to YES, then the content view will be automatically
470
+ sized to meet the left edge of the vertical scroller, wherever it may be.
471
+ This allows you to easily, for example, have “one pixel higher and one
472
+ pixel lower” scroll bars that blend into their parent views.
473
+
474
+ If you do set 'verticalOverlay' to YES, then the scroller view will
475
+ “float on top” of the content view.
476
+
477
+ Example: { top: -1, bottom: -1, right: 0 }
478
+
479
+ @type Hash
480
+ @default null
481
+ */
482
+ verticalScrollerLayout: null,
483
+
484
+ /**
485
+ Use to control the positioning of the horizontal scroller. If you do not
486
+ set 'horizontalOverlay' to YES, then the content view will be
487
+ automatically sized to meet the top edge of the horizontal scroller,
488
+ wherever it may be.
489
+
490
+ If you do set 'horizontalOverlay' to YES, then the scroller view will
491
+ “float on top” of the content view.
492
+
493
+ Example: { left: 0, bottom: 0, right: 0 }
494
+
495
+ @type Hash
496
+ @default null
497
+ */
498
+ horizontalScrollerLayout: null,
499
+
500
+ // ..........................................................
501
+ // CUSTOM VIEWS
502
+ //
503
+
504
+ /**
505
+ The container view that will contain your main content view. You can
506
+ replace this property with your own custom subclass if you prefer.
507
+
508
+ @type SC.ContainerView
509
+ @default SC.ConainerView
510
+ */
511
+ containerView: SC.ContainerView.extend({}),
512
+
513
+
514
+ // ..........................................................
515
+ // METHODS
516
+ //
517
+
518
+ /**
519
+ Scrolls the receiver to the specified x,y coordinate. This should be the
520
+ offset into the contentView you want to appear at the top-left corner of
521
+ the scroll view.
522
+
523
+ This method will contrain the actual scroll based on whether the view
524
+ can scroll in the named direction and the maximum distance it can
525
+ scroll.
526
+
527
+ If you only want to scroll in one direction, pass null for the other
528
+ direction. You can also optionally pass a Hash for the first parameter
529
+ with x and y coordinates.
530
+
531
+ @param {Number} x the x scroll location
532
+ @param {Number} y the y scroll location
533
+ @returns {SC.ScrollView} receiver
534
+ */
535
+ scrollTo: function (x, y) {
536
+ // normalize params
537
+ if (typeof y === "undefined" && SC.typeOf(x) === SC.T_HASH) {
538
+ y = x.y; x = x.x;
539
+ }
540
+
541
+ if (!SC.none(x)) {
542
+ this.set('horizontalScrollOffset', x);
543
+ }
544
+
545
+ if (!SC.none(y)) {
546
+ this.set('verticalScrollOffset', y);
547
+ }
548
+
549
+ return this;
550
+ },
551
+
552
+ /**
553
+ Scrolls the receiver in the horizontal and vertical directions by the
554
+ amount specified, if allowed. The actual scroll amount will be
555
+ constrained by the current scroll view settings.
556
+
557
+ If you only want to scroll in one direction, pass null or 0 for the other
558
+ direction. You can also optionally pass a Hash for the first parameter
559
+ with x and y coordinates.
560
+
561
+ @param {Number} x change in the x direction (or hash)
562
+ @param {Number} y change in the y direction
563
+ @returns {SC.ScrollView} receiver
564
+ */
565
+ scrollBy: function (x, y) {
566
+ // normalize params
567
+ if (typeof y === "undefined" && SC.typeOf(x) === SC.T_HASH) {
568
+ y = x.y;
569
+ x = x.x;
570
+ }
571
+
572
+ // if null, undefined, or 0, pass null; otherwise just add current offset
573
+ x = (x) ? this.get('horizontalScrollOffset') + x : null;
574
+ y = (y) ? this.get('verticalScrollOffset') + y : null;
575
+ return this.scrollTo(x, y);
576
+ },
577
+
578
+ /**
579
+ Scroll the view to make the view's frame visible. For this to make sense,
580
+ the view should be a subview of the contentView. Otherwise the results
581
+ will be undefined.
582
+
583
+ @param {SC.View} view view to scroll or null to scroll receiver visible
584
+ @returns {Boolean} YES if scroll position was changed
585
+ */
586
+ scrollToVisible: function (view) {
587
+ // if no view is passed, do default
588
+ if (typeof view === "undefined") return sc_super();
589
+
590
+ var contentView = this.get('contentView');
591
+ if (!contentView) return NO; // nothing to do if no contentView.
592
+
593
+ // get the frame for the view - should work even for views with static
594
+ // layout, assuming it has been added to the screen.
595
+ var vf = view.get('frame');
596
+ if (!vf) return NO; // nothing to do
597
+
598
+ // convert view's frame to an offset from the contentView origin. This
599
+ // will become the new scroll offset after some adjustment.
600
+ vf = contentView.convertFrameFromView(vf, view.get('parentView'));
601
+
602
+ return this.scrollToRect(vf);
603
+ },
604
+
605
+ /**
606
+ Scroll to the supplied rectangle.
607
+ @param {Rect} rect Rectangle to scroll to.
608
+ @returns {Boolean} YES if scroll position was changed.
609
+ */
610
+ scrollToRect: function (rect) {
611
+ // find current visible frame.
612
+ var vo = SC.cloneRect(this.get('containerView').get('frame')),
613
+ origX = this.get('horizontalScrollOffset'),
614
+ origY = this.get('verticalScrollOffset');
615
+
616
+ vo.x = origX;
617
+ vo.y = origY;
618
+
619
+ // if top edge is not visible, shift origin
620
+ vo.y -= Math.max(0, SC.minY(vo) - SC.minY(rect));
621
+ vo.x -= Math.max(0, SC.minX(vo) - SC.minX(rect));
622
+
623
+ // if bottom edge is not visible, shift origin
624
+ vo.y += Math.max(0, SC.maxY(rect) - SC.maxY(vo));
625
+ vo.x += Math.max(0, SC.maxX(rect) - SC.maxX(vo));
626
+
627
+ // scroll to that origin.
628
+ if ((origX !== vo.x) || (origY !== vo.y)) {
629
+ this.scrollTo(vo.x, vo.y);
630
+ return YES;
631
+ } else return NO;
632
+ },
633
+
634
+
635
+ /**
636
+ Scrolls the receiver down one or more lines if allowed. If number of
637
+ lines is not specified, scrolls one line.
638
+
639
+ @param {Number} lines number of lines
640
+ @returns {SC.ScrollView} receiver
641
+ */
642
+ scrollDownLine: function (lines) {
643
+ if (typeof lines === "undefined") lines = 1;
644
+ return this.scrollBy(null, this.get('verticalLineScroll') * lines);
645
+ },
646
+
647
+ /**
648
+ Scrolls the receiver up one or more lines if allowed. If number of
649
+ lines is not specified, scrolls one line.
650
+
651
+ @param {Number} lines number of lines
652
+ @returns {SC.ScrollView} receiver
653
+ */
654
+ scrollUpLine: function (lines) {
655
+ if (typeof lines === "undefined") lines = 1;
656
+ return this.scrollBy(null, -1 * this.get('verticalLineScroll') * lines);
657
+ },
658
+
659
+ /**
660
+ Scrolls the receiver right one or more lines if allowed. If number of
661
+ lines is not specified, scrolls one line.
662
+
663
+ @param {Number} lines number of lines
664
+ @returns {SC.ScrollView} receiver
665
+ */
666
+ scrollRightLine: function (lines) {
667
+ if (typeof lines === "undefined") lines = 1;
668
+ return this.scrollTo(this.get('horizontalLineScroll') * lines, null);
669
+ },
670
+
671
+ /**
672
+ Scrolls the receiver left one or more lines if allowed. If number of
673
+ lines is not specified, scrolls one line.
674
+
675
+ @param {Number} lines number of lines
676
+ @returns {SC.ScrollView} receiver
677
+ */
678
+ scrollLeftLine: function (lines) {
679
+ if (typeof lines === "undefined") lines = 1;
680
+ return this.scrollTo(-1 * this.get('horizontalLineScroll') * lines, null);
681
+ },
682
+
683
+ /**
684
+ Scrolls the receiver down one or more page if allowed. If number of
685
+ pages is not specified, scrolls one page. The page size is determined by
686
+ the verticalPageScroll value. By default this is the size of the current
687
+ scrollable area.
688
+
689
+ @param {Number} pages number of lines
690
+ @returns {SC.ScrollView} receiver
691
+ */
692
+ scrollDownPage: function (pages) {
693
+ if (typeof pages === "undefined") pages = 1;
694
+ return this.scrollBy(null, this.get('verticalPageScroll') * pages);
695
+ },
696
+
697
+ /**
698
+ Scrolls the receiver up one or more page if allowed. If number of
699
+ pages is not specified, scrolls one page. The page size is determined by
700
+ the verticalPageScroll value. By default this is the size of the current
701
+ scrollable area.
702
+
703
+ @param {Number} pages number of lines
704
+ @returns {SC.ScrollView} receiver
705
+ */
706
+ scrollUpPage: function (pages) {
707
+ if (typeof pages === "undefined") pages = 1;
708
+ return this.scrollBy(null, -1 * this.get('verticalPageScroll') * pages);
709
+ },
710
+
711
+ /**
712
+ Scrolls the receiver right one or more page if allowed. If number of
713
+ pages is not specified, scrolls one page. The page size is determined by
714
+ the verticalPageScroll value. By default this is the size of the current
715
+ scrollable area.
716
+
717
+ @param {Number} pages number of lines
718
+ @returns {SC.ScrollView} receiver
719
+ */
720
+ scrollRightPage: function (pages) {
721
+ if (typeof pages === "undefined") pages = 1;
722
+ return this.scrollBy(this.get('horizontalPageScroll') * pages, null);
723
+ },
724
+
725
+ /**
726
+ Scrolls the receiver left one or more page if allowed. If number of
727
+ pages is not specified, scrolls one page. The page size is determined by
728
+ the verticalPageScroll value. By default this is the size of the current
729
+ scrollable area.
730
+
731
+ @param {Number} pages number of lines
732
+ @returns {SC.ScrollView} receiver
733
+ */
734
+ scrollLeftPage: function (pages) {
735
+ if (typeof pages === "undefined") pages = 1;
736
+ return this.scrollBy(-1 * this.get('horizontalPageScroll') * pages, null);
737
+ },
738
+
739
+ /** @private
740
+ Adjusts the layout for the various internal views. This method is called
741
+ once when the scroll view is first configured and then anytime a scroller
742
+ is shown or hidden. You can call this method yourself as well to retile.
743
+
744
+ You may also want to override this method to handle layout for any
745
+ additional controls you have added to the view.
746
+ */
747
+ tile: function () {
748
+ if (this.get('wantsNativeScrollbars')) return; // Let the browser retile
749
+
750
+ var hasHorizontal = this.get('canScrollHorizontal'),
751
+ hasVertical = this.get('canScrollVertical'),
752
+ hScroll = this.get('hasHorizontalScroller') ? this.get('horizontalScrollerView') : null,
753
+ vScroll = this.get('hasVerticalScroller') ? this.get('verticalScrollerView') : null,
754
+ clipView = this.get('containerView'),
755
+ clipLayout = { left: 0, top: 0 }, layout;
756
+
757
+ var ht = hasHorizontal ? hScroll.get('scrollbarThickness') : 0;
758
+ var vt = hasVertical ? vScroll.get('scrollbarThickness') : 0;
759
+
760
+ if (hasHorizontal) {
761
+ layout = this.get('horizontalScrollerLayout');
762
+ layout = {
763
+ left: layout ? layout.left : 0,
764
+ bottom: layout ? layout.bottom : 0,
765
+ right: layout ? layout.right + vt-1 : vt-1,
766
+ height: ht
767
+ };
768
+ hScroll.set('layout', layout);
769
+ clipLayout.bottom = layout.bottom + (hScroll.get('isTranslucent') ? 0 : layout.height);
770
+ }
771
+
772
+ if ((hasHorizontal && this.get('horizontalOverlay')) || !hasHorizontal) {
773
+ clipLayout.bottom = 0;
774
+ }
775
+
776
+ if (hScroll) hScroll.set('isVisible', hasHorizontal);
777
+
778
+ if (hasVertical) {
779
+ ht = ht + this.get('verticalScrollerBottom');
780
+ layout = this.get('verticalScrollerLayout');
781
+ layout = {
782
+ top: layout ? layout.top : 0,
783
+ bottom: layout ? layout.bottom + ht : ht,
784
+ right: layout ? layout.right : 0,
785
+ width: vt
786
+ };
787
+ vScroll.set('layout', layout);
788
+ clipLayout.right = layout.right + (vScroll.get('isTranslucent') ? 0 : layout.width);
789
+ }
790
+
791
+ if ((hasVertical && this.get('verticalOverlay')) || !hasVertical) {
792
+ clipLayout.right = 0;
793
+ }
794
+
795
+ if (vScroll) vScroll.set('isVisible', hasVertical);
796
+
797
+ clipView.adjust(clipLayout);
798
+ },
799
+
800
+ /** @private
801
+ Called whenever a scroller visibility changes.
802
+ Calls the tile() method.
803
+ */
804
+ scrollerVisibilityDidChange: function () {
805
+ this.tile();
806
+ }.observes('isVerticalScrollerVisible', 'isHorizontalScrollerVisible'),
807
+
808
+ // ..............................................
809
+ // SCALING SUPPORT
810
+ //
811
+
812
+ /**
813
+ Determines whether scaling is allowed.
814
+
815
+ @type Boolean
816
+ @default NO
817
+ */
818
+ canScale: NO,
819
+
820
+ /** @private
821
+ The current scale.
822
+ */
823
+ _scale: 1.0,
824
+
825
+ /**
826
+ @field
827
+ @type Number
828
+ @default 1.0
829
+ */
830
+ scale: function (key, value) {
831
+ if (typeof value !== "undefined") {
832
+ this._scale = Math.min(Math.max(this.get("minimumScale"), value), this.get("maximumScale"));
833
+ }
834
+ return this._scale;
835
+ }.property().cacheable(),
836
+
837
+ /**
838
+ The minimum scale.
839
+
840
+ @type Number
841
+ @default 0.25
842
+ */
843
+ minimumScale: 0.25,
844
+
845
+ /**
846
+ The maximum scale.
847
+
848
+ @type Number
849
+ @default 2.0
850
+ */
851
+ maximumScale: 2.0,
852
+
853
+ /**
854
+ Whether to automatically determine the scale range based on the size of the content.
855
+
856
+ @type Boolean
857
+ @default NO
858
+ */
859
+ autoScaleRange: NO,
860
+
861
+ /** @private */
862
+ _scale_css: "",
863
+
864
+ /** @private */
865
+ updateScale: function (scale) {
866
+ var contentView = this.get("contentView");
867
+ if (!contentView) return;
868
+
869
+ if (contentView.isScalable) {
870
+ this.get("contentView").applyScale(scale);
871
+ this._scale_css = "";
872
+ } else {
873
+ this._scale_css = "scale3d(" + scale + ", " + scale + ", 1)";
874
+ }
875
+ },
876
+
877
+ // ..........................................................
878
+ // INTERNAL SUPPORT
879
+ //
880
+
881
+ /** @private
882
+ Instantiate scrollers & container views as needed. Replace their classes
883
+ in the regular properties.
884
+ */
885
+ createChildViews: function () {
886
+ var childViews = [], view;
887
+
888
+ // create the containerView. We must always have a container view.
889
+ // also, setup the contentView as the child of the containerView...
890
+ if (SC.none(view = this.containerView)) view = SC.ContainerView;
891
+
892
+ childViews.push(this.containerView = this.createChildView(view, {
893
+ contentView: this.contentView,
894
+ isScrollContainer: YES
895
+ }));
896
+
897
+ // and replace our own contentView...
898
+ this.contentView = this.containerView.get('contentView');
899
+
900
+ if (!this.get('wantsNativeScrollbars')) {
901
+ // create a horizontal scroller view if needed...
902
+ view = this.get("horizontalScrollerView");
903
+ if (view) {
904
+ if (this.get('hasHorizontalScroller')) {
905
+ view = this.createChildView(view, {
906
+ layoutDirection: SC.LAYOUT_HORIZONTAL,
907
+ valueBinding: '*owner.horizontalScrollOffset'
908
+ });
909
+ this.set('horizontalScrollerView', view);
910
+ childViews.push(view);
911
+ } else this.horizontalScrollerView = null;
912
+ }
913
+
914
+ // create a vertical scroller view if needed...
915
+ view = this.get("verticalScrollerView");
916
+ if (view) {
917
+ if (this.get('hasVerticalScroller')) {
918
+ view = this.createChildView(view, {
919
+ layoutDirection: SC.LAYOUT_VERTICAL,
920
+ valueBinding: '*owner.verticalScrollOffset'
921
+ });
922
+ this.set('verticalScrollerView', view);
923
+ childViews.push(view);
924
+ } else this.verticalScrollerView = null;
925
+ }
926
+ }
927
+
928
+ // set childViews array.
929
+ this.childViews = childViews;
930
+
931
+ this.contentViewDidChange(); // setup initial display...
932
+ this.tile(); // set up initial tiling
933
+ },
934
+
935
+ /** @private */
936
+ init: function () {
937
+ sc_super();
938
+
939
+ // start observing initial content view. The content view's frame has
940
+ // already been setup in prepareDisplay so we don't need to call
941
+ // viewFrameDidChange...
942
+ this._scroll_contentView = this.get('contentView');
943
+ var contentView = this._scroll_contentView;
944
+
945
+ if (contentView) {
946
+ contentView.addObserver('frame', this, this.contentViewFrameDidChange);
947
+ contentView.addObserver('calculatedWidth', this, this.contentViewFrameDidChange);
948
+ contentView.addObserver('calculatedHeight', this, this.contentViewFrameDidChange);
949
+ }
950
+
951
+ if (this.get('isVisibleInWindow')) this._scsv_registerAutoscroll();
952
+ },
953
+
954
+ /** @private
955
+ Registers/deregisters view with SC.Drag for autoscrolling
956
+ */
957
+ _scsv_registerAutoscroll: function () {
958
+ this.get('isVisibleInWindow') ? SC.Drag.addScrollableView(this) :
959
+ SC.Drag.removeScrollableView(this);
960
+ }.observes('isVisibleInWindow'),
961
+
962
+ /** @private
963
+ Whenever the contentView is changed, we need to observe the content view's
964
+ frame to be notified whenever it's size changes.
965
+ */
966
+ contentViewDidChange: function () {
967
+ var newView = this.get('contentView'),
968
+ oldView = this._scroll_contentView,
969
+ frameObserver = this.contentViewFrameDidChange,
970
+ layerObserver = this.contentViewLayerDidChange;
971
+
972
+ if (newView !== oldView) {
973
+
974
+ // stop observing old content view
975
+ if (oldView) {
976
+ oldView.removeObserver('calculatedWidth', this, this.contentViewFrameDidChange);
977
+ oldView.removeObserver('calculatedHeight', this, this.contentViewFrameDidChange);
978
+ oldView.removeObserver('frame', this, frameObserver);
979
+ oldView.removeObserver('layer', this, layerObserver);
980
+ }
981
+
982
+ // update cache
983
+ this._scroll_contentView = newView;
984
+ if (newView) {
985
+ newView.addObserver('frame', this, frameObserver);
986
+ newView.addObserver('calculatedWidth', this, this.contentViewFrameDidChange);
987
+ newView.addObserver('calculatedHeight', this, this.contentViewFrameDidChange);
988
+ newView.addObserver('layer', this, layerObserver);
989
+ }
990
+
991
+ // replace container
992
+ this.containerView.set('contentView', newView);
993
+
994
+ this.contentViewFrameDidChange();
995
+ }
996
+ }.observes('contentView'),
997
+
998
+ /** @private */
999
+ oldMaxHOffset: 0,
1000
+
1001
+ /** @private */
1002
+ oldMaxVOffset: 0,
1003
+
1004
+ /** @private
1005
+ Invoked whenever the contentView's frame changes. This will update the
1006
+ scroller maxmimum and optionally update the scroller visibility if the
1007
+ size of the contentView changes. We don't care about the origin since
1008
+ that is tracked separately from the offset values.
1009
+
1010
+ @param {Boolean} [force] Re-calculate everything even if the contentView’s frame didn’t change size
1011
+ */
1012
+ contentViewFrameDidChange: function (force) {
1013
+ var view = this.get('contentView'),
1014
+ f = (view) ? view.get('frame') : null,
1015
+ scale = this._scale,
1016
+ width = 0,
1017
+ height = 0,
1018
+ dim, dimWidth, dimHeight, calculatedWidth, calculatedHeight;
1019
+
1020
+ // If no view has been set yet, or it doesn't have a frame,
1021
+ // we can avoid doing any work.
1022
+ if (!view || !f) { return; }
1023
+
1024
+ width = view.get('calculatedWidth') || f.width || 0;
1025
+ height = view.get('calculatedHeight') || f.height || 0;
1026
+
1027
+ width *= scale;
1028
+ height *= scale;
1029
+
1030
+ // cache out scroll settings...
1031
+ if (!force && (width === this._scroll_contentWidth) && (height === this._scroll_contentHeight)) return;
1032
+ this._scroll_contentWidth = width;
1033
+ this._scroll_contentHeight = height;
1034
+
1035
+ dim = this.getPath('containerView.frame');
1036
+ dimWidth = dim.width;
1037
+ dimHeight = dim.height;
1038
+
1039
+ if (this.get('hasHorizontalScroller') && (view = this.get('horizontalScrollerView'))) {
1040
+ // decide if it should be visible or not
1041
+ if (this.get('autohidesHorizontalScroller')) {
1042
+ this.set('isHorizontalScrollerVisible', width > dimWidth);
1043
+ }
1044
+ if (!this.get('wantsNativeScrollbars')) {
1045
+ view.setIfChanged('maximum', width-dimWidth);
1046
+ view.setIfChanged('proportion', dimWidth/width);
1047
+ }
1048
+ }
1049
+
1050
+ if (this.get('hasVerticalScroller') && (view = this.get('verticalScrollerView'))) {
1051
+ // decide if it should be visible or not
1052
+ if (this.get('autohidesVerticalScroller')) {
1053
+ this.set('isVerticalScrollerVisible', height > dimHeight);
1054
+ }
1055
+ if (!this.get('wantsNativeScrollbars')) {
1056
+ view.setIfChanged('maximum', height - dimHeight);
1057
+ view.setIfChanged('proportion', dimHeight / height);
1058
+ }
1059
+ }
1060
+
1061
+ // If there is no vertical scroller and auto hiding is on, make
1062
+ // sure we are at the top if not already there
1063
+ if (!this.get('isVerticalScrollerVisible') && (this.get('verticalScrollOffset') !== 0) &&
1064
+ this.get('autohidesVerticalScroller')) {
1065
+ this.set('verticalScrollOffset', 0);
1066
+ }
1067
+
1068
+ // Same thing for horizontal scrolling.
1069
+ if (!this.get('isHorizontalScrollerVisible') && (this.get('horizontalScrollOffset') !== 0) &&
1070
+ this.get('autohidesHorizontalScroller')) {
1071
+ this.set('horizontalScrollOffset', 0);
1072
+ }
1073
+
1074
+ // This forces to recalculate the height of the frame when is at the bottom
1075
+ // of the scroll and the content dimension are smaller that the previous one
1076
+ var mxVOffSet = this.get('maximumVerticalScrollOffset'),
1077
+ vOffSet = this.get('verticalScrollOffset'),
1078
+ mxHOffSet = this.get('maximumHorizontalScrollOffset'),
1079
+ hOffSet = this.get('horizontalScrollOffset'),
1080
+ forceHeight = mxVOffSet < vOffSet,
1081
+ forceWidth = mxHOffSet < hOffSet;
1082
+
1083
+ if (forceHeight || forceWidth) {
1084
+ this.forceDimensionsRecalculation(forceWidth, forceHeight, vOffSet, hOffSet);
1085
+ }
1086
+ },
1087
+
1088
+ /** @private
1089
+ If our frame changes, then we need to re-calculate the visiblility of our
1090
+ scrollers, etc.
1091
+ */
1092
+ frameDidChange: function () {
1093
+ this.contentViewFrameDidChange(YES);
1094
+ }.observes('frame'),
1095
+
1096
+ /** @private
1097
+ If the layer of the content view changes, we need to readjust the
1098
+ scrollTop and scrollLeft properties on the new DOM element.
1099
+ */
1100
+ contentViewLayerDidChange: function () {
1101
+ // Invalidate these cached values, as they're no longer valid
1102
+ if (this._verticalScrollOffset !== 0) this._verticalScrollOffset = -1;
1103
+ if (this._horizontalScrollOffset !== 0) this._horizontalScrollOffset = -1;
1104
+ this.invokeLast(this.adjustElementScroll);
1105
+ },
1106
+
1107
+ willScroll: function () {
1108
+ var content = this.get('contentView');
1109
+ content && content.willScroll && content.willScroll(this);
1110
+ },
1111
+
1112
+ didScroll: function () {
1113
+ var content = this.get('contentView');
1114
+ content && content.didScroll && content.didScroll(this);
1115
+ },
1116
+
1117
+ /** @private
1118
+ Whenever the horizontal scroll offset changes, update the scrollers and
1119
+ edit the location of the contentView.
1120
+ */
1121
+ _scsv_offsetDidChange: function () {
1122
+ this.invokeLast(this.adjustElementScroll);
1123
+ }.observes('horizontalScrollOffset', 'verticalScrollOffset'),
1124
+
1125
+ /** @private
1126
+ Called at the end of the run loop to actually adjust the scrollTop
1127
+ and scrollLeft properties of the container view.
1128
+ */
1129
+ adjustElementScroll: function () {
1130
+ var content = this.get('contentView');
1131
+
1132
+ // We notify the content view that its frame property has changed
1133
+ // before we actually update the scrollTop/scrollLeft properties.
1134
+ // This gives views that use incremental rendering a chance to render
1135
+ // newly-appearing elements before they come into view.
1136
+ if (content && content._viewFrameDidChange) {
1137
+ content._viewFrameDidChange();
1138
+ }
1139
+ },
1140
+
1141
+ /** @private */
1142
+ forceDimensionsRecalculation: function (forceWidth, forceHeight, vOffSet, hOffSet) {
1143
+ var oldScrollHOffset = hOffSet;
1144
+ var oldScrollVOffset = vOffSet;
1145
+ this.scrollTo(0, 0);
1146
+ if (forceWidth && forceHeight) {
1147
+ this.scrollTo(this.get('maximumHorizontalScrollOffset'),
1148
+ this.get('maximumVerticalScrollOffset'));
1149
+ }
1150
+ if (forceWidth && !forceHeight) {
1151
+ this.scrollTo(this.get('maximumHorizontalScrollOffset'), oldScrollVOffset);
1152
+ }
1153
+ if (!forceWidth && forceHeight) {
1154
+ this.scrollTo(oldScrollHOffset, this.get('maximumVerticalScrollOffset'));
1155
+ }
1156
+ },
1157
+
1158
+ /** @private */
1159
+ _scroll_verticalScrollOffset: 0,
1160
+
1161
+ /** @private */
1162
+ _scroll_horizontalScrollOffset: 0
1163
+
1164
+ });