sproutcore 0.9.0

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 (270) hide show
  1. data/History.txt +4 -0
  2. data/License.txt +20 -0
  3. data/Manifest.txt +269 -0
  4. data/README.txt +67 -0
  5. data/Rakefile +4 -0
  6. data/app_generators/sproutcore/USAGE +5 -0
  7. data/app_generators/sproutcore/sproutcore_generator.rb +66 -0
  8. data/app_generators/sproutcore/templates/README +77 -0
  9. data/app_generators/sproutcore/templates/environment.yml +4 -0
  10. data/bin/sc-build +145 -0
  11. data/bin/sc-gen +24 -0
  12. data/bin/sc-server +63 -0
  13. data/bin/sproutcore +21 -0
  14. data/clients/sc_docs/controllers/docs.js +118 -0
  15. data/clients/sc_docs/core.js +19 -0
  16. data/clients/sc_docs/english.lproj/body.css +159 -0
  17. data/clients/sc_docs/english.lproj/body.rhtml +33 -0
  18. data/clients/sc_docs/english.lproj/controls.css +0 -0
  19. data/clients/sc_docs/english.lproj/icons/small/next.png +0 -0
  20. data/clients/sc_docs/english.lproj/icons/small/reset.png +0 -0
  21. data/clients/sc_docs/english.lproj/images/gradients.png +0 -0
  22. data/clients/sc_docs/english.lproj/images/indicator.gif +0 -0
  23. data/clients/sc_docs/english.lproj/images/toolbar.png +0 -0
  24. data/clients/sc_docs/english.lproj/no_docs.rhtml +7 -0
  25. data/clients/sc_docs/english.lproj/strings.js +14 -0
  26. data/clients/sc_docs/english.lproj/warning.rhtml +6 -0
  27. data/clients/sc_docs/fixtures/doc.js +11 -0
  28. data/clients/sc_docs/main.js +21 -0
  29. data/clients/sc_docs/models/doc.js +9 -0
  30. data/clients/sc_docs/tests/controllers/docs.rhtml +21 -0
  31. data/clients/sc_docs/tests/models/doc.rhtml +21 -0
  32. data/clients/sc_docs/tests/views/doc_frame.rhtml +21 -0
  33. data/clients/sc_docs/tests/views/doc_label_view.rhtml +21 -0
  34. data/clients/sc_docs/views/doc_frame.js +33 -0
  35. data/clients/sc_docs/views/doc_label.js +20 -0
  36. data/clients/sc_test_runner/controllers/runner.js +175 -0
  37. data/clients/sc_test_runner/core.js +19 -0
  38. data/clients/sc_test_runner/english.lproj/body.css +151 -0
  39. data/clients/sc_test_runner/english.lproj/body.rhtml +35 -0
  40. data/clients/sc_test_runner/english.lproj/controls.css +0 -0
  41. data/clients/sc_test_runner/english.lproj/icons/small/next.png +0 -0
  42. data/clients/sc_test_runner/english.lproj/icons/small/reset.png +0 -0
  43. data/clients/sc_test_runner/english.lproj/images/gradients.png +0 -0
  44. data/clients/sc_test_runner/english.lproj/images/indicator.gif +0 -0
  45. data/clients/sc_test_runner/english.lproj/images/toolbar.png +0 -0
  46. data/clients/sc_test_runner/english.lproj/no_tests.rhtml +6 -0
  47. data/clients/sc_test_runner/english.lproj/strings.js +14 -0
  48. data/clients/sc_test_runner/english.lproj/warning.rhtml +6 -0
  49. data/clients/sc_test_runner/fixtures/test.js +12 -0
  50. data/clients/sc_test_runner/main.js +26 -0
  51. data/clients/sc_test_runner/models/test.js +11 -0
  52. data/clients/sc_test_runner/views/runner_frame.js +72 -0
  53. data/clients/sc_test_runner/views/test_label.js +20 -0
  54. data/config/hoe.rb +70 -0
  55. data/config/requirements.rb +17 -0
  56. data/environment.yml +9 -0
  57. data/frameworks/prototype/prototype.js +4186 -0
  58. data/frameworks/sproutcore/Core.js +378 -0
  59. data/frameworks/sproutcore/README +3 -0
  60. data/frameworks/sproutcore/controllers/array.js +236 -0
  61. data/frameworks/sproutcore/controllers/collection.js +305 -0
  62. data/frameworks/sproutcore/controllers/controller.js +323 -0
  63. data/frameworks/sproutcore/controllers/object.js +372 -0
  64. data/frameworks/sproutcore/drag/drag.js +549 -0
  65. data/frameworks/sproutcore/drag/drag_data_source.js +32 -0
  66. data/frameworks/sproutcore/drag/drag_source.js +64 -0
  67. data/frameworks/sproutcore/drag/drop_target.js +153 -0
  68. data/frameworks/sproutcore/english.lproj/blank.gif +0 -0
  69. data/frameworks/sproutcore/english.lproj/buttons.css +589 -0
  70. data/frameworks/sproutcore/english.lproj/buttons.png +0 -0
  71. data/frameworks/sproutcore/english.lproj/inline_text_editor.css +21 -0
  72. data/frameworks/sproutcore/english.lproj/menu.css +121 -0
  73. data/frameworks/sproutcore/english.lproj/panels/background-fat.jpg +0 -0
  74. data/frameworks/sproutcore/english.lproj/panels/background-thin.jpg +0 -0
  75. data/frameworks/sproutcore/english.lproj/panels/bottom-edge.png +0 -0
  76. data/frameworks/sproutcore/english.lproj/panels/bottom-left-corner.png +0 -0
  77. data/frameworks/sproutcore/english.lproj/panels/bottom-right-corner.png +0 -0
  78. data/frameworks/sproutcore/english.lproj/panels/left-edge.png +0 -0
  79. data/frameworks/sproutcore/english.lproj/panels/overlay.png +0 -0
  80. data/frameworks/sproutcore/english.lproj/panels/right-edge.png +0 -0
  81. data/frameworks/sproutcore/english.lproj/panels/top-edge.png +0 -0
  82. data/frameworks/sproutcore/english.lproj/panels/top-left-corner.png +0 -0
  83. data/frameworks/sproutcore/english.lproj/panels/top-right-corner.png +0 -0
  84. data/frameworks/sproutcore/english.lproj/panes.css +155 -0
  85. data/frameworks/sproutcore/english.lproj/picker.css +22 -0
  86. data/frameworks/sproutcore/english.lproj/strings.js +15 -0
  87. data/frameworks/sproutcore/english.lproj/tab.css +23 -0
  88. data/frameworks/sproutcore/english.lproj/tests.css +67 -0
  89. data/frameworks/sproutcore/english.lproj/theme.css +77 -0
  90. data/frameworks/sproutcore/foundation/animator.js +670 -0
  91. data/frameworks/sproutcore/foundation/application.js +199 -0
  92. data/frameworks/sproutcore/foundation/array.js +348 -0
  93. data/frameworks/sproutcore/foundation/benchmark.js +211 -0
  94. data/frameworks/sproutcore/foundation/binding.js +384 -0
  95. data/frameworks/sproutcore/foundation/date.js +357 -0
  96. data/frameworks/sproutcore/foundation/error.js +39 -0
  97. data/frameworks/sproutcore/foundation/input_manager.js +153 -0
  98. data/frameworks/sproutcore/foundation/json.js +296 -0
  99. data/frameworks/sproutcore/foundation/mock.js +42 -0
  100. data/frameworks/sproutcore/foundation/node_descriptor.js +56 -0
  101. data/frameworks/sproutcore/foundation/object.js +777 -0
  102. data/frameworks/sproutcore/foundation/observable.js +451 -0
  103. data/frameworks/sproutcore/foundation/page.js +63 -0
  104. data/frameworks/sproutcore/foundation/path_module.js +413 -0
  105. data/frameworks/sproutcore/foundation/responder.js +310 -0
  106. data/frameworks/sproutcore/foundation/routes.js +371 -0
  107. data/frameworks/sproutcore/foundation/run_loop.js +21 -0
  108. data/frameworks/sproutcore/foundation/server.js +491 -0
  109. data/frameworks/sproutcore/foundation/set.js +96 -0
  110. data/frameworks/sproutcore/foundation/string.js +149 -0
  111. data/frameworks/sproutcore/foundation/undo_manager.js +186 -0
  112. data/frameworks/sproutcore/foundation/unittest.js +622 -0
  113. data/frameworks/sproutcore/foundation/utils.js +61 -0
  114. data/frameworks/sproutcore/globals/panels.js +182 -0
  115. data/frameworks/sproutcore/globals/popups.js +60 -0
  116. data/frameworks/sproutcore/globals/window.js +381 -0
  117. data/frameworks/sproutcore/lib/index.rhtml +66 -0
  118. data/frameworks/sproutcore/models/collection.js +395 -0
  119. data/frameworks/sproutcore/models/record.js +622 -0
  120. data/frameworks/sproutcore/models/store.js +295 -0
  121. data/frameworks/sproutcore/panes/dialog.js +16 -0
  122. data/frameworks/sproutcore/panes/manager.js +164 -0
  123. data/frameworks/sproutcore/panes/menu.js +45 -0
  124. data/frameworks/sproutcore/panes/overlay.js +231 -0
  125. data/frameworks/sproutcore/panes/pane.js +90 -0
  126. data/frameworks/sproutcore/panes/panel.js +19 -0
  127. data/frameworks/sproutcore/panes/picker.js +45 -0
  128. data/frameworks/sproutcore/tests/controllers/array.rhtml +86 -0
  129. data/frameworks/sproutcore/tests/controllers/controller.rhtml +273 -0
  130. data/frameworks/sproutcore/tests/controllers/object.rhtml +327 -0
  131. data/frameworks/sproutcore/tests/foundation/application.rhtml +125 -0
  132. data/frameworks/sproutcore/tests/foundation/array.rhtml +221 -0
  133. data/frameworks/sproutcore/tests/foundation/object.rhtml +69 -0
  134. data/frameworks/sproutcore/tests/globals/window.rhtml +45 -0
  135. data/frameworks/sproutcore/tests/panes/pane.rhtml +88 -0
  136. data/frameworks/sproutcore/tests/views/collection.rhtml +137 -0
  137. data/frameworks/sproutcore/tests/views/popup_button.rhtml +115 -0
  138. data/frameworks/sproutcore/tests/views/text_field.rhtml +37 -0
  139. data/frameworks/sproutcore/validators/credit_card.js +92 -0
  140. data/frameworks/sproutcore/validators/date.js +36 -0
  141. data/frameworks/sproutcore/validators/email.js +29 -0
  142. data/frameworks/sproutcore/validators/not_empty.js +24 -0
  143. data/frameworks/sproutcore/validators/number.js +55 -0
  144. data/frameworks/sproutcore/validators/password.js +78 -0
  145. data/frameworks/sproutcore/validators/validator.js +304 -0
  146. data/frameworks/sproutcore/views/button.js +425 -0
  147. data/frameworks/sproutcore/views/checkbox_field.js +30 -0
  148. data/frameworks/sproutcore/views/collection.js +1521 -0
  149. data/frameworks/sproutcore/views/container.js +62 -0
  150. data/frameworks/sproutcore/views/error_explanation.js +45 -0
  151. data/frameworks/sproutcore/views/field.js +214 -0
  152. data/frameworks/sproutcore/views/filter_button.js +29 -0
  153. data/frameworks/sproutcore/views/form.js +591 -0
  154. data/frameworks/sproutcore/views/image.js +141 -0
  155. data/frameworks/sproutcore/views/inline_text_editor.js +96 -0
  156. data/frameworks/sproutcore/views/label.js +176 -0
  157. data/frameworks/sproutcore/views/menu_item.js +90 -0
  158. data/frameworks/sproutcore/views/pagination.js +54 -0
  159. data/frameworks/sproutcore/views/popup_button.js +86 -0
  160. data/frameworks/sproutcore/views/popup_menu.js +137 -0
  161. data/frameworks/sproutcore/views/progress.js +100 -0
  162. data/frameworks/sproutcore/views/radio_field.js +107 -0
  163. data/frameworks/sproutcore/views/radio_group.js +48 -0
  164. data/frameworks/sproutcore/views/segmented.js +80 -0
  165. data/frameworks/sproutcore/views/select_field.js +272 -0
  166. data/frameworks/sproutcore/views/spinner.js +11 -0
  167. data/frameworks/sproutcore/views/tab.js +126 -0
  168. data/frameworks/sproutcore/views/text_field.js +179 -0
  169. data/frameworks/sproutcore/views/textarea_field.js +14 -0
  170. data/frameworks/sproutcore/views/toolbar.js +29 -0
  171. data/frameworks/sproutcore/views/view.js +1389 -0
  172. data/frameworks/sproutcore/views/workspace.js +170 -0
  173. data/generators/client/README +3 -0
  174. data/generators/client/USAGE +12 -0
  175. data/generators/client/client_generator.rb +53 -0
  176. data/generators/client/templates/core.js +19 -0
  177. data/generators/client/templates/english.lproj/body.css +0 -0
  178. data/generators/client/templates/english.lproj/body.rhtml +3 -0
  179. data/generators/client/templates/english.lproj/controls.css +0 -0
  180. data/generators/client/templates/english.lproj/strings.js +14 -0
  181. data/generators/client/templates/main.js +37 -0
  182. data/generators/controller/USAGE +16 -0
  183. data/generators/controller/controller_generator.rb +51 -0
  184. data/generators/controller/templates/controller.js +21 -0
  185. data/generators/controller/templates/test.rhtml +21 -0
  186. data/generators/framework/README +7 -0
  187. data/generators/framework/USAGE +12 -0
  188. data/generators/framework/framework_generator.rb +53 -0
  189. data/generators/framework/templates/core.js +20 -0
  190. data/generators/framework/templates/english.lproj/body.css +0 -0
  191. data/generators/framework/templates/english.lproj/body.rhtml +3 -0
  192. data/generators/framework/templates/english.lproj/controls.css +0 -0
  193. data/generators/framework/templates/english.lproj/strings.js +14 -0
  194. data/generators/language/USAGE +16 -0
  195. data/generators/language/language_generator.rb +47 -0
  196. data/generators/language/templates/strings.js +10 -0
  197. data/generators/model/USAGE +24 -0
  198. data/generators/model/model_generator.rb +55 -0
  199. data/generators/model/templates/fixture.js +11 -0
  200. data/generators/model/templates/model.js +20 -0
  201. data/generators/model/templates/test.rhtml +21 -0
  202. data/generators/test/USAGE +16 -0
  203. data/generators/test/templates/test.rhtml +21 -0
  204. data/generators/test/test_generator.rb +47 -0
  205. data/generators/view/USAGE +16 -0
  206. data/generators/view/templates/test.rhtml +21 -0
  207. data/generators/view/templates/view.js +20 -0
  208. data/generators/view/view_generator.rb +51 -0
  209. data/jsdoc/README.txt +119 -0
  210. data/jsdoc/app/DocFile.js +137 -0
  211. data/jsdoc/app/DocTag.js +110 -0
  212. data/jsdoc/app/Doclet.js +63 -0
  213. data/jsdoc/app/Dumper.js +143 -0
  214. data/jsdoc/app/JsDoc.js +103 -0
  215. data/jsdoc/app/JsHilite.js +45 -0
  216. data/jsdoc/app/JsIO.js +163 -0
  217. data/jsdoc/app/JsParse.js +385 -0
  218. data/jsdoc/app/JsPlate.js +130 -0
  219. data/jsdoc/app/JsTestrun.js +129 -0
  220. data/jsdoc/app/JsToke.js +564 -0
  221. data/jsdoc/app/Symbol.js +298 -0
  222. data/jsdoc/app/Transformer.js +14 -0
  223. data/jsdoc/app/Util.js +97 -0
  224. data/jsdoc/app/js.jar +0 -0
  225. data/jsdoc/app/run.js +144 -0
  226. data/jsdoc/plugins/min.js +316 -0
  227. data/jsdoc/plugins/strip.js +20 -0
  228. data/jsdoc/templates/sproutcore/class.tmpl +438 -0
  229. data/jsdoc/templates/sproutcore/default.css +241 -0
  230. data/jsdoc/templates/sproutcore/index.html +13 -0
  231. data/jsdoc/templates/sproutcore/index.tmpl +21 -0
  232. data/jsdoc/templates/sproutcore/prototype.js +4186 -0
  233. data/jsdoc/templates/sproutcore/publish.js +236 -0
  234. data/jsdoc/templates/sproutcore/splash.html +7 -0
  235. data/lib/sproutcore/build_tools/html_builder.rb +88 -0
  236. data/lib/sproutcore/build_tools/resource_builder.rb +194 -0
  237. data/lib/sproutcore/build_tools.rb +44 -0
  238. data/lib/sproutcore/bundle.rb +517 -0
  239. data/lib/sproutcore/bundle_manifest.rb +397 -0
  240. data/lib/sproutcore/generator_helper.rb +170 -0
  241. data/lib/sproutcore/helpers/capture_helper.rb +42 -0
  242. data/lib/sproutcore/helpers/static_helper.rb +80 -0
  243. data/lib/sproutcore/helpers/tag_helper.rb +110 -0
  244. data/lib/sproutcore/helpers/text_helper.rb +336 -0
  245. data/lib/sproutcore/helpers.rb +3 -0
  246. data/lib/sproutcore/jsdoc.rb +40 -0
  247. data/lib/sproutcore/jsmin.rb +247 -0
  248. data/lib/sproutcore/library.rb +258 -0
  249. data/lib/sproutcore/merb/bundle_controller.rb +179 -0
  250. data/lib/sproutcore/merb/router.rb +43 -0
  251. data/lib/sproutcore/merb.rb +27 -0
  252. data/lib/sproutcore/version.rb +9 -0
  253. data/lib/sproutcore/view_helpers/button_views.rb +302 -0
  254. data/lib/sproutcore/view_helpers/core_views.rb +284 -0
  255. data/lib/sproutcore/view_helpers/form_views.rb +258 -0
  256. data/lib/sproutcore/view_helpers/menu_views.rb +94 -0
  257. data/lib/sproutcore/view_helpers.rb +628 -0
  258. data/lib/sproutcore.rb +30 -0
  259. data/script/destroy +14 -0
  260. data/script/generate +14 -0
  261. data/script/txt2html +74 -0
  262. data/setup.rb +1585 -0
  263. data/spec/spec.opts +1 -0
  264. data/spec/spec_helper.rb +7 -0
  265. data/spec/sproutcore_spec.rb +11 -0
  266. data/tasks/deployment.rake +34 -0
  267. data/tasks/environment.rake +7 -0
  268. data/tasks/rspec.rake +21 -0
  269. data/tasks/website.rake +17 -0
  270. metadata +365 -0
