sproutcore 0.9.1 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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)) ;