sproutcore 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }) ;