sproutcore 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. data/History.txt +233 -0
  2. data/Manifest.txt +67 -34
  3. data/bin/sc-build +12 -1
  4. data/bin/sc-gen +1 -1
  5. data/bin/sproutcore +14 -0
  6. data/clients/sc_docs/controllers/docs.js +38 -8
  7. data/clients/sc_docs/english.lproj/body.css +80 -127
  8. data/clients/sc_docs/english.lproj/body.rhtml +43 -23
  9. data/clients/sc_docs/english.lproj/no_docs.rhtml +2 -1
  10. data/clients/sc_docs/english.lproj/tabs.rhtml +16 -0
  11. data/clients/sc_docs/main.js +14 -9
  12. data/clients/sc_docs/models/doc.js +1 -1
  13. data/clients/sc_docs/tests/controllers/docs.rhtml +1 -2
  14. data/clients/sc_docs/tests/models/doc.rhtml +1 -2
  15. data/clients/sc_docs/tests/views/doc_frame.rhtml +1 -2
  16. data/clients/sc_docs/tests/views/doc_label_view.rhtml +1 -2
  17. data/clients/sc_docs/views/doc_frame.js +1 -1
  18. data/clients/sc_test_runner/controllers/runner.js +31 -8
  19. data/clients/sc_test_runner/english.lproj/body.css +62 -122
  20. data/clients/sc_test_runner/english.lproj/body.rhtml +62 -26
  21. data/clients/sc_test_runner/main.js +1 -6
  22. data/clients/sc_test_runner/models/test.js +14 -1
  23. data/clients/sc_test_runner/views/runner_frame.js +4 -2
  24. data/clients/view_builder/builders/builder.js +339 -0
  25. data/clients/view_builder/builders/button.js +81 -0
  26. data/clients/view_builder/controllers/document.js +21 -0
  27. data/clients/view_builder/core.js +19 -0
  28. data/clients/view_builder/english.lproj/body.css +77 -0
  29. data/clients/view_builder/english.lproj/body.rhtml +41 -0
  30. data/clients/{sc_docs → view_builder}/english.lproj/controls.css +0 -0
  31. data/clients/view_builder/english.lproj/strings.js +14 -0
  32. data/clients/view_builder/main.js +38 -0
  33. data/clients/view_builder/tests/controllers/document.rhtml +20 -0
  34. data/clients/view_builder/tests/views/builder.rhtml +20 -0
  35. data/clients/view_builder/views/builder.js +23 -0
  36. data/frameworks/prototype/prototype.js +1 -1
  37. data/frameworks/sproutcore/Core.js +32 -7
  38. data/frameworks/sproutcore/README +1 -1
  39. data/frameworks/sproutcore/animation/animation.js +411 -0
  40. data/frameworks/sproutcore/controllers/array.js +17 -9
  41. data/frameworks/sproutcore/controllers/collection.js +9 -110
  42. data/frameworks/sproutcore/controllers/controller.js +1 -1
  43. data/frameworks/sproutcore/controllers/object.js +2 -1
  44. data/frameworks/sproutcore/drag/drag.js +267 -56
  45. data/frameworks/sproutcore/drag/drag_data_source.js +24 -16
  46. data/frameworks/sproutcore/drag/drag_source.js +53 -42
  47. data/frameworks/sproutcore/drag/drop_target.js +2 -2
  48. data/frameworks/sproutcore/english.lproj/buttons.css +337 -236
  49. data/frameworks/sproutcore/english.lproj/core.css +115 -0
  50. data/frameworks/sproutcore/english.lproj/icons.css +227 -0
  51. data/{clients/sc_docs → frameworks/sproutcore}/english.lproj/images/indicator.gif +0 -0
  52. data/frameworks/sproutcore/english.lproj/images/sc-theme-sprite.png +0 -0
  53. data/frameworks/sproutcore/english.lproj/images/sc-theme-ysprite.png +0 -0
  54. data/frameworks/sproutcore/english.lproj/images/shared-icons.png +0 -0
  55. data/frameworks/sproutcore/english.lproj/menu.css +1 -1
  56. data/frameworks/sproutcore/english.lproj/strings.js +1 -1
  57. data/frameworks/sproutcore/english.lproj/theme.css +405 -31
  58. data/frameworks/sproutcore/foundation/application.js +15 -11
  59. data/frameworks/sproutcore/foundation/benchmark.js +1 -1
  60. data/frameworks/sproutcore/foundation/binding.js +2 -2
  61. data/frameworks/sproutcore/foundation/date.js +1 -1
  62. data/frameworks/sproutcore/foundation/error.js +1 -1
  63. data/frameworks/sproutcore/foundation/input_manager.js +32 -21
  64. data/frameworks/sproutcore/foundation/mock.js +1 -1
  65. data/frameworks/sproutcore/foundation/node_descriptor.js +9 -6
  66. data/frameworks/sproutcore/foundation/object.js +249 -177
  67. data/frameworks/sproutcore/foundation/page.js +5 -2
  68. data/frameworks/sproutcore/foundation/path_module.js +11 -10
  69. data/frameworks/sproutcore/foundation/responder.js +5 -2
  70. data/frameworks/sproutcore/foundation/routes.js +17 -13
  71. data/frameworks/sproutcore/foundation/run_loop.js +249 -11
  72. data/frameworks/sproutcore/foundation/server.js +1 -1
  73. data/frameworks/sproutcore/foundation/set.js +3 -3
  74. data/frameworks/sproutcore/foundation/string.js +5 -3
  75. data/frameworks/sproutcore/foundation/timer.js +371 -0
  76. data/frameworks/sproutcore/foundation/undo_manager.js +1 -1
  77. data/frameworks/sproutcore/foundation/unittest.js +3 -3
  78. data/frameworks/sproutcore/foundation/utils.js +161 -2
  79. data/frameworks/sproutcore/globals/panels.js +1 -1
  80. data/frameworks/sproutcore/globals/popups.js +4 -3
  81. data/frameworks/sproutcore/globals/window.js +44 -4
  82. data/frameworks/sproutcore/lib/button_views.rb +328 -0
  83. data/frameworks/sproutcore/lib/collection_view.rb +80 -0
  84. data/frameworks/sproutcore/lib/core_views.rb +281 -0
  85. data/frameworks/sproutcore/lib/form_views.rb +253 -0
  86. data/frameworks/sproutcore/lib/index.rhtml +2 -0
  87. data/frameworks/sproutcore/lib/menu_views.rb +88 -0
  88. data/frameworks/sproutcore/{foundation → mixins}/array.js +60 -29
  89. data/frameworks/sproutcore/mixins/control.js +265 -0
  90. data/frameworks/sproutcore/mixins/delegate_support.js +66 -0
  91. data/frameworks/sproutcore/{foundation → mixins}/observable.js +176 -6
  92. data/frameworks/sproutcore/mixins/scrollable.js +245 -0
  93. data/frameworks/sproutcore/mixins/selection_support.js +148 -0
  94. data/frameworks/sproutcore/mixins/validatable.js +152 -0
  95. data/frameworks/sproutcore/models/collection.js +5 -5
  96. data/frameworks/sproutcore/models/record.js +1 -1
  97. data/frameworks/sproutcore/models/store.js +1 -1
  98. data/frameworks/sproutcore/panes/dialog.js +1 -1
  99. data/frameworks/sproutcore/panes/manager.js +1 -1
  100. data/frameworks/sproutcore/panes/menu.js +1 -1
  101. data/frameworks/sproutcore/panes/overlay.js +2 -2
  102. data/frameworks/sproutcore/panes/panel.js +1 -1
  103. data/frameworks/sproutcore/panes/picker.js +1 -1
  104. data/frameworks/sproutcore/tests/controllers/array.rhtml +44 -4
  105. data/frameworks/sproutcore/tests/foundation/timer/invalidate.rhtml +33 -0
  106. data/frameworks/sproutcore/tests/foundation/timer/invokeLater.rhtml +145 -0
  107. data/frameworks/sproutcore/tests/foundation/timer/isPaused.rhtml +70 -0
  108. data/frameworks/sproutcore/tests/foundation/timer/schedule.rhtml +145 -0
  109. data/frameworks/sproutcore/tests/views/{scroll.rhtml → checkbox.rhtml} +3 -3
  110. data/frameworks/sproutcore/tests/views/{collection.rhtml → collection/base.rhtml} +33 -32
  111. data/frameworks/sproutcore/tests/views/collection/incremental_rendering.rhtml +260 -0
  112. data/frameworks/sproutcore/tests/views/image_cell.rhtml +19 -0
  113. data/frameworks/sproutcore/tests/views/label_item.rhtml +2 -4
  114. data/frameworks/sproutcore/tests/views/list.rhtml +2 -3
  115. data/frameworks/sproutcore/tests/views/list_item.rhtml +20 -0
  116. data/frameworks/sproutcore/tests/views/slider.rhtml +20 -0
  117. data/frameworks/sproutcore/tests/views/text_cell.rhtml +19 -0
  118. data/frameworks/sproutcore/tests/views/view/clippingFrame.rhtml +395 -0
  119. data/frameworks/sproutcore/tests/views/view/frame.rhtml +353 -0
  120. data/frameworks/sproutcore/tests/views/view/innerFrame.rhtml +347 -0
  121. data/frameworks/sproutcore/tests/views/view/isVisibleInWindow.rhtml +148 -0
  122. data/frameworks/sproutcore/tests/views/view/scrollFrame.rhtml +468 -0
  123. data/frameworks/sproutcore/validators/credit_card.js +33 -13
  124. data/frameworks/sproutcore/validators/date.js +26 -6
  125. data/frameworks/sproutcore/validators/email.js +21 -3
  126. data/frameworks/sproutcore/validators/not_empty.js +11 -1
  127. data/frameworks/sproutcore/validators/number.js +18 -4
  128. data/frameworks/sproutcore/validators/password.js +12 -1
  129. data/frameworks/sproutcore/validators/validator.js +204 -194
  130. data/frameworks/sproutcore/views/{button.js → button/button.js} +96 -94
  131. data/frameworks/sproutcore/views/button/checkbox.js +29 -0
  132. data/frameworks/sproutcore/views/button/disclosure.js +42 -0
  133. data/frameworks/sproutcore/views/button/radio.js +29 -0
  134. data/frameworks/sproutcore/views/{collection.js → collection/collection.js} +1373 -1024
  135. data/frameworks/sproutcore/views/collection/grid.js +124 -46
  136. data/frameworks/sproutcore/views/collection/image_cell.js +17 -46
  137. data/frameworks/sproutcore/views/collection/list.js +45 -35
  138. data/frameworks/sproutcore/views/collection/source_list.js +386 -0
  139. data/frameworks/sproutcore/views/collection/table.js +118 -0
  140. data/frameworks/sproutcore/views/container.js +7 -2
  141. data/frameworks/sproutcore/views/error_explanation.js +23 -10
  142. data/frameworks/sproutcore/views/{checkbox_field.js → field/checkbox_field.js} +16 -6
  143. data/frameworks/sproutcore/views/field/field.js +219 -0
  144. data/frameworks/sproutcore/views/{radio_field.js → field/radio_field.js} +27 -12
  145. data/frameworks/sproutcore/views/{select_field.js → field/select_field.js} +116 -90
  146. data/frameworks/sproutcore/views/{text_field.js → field/text_field.js} +57 -8
  147. data/frameworks/sproutcore/views/{textarea_field.js → field/textarea_field.js} +13 -3
  148. data/frameworks/sproutcore/views/filter_button.js +2 -2
  149. data/frameworks/sproutcore/views/form.js +3 -3
  150. data/frameworks/sproutcore/views/image.js +128 -21
  151. data/frameworks/sproutcore/views/inline_text_editor.js +1 -1
  152. data/frameworks/sproutcore/views/label.js +149 -92
  153. data/frameworks/sproutcore/views/list_item.js +225 -0
  154. data/frameworks/sproutcore/views/menu_item.js +10 -4
  155. data/frameworks/sproutcore/views/pagination.js +11 -4
  156. data/frameworks/sproutcore/views/popup_button.js +25 -21
  157. data/frameworks/sproutcore/views/popup_menu.js +10 -4
  158. data/frameworks/sproutcore/views/progress.js +29 -16
  159. data/frameworks/sproutcore/views/radio_group.js +1 -1
  160. data/frameworks/sproutcore/views/scroll.js +60 -20
  161. data/frameworks/sproutcore/views/segmented.js +1 -1
  162. data/frameworks/sproutcore/views/slider.js +132 -0
  163. data/frameworks/sproutcore/views/source_list_group.js +130 -0
  164. data/frameworks/sproutcore/views/spinner.js +1 -1
  165. data/frameworks/sproutcore/views/split.js +292 -0
  166. data/frameworks/sproutcore/views/split_divider.js +109 -0
  167. data/frameworks/sproutcore/views/tab.js +1 -1
  168. data/frameworks/sproutcore/views/toolbar.js +1 -1
  169. data/frameworks/sproutcore/views/view.js +1272 -591
  170. data/generators/client/templates/english.lproj/body.css +1 -1
  171. data/generators/controller/controller_generator.rb +1 -1
  172. data/generators/controller/templates/test.rhtml +2 -1
  173. data/generators/model/templates/test.rhtml +1 -1
  174. data/generators/test/templates/test.rhtml +1 -1
  175. data/generators/view/templates/test.rhtml +1 -1
  176. data/jsdoc/templates/sproutcore/class.tmpl +241 -338
  177. data/jsdoc/templates/sproutcore/default.css +105 -155
  178. data/jsdoc/templates/sproutcore/index.tmpl +43 -8
  179. data/jsdoc/templates/sproutcore/publish.js +9 -4
  180. data/lib/sproutcore/build_tools/html_builder.rb +29 -13
  181. data/lib/sproutcore/build_tools/resource_builder.rb +1 -1
  182. data/lib/sproutcore/bundle.rb +86 -25
  183. data/lib/sproutcore/jsdoc.rb +2 -0
  184. data/lib/sproutcore/version.rb +1 -1
  185. data/lib/sproutcore/view_helpers.rb +36 -3
  186. data/tasks/deployment.rake +1 -1
  187. metadata +69 -36
  188. data/clients/sc_docs/english.lproj/icons/small/next.png +0 -0
  189. data/clients/sc_docs/english.lproj/icons/small/reset.png +0 -0
  190. data/clients/sc_docs/english.lproj/images/gradients.png +0 -0
  191. data/clients/sc_docs/english.lproj/images/toolbar.png +0 -0
  192. data/clients/sc_docs/english.lproj/warning.rhtml +0 -6
  193. data/clients/sc_test_runner/english.lproj/warning.rhtml +0 -6
  194. data/frameworks/sproutcore/english.lproj/buttons.png +0 -0
  195. data/frameworks/sproutcore/english.lproj/collections.css +0 -82
  196. data/frameworks/sproutcore/english.lproj/images/buttons-sprite.png +0 -0
  197. data/frameworks/sproutcore/views/collection/collection_item.js +0 -36
  198. data/frameworks/sproutcore/views/collection/text_cell.js +0 -128
  199. data/frameworks/sproutcore/views/field.js +0 -214
  200. data/frameworks/sproutcore/views/workspace.js +0 -170
  201. data/generators/client/templates/english.lproj/controls.css +0 -0
  202. data/generators/framework/templates/english.lproj/body.css +0 -0
  203. data/generators/framework/templates/english.lproj/body.rhtml +0 -3
  204. data/generators/framework/templates/english.lproj/controls.css +0 -0
  205. data/lib/sproutcore/view_helpers/button_views.rb +0 -302
  206. data/lib/sproutcore/view_helpers/core_views.rb +0 -292
  207. data/lib/sproutcore/view_helpers/form_views.rb +0 -258
  208. data/lib/sproutcore/view_helpers/menu_views.rb +0 -94