@@ -0,0 +1,305 @@
1
+ // ========================================================================
2
+ // SproutCore
3
+ // copyright 2006-2007 Sprout Systems, Inc.
4
+ // ========================================================================
5
+
6
+ require('controllers/object') ;
7
+
8
+ /** @class
9
+
10
+ A CollectionController works just like an ObjectController except that
11
+ it includes support for a few extra items including the arrangedObjects
12
+ property (which may have a filter applied), and a selection.
13
+
14
+ This expects the content object to be a Collection object.
15
+
16
+ @extends SC.ObjectController
17
+ */
18
+ SC.CollectionController = SC.ObjectController.extend(
19
+ /** @scope SC.CollectionController.prototype */
20
+ {
21
+
22
+ // ...................................
23
+ // PROPERTIES
24
+ //
25
+
26
+ /**
27
+ This is the current set of objects for the UI.
28
+
29
+ @type Array
30
+ */
31
+ arrangedObjects: [],
32
+
33
+ /**
34
+ @property {Array} selection
35
+ This is the current selection. You can make this selection and another
36
+ controller's selection work in concert by binding them together. You
37
+ generally have a master selection that relays changes TO all the others.
38
+ */
39
+ selection: function(key,value)
40
+ {
41
+ if (value !== undefined)
42
+ {
43
+ // selection must be an array... force if needed....
44
+ value = (value) ? ((value instanceof Array) ? value : [value]) : [];
45
+
46
+ var allowsSelection = this.get('allowsSelection');
47
+ var allowsEmptySelection = this.get('allowsEmptySelection');
48
+ var allowsMultipleSelection = this.get('allowsMultipleSelection');
49
+
50
+ // are we even allowing selection at all?
51
+ // if not, then bail out.
52
+ if ( !allowsSelection ) return this._selection;
53
+
54
+ // ok, new decide if the *type* of seleciton is allowed...
55
+ switch ( value.length )
56
+ {
57
+ case 0:
58
+ // check to see if we're attemting to set an empty array
59
+ // if that's not allowed, set to the first available item in arrangedObjects
60
+ this._selection = allowsEmptySelection ? value : (this.get('arrangedObjects') || []).first();
61
+ break;
62
+ case 1:
63
+ // check to see id we've just taken focus away from a new record
64
+ // if so, queue up the callback to be triggered...
65
+ if ( this._editingNewRecord && (this._editingNewRecord != value[0]) )
66
+ {
67
+ setTimeout(this._newRecordDidLoseFocus.bind(this,this._editingNewRecord),1);
68
+ this._editingNewRecord = null;
69
+ }
70
+ this._selection = value;
71
+ break;
72
+ default:
73
+ // fall through for >= 2 items...
74
+ // only allow if configured for multi-select
75
+ this._selection = allowsMultipleSelection ? value : this._selection;
76
+ break;
77
+ }
78
+
79
+ if ( this._selection && (this._selection.length > 1) )
80
+ {
81
+ var collection = this.get('content');
82
+ var order = collection ? collection.get('orderBy') : null;
83
+ if ( !order ) order = ['guid'];
84
+ this._selection = this._selection.sort(function(a,b) { return a.compareTo(b,order); });
85
+ }
86
+ }
87
+
88
+ return this._selection;
89
+ }.property(),
90
+
91
+ /**
92
+ If true, selection is allowed.
93
+
94
+ @type bool
95
+ */
96
+ allowsSelection: true,
97
+
98
+ /**
99
+ If true, multiple selection is allowed.
100
+
101
+ @type bool
102
+ */
103
+ allowsMultipleSelection: true,
104
+
105
+ /**
106
+ If true, allow empty selection
107
+
108
+ @type bool
109
+ */
110
+ allowsEmptySelection: true,
111
+
112
+ /**
113
+ If true, new, add, remove will work.
114
+
115
+ @type bool
116
+ */
117
+ canEditCollection: false,
118
+
119
+ /**
120
+ Set to the total number of items to show on a single page. If set to
121
+ zero, then no pagination will be performed.
122
+
123
+ @type number
124
+ */
125
+ pageSize: 0,
126
+
127
+ /**
128
+ [RO] read only property with the current total number of pages.
129
+ */
130
+ pageCount: function() {
131
+ var pageSize = this.get('pageSize') ;
132
+ if (pageSize <= 0) return 1 ;
133
+
134
+ var content = this.get('content') ;
135
+ var count = (content && content.get) ? content.get('count') : 0 ;
136
+ if (count === null) count = 0 ;
137
+ return Math.ceil(count / pageSize) ;
138
+ }.property(),
139
+
140
+ /**
141
+ Set to the current page. This will change the offset and limit shown in
142
+ the collection. If you try to set the page to a number greater than the
143
+ maximum, then it will be set to the last page.
144
+ */
145
+ currentPage: function(key, value) {
146
+ if (value !== undefined) {
147
+ // constrain pages.
148
+ if (this._currentPage != value) {
149
+ var pc = Math.max(this.get('pageCount')-1,0);
150
+ if (value > pc) value = pc ;
151
+ if (value < 0) value = 0 ;
152
+ this._currentPage = value ;
153
+ }
154
+ }
155
+ return this._currentPage || 0 ;
156
+ }.property(),
157
+
158
+ // ...................................
159
+ // METHODS
160
+ //
161
+
162
+ // adds a new object to the current collection, if allowed, and sets it
163
+ // as the current selection.
164
+ newObject: function(settings) {
165
+ var content = this.get('content') ;
166
+ if (!content || !this.get('canEditCollection')) return ; // only if allowed
167
+ try {
168
+ if (content.newRecord) {
169
+ var rec = content.newRecord(settings) ;
170
+ var controller = this ;
171
+ setTimeout(function() {
172
+ controller.set('selection',(rec) ? [rec] : []) ;
173
+ controller._editingNewRecord = rec ;
174
+ },1) ;
175
+ return rec;
176
+ }
177
+ }
178
+ catch (e) {
179
+ // DO SOMETHING
180
+ }
181
+ },
182
+
183
+ addObjects: function(objects) {
184
+ var content = this.get('content') ;
185
+ if (!content || !this.get('canEditCollection')) return ; // only if allowed
186
+ try {
187
+ objects = $A(arguments).flatten() ;
188
+ if (content.addRecords) {
189
+ content.addRecords(objects) ;
190
+ this.set('selection',(objects) ? objects : []) ;
191
+ }
192
+ }
193
+ catch (e) {
194
+ // DO SOMETHING
195
+ }
196
+ },
197
+
198
+ addSelection: function() {
199
+ return this.addObjects(this.get('selection'));
200
+ },
201
+
202
+ // adds a new object to the current collection, if allowed, and sets it
203
+ // as the current selection.
204
+ removeObjects: function(objects) {
205
+ var content = this.get('content') ;
206
+ if (!content || !this.get('canEditCollection')) return ; // only if allowed
207
+ try {
208
+ objects = $A(arguments).flatten() ;
209
+ if (content.removeRecords) {
210
+ var rec = content.removeRecords(objects) ;
211
+ var sel = (this.get('selection') || []).without(objects) ;
212
+ this.set('selection',(sel) ? sel : []) ;
213
+ }
214
+ }
215
+ catch (e) {
216
+ // DO SOMETHING
217
+ }
218
+ },
219
+
220
+ removeSelection: function() {
221
+ return this.removeObjects(this.get('selection')) ;
222
+ },
223
+
224
+ // this method is called if a new object was created through the controller
225
+ // and then the selection was changed from the new record without the
226
+ // record being saved first. By default, this will remove the object, but
227
+ // you could override it to just do a commit.
228
+ newObjectDidLoseFocus: function(rec) { rec.destroy() ; },
229
+
230
+ // ...................................
231
+ // PRIVATE
232
+ //
233
+
234
+ _newRecordDidLoseFocus: function(rec) {
235
+ if (rec.get('newRecord')) this.newObjectDidLoseFocus(rec) ;
236
+ },
237
+
238
+ // Update the current page.
239
+ _pageObserver: function() {
240
+ // get content -- nothing to do if no content.
241
+ var content = this.get('content') ;
242
+ if (content instanceof Array) content = content[0] ;
243
+ if (!content) return ;
244
+
245
+ var curOffset = content.get('offset') || 0 ;
246
+ var curLimit = content.get('limit') || 0 ;
247
+ var count = content.get('count') || 0 ;
248
+
249
+ // calculate the offset and limit.
250
+ var currentPage = this.get('currentPage') ;
251
+ var pageSize = this.get('pageSize') ;
252
+ var newOffset, newLimit ;
253
+ if (pageSize == 0) {
254
+ newOffset = 0; newLimit = 0 ;
255
+ } else {
256
+ newOffset = currentPage * pageSize ;
257
+ newLimit = pageSize ;
258
+ }
259
+
260
+ // set new page info.
261
+ if ((newOffset != curOffset) || (newLimit != curLimit)) {
262
+ content.beginPropertyChanges() ;
263
+ content.set('offset',newOffset) ;
264
+ content.set('limit',newLimit) ;
265
+ content.endPropertyChanges() ;
266
+ }
267
+ }.observes('currentPage','pageCount','pageSize'),
268
+
269
+ // invoked whenever the list changes. Updated the arrangedObjects and
270
+ // potentially the selection.
271
+ _recordsObserver: function(target,key,value) {
272
+ var old = this.get('arrangedObjects') ;
273
+ value = Array.asArray(target.get(key)) ;
274
+
275
+ this.set('arrangedObjects',value.slice()) ;
276
+ // update selection.
277
+ var objects = this.get('arrangedObjects') || [] ;
278
+ if (!(objects instanceof Array)) objects = [objects] ;
279
+
280
+ var currentSelection = this.get('selection') || [] ;
281
+ var sel = [] ;
282
+
283
+ // the new selection is the current selection that exists in
284
+ // arrangedObjects.
285
+ currentSelection.each(function(obj) {
286
+ if (objects.include(obj)) sel.push(obj) ;
287
+ }) ;
288
+
289
+ // make new selection match current selection settings.
290
+ if (!this.allowsSelection) {
291
+ sel = [] ;
292
+ }
293
+
294
+ if ((sel.length>1) && !this.allowsMultipleSelection) {
295
+ sel = [sel[0]] ;
296
+ }
297
+
298
+ if ((sel.length == 0) && !this.allowsEmptySelection) {
299
+ if (objects.length > 0) sel = [objects[0]] ;
300
+ }
301
+
302
+ this.set('selection',sel) ;
303
+ }.observes('records')
304
+
305
+ }) ;
@@ -0,0 +1,323 @@
1
+ // ========================================================================
2
+ // SproutCore
3
+ // copyright 2006-2007 Sprout Systems, Inc.
4
+ // ========================================================================
5
+
6
+ require('foundation/object') ;
7
+
8
+ /**
9
+ @class SC.Controller
10
+
11
+ The controller base class provides some common functions you will need
12
+ for controllers in your applications, especially related to maintaining
13
+ an editing context.
14
+
15
+ In general you will not use this class, but you can use a subclass such
16
+ as ObjectController, CollectionController, or ArrayController.
17
+
18
+ h2. EDITING CONTEXTS
19
+
20
+ One major function of a controller is to mediate between changes in the
21
+ UI and changes in the model. In particular, you usually do not want
22
+ changes you make in the UI to be applied to a model object directly.
23
+ Instead, you often will want to collect changes to an object and then
24
+ apply them only when the user is ready to commit their changes.
25
+
26
+ The editing contact support in the controller class will help you
27
+ provide this capability.
28
+
29
+ @extends SC.Object
30
+ */
31
+ SC.Controller = SC.Object.extend(
32
+ /** @scope SC.Controller.prototype */
33
+ {
34
+
35
+ /**
36
+ The controller will set this property to true whenever there are
37
+ changes that need to be committed. This property is true whenever
38
+ the controller itself has uncommitted changes or when any dependent
39
+ editors have uncommitted changes. In your own subclass, call
40
+ this.objectDidChange(this) to register changes.
41
+
42
+ @type Boolean
43
+ */
44
+ hasChanges: false,
45
+
46
+ /**
47
+ This is the controller's parent controller usually. The controller will
48
+ notify this controller when its changes are committed or discarded.
49
+
50
+ @type SC.Controller
51
+ */
52
+ context: null,
53
+
54
+ /**
55
+ If this is false, then the controller will only commit changes when you
56
+ explicitly call commitChanges. Otherwise it will commit them
57
+ immediately. You usually want this set to false. It is initially set to
58
+ true for compatibility.
59
+
60
+ @type Boolean
61
+ */
62
+ commitChangesImmediately: true,
63
+
64
+ /**
65
+ * Sets the commitChangesImmediately to the parent context's value if a context was passed.
66
+ * The Controller also observes changes to the context property and adjusts the commitChangesImmediately prop
67
+ */
68
+ init: function()
69
+ {
70
+ arguments.callee.base.apply(this,arguments);
71
+ this._contextObserver();
72
+ },
73
+
74
+ /**
75
+ * @private
76
+ */
77
+ _contextObserver: function()
78
+ {
79
+ if ( this.context )
80
+ {
81
+ // inherit the parent contexts inherit property
82
+ this.commitChangesImmediately = this.context.commitChangesImmediately;
83
+ }
84
+ }.observes('context'),
85
+
86
+ /**
87
+ If the controller has uncommitted changes, call this method to
88
+ commit them. This method will commit the changes for any dependent
89
+ editors as well. This will return true if the commit completed and
90
+ false or an error object if it failed.
91
+ */
92
+ commitChanges: function() {
93
+ this._commitTimeout = null ; // clear timeout
94
+ var ret = this._canCommitChanges() ;
95
+ if (!$ok(ret)) return ret ;
96
+ return this._performCommitChanges() ;
97
+ },
98
+
99
+ /**
100
+ If this controller has uncommitted changes that you do not want to keep,
101
+ call this method to discard them. This method will also discard
102
+ changes for any dependent editors as well.
103
+ */
104
+ discardChanges: function() {
105
+ var ret = this._canDiscardChanges() ;
106
+ if (!$ok(ret)) return ret ;
107
+ return this._performDiscardChanges() ;
108
+ },
109
+
110
+ /**
111
+ This method will return an appropriate controller object for the
112
+ value of the property you name. This will return one of:
113
+
114
+ <table>
115
+ <tr> <th>Value Type</th> <th>Returns</th> </tr>
116
+ <tr> <td>Array-compatible</td> <td>SC.ArrayController</td></tr>
117
+ <tr> <td>SC.Collection</td> <td>SC.CollectionController</td></tr>
118
+ <tr> <td>Kind of SC.Object</td> <td>SC.ObjectController</td></tr>
119
+ <tr> <td>other</td> <td>value</td></tr>
120
+ </table>
121
+
122
+ This is a helper method used by subclasses to create the appropriate
123
+ type of controller.
124
+
125
+ */
126
+ controllerForValue: function(value) {
127
+ var ret = null ;
128
+ switch($type(value)) {
129
+ case T_OBJECT:
130
+ if (value.kindOf(SC.Collection)) {
131
+ ret = SC.CollectionController ;
132
+ } else ret = SC.ObjectController ;
133
+ break ;
134
+ case T_ARRAY:
135
+ ret = SC.ArrayController ;
136
+ break ;
137
+ default:
138
+ ret = null ;
139
+ }
140
+
141
+ return (ret) ? ret.create({ content: value, context: this }) : value;
142
+ },
143
+
144
+ /**
145
+ Call this method whenever you have uncommitted changes. This will
146
+ handle notifying your parent context as well.
147
+
148
+ @param {SC.Controller} editor
149
+ This is the object that has uncommitted changes. Normally you should
150
+ not pass a value. If you do pass an object, then that object will
151
+ become a dependent editor of the receiver.
152
+ */
153
+ editorDidChange: function(editor) {
154
+ if (!editor) editor = this ; // set default value
155
+
156
+ // if this is another editor, add it to the list of editors that need
157
+ // to be notified of a change.
158
+ if (editor != this) {
159
+ if (!this._dirtyEditors) this._dirtyEditors = SC.Set.create();
160
+ this._dirtyEditors.add(editor) ;
161
+ } else {
162
+ this._hasLocalChanges = true ;
163
+ }
164
+ if (!this.get('hasChanges')) {
165
+ this.set('hasChanges', true) ;
166
+
167
+ // if we have a parent context notify them
168
+ if (this.context) {
169
+ this.context.editorDidChange(this) ;
170
+
171
+ // otherwise, if commit changes immediately is true, schedule commit.
172
+ // commit is only done once per cycle so that at least all the
173
+ // changes you might make at one time will be batched.
174
+ } else if (this.get('commitChangesImmediately')) {
175
+ if (!this._commitTimeout) {
176
+ this._commitTimeout = this.commitChanges.bind(this).defer();
177
+ }
178
+ }
179
+ }
180
+ },
181
+
182
+ /**
183
+ Call this method when your object no longer has uncommitted changes.
184
+ This will clear your hasChanges property and notify your parent context.
185
+ This is called automatically whenever changes are committed or discarded
186
+ on your controller.
187
+ */
188
+ editorDidClearChanges: function(editor) {
189
+ if (!editor) editor = this ; // set default value
190
+
191
+ if (editor != this) {
192
+ // if we are currently clearing changes, then we will clean up the
193
+ // hasChanges state and dirtyeditors in bulk when this is all done.
194
+ // so do nothing.
195
+ if (this._clearingChanges) return ;
196
+ if (this._dirtyEditors) this._dirtyEditors.remove(editor) ;
197
+ } else {
198
+ this._hasLocalChanges = false ;
199
+ }
200
+
201
+ // _dirtyEditors may be undefined so use !! to force this to a bool value.
202
+ var hasChanges = !!(this._hasLocalChanges || (this._dirtyEditors && this._dirtyEditors.length > 0)) ;
203
+
204
+ if (this.get('hasChanges') != hasChanges) {
205
+ this.set('hasChanges', hasChanges) ;
206
+ if (this.context) this.context.editorDidClearChanges(editor) ;
207
+ }
208
+ },
209
+
210
+ /**
211
+ Override this method to determine if your controller can commit the
212
+ changes. This should validate your changes. Return false or an error
213
+ object if you cannot commit the change. This method will not be called
214
+ unless hasChanges is true and all your dependent editors are return
215
+ true as well.
216
+ */
217
+ canCommitChanges: function() {
218
+ return true ;
219
+ },
220
+
221
+ /**
222
+ Override this method to actually commit the changes for your controller.
223
+ This will only be called if all controllers indicate that they can
224
+ commit. Return true if you succeeded or false or an error if you failed.
225
+ */
226
+ performCommitChanges: function() {
227
+ return $error('performCommitChanges is not implemented') ;
228
+ },
229
+
230
+ /**
231
+ Override this method to determine if your controller can discard the
232
+ changes it has built up. This method will not be called unless you
233
+ have set hasChanges to true. Return false or an error object if you
234
+ cannot discard the change.
235
+ */
236
+ canDiscardChanges: function() {
237
+ return true ;
238
+ },
239
+
240
+ /**
241
+ Override this method to actually discard the changes for your controller.
242
+ This will only be called if all controllers indicate that they can discard
243
+ their changes. Return true if you succeed or false or an error if you
244
+ failed.
245
+ */
246
+ performDiscardChanges: function() {
247
+ return $error('performDiscardChanges is not implemented');
248
+ },
249
+
250
+ // ....................................
251
+ // PRIVATE
252
+
253
+ _canCommitChanges: function() {
254
+ if (!this.get('hasChanges')) return false ;
255
+
256
+ // validate editors.
257
+ var ret = true ;
258
+ if (this._dirtyEditors) {
259
+ ret = this._dirtyEditors.invokeWhile(true, '_canCommitChanges') ;
260
+ if (!$ok(ret)) return ret ;
261
+ }
262
+
263
+ // then validate receiver
264
+ return this.canCommitChanges() ;
265
+ },
266
+
267
+ _performCommitChanges: function() {
268
+ if (!this.get('hasChanges')) return true ;
269
+
270
+ // first commit any editors. If not successful, return. otherwise,
271
+ // clear editors.
272
+ var ret = true ;
273
+ if (this._dirtyEditors) {
274
+ this._clearingChanges = true ;
275
+ ret = this._dirtyEditors.invokeWhile(true, '_performCommitChanges') ;
276
+ this._clearingChanges = false ;
277
+
278
+ if ($ok(ret)) {
279
+ this._dirtyEditors = null ;
280
+ } else return ret ;
281
+ }
282
+
283
+ // now commit changes for the receiver.
284
+ ret = this.performCommitChanges() ;
285
+ if ($ok(ret)) this.editorDidClearChanges() ;
286
+ return ret ;
287
+ },
288
+
289
+ _canDiscardChanges: function() {
290
+ if (!this.get('hasChanges')) return false ;
291
+ // validate editors.
292
+ var ret = true ;
293
+ if (this._dirtyEditors) {
294
+ ret = this._dirtyEditors.invokeWhile(true, '_canDiscardChanges') ;
295
+ if (!$ok(ret)) return ret ;
296
+ }
297
+
298
+ // then validate receiver
299
+ return this.canDiscardChanges() ;
300
+ },
301
+
302
+ _performDiscardChanges: function() {
303
+ if (!this.get('hasChanges')) return true ;
304
+
305
+ // first discard changes for any editors. If not successful, return.
306
+ // otherwise, clear editors.
307
+ var ret = true ;
308
+ if (this._dirtyEditors) {
309
+ this._clearingChanges = true ;
310
+ ret = this._dirtyEditors.invokeWhile(true, '_performDiscardChanges') ;
311
+ this._clearingChanges = false ;
312
+ if ($ok(ret)) {
313
+ this._dirtyEditors = null ;
314
+ } else return ret ;
315
+ }
316
+
317
+ // now discard changes for the receiver.
318
+ ret = this.performDiscardChanges() ;
319
+ if ($ok(ret)) this.editorDidClearChanges() ;
320
+ return ret ;
321
+ }
322
+
323
+ }) ;