@@ -0,0 +1,109 @@
1
+ // ========================================================================
2
+ // SproutCore
3
+ // copyright 2006-2008 Sprout Systems, Inc.
4
+ // ========================================================================
5
+
6
+ require('views/view') ;
7
+ require('views/split');
8
+
9
+ /**
10
+ @class
11
+
12
+ A SplitDividerView displays a divider between two split views. Clicking
13
+ and dragging the divider will change the thickness of the view either to
14
+ the left or right of the divider, depending on which side of the flexible
15
+ view the divider is on.
16
+
17
+ Double-clicking will try to collapse the same view so it is not visible
18
+ unless you have canCollapse disabled on the SplitView.
19
+
20
+ This view must be a direct child of the split view it works with.
21
+
22
+ @extends SC.View
23
+
24
+ @author Charles Jolley
25
+ */
26
+ SC.SplitDividerView = SC.View.extend(
27
+ /** @scope SC.SplitDividerView.prototype */ {
28
+
29
+ emptyElement: '<div class="sc-split-divider-view"></div>',
30
+
31
+ /**
32
+ Returns the view to be managed by the divider view.
33
+ */
34
+ targetView: function() {
35
+ var splitView = this.get('parentNode') ;
36
+ if (!splitView) return null ;
37
+
38
+ var flexibleView = splitView.computeFlexibleView() ;
39
+ var views = splitView.get('childNodes') ;
40
+ var myIndex = views.indexOf(this) ;
41
+ var flexibleIndex = views.indexOf(flexibleView) ;
42
+
43
+ if (myIndex < 0) throw "SplitDividerView must belong to the SplitView";
44
+
45
+ return (myIndex <= flexibleIndex) ? this.get('previousSibling') : this.get('nextSibling') ;
46
+
47
+ }.property(),
48
+
49
+ mouseDown: function(evt) {
50
+
51
+ var splitView = this.get('parentNode') ;
52
+ if (!splitView) return ;
53
+
54
+ // cache some info for later use.
55
+ this._mouseDownLocation = Event.pointerLocation(evt) ;
56
+
57
+ this._targetView = this.get('targetView') ;
58
+
59
+ // determine the view to change.
60
+ this._originalThickness = splitView.thicknessForView(this._targetView);
61
+
62
+ this._direction = splitView.get('layoutDirection') ;
63
+
64
+ // return true so we can track mouse dragged.
65
+ return true ;
66
+ },
67
+
68
+ mouseDragged: function(evt) {
69
+
70
+ // calculate new thickness
71
+ var loc = Event.pointerLocation(evt) ;
72
+
73
+ if (this._direction == SC.HORIZONTAL) {
74
+ var offset = loc.x - this._mouseDownLocation.x ;
75
+ } else {
76
+ var offset = loc.y - this._mouseDownLocation.y ;
77
+ }
78
+
79
+ var thickness = this._originalThickness + offset ;
80
+ var splitView = this.get('parentNode') ;
81
+ splitView.setThicknessForView(this._targetView, thickness) ;
82
+
83
+ return true ;
84
+ },
85
+
86
+ // clear left overs.
87
+ mouseUp: function(evt) {
88
+ this._targetView = this._originalThickness = this._direction = this._mouseDownLocation = null ;
89
+ },
90
+
91
+ doubleClick: function(evt) {
92
+ var splitView = this.get('parentNode') ;
93
+ if (!splitView) return; // nothing to do.
94
+
95
+ // try to collapse or un-collapse.
96
+ var targetView = this.get('targetView');
97
+ var isCollapsed = targetView.get('isCollapsed') || NO;
98
+
99
+ // do not collapse if not allowed.
100
+ if (!isCollapsed && !splitView.canCollapseView(targetView)) return;
101
+
102
+ // now set the collapsed state and layout.
103
+ targetView.set('isCollapsed', !isCollapsed) ;
104
+ splitView.layout() ;
105
+
106
+ return true ;
107
+ }
108
+
109
+ });
@@ -1,6 +1,6 @@
1
1
  // ========================================================================
2
2
  // SproutCore
3
- // copyright 2006-2007 Sprout Systems, Inc.
3
+ // copyright 2006-2008 Sprout Systems, Inc.
4
4
  // ========================================================================
5
5
 
6
6
  require('views/view') ;
@@ -1,6 +1,6 @@
1
1
  // ========================================================================
2
2
  // SproutCore
3
- // copyright 2006-2007 Sprout Systems, Inc.
3
+ // copyright 2006-2008 Sprout Systems, Inc.
4
4
  // ========================================================================
5
5
 
6
6
  // A menu is not a view exactly, but you can use it to bundle together
@@ -1,6 +1,6 @@
1
1
  // ========================================================================
2
2
  // SproutCore
3
- // copyright 2006-2007 Sprout Systems, Inc.
3
+ // copyright 2006-2008 Sprout Systems, Inc.
4
4
  // ========================================================================
5
5
 
6
6
  require('foundation/object') ;
@@ -9,20 +9,33 @@ require('foundation/node_descriptor') ;
9
9
  require('foundation/binding');
10
10
  require('foundation/path_module');
11
11
 
12
- BENCHMARK_OUTLETS = NO ;
13
- SC.FIXED = 'fixed';
14
- SC.FLEXIBLE = 'flexible';
12
+ require('mixins/delegate_support') ;
13
+
14
+ SC.BENCHMARK_OUTLETS = NO ;
15
+ SC.BENCHMARK_CONFIGURE_OUTLETS = NO ;
15
16
 
16
17
  /**
17
- @class Manages a DOM element for display.
18
+ @class
19
+
20
+ A view is the root class you use to manage the web page DOM in your
21
+ application. You can use views to render visible content on your page,
22
+ provide animations, and to capture and respond to events.
23
+
24
+ You can use SC.View directly to manage DOM elements or you can extend one
25
+ of the many subclasses provided by SproutCore. This documentation describes
26
+ the general concepts you need to understand when working with views, though
27
+ most often you will want to work with one of the subclasses instead.
18
28
 
19
- Views are how you interact with the DOM.
20
29
 
21
30
  @extends SC.Responder
31
+ @extends SC.PathModule
32
+ @extends SC.DelegateSupport
33
+
34
+ @author Charles Jolley
35
+ @version 1.0
22
36
  */
23
- SC.View = SC.Responder.extend(SC.PathModule,
24
- /** @scope SC.View.prototype */
25
- {
37
+ SC.View = SC.Responder.extend(SC.PathModule, SC.DelegateSupport,
38
+ /** @scope SC.View.prototype */ {
26
39
 
27
40
  // ..........................................
28
41
  // VIEW API
@@ -33,14 +46,27 @@ SC.View = SC.Responder.extend(SC.PathModule,
33
46
  // array and use standard iterators.
34
47
  //
35
48
 
36
- /*
37
- insert the view before the specified view. pass null to insert at the
38
- end.
49
+ /**
50
+ Insert the view into the the receiver's childNodes array.
51
+
52
+ The view will be added to the childNodes array before the beforeView. If
53
+ beforeView is null, then the view will be added to the end of the array.
54
+ This will also add the view's rootElement DOM node to the receivers
55
+ containerElement DOM node as a child.
56
+
57
+ If the specified view already belongs to another parent, it will be
58
+ removed from that view first.
59
+
60
+ @param view {SC.View} the view to insert as a child node.
61
+ @param beforeView {SC.View} view to insert before, or null to insert at
62
+ end
63
+ @returns {void}
39
64
  */
40
65
  insertBefore: function(view, beforeView) {
41
66
  this._insertBefore(view,beforeView,true);
42
67
  },
43
-
68
+
69
+ /** @private */
44
70
  _insertBefore: function(view, beforeView, updateDom) {
45
71
  // verify that beforeView is a child.
46
72
  if (beforeView) {
@@ -77,11 +103,12 @@ SC.View = SC.Responder.extend(SC.PathModule,
77
103
 
78
104
  // regenerate the childNodes array.
79
105
  this._rebuildChildNodes();
80
-
81
- // update parent state.
82
- view._updateIsVisibleInWindow() ;
83
106
  }
84
107
 
108
+ // update cached states.
109
+ view._updateIsVisibleInWindow() ;
110
+ view._flushInternalCaches() ;
111
+ view._invalidateClippingFrame() ;
85
112
 
86
113
  // call notices.
87
114
  view.didAddToParent(this, beforeView) ;
@@ -89,8 +116,15 @@ SC.View = SC.Responder.extend(SC.PathModule,
89
116
 
90
117
  return this ;
91
118
  },
92
-
93
- // remove the current child
119
+
120
+ /**
121
+ Remove the view from the receiver's childNodes array.
122
+
123
+ This will also remove the view's DOM element from the recievers DOM.
124
+
125
+ @param view {SC.View} the view to remove
126
+ @returns {void}
127
+ */
94
128
  removeChild: function(view) {
95
129
  if (!view) return ;
96
130
  if (view.parentNode != this) throw "removeChild: view must belong to parent";
@@ -116,53 +150,126 @@ SC.View = SC.Responder.extend(SC.PathModule,
116
150
  // regenerate the childNodes array.
117
151
  this._rebuildChildNodes();
118
152
 
119
- // update parent state.
120
- view._updateIsVisibleInWindow() ;
121
-
122
-
123
153
  view.set('nextSibling', null);
124
154
  view.set('previousSibling', null);
125
155
  view.set('parentNode', null) ;
156
+
157
+ // update parent state.
158
+ view._updateIsVisibleInWindow() ;
159
+ view._flushInternalCaches();
160
+ view._invalidateClippingFrame() ;
161
+
126
162
  view.didRemoveFromParent(this) ;
127
163
  this.didRemoveChild(view);
128
164
  },
129
-
130
- // replace the oldView with the new view.
165
+
166
+ /**
167
+ Replace the oldView with the specified view in the receivers childNodes
168
+ array. This will also replace the DOM node of the oldView with the DOM
169
+ node of the new view in the receivers DOM.
170
+
171
+ If the specified view already belongs to another parent, it will be
172
+ removed from that view first.
173
+
174
+ @param view {SC.View} the view to insert in the DOM
175
+ @param view {SC.View} the view to remove from the DOM.
176
+ @returns {void}
177
+ */
131
178
  replaceChild: function(view, oldView) {
132
179
  this.insertBefore(view,oldView) ; this.removeChild(oldView) ;
133
180
  },
134
-
135
- // remove the receiver from the parent view. Safe to call even if there
136
- // is no parent node.
181
+
182
+ /**
183
+ Removes the receiver from its parentNode. If the receiver does not belong
184
+ to a parentNode, this method does nothing.
185
+
186
+ @returns {void}
187
+ */
137
188
  removeFromParent: function() {
138
189
  if (this.parentNode) this.parentNode.removeChild(this) ;
139
190
  },
140
-
141
- // add a child to the end of the current views.
191
+
192
+ /**
193
+ Appends the specified view to the end of the receivers childNodes array.
194
+ This is equivalent to calling insertBefore(view, null);
195
+
196
+ @param view {SC.View} the view to insert
197
+ @returns {void}
198
+ */
142
199
  appendChild: function(view) {
143
200
  this.insertBefore(view,null) ;
144
201
  },
145
-
146
- // this array contains the childViews associated with this view. You should
147
- // always access this via a GET.
202
+
203
+ /**
204
+ The array of views that are direct children of the receiver view. The DOM
205
+ elements managed by the views are also directl children of the
206
+ containerElement for the receiver.
207
+
208
+ @property
209
+ @type Array
210
+ */
148
211
  childNodes: [],
149
-
150
- // the first child in the chain.
212
+
213
+ /**
214
+ The first child view in the childNodes array. If the view does not have
215
+ any children, this property will be null.
216
+
217
+ @property
218
+ @type SC.View
219
+ */
151
220
  firstChild: null,
152
221
 
153
- // the last child in the chain.
222
+ /**
223
+ The last child view in the childNodes array. If the view does not have any children,
224
+ this property will be null.
225
+
226
+ @property
227
+ @type SC.View
228
+ */
154
229
  lastChild: null,
155
230
 
156
- // the next child view. access via a get()
231
+ /**
232
+ The next sibling view in the childNodes array of the receivers parentNode.
233
+ If the receiver is the last view in the array or if the receiver does not
234
+ belong to a parent view this property will be null.
235
+
236
+ @property
237
+ @type SC.View
238
+ */
157
239
  nextSibling: null,
158
240
 
159
- // the previous view
241
+ /**
242
+ The previous sibling view in the childNodes array of the receivers
243
+ parentNode. If the receiver is the first view in the array or if the
244
+ receiver does not belong to a parent view this property will be null.
245
+
246
+ @property
247
+ @type SC.View
248
+ */
160
249
  previousSibling: null,
161
250
 
162
- // the parent node. null if not in the hierarchy.
251
+ /**
252
+ The parent view this view belongs to. If the receiver does not belong to a parent view
253
+ then this property is null.
254
+
255
+ @property
256
+ @type SC.View
257
+ */
163
258
  parentNode: null,
164
259
 
165
-
260
+
261
+ /**
262
+ The pane this view belongs to. The pane is the root of the responder
263
+ chain that this view belongs to. Typically a view's pane will be the
264
+ SC.window object. However, if you have added the view to a dialog, panel,
265
+ popup or other pane, this property will point to that pane instead.
266
+
267
+ If the view does not belong to a parentNode or if the view is not
268
+ onscreen, this property will be null.
269
+
270
+ @property
271
+ @type SC.View
272
+ */
166
273
  pane: function()
167
274
  {
168
275
  var view = this;
@@ -173,34 +280,125 @@ SC.View = SC.Responder.extend(SC.PathModule,
173
280
  return view;
174
281
  }.property(),
175
282
 
176
-
177
- // This will remove all child views.
283
+
284
+ /**
285
+ Removes all child views from the receiver.
286
+
287
+ @returns {void}
288
+ */
178
289
  clear: function() {
179
290
  while(this.firstChild) this.removeChild(this.firstChild) ;
180
291
  },
181
-
182
- // This callback is invoke just before your view is added to a new parent.
292
+
293
+ /**
294
+ This method is called on the view just before it is added to a new parent
295
+ view.
296
+
297
+ You can override this method to do any setup you need on your view or to
298
+ reset any cached values that are impacted by being added to a view. The
299
+ default implementation does nothing.
300
+
301
+ @param parent {SC.View} the new parent
302
+ @paran beforeView {SC.View} the view in the parent's childNodes array that
303
+ will follow this view once it is added. If the view is being added to
304
+ the end of the array, this will be null.
305
+ @returns {void}
306
+ */
183
307
  willAddToParent: function(parent, beforeView) {},
184
308
 
185
- // This callback is invoked just after your view added to a new parent.
309
+ /**
310
+ This method is called on the view just after it is added to a new parent
311
+ view.
312
+
313
+ You can override this method to do any setup you need on your view or to
314
+ reset any cached values that are impacted by being added to a view. The
315
+ default implementation does nothing.
316
+
317
+ @param parent {SC.View} the new parent
318
+ @paran beforeView {SC.View} the view in the parent's childNodes array that
319
+ will follow this view once it is added. If the view is being added to
320
+ the end of the array, this will be null.
321
+ @returns {void}
322
+ */
186
323
  didAddToParent: function(parent, beforeView) {},
187
324
 
188
- // This callback is invoked just before your view is removed from a parent.
325
+ /**
326
+ This method is called on the view just before it is removed from a parent
327
+ view.
328
+
329
+ You can override this method to clear out any values that depend on the
330
+ view belonging to the current parentNode. The default implementation does
331
+ nothing.
332
+
333
+ @returns {void}
334
+ */
189
335
  willRemoveFromParent: function() {},
190
336
 
191
- // This callback is invoked just after your view is remove from a parent.
337
+ /**
338
+ This method is called on the view just after it is removed from a parent
339
+ view.
340
+
341
+ You can override this method to clear out any values that depend on the
342
+ view belonging to the current parentNode. The default implementation does
343
+ nothing.
344
+
345
+ @param oldParent {SC.View} the old parent view
346
+ @returns {void}
347
+ */
192
348
  didRemoveFromParent: function(oldParent) {},
193
349
 
194
- // This callback is invoked just before a new child is added to view.
350
+ /**
351
+ This method is called just before a new child view is added to the
352
+ receiver's childNodes array. You can use this to prepare for any layout
353
+ or other cleanup you might need to do.
354
+
355
+ The default implementation does nothing.
356
+
357
+ @param child {SC.View} the view to be added
358
+ @param beforeView {SC.View} and existing child view that will follow the
359
+ child view in the array once it is added. If adding to the end of the
360
+ array, this param will be null.
361
+ @returns {void}
362
+ */
195
363
  willAddChild: function(child, beforeView) {},
196
364
 
197
- // This callback is invoked just after a new child is added to a view.
365
+ /**
366
+ This method is called just after a new child view is added to the
367
+ receiver's childNodes array. You can use this to prepare for any layout
368
+ or other cleanup you might need to do.
369
+
370
+ The default implementation does nothing.
371
+
372
+ @param child {SC.View} the view that was added
373
+ @param beforeView {SC.View} and existing child view that will follow the
374
+ child view in the array once it is added. If adding to the end of the
375
+ array, this param will be null.
376
+ @returns {void}
377
+ */
198
378
  didAddChild: function(child, beforeView) {},
199
379
 
200
- // This callback is invoke just before a child is removed from a view.
380
+ /**
381
+ This method is called just before a child view is removed from the
382
+ receiver's childNodes array. You can use this to prepare for any layout
383
+ or other cleanup you might need to do.
384
+
385
+ The default implementation does nothing.
386
+
387
+ @param child {SC.View} the view to be removed
388
+ @returns {void}
389
+ */
201
390
  willRemoveChild: function(child) {},
202
391
 
203
- // This callback is invoked this just after a child is removed from a view.
392
+ /**
393
+ This method is called just after a child view is removed from the
394
+ receiver's childNodes array. You can use this to prepare for any layout
395
+ or other cleanup you might need to do.
396
+
397
+ The default implementation does nothing.
398
+
399
+ @param child {SC.View} the view that was removed
400
+ @returns {void}
401
+ */
204
402
  didRemoveChild: function(child) {},
205
403
 
206
404
 
@@ -212,7 +410,9 @@ SC.View = SC.Responder.extend(SC.PathModule,
212
410
  var view = this;
213
411
  while (view = view.get('nextKeyView'))
214
412
  {
215
- if (view.get('isVisible') && view.get('acceptsFirstResponder')) return view;
413
+ if (view.get('isVisible') && view.get('acceptsFirstResponder')) {
414
+ return view;
415
+ }
216
416
  }
217
417
  return null;
218
418
  },
@@ -221,11 +421,25 @@ SC.View = SC.Responder.extend(SC.PathModule,
221
421
  var view = this;
222
422
  while (view = view.get('previousKeyView'))
223
423
  {
224
- if (view.get('isVisible') && view.get('acceptsFirstResponder')) return view;
424
+ if (view.get('isVisible') && view.get('acceptsFirstResponder')) {
425
+ return view;
426
+ }
225
427
  }
226
428
  return null;
227
429
  },
228
430
 
431
+ /** @private
432
+ Invoked whenever the child hierarchy changes and any internally cached
433
+ values might need to be recalculated.
434
+ */
435
+ _flushInternalCaches: function() {
436
+ // only flush cache for parent if this item was cached since the top level
437
+ // cached can only be populated if this one is populated also...
438
+ if ((this._needsClippingFrame != null) || (this._needsFrameChanges != null)) {
439
+ this._needsClippingFrame = this._needsFrameChanges = null ;
440
+ if (this.parentNode) this.parentNode._flushInternalCaches() ;
441
+ }
442
+ },
229
443
 
230
444
  // ..........................................
231
445
  // SC.Responder implementation
@@ -258,49 +472,77 @@ SC.View = SC.Responder.extend(SC.PathModule,
258
472
 
259
473
  // returns the CSS classNames for the element.
260
474
  classNames: function() {
261
- return Element.classNames(this.rootElement);
475
+ if (!this._classNames) {
476
+ var classNames = this.rootElement.className;
477
+ this._classNames = (classNames && classNames.length > 0) ? classNames.split(' ') : [] ;
478
+ }
479
+ return this._classNames ;
262
480
  }.property(),
263
481
 
264
482
  // return true if the element has the classname.
265
483
  hasClassName: function(className) {
266
- var ret = Element.hasClassName(this.rootElement,className) ;
267
- this.propertyDidChange('classNames') ;
268
- return ret ;
484
+ return (this._classNames || this.get('classNames')).indexOf(className) >= 0 ;
269
485
  },
270
486
 
271
487
  // add the specified class name.
272
488
  addClassName: function(className) {
273
- var ret = Element.addClassName(this.rootElement,className) ;
489
+ if (this.hasClassName(className)) return ; // nothing to do
490
+
491
+ this.propertyWillChange('classNames') ;
492
+ var classNames = this._classNames || this.get('classNames') ;
493
+ classNames.push(className) ;
494
+ if (this.rootElement) this.rootElement.className = classNames.join(' ');
274
495
  this.propertyDidChange('classNames') ;
275
- return ret ;
496
+ return className ;
276
497
  },
277
498
 
278
499
  // remove the specified class name.
279
500
  removeClassName: function(className) {
280
- var ret = Element.removeClassName(this.rootElement,className) ;
501
+ if (!this.hasClassName(className)) return ; // nothing to do
502
+
503
+ this.propertyWillChange('classNames') ;
504
+ var classNames = this._classNames || this.get('classNames') ;
505
+ classNames = this._classNames = classNames.without(className) ;
506
+ if (this.rootElement) this.rootElement.className = classNames.join(' ');
281
507
  this.propertyDidChange('classNames') ;
282
- return ret ;
508
+ return className ;
283
509
  },
284
510
 
285
511
  setClassName: function(className, flag) {
286
- (!!flag) ? this.addClassName(className) : this.removeClassName(className);
512
+ return (!!flag) ? this.addClassName(className) : this.removeClassName(className);
287
513
  },
288
514
 
289
515
  // toggler specified class name..
290
516
  toggleClassName: function(className) {
291
- var ret = Element.toggleClassName(this.rootElement,className) ;
292
- this.propertyDidChange('classNames') ;
293
- return ret ;
294
- },
295
-
296
- // scroll the main window to the selected element.
297
- scrollTo: function() {
298
- Element.scrollTo(this.rootElement) ;
517
+ return this.setClassName(className, !this.hasClassName(className)) ;
299
518
  },
300
519
 
301
520
  // get the named style. (see also style properties)
302
521
  getStyle: function(style) {
303
- return Element.getStyle(this.rootElement,style) ;
522
+ var element = this.rootElement ;
523
+ if (!this._computedStyle) {
524
+ this._computedStyle = document.defaultView.getComputedStyle(element, null) ;
525
+ }
526
+
527
+ //if (style == 'float') style = 'cssFloat' ;
528
+ style = (style == 'float') ? 'cssFloat' : style.camelize() ;
529
+ var value = element.style[style];
530
+ if (!value) {
531
+ value = this._computedStyle ? this._computedStyle[style] : null ;
532
+ }
533
+
534
+ switch(style) {
535
+ case 'opacity':
536
+ value = value ? parseFloat(value) : 1.0;
537
+ break ;
538
+ case 'auto':
539
+ value = null;
540
+ break ;
541
+ default:
542
+ break ;
543
+ }
544
+
545
+ return value ;
304
546
  },
305
547
 
306
548
  // set the passed styles.
@@ -310,10 +552,10 @@ SC.View = SC.Responder.extend(SC.PathModule,
310
552
 
311
553
  // use this method to update the HTML of an element. This takes care of
312
554
  // nasties like processing scripts and inserting HTML into a table. You can
313
- // also use asHTML, which builds on this method.
555
+ // also use innerHTML, which builds on this method.
314
556
  update: function(html) {
315
557
  Element.update((this.containerElement || this.rootElement),html) ;
316
- this.propertyDidChange('asHTML') ;
558
+ this.propertyDidChange('innerHTML') ;
317
559
  },
318
560
 
319
561
  // this works like the element getAttribute() except it is standardized
@@ -336,17 +578,37 @@ SC.View = SC.Responder.extend(SC.PathModule,
336
578
  // The methods in this section give you some low-level control over how the
337
579
  // view interacts with the DOM. You do not normally need to work with this.
338
580
 
339
- // This is the DOM element actually managed by this view. This will be set
340
- // by the view when it is created. Changing it afterwards will likely
341
- // break things.
581
+ /**
582
+ This is the DOM element actually managed by this view. This will be set
583
+ by the view when it is created. You should rarely need to access this
584
+ property directly. When you do access it, you should only do so from
585
+ within methods you write on your SC.View subclasses, never from outside
586
+ the view.
587
+
588
+ Unlike most properties, you do not need to use get()/set() to access this
589
+ property. It is not currently safe to edit this property once the view
590
+ has been createde.
591
+
592
+ @property
593
+ @type {Element}
594
+ */
342
595
  rootElement: null,
343
596
 
344
- // Normally when you add child views to your view, their DOM elements will
345
- // be set as direct children of the root element. However you can
346
- // choose instead to designate an alertnative child node using this
347
- // property. Set this to a selector string to begin with. The first time
348
- // it is access, the view will convert it to an actual element. It is not
349
- // currently safe to edit this property once the view has been created.
597
+ /**
598
+ Normally when you add child views to your view, their DOM elements will
599
+ be set as direct children of the root element. However you can
600
+ choose instead to designate an alertnative child node using this
601
+ property. Set this to a selector string to begin with. The first time
602
+ it is accessed, the view will convert it to an actual element. It is not
603
+ currently safe to edit this property once the view has been created.
604
+
605
+ Like rootElement, you should only access this property from within
606
+ methods you write on an SC.View subclass, never from outside the view.
607
+ Unlike most properties, it is not necessary to use get()/set().
608
+
609
+ @property
610
+ @type {Element}
611
+ */
350
612
  containerElement: null,
351
613
 
352
614
  // ..........................................
@@ -357,14 +619,79 @@ SC.View = SC.Responder.extend(SC.PathModule,
357
619
  // location and size of your views. You can then use the automatic
358
620
  // resizing.
359
621
 
622
+ /**
623
+ Returns true if the view or any of its contained views implement the
624
+ clippingFrameDidChange method.
625
+
626
+ If this property returns false, then notifications about changes to the
627
+ clippingFrame will probably not be called on the receiver. Normally if you
628
+ do not need to worry about this property since implementing the clippingFrameDidChange()
629
+ method will change its value and cause your method to be invoked.
630
+
631
+ This property is automatically updated whenever you add or remove a child view.
632
+ */
633
+ needsClippingFrame: function() {
634
+ if (this._needsClippingFrame == null) {
635
+ var ret = this.clippingFrameDidChange != SC.View.prototype.clippingFrameDidChange;
636
+ var view = this.get('firstChild') ;
637
+ while(!ret && view) {
638
+ ret = view.get('needsClippingFrame') ;
639
+ view = view.get('nextSibling') ;
640
+ }
641
+ this._needsClippingFrame = ret ;
642
+ }
643
+ return this._needsClippingFrame ;
644
+ }.property(),
645
+
646
+ /**
647
+ Returns true if the view or any of its contained views implements any resize
648
+ methods.
649
+
650
+ If this property returns false, changes to your frame view may not be
651
+ relayed to child methods. This may mean that your various frame properties could
652
+ become stale unless you call refreshFrames() first.
653
+
654
+ If you want you make sure your frames are up to date, see hasManualLayout.
655
+
656
+ This property is automatically updated whenever you add or remove a child view. It
657
+ returns true if you implement any of the resize methods or if hasManualLayout is true.
658
+ */
659
+ needsFrameChanges: function() {
660
+ if (this._needsFrameChanges == null) {
661
+ var ret = this.get('needsClippingFrame') || this.get('hasManualLayout') ;
662
+ var view = this.get('firstChild') ;
663
+ while(!ret && view) {
664
+ ret = view.get('needsFrameChanges') ;
665
+ view = view.get('nextSibling') ;
666
+ }
667
+ this._needsFrameChanges = ret ;
668
+ }
669
+ return this._needsFrameChanges ;
670
+ }.property(),
360
671
 
672
+
673
+ /**
674
+ Returns true if the receiver manages the layout for itself or its children.
675
+
676
+ Normally this property returns true automatically if you implement
677
+ resizeChildrenWithOldSize() or resizeWithOldParentSize() or clippingFrameDidChange().
678
+
679
+ If you do not implement these methods but need to make sure your frame is always up-to-date
680
+ anyway, set this property to true.
681
+ */
682
+ hasManualLayout: function() {
683
+ return (this.resizeChildrenWithOldSize != SC.View.prototype.resizeChildrenWithOldSize) ||
684
+ (this.resizeWithOldParentSize != SC.View.prototype.resizeWithOldParentSize) ||
685
+ (this.clippingFrameDidChange != SC.View.prototype.clippingFrameDidChange) ;
686
+ }.property(),
687
+
361
688
  /**
362
689
  Convert a point _from_ the offset parent of the passed view to the current view.
363
690
 
364
691
  This is a useful utility for converting points in the coordinate system of
365
692
  another view to the coordinate system of the receiver. Pass null for
366
- targetView to convert a point from a window offset. This is the inverse of
367
- convertFrameToView().
693
+ targetView to convert a point from a window offset. This is the inverse
694
+ of convertFrameToView().
368
695
 
369
696
  Note that if your view is not visible on the screen, this may not work.
370
697
 
@@ -376,12 +703,12 @@ SC.View = SC.Responder.extend(SC.PathModule,
376
703
  convertFrameFromView: function(f, targetView) {
377
704
 
378
705
  // first, convert to root level offset.
379
- var thisOffset = Element.viewportOffset(this.get('offsetParent')) ;
380
- var thatOffset = (targetView) ? Element.viewportOffset(targetView.get('offsetParent')) : [0,0] ;
706
+ var thisOffset = SC.viewportOffset(this.get('offsetParent')) ;
707
+ var thatOffset = (targetView) ? SC.viewportOffset(targetView.get('offsetParent')) : SC.ZERO_POINT;
381
708
 
382
709
  // now get adjustment.
383
- var adjustX = thatOffset[0] - thisOffset[0] ;
384
- var adjustY = thatOffset[1] - thisOffset[1] ;
710
+ var adjustX = thatOffset.x - thisOffset.x ;
711
+ var adjustY = thatOffset.y - thisOffset.y ;
385
712
  return { x: (f.x + adjustX), y: (f.y + adjustY), width: f.width, height: f.height };
386
713
  },
387
714
 
@@ -402,309 +729,566 @@ SC.View = SC.Responder.extend(SC.PathModule,
402
729
  */
403
730
  convertFrameToView: function(f, sourceView) {
404
731
  // first, convert to root level offset.
405
- var thisOffset = Element.viewportOffset(this.get('offsetParent')) ;
406
- var thatOffset = (sourceView) ? Element.viewportOffset(sourceView.get('offsetParent')) : [0,0] ;
732
+ var thisOffset = SC.viewportOffset(this.get('offsetParent')) ;
733
+ var thatOffset = (sourceView) ? SC.viewportOffset(sourceView.get('offsetParent')) : SC.ZERO_POINT ;
407
734
 
408
735
  // now get adjustment.
409
- var adjustX = thisOffset[0] - thatOffset[0] ;
410
- var adjustY = thisOffset[1] - thatOffset[1] ;
736
+ var adjustX = thisOffset.x - thatOffset.x ;
737
+ var adjustY = thisOffset.y - thatOffset.y ;
411
738
  return { x: (f.x + adjustX), y: (f.y + adjustY), width: f.width, height: f.height };
412
739
  },
413
740
 
414
- // if a view isPositioned, then you can manually control the size and
415
- // origin of the view using the frame property. If isPositioned is false,
416
- // then this view will be sized and positioned by the browser using CSS.
417
- // You can read the current frame, but you cannot make edits.
418
- //
419
- // You can edit the innerFrame of a view anytime, even if the element is
420
- // not positioned.
421
- //
422
- isPositioned: false,
423
-
424
- changePositionObserver: function() {
425
- var isPositioned = this.get('isPositioned') ;
426
- if (this._wasPositioned == isPositioned) return ;
741
+ /**
742
+ This property returns a DOM ELEMENT that is the offset parent for
743
+ this view's frame coordinates. Depending on your CSS, this parent
744
+ may or may not match with the parent view.
427
745
 
428
- // make absolute positioned. Also get default frame.
429
- if (isPositioned) {
430
- var el = this.rootElement;
431
- this.cacheFrame();
432
-
433
- this.setStyle({
434
- position: 'absolute',
435
- top: Math.floor(this._frame.y) + 'px',
436
- left: Math.floor(this._frame.x) + 'px',
437
- width: Math.floor(this._frame.width) + 'px',
438
- height: Math.floor(this._frame.height) + 'px'
439
- }) ;
440
-
441
- } else {
442
- var el = this.rootElement;
443
- el.style.position =
444
- el.style.top =
445
- el.style.left =
446
- el.style.width =
447
- el.style.height = '' ;
448
- this._frame = null ;
449
- }
450
- this._wasPositioned = isPositioned ;
451
- }.observes('isPositioned'),
452
-
453
- // Normally we don't get the dimensions of a view until you actually ask
454
- // for them. However, sometimes you need to get the frame before you
455
- // remove the view from the parent, etc. This will cache the frame.
456
- cacheFrame: function() {
457
- if (this._frame || this._frameCached) return ; // don't cache twice
746
+ @example
747
+ offsetView = $view(this.get('offsetParent')) ;
458
748
 
459
- var el = this.rootElement ;
460
- this._frame = Element.getDimensions(el);
461
- this._frame.x = el.offsetLeft ;
462
- this._frame.y = el.offsetTop ;
463
- this._frameCached = true ;
464
- },
465
-
466
- // if you cached the frame, you can use this to clear that cache so that it
467
- // will now track with the frame in the document.
468
- flushFrameCache: function() {
469
- this._frame = null ;
470
- this._frameCached = false;
471
- },
472
-
473
- // This property returns a DOM ELEMENT that is the offset parent for
474
- // this view's frame coordinates. Depending on your CSS, this parent
475
- // may or may not match with the parent view.
749
+ @property
750
+ @type {Element}
751
+ */
476
752
  offsetParent: function() {
477
753
  return Position.offsetParent(this.rootElement) ;
478
754
  }.property(),
479
-
480
- // This property is used to set the internal padding of an element. The
481
- // innerFrame is an offset from the outer frame. Changing these settings
482
- // will adjust the height, width, and padding of the element.
755
+
756
+ /**
757
+ The inner bounds for the content shown inside of this frame. Reflects scroll position
758
+ and other properties.
759
+
760
+ The inner frame returns the actual available frame for child elements, less any borders
761
+ or scroll bars.
762
+
763
+ This value can change when:
764
+ - the receiver's frame changes
765
+ - the receiver's child views change, adding or removing scrollbars
766
+ - You can the CSS or applied style that effects the borders or scrollbar visibility
767
+ */
483
768
  innerFrame: function(key, value) {
484
769
 
485
- // get the basic inner framce
486
- var el = this.rootElement ;
487
- var f = {
488
- x: parseInt(this.getStyle('padding-left'),0) || 0,
489
- y: parseInt(this.getStyle('padding-top'), 0) || 0,
490
- width: parseInt(this.getStyle('width'), 0) || 0,
491
- height: parseInt(this.getStyle('height'),0) || 0
492
- } ;
770
+ var f ;
771
+ if (this._innerFrame == null) {
772
+
773
+ // get the base frame
774
+ var el = this.rootElement ;
775
+ f = this._collectFrame(function() {
776
+ return {
777
+ x: el.offsetLeft,
778
+ y: el.offsetTop,
779
+ width: Math.min(el.scrollWidth, el.clientWidth),
780
+ height: Math.min(el.scrollHeight, el.clientHeight)
781
+ };
782
+ }) ;
493
783
 
494
- // get the current frame size.
495
- var size = {
496
- width: f.x + f.width + parseInt(this.getStyle('padding-right'),0),
497
- height: f.y + f.height + parseInt(this.getStyle('padding-bottom'),0)
498
- };
784
+ // bizarely for FireFox if your offsetParent has a border, then it can
785
+ // impact the offset
786
+ if (SC.Platform.Firefox) {
787
+ var parent = el.offsetParent ;
788
+ if (parent && (Element.getStyle(parent, 'overflow') != 'visible')) {
789
+ var left = parseInt(Element.getStyle(parent, 'borderLeftWidth'),0) || 0 ;
790
+ var top = parseInt(Element.getStyle(parent, 'borderTopWidth'),0) || 0 ;
791
+ f.x += left; f.y += top ;
792
+ }
793
+ }
794
+
795
+ // fix the x & y with the clientTop/clientLeft
796
+ var clientLeft, clientTop ;
797
+ if (el.clientLeft == null) {
798
+ clientLeft = parseInt(this.getStyle('border-left-width'),0) || 0 ;
799
+ } else clientLeft = el.clientLeft ;
499
800
 
500
- // now update the innerFrame if needed. Change only the bits that are
501
- // passed in.
801
+ if (el.clientTop == null) {
802
+ clientTop = parseInt(this.getStyle('border-top-width'),0) || 0 ;
803
+ } else clientTop = el.clientTop ;
804
+
805
+ f.x += clientLeft; f.y += clientTop;
806
+
807
+ // cache this frame if using manual layout mode
808
+ this._innerFrame = SC.cloneRect(f);
809
+ } else f = SC.cloneRect(this._innerFrame) ;
810
+ return f ;
811
+ }.property('frame'),
812
+
813
+ /**
814
+ The outside bounds of your view, offset top/left from its offsetParent
815
+
816
+ The frame rect is the area actually occupied by a view including any
817
+ borders or padding, but excluding margins.
818
+
819
+ The frame is calculated and cached the first time you get it. Afer that,
820
+ the frame cache should automatically update when you make changes that
821
+ will effect the view frames unless you change the frame indirectly, such
822
+ as through changing CSS classes or by-passing the view to edit the DOM.
823
+
824
+ If you make a change like this, be sure to wrap the code that makes this
825
+ change with calls to viewFrameWillChange() and viewFrameDidChange() on the
826
+ highest-level view that will be impacted by the change. Calling this
827
+ method will automatically update child frames as well.
828
+
829
+ When you set the frame property, it will update the left, top, height,
830
+ and width CSS attributes on the element. Since the height and width in
831
+ the frame rect includes borders and padding, the view will automatically
832
+ adjust the height and width CSS it sets to account for this.
833
+
834
+ If you would prefer to edit the CSS attributes for the frame directly
835
+ instead, you can do so by using the styleTop, styleLeft, styleRight,
836
+ styleBottom, styleWidth, and styleHeight properties on the view. These
837
+ properties will update the CSS attributes and call viewFrameDidChange()/
838
+ viewFrameWillChange().
839
+
840
+ @field
841
+ */
842
+ frame: function(key, value) {
843
+
844
+ // if value was passed, set the values in the style
845
+ // now update the frame if needed. Only actually change the style for
846
+ // those parts of the frame that were passed in.
502
847
  if (value !== undefined) {
848
+
849
+ this.viewFrameWillChange() ;
850
+
851
+ var f= value ;
503
852
  var style = {} ;
504
853
  var didResize = false ;
505
- var clearFrame = false ;
506
854
 
855
+ // collect required info
507
856
  // reposition X
508
857
  if (value.x !== undefined) {
509
- f.x = value.x ;
510
- style.paddingLeft = Math.floor(f.x) + 'px' ;
858
+ style.left = Math.floor(f.x) + 'px' ;
859
+ style.right = 'auto';
511
860
  }
512
861
 
513
862
  // reposition Y
514
863
  if (value.y !== undefined) {
515
- f.y = value.y ;
516
- style.paddingTop = Math.floor(f.y) + 'px' ;
864
+ style.top = Math.floor(f.y) + 'px' ;
865
+ style.bottom = 'auto';
517
866
  }
518
-
519
- // resize Width
520
- // adjust both the element width and padding right so that the overall
521
- // frame size does not change.
867
+
868
+ // Resize width
522
869
  if (value.width !== undefined) {
523
870
  didResize = true ;
524
- f.width = value.width ;
525
- style.width = Math.floor(f.width).toString() + 'px' ;
526
-
527
- var padding = size.width - f.width - f.x ;
528
- if (padding < 0) {
529
- clearFrame = true ;
530
- padding = 0 ;
871
+ var padding = 0 ;
872
+ var idx = SC.View.WIDTH_PADDING_STYLES.length;
873
+ while(--idx >= 0) {
874
+ padding += parseInt(this.getStyle(SC.View.WIDTH_PADDING_STYLES[idx]), 0) || 0;
531
875
  }
532
- style.paddingRight = Math.floor(padding).toString() + 'px' ;
876
+ style.width = (Math.floor(f.width) - padding).toString() + 'px' ;
533
877
  }
534
878
 
535
879
  // Resize Height
536
- // adjust both the element height and padding bottom so that the
537
- // overall frame size does not change.
538
880
  if (value.height !== undefined) {
539
881
  didResize = true ;
540
- f.height = value.height ;
541
- style.height = Math.floor(f.height).toString() + 'px' ;
882
+ var padding = 0 ;
883
+ var idx = SC.View.HEIGHT_PADDING_STYLES.length;
884
+ while(--idx >= 0) {
885
+ padding += parseInt(this.getStyle(SC.View.HEIGHT_PADDING_STYLES[idx]), 0) || 0;
886
+ }
887
+ style.height = (Math.floor(f.height) - padding).toString() + 'px' ;
888
+ }
889
+
890
+ // now apply style change and clear the cached frame
891
+ this.setStyle(style) ;
892
+
893
+ // notify for a resize only.
894
+ this.viewFrameDidChange() ;
895
+ }
896
+
897
+ // build frame. We can use a cached version but only
898
+ // if layoutMode == SC.MANUAL_MODE
899
+ var f;
900
+ if (this._frame == null) {
901
+ var el = this.rootElement ;
902
+ f = this._collectFrame(function() {
903
+ return {
904
+ x: el.offsetLeft,
905
+ y: el.offsetTop,
906
+ width: el.offsetWidth,
907
+ height: el.offsetHeight
908
+ };
909
+ }) ;
910
+
911
+ // bizarely for FireFox if your offsetParent has a border, then it can
912
+ // impact the offset
913
+ if (SC.Platform.Firefox) {
914
+ var parent = el.offsetParent ;
915
+ if (parent && (Element.getStyle(parent, 'overflow') != 'visible')) {
916
+ var left = parseInt(Element.getStyle(parent, 'borderLeftWidth'),0) || 0 ;
917
+ var top = parseInt(Element.getStyle(parent, 'borderTopWidth'),0) || 0 ;
918
+ f.x += left; f.y += top ;
919
+ }
920
+ }
921
+
922
+ // cache this frame if using manual layout mode
923
+ this._frame = SC.cloneRect(f);
924
+ } else f = SC.cloneRect(this._frame) ;
925
+
926
+ // finally return the frame.
927
+ return f ;
928
+ }.property(),
929
+
930
+ /**
931
+ The current frame size.
932
+
933
+ This property will actually return the same value as the frame property,
934
+ however setting this property will set only the frame size and ignore any
935
+ origin you might pass.
936
+
937
+ @field
938
+ */
939
+ size: function(key, value) {
940
+ if (value !== undefined) {
941
+ this.set('frame',{ width: value.width, height: value.height }) ;
942
+ }
943
+ return this.get('frame') ;
944
+ }.property('frame'),
945
+
946
+ /**
947
+ The current frame origin.
948
+
949
+ This property will actually return the same value as the frame property,
950
+ however setting this property will set only the frame origin and ignore
951
+ any size you might pass.
952
+
953
+ @field
954
+ */
955
+ origin: function(key, value) {
956
+ if (value !== undefined) {
957
+ this.set('frame',{ x: value.x, y: value.y }) ;
958
+ }
959
+ return this.get('frame') ;
960
+ }.property('frame'),
961
+
962
+ /**
963
+ Style-width for the views rootElement
964
+
965
+ Setting this property will also notify of a view frame change.
966
+
967
+ @field
968
+ */
969
+ styleWidth: function(key, value) {
970
+ if (value !== undefined) {
971
+ this.viewFrameWillChange() ;
972
+ this.setStyle({ width: ((value != null) ? value+'px' : 'auto') }) ;
973
+ this.viewFrameDidChange() ;
974
+ this._styleWidth = null;
975
+ }
976
+
977
+ var ret = this.getStyle('width') ;
978
+ ret = (ret == 'auto') ? null : parseInt(ret, 0) ;
979
+ }.property(),
980
+
981
+ /**
982
+ Style-height for the views rootElement
983
+
984
+ Setting this property will also notify of a view frame change.
985
+
986
+ @field
987
+ */
988
+ styleHeight: function(key, value) {
989
+ if (value !== undefined) {
990
+ this.viewFrameWillChange() ;
991
+ this.setStyle({ height: ((value != null) ? value+'px' : 'auto') }) ;
992
+ this.viewFrameDidChange() ;
993
+ }
994
+ var ret = this.getStyle('height') ;
995
+ return (ret == 'auto') ? null : parseInt(ret, 0) ;
996
+ }.property(),
997
+
998
+ /**
999
+ Style-top for the views rootElement
1000
+
1001
+ Setting this property will also notify of a view frame change.
1002
+
1003
+ @field
1004
+ */
1005
+ styleTop: function(key, value) {
1006
+ if (value !== undefined) {
1007
+ this.viewFrameWillChange() ;
1008
+ this.setStyle({ top: ((value != null) ? value+'px' : 'auto') }) ;
1009
+ this.viewFrameDidChange() ;
1010
+ }
1011
+ var ret = this.getStyle('top') ;
1012
+ return (ret == 'auto') ? null : parseInt(ret, 0) ;
1013
+ }.property(),
1014
+
1015
+ /**
1016
+ Style-bottom for the views rootElement
1017
+
1018
+ Setting this property will also notify of a view frame change.
1019
+
1020
+ @field
1021
+ */
1022
+ styleBottom: function(key, value) {
1023
+ if (value !== undefined) {
1024
+ this.viewFrameWillChange() ;
1025
+ this.setStyle({ bottom: ((value != null) ? value+'px' : 'auto') }) ;
1026
+ this.viewFrameDidChange() ;
1027
+ }
1028
+ var ret = this.getStyle('bottom') ;
1029
+ return (ret == 'auto') ? null : parseInt(ret, 0) ;
1030
+ }.property(),
1031
+
1032
+ /**
1033
+ Style-left for the views rootElement
1034
+
1035
+ Setting this property will also notify of a view frame change.
1036
+
1037
+ @field
1038
+ */
1039
+ styleLeft: function(key, value) {
1040
+ if (value !== undefined) {
1041
+ this.viewFrameWillChange() ;
1042
+ this.setStyle({ left: ((value != null) ? value+'px' : 'auto') }) ;
1043
+ this.viewFrameDidChange() ;
1044
+ }
1045
+ var ret = this.getStyle('left') ;
1046
+ return (ret == 'auto') ? null : parseInt(ret, 0) ;
1047
+ }.property(),
1048
+
1049
+ /**
1050
+ Style-right for the views rootElement
1051
+
1052
+ Setting this property will also notify of a view frame change.
1053
+
1054
+ @field
1055
+ */
1056
+ styleRight: function(key, value) {
1057
+ if (value !== undefined) {
1058
+ this.viewFrameWillChange() ;
1059
+ this.setStyle({ right: ((value != null) ? value+'px' : 'auto') }) ;
1060
+ this.viewFrameDidChange() ;
1061
+ }
1062
+ var ret = this.getStyle('right') ;
1063
+ return (ret == 'auto') ? null : parseInt(ret, 0) ;
1064
+ }.property(),
1065
+
1066
+
1067
+ /**
1068
+ Call this method before you make a change that will impact the frame of
1069
+ the view such as changing the border thickness or adding/removing a CSS
1070
+ style.
1071
+
1072
+ Once you finish making your changes, be sure to call viewFrameDidChange()
1073
+ as well. This will deliver any relevant resizing and other notifications.
1074
+ It is safe to nest multiple calls to this method.
1075
+
1076
+ This method is called automatically anytime you set the frame.
1077
+
1078
+ @returns {void}
1079
+ */
1080
+ viewFrameWillChange: function() {
1081
+ if (this._frameChangeLevel++ <= 0) {
1082
+ this._frameChangeLevel = 1 ;
1083
+
1084
+ // save frame information if view has manual layout.
1085
+ if (this.get('needsFrameChanges')) {
1086
+ this._cachedFrames = this.getEach('innerFrame', 'clippingFrame', 'frame') ;
1087
+ } else this._cachedFrames = null ;
1088
+ this.beginPropertyChanges(); // suspend change notifications
1089
+ }
1090
+ },
1091
+
1092
+ /**
1093
+ Call this method just after you finish making changes that will impace the frame
1094
+ of the view such as changing the border thickness or adding/removing a CSS style.
1095
+
1096
+ It is safe to next multiple calls to this method. This method is called automatically
1097
+ anytime you set the frame.
1098
+
1099
+ @returns {void}
1100
+ */
1101
+ viewFrameDidChange: function(force) {
1102
+
1103
+ // clear the frame caches
1104
+ this.recacheFrames() ;
1105
+
1106
+ // if this is a top-level call then also deliver notifications as needed.
1107
+ if (--this._frameChangeLevel <= 0) {
1108
+ this._frameChangeLevel = 0 ;
1109
+ if (this._cachedFrames) {
1110
+ var newFrames = this.getEach('innerFrame', 'clippingFrame') ;
1111
+
1112
+ // notify if clippingFrame has changed and clippingFrameDidChange is
1113
+ // implemented.
1114
+ var nf = newFrames[1]; var of = this._cachedFrames[1] ;
1115
+ if (force || (nf.width != of.width) || (nf.height != of.height)) {
1116
+ this._invalidateClippingFrame() ;
1117
+ }
1118
+
1119
+ // notify children if the size of the innerFrame has changed.
1120
+ var nf = newFrames[0]; var of = this._cachedFrames[0] ;
1121
+ if (force || (nf.width != of.width) || (nf.height != of.height)) {
1122
+ this.resizeChildrenWithOldSize(this._cachedFrames.last()) ;
1123
+ }
1124
+
1125
+ // clear parent scrollFrame if needed
1126
+ var parent = this.parentNode ;
1127
+ while (parent && parent != SC.window) {
1128
+ if (parent._scrollFrame) parent._scrollFrame = null ;
1129
+ parent = parent.parentNode ;
1130
+ }
1131
+
1132
+ this.notifyPropertyChange('frame') ; // trigger notifications.
1133
+ }
1134
+
1135
+ // allow notifications again
1136
+ this.endPropertyChanges() ;
1137
+ }
1138
+ },
1139
+
1140
+
1141
+ /**
1142
+ Clears any cached frames so the next get will recompute them.
1143
+
1144
+ This method does not notify any observers of changes to the frames. It should
1145
+ only be used when you need to make sure your frame info is up to date but you do
1146
+ not expect anything to have happened that frame observers would be interested in.
1147
+ */
1148
+ recacheFrames: function() {
1149
+ this._innerFrame = this._frame = this._clippingFrame = this._scrollFrame = null ;
1150
+ },
1151
+
1152
+ /**
1153
+ Set to true if you expect this view to have scrollable content.
542
1154
 
543
- var padding = size.height - f.height - f.y ;
544
- if (padding < 0) {
545
- clearFrame = true ;
546
- padding = 0 ;
547
- }
548
- style.paddingBottom = Math.floor(padding).toString() + 'px' ;
549
- }
1155
+ Normally views do not monitor their onscroll event. If you set this property to true,
1156
+ however, the view will observe its onscroll event and update its scrollFrame and
1157
+ clippedFrame.
550
1158
 
551
- // now apply style change
552
- this.setStyle(style) ;
553
-
554
- // if the user sets an innerFrame size that cannot fit within the
555
- // current outer frame, then the outer frame will be adjusted to fit.
556
- // clear the frame so that this can happen.
557
- if (clearFrame) {
558
- this.propertyWillChange('frame') ;
559
- this._frame = null ;
560
- this.propertyDidChange('frame') ;
561
- }
1159
+ This will also register the view as a scrollable area that can be auto-scrolled during
1160
+ a drag/drop event.
1161
+ */
1162
+ isScrollable: false,
1163
+
1164
+ /**
1165
+ The frame used to control scrolling of content.
1166
+
1167
+ x,y => offset from the innerFrame root.
1168
+ width,height => total size of the frame
1169
+
1170
+ If the frame does not have scrollable content, then the size will be equal to the
1171
+ innerFrame size.
1172
+
1173
+ This frame changes when:
1174
+ - the receiver's innerFrame changes
1175
+ - the scroll location is changed programatically
1176
+ - the size of child views changes
1177
+ - the user scrolls the view
1178
+
1179
+ @field
1180
+ */
1181
+ scrollFrame: function(key, value) {
562
1182
 
563
- // also notify children so they can resize also.
564
- if (didResize) this.resizeChildrenWithOldSize(size) ;
1183
+ // if value was passed, update the scroll x,y only.
1184
+ if (value != undefined) {
1185
+ var el = this.rootElement ;
1186
+ if (value.x != null) el.scrollLeft = 0-value.x ;
1187
+ if (value.y != null) el.scrollTop = 0-value.y ;
1188
+ this._scrollFrame = null ;
1189
+ this._invalidateClippingFrame() ;
565
1190
  }
1191
+
1192
+ // build frame. We can use a cached version but only
1193
+ var f;
1194
+ if (this._scrollFrame == null) {
1195
+ var el = this.rootElement ;
1196
+ f = this._collectFrame(function() {
1197
+ return {
1198
+ x: 0 - el.scrollLeft,
1199
+ y: 0 - el.scrollTop,
1200
+ width: el.scrollWidth,
1201
+ height: el.scrollHeight
1202
+ };
1203
+ }) ;
1204
+
1205
+ // cache this frame if using manual layout mode
1206
+ this._scrollFrame = SC.cloneRect(f);
1207
+ } else f = SC.cloneRect(this._scrollFrame) ;
566
1208
 
567
1209
  // finally return the frame.
568
- return f ;
1210
+ return f ;
569
1211
  }.property('frame'),
570
1212
 
571
- innerSize: function(key, value) {
572
- if (value !== undefined) {
573
- this.set('innerFrame',{ width: value.width, height: value.height }) ;
574
- }
575
- return this.get('innerFrame') ;
576
- }.property('innerFrame'),
577
-
578
- innerOrigin: function(key, value) {
579
- if (value !== undefined) {
580
- this.set('innerFrame',{ x: value.x, y: value.y }) ;
581
- }
582
- return this.get('innerFrame') ;
583
- }.property('innerFrame'),
584
-
585
- // This property identifies the height and offset of your view with
586
- // respect to the parent view and its bounds. To resize your view, edit
587
- // this property.
588
- //
589
- // This method is carefully constructed to use the computed CSS style
590
- // until you actually override it by setting your own size and location
591
- // at which point it will use its own settings.
592
- frame: function(key, value) {
593
-
594
- // build frame
595
- var el = this.rootElement ;
596
- var f = Object.clone(this._frame) ;
597
- if (f.x === undefined) f.x = el.offsetLeft ;
598
- if (f.y === undefined) f.y = el.offsetTop ;
599
-
600
- // get the current size if needed.
601
- var size ;
602
- if ((f.width === undefined) || (f.height === undefined)) {
603
- var isVisibleInWindow = this.get('isVisibleInWindow') ;
604
-
605
- // if not visible in window, move parent node into window and get
606
- // dim and offset. If the element has no parentNode, then just move
607
- // the element in.
608
- if (!isVisibleInWindow) {
609
- var pn = el.parentNode || el ;
610
- var pnParent = pn.parentNode ;
611
- var pnSib = pn.nextSibling ;
612
- SC.window.rootElement.insertBefore(pn, null) ;
613
- }
614
-
615
- size = Element.getDimensions(el);
616
- f.width = size.width ;
617
- f.height = size.height;
618
-
619
- if (!isVisibleInWindow) {
620
- if (pnParent) {
621
- pnParent.insertBefore(pn, pnSib) ;
622
- } else SC.window.removeChild(pn) ;
623
- }
624
- } else size = f ;
625
-
626
- // now update the frame if needed. Only actually change the style for
627
- // those parts of the frame that were passed in.
628
- if (value !== undefined) {
629
- var style = {} ;
630
- var didResize = false ;
1213
+ /**
1214
+ The visible portion of the view.
631
1215
 
632
- // reposition X
633
- if (value.x !== undefined) {
634
- f.x = value.x ;
635
- style.left = Math.floor(f.x) + 'px' ;
636
- }
1216
+ Returns the subset of the receivers frame that is actually visible on screen.
1217
+ This frame is automatically updated whenever one of the following changes:
1218
+
1219
+ - A parent view is resized
1220
+ - A parent view's scrollFrame changes.
1221
+ - The receiver is moved or resized
1222
+ - The receiver or a parent view is added to or removed from the window.
1223
+
1224
+ @field
1225
+ */
1226
+ clippingFrame: function() {
1227
+ var f ;
1228
+ if (this._clippingFrame == null) {
637
1229
 
638
- // reposition Y
639
- if (value.y !== undefined) {
640
- f.y = value.y ;
641
- style.top = Math.floor(f.y) + 'px' ;
642
- }
1230
+ //if (this instanceof SC.SplitView) debugger ;
643
1231
 
644
- // Resize width
645
- if (value.width !== undefined) {
646
- didResize = true ;
647
- f.width = value.width ;
648
- var padding = parseInt(this.getStyle('padding-left'),0) + parseInt(this.getStyle('padding-right'),0) ;
649
- style.width = (Math.floor(f.width) - padding).toString() + 'px' ;
650
- }
1232
+ // my clipping frame is usually my frame
1233
+ f = this.get('frame') ;
651
1234
 
652
- // Resize Height
653
- if (value.height !== undefined) {
654
- didResize = true ;
655
- f.height = value.height ;
656
- var padding = parseInt(this.getStyle('padding-top'),0) + parseInt(this.getStyle('padding-bottom'),0) ;
657
- style.height = (Math.floor(f.height) - padding).toString() + 'px' ;
1235
+ // scope to my parents clipping frame.
1236
+ if (this.parentNode) {
1237
+
1238
+ // use only the visible portion of the parent's innerFrame.
1239
+ var parent = this.parentNode ;
1240
+ var prect = SC.intersectRects(parent.get('clippingFrame'), parent.get('innerFrame'));
1241
+
1242
+ // convert the local view's coordinates
1243
+ prect = this.convertFrameFromView(prect, parent) ;
1244
+
1245
+ // if parent is scrollable, then adjust by scroll frame also.
1246
+ if (this.parentNode.get('isScrollable')) {
1247
+ var scrollFrame = this.get('scrollFrame') ;
1248
+ prect.x -= scrollFrame.x ;
1249
+ prect.y -= scrollFrame.y ;
1250
+ }
1251
+
1252
+ // blend with current frame
1253
+ f = SC.intersectRects(f, prect) ;
1254
+ } else {
1255
+ f.width = f.height = 0 ;
658
1256
  }
1257
+
1258
+ this._clippingFrame = SC.cloneRect(f) ;
659
1259
 
660
- // now apply style change and save new frame.
661
- this.setStyle(style) ;
662
- this._frame = Object.clone(f) ;
663
-
664
- // also notify children so they can resize also.
665
- if (didResize) this.resizeChildrenWithOldSize(size) ;
666
- }
667
-
668
- // finally return the frame.
1260
+ } else f = SC.cloneRect(this._clippingFrame) ;
669
1261
  return f ;
670
- }.property('innerFrame'),
671
-
672
- size: function(key, value) {
673
- if (value !== undefined) {
674
- this.set('frame',{ width: value.width, height: value.height }) ;
675
- }
676
- return this.get('frame') ;
677
- }.property('frame'),
1262
+ }.property('frame', 'scrollFrame'),
678
1263
 
679
- origin: function(key, value) {
680
- if (value !== undefined) {
681
- this.set('frame',{ x: value.x, y: value.y }) ;
682
- }
683
- return this.get('frame') ;
684
- }.property('frame'),
1264
+ /**
1265
+ Called whenever the receivers clippingFrame has changed. You can override this
1266
+ method to perform partial rendering or other clippingFrame-dependent actions.
1267
+
1268
+ The default implementation does nothing (and may not even be called do to optimizations).
1269
+ Note that this is the preferred way to respond to changes in the clippingFrame
1270
+ of using an observer since this method is gauranteed to happen in the correct
1271
+ order. You can use observers and bindings as well if you wish to handle anything
1272
+ that need not be handled synchronously.
1273
+ */
1274
+ clippingFrameDidChange: function() {
1275
+
1276
+ },
685
1277
 
686
1278
  /**
687
- The current scroll frame for the view.
1279
+ Called whenever the view's innerFrame size changes. You can override this
1280
+ method to perform your own layout of your child views.
1281
+
1282
+ If you do not override this method, the view will assume you are using
1283
+ CSS to layout your child views. As an optimization the view may not always
1284
+ call this method if it determines that you have not overridden it.
688
1285
 
689
- This will tell you the total scroll height and width of the view as well
690
- as any current scroll offset. You can also set the x and y properties of
691
- the scrollFrame. Any changes to height and width will be ignored.
1286
+ This default version simply calls resizeWithOldParentSize() on all of its
1287
+ children.
692
1288
 
693
- @returns frame
1289
+ @param oldSize {Size} The old frame size of the view.
1290
+ @returns {void}
694
1291
  */
695
- scrollFrame: function(key, value) {
696
- var el = this.rootElement ;
697
- if (value !== undefined) {
698
- el.scrollTop = value.y ;
699
- el.scrollLeft = value.x ;
700
- }
701
-
702
- return { x: el.scrollLeft, y: el.scrollTop, height: el.scrollHeight, width: el.scrollWidth } ;
703
- }.property('frame'),
704
-
705
- // called on the view when you need to resize your child views. Normally
706
- // this will call resizeWithOldParentSize() on the child views, but you
707
- // can override this to do whatever funky layout to want.
708
1292
  resizeChildrenWithOldSize: function(oldSize) {
709
1293
  var child = this.get('firstChild') ;
710
1294
  while(child) {
@@ -712,117 +1296,234 @@ SC.View = SC.Responder.extend(SC.PathModule,
712
1296
  child = child.get('nextSibling') ;
713
1297
  }
714
1298
  },
715
-
716
- // called by the parentNode when it is resized. If you define the
717
- // resizeOptions property, then this will respect those properties,
718
- // otherwise it will let the browser do all the resizing and simply informs
719
- // the child views that they need to resize also.
1299
+
1300
+ /**
1301
+ Called whenever the parent's innerFrame size has changed. You can
1302
+ override this method to change how your view responds to this change.
1303
+
1304
+ If you do not override this method, the view will assume you are using CSS
1305
+ to control your layout and it will simply relay the change information to
1306
+ your child views. As an optmization, the view may not always call this
1307
+ method if it determines that you have not overridden it.
1308
+
1309
+ @param oldSize {Size} The old frame size of the parent view.
1310
+ */
720
1311
  resizeWithOldParentSize: function(oldSize) {
721
- var opts = this.get('resizeOptions') ;
1312
+ this.viewFrameWillChange() ;
1313
+ this.viewFrameDidChange(YES) ;
1314
+ },
1315
+
1316
+ /** @private
1317
+ Handler for the onscroll event. Hooked in on init if isScrollable is true.
1318
+ Notify children that their clipping frame has changed.
1319
+ */
1320
+ _onscroll: function() {
1321
+ this._scrollFrame = null ;
1322
+ this.notifyPropertyChange('scrollFrame') ;
1323
+ SC.Benchmark.start('%@.onscroll'.fmt(this)) ;
1324
+ this._invalidateClippingFrame() ;
1325
+ SC.Benchmark.end('%@.onscroll'.fmt(this)) ;
1326
+ },
1327
+
1328
+ _frameChangeLevel: 0,
1329
+
1330
+ /** @private
1331
+ Used internally to collect client offset and location info. If the element is
1332
+ not in the main window or hidden, it will be added temporarily and then the passed
1333
+ function will be called.
1334
+ */
1335
+ _collectFrame: function(func) {
1336
+ var el = this.rootElement ;
722
1337
 
723
- // if there are no options, then just notify the children and return.
724
- if (opts == null) {
725
- if (this.firstChild) {
726
- var oldSize = (this._frame) ? { width: this._frame.width, height: this._frame.height } : this.get('size') ;
727
- this.resizeChildrenWithOldSize(oldSize) ;
728
- }
729
- return ;
1338
+ // if not visible in window, move parent node into window and get
1339
+ // dim and offset. If the element has no parentNode, then just move
1340
+ // the element in.
1341
+ var isVisibleInWindow = this.get('isVisibleInWindow') ;
1342
+ if (!isVisibleInWindow) {
1343
+ var pn = el.parentNode || el ;
1344
+ var pnParent = pn.parentNode ; // cache former parent node
1345
+ var pnSib = pn.nextSibling ; // cache next sibling
1346
+ SC.window.rootElement.insertBefore(pn, null) ;
1347
+ }
1348
+
1349
+ // if view is not displayed, temporarily display it also
1350
+ var display = this.getStyle('display') ;
1351
+ var isHidden = !(display != 'none' && display != null) ;
1352
+
1353
+ // All *Width and *Height properties give 0 on elements with display none,
1354
+ // so enable the element temporarily
1355
+ if (isHidden) {
1356
+ var els = this.rootElement.style;
1357
+ var originalVisibility = els.visibility;
1358
+ var originalPosition = els.position;
1359
+ var originalDisplay = els.display;
1360
+ els.visibility = 'hidden';
1361
+ els.position = 'absolute';
1362
+ els.display = 'block';
730
1363
  }
731
1364
 
732
- // if there are options, then handle the resizing. This will
733
- // notify the children also.
734
- if (this.get('isPositioned')) this.set('isPositioned',true) ;
1365
+ var ret = func.call(this) ;
735
1366
 
736
- var f = Object.clone(this.get('frame')) ;
737
- var newSize = this.get('parentNode').get('size') ;
1367
+ if (isHidden) {
1368
+ els.display = originalDisplay;
1369
+ els.position = originalPosition;
1370
+ els.visibility = originalVisibility;
1371
+ }
1372
+
1373
+ if (!isVisibleInWindow) {
1374
+ if (pnParent) {
1375
+ pnParent.insertBefore(pn, pnSib) ;
1376
+ } else SC.window.rootElement.removeChild(pn) ;
1377
+ }
738
1378
 
739
- var adjust = function(props, apts, newSize, oldSize) {
740
- var loc ;
741
-
742
- // first, compute the dimensions for old size.
743
- var dims = [f[apts[0]], f[apts[1]]] ;
744
- dims.push(oldSize - (dims[0] + dims[1])) ;
745
-
746
- // next, subtract the dimensions of fixed elements from the old and
747
- // new sizes.
748
- for(loc=0;loc < 3;loc++) {
749
- if (opts[props[loc]] != SC.FLEXIBLE) {
750
- newSize -= dims[loc]; oldSize -= dims[loc] ;
751
- }
752
- }
753
-
754
- // finally, adjust the flexible area as a percentage of the limited
755
- // dimensions.
756
- for(loc=0;loc < 2; loc++) {
757
- if (opts[props[loc]] == SC.FLEXIBLE) {
758
- f[apts[loc]] = newSize * dims[loc] / oldSize ;
759
- }
760
- }
761
- };
1379
+ return ret;
1380
+ },
1381
+
1382
+ /** @private
1383
+ Called whenever some aspect of the receiver's frames have changed that
1384
+ probably has invalidated the child views clippingFrames. Events that cause
1385
+ this include:
762
1386
 
763
- // handle horizontal
764
- adjust(['left','width','right'], ['x','width'], newSize.width, oldSize.width) ;
765
-
766
- adjust(['top','height','bottom'], ['y','height'], newSize.height, oldSize.height) ;
1387
+ - change to the innerFrame size
1388
+ - change to the scrollFrame
1389
+ - change to the clippingFrame
1390
+
1391
+ For performance reasons, this only passes onto children if they or a decendent
1392
+ implements the clippingFrameDidChange method.
1393
+ */
1394
+ _invalidateChildrenClippingFrames: function() {
1395
+ var view = this.get('firstChild') ;
1396
+ while(view) {
1397
+ view._invalidateClippingFrame() ;
1398
+ view = view.get('nextSibling') ;
1399
+ }
1400
+ },
767
1401
 
768
- this.set('frame',f) ;
1402
+ /** @private
1403
+ Called by a parentNode whenever the clippingFrame needs to be recalculated.
1404
+ */
1405
+ _invalidateClippingFrame: function() {
1406
+ if (this.get('needsClippingFrame')) {
1407
+ this._clippingFrame = null ;
1408
+ this.clippingFrameDidChange() ;
1409
+ this.notifyPropertyChange('clippingFrame') ;
1410
+ this._invalidateChildrenClippingFrames() ;
1411
+ }
769
1412
  },
770
1413
 
771
- // These properties are provide simple control for autoresizing. If you
772
- // set these, then resizeWithOldParentSize() will autoresize for you.
773
- // The allowed options are: SC.FLEXIBLE, SC.FIXED.
774
- resizeOptions: null,
775
-
776
1414
  // ..........................................
777
1415
  // PROPERTIES
778
1416
  //
779
1417
 
780
- // set isVisible to false to hide a view or true to display it. You can
781
- // optionally setup a visibleAnimation that will be used to transition the
782
- // view in and out.
783
- //
784
- // If you would instead like to be notified when the view's actual
785
- // visibility state changes (i.e. when animations are complete) bind to
786
- // isDisplayVisible.
1418
+ /**
1419
+ Makes the view visible.
1420
+
1421
+ If false, sets display: none on the DOM element as well. You will
1422
+ often want to bind this property to some setting in your application
1423
+ to make various parts of your app visible as needed.
1424
+
1425
+ If you have animation enabled, then changing this property will actually
1426
+ trigger the animation to bring the view in or out.
1427
+
1428
+ The default binding format is SC.Binding.Bool
1429
+
1430
+ @property
1431
+ @type Boolean
1432
+ */
787
1433
  isVisible: true,
788
- isVisibleBindingDefault: SC.Binding.Flag,
789
1434
 
790
- // [RO] This property reflects the current display visibility of the view.
791
- // Usually, this property will mirror the current state of the isVisible
792
- // property. However, if your view animates its visibility in and out, then
793
- // this will not become false until the animation completes.
1435
+ /** @private */
1436
+ isVisibleBindingDefault: SC.Binding.Bool,
1437
+
1438
+ /**
1439
+ (Read Only) The current display visibility of the view.
1440
+
1441
+ Usually, this property will mirror the current state of the isVisible
1442
+ property. However, if your view animates its visibility in and out, then
1443
+ this will not become false until the animation completes.
1444
+
1445
+ @type {Boolean}
1446
+ */
794
1447
  displayIsVisible: true,
795
1448
 
796
- // This property is set to true only when the view is (a) in the main DOM
797
- // hierarchy and (b) all parent nodes are visible and (c) the receiver node
798
- // is visible.
799
- isVisibleInWindow: true,
1449
+ /**
1450
+ true when the view is actually visible in the DOM window.
1451
+
1452
+ This property is set to true only when the view is (a) in the main DOM
1453
+ hierarchy and (b) all parent nodes are visible and (c) the receiver node
1454
+ is visible.
1455
+
1456
+ @type {Boolean}
1457
+ @property
1458
+ */
1459
+ isVisibleInWindow: YES,
800
1460
 
801
- // Localize boolean. This is used if you need toolTips.
1461
+ /**
1462
+ If true, the tooltip will be localized. Also used by some subclasses.
1463
+
1464
+ @type {Boolean}
1465
+ @property
1466
+ */
802
1467
  localize: false,
803
-
804
- // Tool tip gets applied to the title attribute if set.
1468
+
1469
+ /**
1470
+ Applied to the title attribute of the rootElement DOM if set.
1471
+
1472
+ If localize is true, then the toolTip will be localized first.
1473
+
1474
+ @type {String}
1475
+ @property
1476
+ */
805
1477
  toolTip: '',
806
1478
 
807
- // set this to the HTML you want to use when creating a new element. You
808
- // can specify the HTML as a string of text, using the NodeDescriptor, or
809
- // by pointing directly to an element.
1479
+
1480
+ /**
1481
+ The HTML you want to use when creating a new element.
1482
+
1483
+ You can specify the HTML as a string of text, using the NodeDescriptor, or
1484
+ by pointing directly to an element.
1485
+
1486
+ Note that as an optimization, SC.View will actually convert the value of this
1487
+ property to an actual DOM structure the first time you create a view and then
1488
+ clone the DOM structure for future views.
1489
+
1490
+ This means that in general you should only set the value of emptyElement when
1491
+ you create a view subclass. Changing this property value at other times will
1492
+ often have no effect.
1493
+
1494
+ @property
1495
+ @type {String}
1496
+ */
810
1497
  emptyElement: "<div></div>",
811
1498
 
812
- // Set to true and the view will display in a lightbox when you show it.
1499
+ /**
1500
+ If true, view will display in a lightbox when you show it.
1501
+
1502
+ @property
1503
+ @type {Boolean}
1504
+ */
813
1505
  isPanel: false,
814
1506
 
815
- // Set to true if the view should be modal when shown as a panel.
1507
+ /**
1508
+ If true, the view should be modal when shown as a panel.
1509
+
1510
+ @property
1511
+ @type {Boolean}
1512
+ */
816
1513
  isModal: true,
817
1514
 
818
- // Enable visible animation by default.
1515
+ /**
1516
+ Enable visible animation by default.
1517
+ */
819
1518
  isAnimationEnabled: true,
820
-
821
- // General support for animation. Just call this method and it will build
822
- // and play an animation starting from the current state. The second param
823
- // is optional. It should either be a hash of animator options or an
824
- // animator object returned by a previous call to transitionTo().
825
- //
1519
+
1520
+ /**
1521
+ General support for animation. Just call this method and it will build
1522
+ and play an animation starting from the current state. The second param
1523
+ is optional. It should either be a hash of animator options or an
1524
+ animator object returned by a previous call to transitionTo().
1525
+
1526
+ */
826
1527
  transitionTo: function(target,animator,opts) {
827
1528
  var animatorOptions = opts || {} ;
828
1529
 
@@ -848,159 +1549,91 @@ SC.View = SC.Responder.extend(SC.PathModule,
848
1549
  }
849
1550
  return animator ;
850
1551
  },
851
-
852
- // returns the contents of the element as HTML. Accounts for browser
853
- // bugs.
854
- asHTML: function(key, value) {
1552
+
1553
+ /**
1554
+ The contents of the view as HTML. You can use this property to both
1555
+ retrieve the content and to change it. Use this property instead of
1556
+ manually changing the content of your view as this property works around
1557
+ certain cross-browser bugs.
1558
+
1559
+ @field
1560
+ */
1561
+ innerHTML: function(key, value) {
855
1562
  if (value !== undefined) {
1563
+
1564
+ // Clear the text node.
1565
+ this._textNode = null ;
1566
+
856
1567
  // Safari2 has a bad habit of sometimes not actually changing its
857
1568
  // innerHTML. This will make sure the innerHTML get's changed properly.
858
1569
  if (SC.isSafari() && !SC.isSafari3()) {
859
1570
  var el = (this.containerElement || this.rootElement) ; var reps = 0 ;
860
1571
  var f = function() {
861
1572
  el.innerHTML = '' ; el.innerHTML = value ;
862
- if ((reps++ < 5) && (value.length>0) && (el.innerHTML == '')) setTimeout(f,1) ;
1573
+ if ((reps++ < 5) && (value.length>0) && (el.innerHTML == '')) {
1574
+ f.invokeLater() ;
1575
+ }
863
1576
  };
864
1577
  f();
865
1578
  } else (this.containerElement || this.rootElement).innerHTML = value;
866
1579
  } else value = (this.containerElement || this.rootElement).innerHTML ;
867
1580
  return value ;
868
1581
  }.property(),
1582
+
1583
+ /**
1584
+ The contents of the view as plain text. You can use this property to
1585
+ both retrieve the content and to change it. Use this property instead of
1586
+ the innerHTML property when you want to set plain text only as this
1587
+ property is much faster.
869
1588
 
870
- // returns the contents of the element as plain text. Accounts for browser
871
- // bugs.
872
- asText: function(key, value) {
1589
+ @field
1590
+ */
1591
+ innerText: function(key, value) {
873
1592
  if (value !== undefined) {
874
1593
  if (value == null) value = '' ;
875
- this.asHTML(key,value.toString().escapeHTML()) ;
876
- }
877
- return this.asHTML().unescapeHTML() ;
878
- }.property(),
879
-
880
-
881
-
882
-
883
- //
884
- // Command methods (used by the command manager)
885
- //
886
-
887
- /**
888
- * Queries to see if the view has function matching the passed name .
889
- * @param {String} name The name of the function
890
- * @return Boolean
891
- **/
892
- hasNamedFunction: function( name )
893
- {
894
- return ( this[name] && ($type(this[name]) == T_FUNCTION) );
895
- },
896
- /**
897
- * Queries to see if the view has a named command.
898
- * @param {String} name The name of the command
899
- * @return Boolean
900
- **/
901
- hasCommand: function( name )
902
- {
903
- return this.hasNamedFunction(name);
904
- },
905
- /**
906
- * Queries to see if the view has a validator for the named command.
907
- * @param {String} name The name of the command
908
- * @return Boolean
909
- **/
910
- hasCommandValidator: function( name )
911
- {
912
- var name = this._commandValidatorForCommand(name);
913
- return this.hasNamedFunction(name);
914
- },
915
1594
 
916
- /**
917
- * Queries to see if the view is capable of executing a named command.
918
- * The view must have a method named after the command and, if there is a command validator method, it must pass validation.
919
- * @param {String} name The name of the command
920
- * @return Boolean
921
- **/
922
- canExecuteCommand: function( name )
923
- {
924
- var hasCommand = this.hasCommand(name);
925
- var hasCommandValidator = this.hasCommandValidator(name);
926
- // can't execute what you haven't got...
927
- if ( !hasCommand ) return false;
928
- // got it and not validating before usage...
929
- if ( hasCommand && !hasCommandValidator ) return true;
930
- // ok... we got it, and we need to check before using...
931
- if ( hasCommand && hasCommandValidator )
932
- {
933
- return this.executeCommandValidator(name);
1595
+ // add a textNode if necessary
1596
+ if (this._textNode == null) {
1597
+ this._textNode = document.createTextNode(value) ;
1598
+ var el = this.rootElement || this.containerElement ;
1599
+ while(el.firstChild) el.removeChild(el.firstChild) ;
1600
+ el.appendChild(this._textNode) ;
1601
+ } else this._textNode.data = value ;
934
1602
  }
935
- },
936
- /**
937
- * Executes the command (if permitted).
938
- * @param {String} name The name of the command
939
- * @return Boolean Either the return value of executing the command, or false.
940
- **/
941
- executeCommand: function( name )
942
- {
943
- return this.canExecuteCommand(name) ? this.executeCommandWithoutValidation(name) : false;
944
- },
945
- /**
946
- * Executes the command without performing any validation.
947
- * @param {String} name The name of the command
948
- * @return Boolean The return value of executing the command.
949
- **/
950
- executeCommandWithoutValidation: function( name )
951
- {
952
- return this[name]();
953
- },
954
- /**
955
- * Executes the command validator.
956
- * @param {String} name The name of the command to be validated.
957
- * @return Boolean Wether or not the command can be executed.
958
- **/
959
- executeCommandValidator: function( name )
960
- {
961
- var name = this._commandValidatorForCommand(name);
962
- return this[name]();
963
- },
964
-
965
- /**
966
- * Utility to construct the command alidator method name.
967
- * @private
968
- * @param {String} name The name of the command
969
- * @return String
970
- **/
971
- _commandValidatorForCommand: function( name )
972
- {
973
- return "can" + name.capitalize();
974
- },
975
-
976
-
977
-
1603
+
1604
+ return (this._textNode) ? this._textNode.data : this.innerHTML().unescapeHTML() ;
1605
+
1606
+ }.property(),
1607
+
978
1608
 
979
1609
  // ..........................................
980
1610
  // SUPPORT METHODS
981
1611
  //
982
1612
  init: function() {
983
- this._frame = {} ;
984
1613
  arguments.callee.base.call(this) ;
985
1614
 
986
1615
  // configure them outlets.
987
- var r = SC.idt.active ; var idtStart ; var idtSt ;
988
- if (r) { idtSt = new Date().getTime(); }
1616
+ if (SC.BENCHMARK_CONFIGURE_OUTLETS) SC.Benchmark.start('SC.View.configureOutlets') ;
989
1617
  this.configureOutlets() ;
990
- if (r) { SC.idt.conf_t += ((new Date().getTime()) - idtSt); }
1618
+ if (SC.BENCHMARK_CONFIGURE_OUTLETS) SC.Benchmark.end('SC.View.configureOutlets') ;
991
1619
 
992
1620
  var toolTip = this.get('toolTip') ;
993
1621
  if(toolTip && (toolTip != '')) this._updateToolTipObserver();
994
-
995
- // despite what was written in the comments for the containerElement property, it was not being converted
996
- // from a sring to an element on access... doing so here...
997
- // shouldn't be a bottleneck since if containerElement is set, you are likely to need the DOM element at some point.
998
- if ( this.containerElement && (SC.typeOf(this.containerElement) == T_STRING) )
999
- {
1622
+
1623
+ // if container element is a string, convert it to an actual DOM element.
1624
+ if (this.containerElement && ($type(this.containerElement) == T_STRING)) {
1000
1625
  this.containerElement = this.$sel(this.containerElement);
1001
1626
  }
1002
-
1627
+
1628
+ // register as a drop target and scrollable.
1003
1629
  if (this.get('isDropTarget')) SC.Drag.addDropTarget(this) ;
1630
+ if (this.get('isScrollable')) SC.Drag.addScrollableView(this) ;
1631
+
1632
+ // add scrollable handler
1633
+ if (this.isScrollable) this.rootElement.onscroll = SC.View._onscroll ;
1634
+
1635
+ // setup isVisibleInWindow ;
1636
+ this.isVisibleInWindow = (this.parentNode) ? this.parentNode.get('isVisibleInWindow') : NO;
1004
1637
  },
1005
1638
 
1006
1639
  // this method looks through your outlets array and will try to
@@ -1014,23 +1647,6 @@ SC.View = SC.Responder.extend(SC.PathModule,
1014
1647
  this.beginPropertyChanges(); // bundle changes
1015
1648
  for(var oloc=0;oloc < this.outlets.length;oloc++) {
1016
1649
  var view = this.outlet(this.outlets[oloc]) ;
1017
-
1018
- // if the HTML for the view is already in the DOM, then walk up the
1019
- // DOM tree to find the first parent element managed by a view (incl
1020
- // the receiver. Add the view to the list of child views also.
1021
- if (view && view.rootElement && view.rootElement.parentNode) {
1022
- var node = view.rootElement.parentNode;
1023
- var parentView ;
1024
- while(node && (node != this.rootElement) && !(parentView = $view(node))) node = node.parentNode;
1025
- if (node == this.rootElement) parentView = this;
1026
- if (parentView) parentView._insertBefore(view,null,false) ;
1027
- }
1028
- this._rebuildChildNodes() ; // this is not done with _insertBefore.
1029
-
1030
- // update parent state.
1031
- if (view && view._updateIsVisibleInWindow) {
1032
- view._updateIsVisibleInWindow() ;
1033
- }
1034
1650
  }
1035
1651
  this.endPropertyChanges() ;
1036
1652
  },
@@ -1249,15 +1865,20 @@ SC.View = SC.Responder.extend(SC.PathModule,
1249
1865
  Event.observe(this.rootElement,methodMap[name],method) ;
1250
1866
  }
1251
1867
  }
1252
- },
1253
-
1254
- toString: function() {
1255
- var el = this.rootElement ;
1256
- var attrs = el.attributes ;
1257
- attrs = (attrs) ? $A(attrs).map(function(atr) { return [atr.nodeName,atr.nodeValue].join("="); }).join(' ') : '';
1258
- var tagName = (!!el.tagName) ? el.tagName.toLowerCase() : 'document' ;
1259
- return "View(<%@>)".format([tagName,attrs].join(' ')) ;
1260
- }
1868
+ }//,
1869
+
1870
+ // toString: function() {
1871
+ // var el = this.rootElement ;
1872
+ // var tagName = (!!el.tagName) ? el.tagName.toLowerCase() : 'document' ;
1873
+ //
1874
+ // var className = el.className ;
1875
+ // className = (className && className.length>0) ? 'class=%@'.fmt(className) : null;
1876
+ //
1877
+ // var idName = el.id ;
1878
+ // idName = (idName && idName.length>0) ? 'id=%@'.fmt(idName) : null;
1879
+ //
1880
+ // return "%@:%@<%@>".fmt(this._type, this._guid, [tagName,idName, className].compact().join(' ')) ;
1881
+ // }
1261
1882
 
1262
1883
  }) ;
1263
1884
 
@@ -1349,43 +1970,105 @@ SC.View.mixin({
1349
1970
  ret.prototype._cachedEmptyElement = null ;
1350
1971
  return ret ;
1351
1972
  },
1352
-
1353
- // define your view as an outlet.
1973
+
1974
+ /**
1975
+ Defines a view as an outlet. This will return an function that
1976
+ can be executed at a later time to actually create itself as an outlet.
1977
+ */
1354
1978
  outletFor: function(path) {
1355
- var view = this ;
1356
- var _func = function() {
1357
- if (path === null) return view.viewFor(null) ;
1979
+ var viewClass = this ; // save the view class
1980
+ var func = function() {
1981
+ if (SC.BENCHMARK_OUTLETS) SC.Benchmark.start("OUTLET(%@)".format(path)) ;
1358
1982
 
1359
-
1360
- var ret = (this.$$sel) ? this.$$sel(path) : $$sel(path) ;
1361
- if (ret) {
1362
- var owner = this ;
1363
- var newRet = [] ;
1364
- for(var loc=0;loc<ret.length;loc++) {
1365
- newRet.push(view.viewFor(ret[loc], { owner: owner }));
1983
+ // if no path was passed, then create the view from scratch
1984
+ if (path == null) {
1985
+ var ret = viewClass.viewFor(null) ;
1986
+
1987
+ // otherwise, try to find the HTML element identified by the path.
1988
+ // If the element cannot be found in the caller (the owner view), then
1989
+ // search the entire document.
1990
+ } else {
1991
+ var ret = (this.$$sel) ? this.$$sel(path) : $$sel(path) ;
1992
+
1993
+ // if some HTML has been found, then loop through and create views for each
1994
+ // one. Be sure to setup the proper parent view.
1995
+ if (ret) {
1996
+ var owner = this ; var views = [] ;
1997
+ for(var loc=0;loc<ret.length;loc++) {
1998
+
1999
+ // create the new view instance
2000
+ var view = viewClass.viewFor(ret[loc], { owner: owner }) ;
2001
+
2002
+ // if successful, then we need to determine the new parentNode.
2003
+ // then walk up the DOM tree to find the first parent element
2004
+ // managed by a view (including this).
2005
+ //
2006
+ // If a matching view is not found, but the view IS in a DOM
2007
+ // somewhere then make the view a child of either SC.page or
2008
+ // SC.window.
2009
+ //
2010
+ // Add the view to the list of child views also.
2011
+ //
2012
+ if (view && view.rootElement && view.rootElement.parentNode) {
2013
+ var node = view.rootElement.parentNode;
2014
+ var parentView = null ;
2015
+
2016
+ // go up the chain. stop when we find a parent view, or the rootElement
2017
+ // for SC.page.
2018
+ while(node && !parentView) {
2019
+ switch(node) {
2020
+ case this.rootElement:
2021
+ parentView = this;
2022
+ break ;
2023
+ case SC.page.rootElement:
2024
+ parentView = SC.page ;
2025
+ break;
2026
+ case SC.window.rootElement:
2027
+ parentView = SC.window ;
2028
+ default:
2029
+ node = node.parentNode ;
2030
+ }
2031
+ }
2032
+
2033
+ // if a parentView was found, then add to parentView.
2034
+ if (parentView) {
2035
+ parentView._insertBefore(view,null,false) ;
2036
+ parentView._rebuildChildNodes() ; // this is not done with _insertBefore.
2037
+ view._updateIsVisibleInWindow();
2038
+ }
2039
+
2040
+ // view is not in a DOM. nothing to do.
2041
+ }
2042
+
2043
+ // add to return array
2044
+ views[views.length] = view ;
2045
+
2046
+ }
2047
+ ret = views ;
2048
+ ret = (ret.length == 0) ? null : ((ret.length == 1) ? ret[0] : ret);
1366
2049
  }
1367
- ret = newRet ;
1368
- ret = (ret.length == 0) ? null : ((ret.length == 1) ? ret[0] : ret);
2050
+
1369
2051
  }
1370
-
2052
+
2053
+ if (SC.BENCHMARK_OUTLETS) SC.Benchmark.end("OUTLET(%@)".format(path)) ;
1371
2054
  return ret ;
1372
2055
  } ;
1373
-
1374
- var func ;
1375
- if (BENCHMARK_OUTLETS) {
1376
- func = function() {
1377
- var that = this ;
1378
- return SC.Benchmark._bench(function() {
1379
- return _func.call(that);
1380
- }, "OUTLET(%@)".format(path)) ;
1381
- };
1382
- } else func = _func ;
1383
2056
  func.isOutlet = true ;
1384
2057
  return func ;
1385
2058
  }
1386
2059
 
1387
2060
  }) ;
1388
2061
 
2062
+ // this handler goes through the guid to avoid any potential memory leaks
2063
+ SC.View._onscroll = function(evt) { $view(this)._onscroll(evt); } ;
2064
+
2065
+ SC.View.WIDTH_PADDING_STYLES = ['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'];
2066
+
2067
+ SC.View.HEIGHT_PADDING_STYLES = ['paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'];
2068
+
2069
+ SC.View.SCROLL_WIDTH_PADDING_STYLES = ['borderLeftWidth', 'borderRightWidth'];
2070
+ SC.View.SCROLL_HEIGHT_PADDING_STYLES = ['borderTopWidth', 'borderBottomWidth'];
2071
+
1389
2072
  SC.View.elementFor = SC.View.viewFor ; // Old Sprout Compatibility.
1390
2073
 
1391
2074
  // This div is used to create nodes. It should normally remain empty.
@@ -1393,5 +2076,3 @@ SC._ViewCreator = document.createElement('div') ;
1393
2076
 
1394
2077
  // This div can be used to hold elements you don't want on the page right now.
1395
2078
  SC.NodeCache = document.createElement('div') ;
1396
-
1397
- console.log('SC.View = %@'.fmt(SC.View)) ;