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,1521 @@
1
+ // ========================================================================
2
+ // SproutCore
3
+ // copyright 2006-2007 Sprout Systems, Inc.
4
+ // ========================================================================
5
+
6
+ require('views/view') ;
7
+ require('views/label') ;
8
+
9
+ /** Indicates that selection points should be selected using horizontal
10
+ orientation.
11
+ */
12
+ SC.HORIZONTAL_ORIENTATION = 'horizontal';
13
+
14
+ /** Selection points should be selected using vertical orientation. */
15
+ SC.VERTICAL_ORIENTATION = 'vertical' ;
16
+
17
+ /**
18
+ @class
19
+
20
+ Renders a collection of views from a source array of model objects.
21
+
22
+ The CollectionView is the root view class for rendering collections of
23
+ views based on a source array of objects. It can automatically create the
24
+ and layout the views, including displaying them in groups. It also
25
+ handles event input for the entire collection.
26
+
27
+ To use CollectionView, just create the view and set the 'content' property
28
+ to an array of objects. (Note that if you setup a binding, it will
29
+ always transform content to an array.) The view will create instances of
30
+ exampleView to render the array. You can also bind to the selection
31
+ property if you want to monitor selection. (be sure to set the isEnabled
32
+ property to allow selection.)
33
+
34
+ h4. INCREMENTAL RENDERING
35
+
36
+ incremental rendering can be used in certain collection views to
37
+ display only the visible views in your collection. This will yield
38
+ dramatically improved performance over the typical full-rendering
39
+ facility.
40
+
41
+ to activate incremental rendering you need to override the two methods
42
+ below to return valid values and also implement layoutChildViewsFor()
43
+ above.
44
+
45
+ @extends SC.View
46
+ */
47
+ SC.CollectionView = SC.View.extend(
48
+ /** @scope SC.CollectionView.prototype */
49
+ {
50
+
51
+ // ......................................
52
+ // PROPERTIES
53
+ //
54
+
55
+ /**
56
+ An array of content objects
57
+
58
+ This array should contain the content objects you want the collection view
59
+ to display. An item view (based on the exampleView view class) will be
60
+ created for each content object, in the order the content objects appear in
61
+ this array.
62
+
63
+ If you make the collection editable, the collection view will also modify
64
+ this array using the observable array methods of SC.Array.
65
+
66
+ Usually you will want to bind this property to a controller property
67
+ that actually contains the array of objects you to display.
68
+
69
+ @type Array
70
+ */
71
+ content: [],
72
+
73
+ /** @private */
74
+ contentBindingDefault: SC.Binding.MultipleNotEmpty,
75
+
76
+ /**
77
+ The array of currently selected objects.
78
+
79
+ This array should contain the currently selected content objects.
80
+ It is modified automatically by the collection view when the user
81
+ changes the selection on the collection.
82
+
83
+ Any item views representing content objects in this array will
84
+ have their isSelected property set to YES automatically.
85
+
86
+ The CollectionView can deal with selection arrays that contain content
87
+ objects that do not belong to the content array itself. Sometimes this
88
+ will happen if you share the same selection across multiple collection
89
+ views.
90
+
91
+ Usually you will want to bind this property to a controller property
92
+ that actually manages the selection for your display.
93
+
94
+ @type Array
95
+ */
96
+ selection: [],
97
+
98
+ /** @private */
99
+ selectionBindingDefault: SC.Binding.Multiple,
100
+
101
+ /**
102
+ Allow user to select content using the mouse and keyboard
103
+
104
+ Set this property to NO to disallow the user from selecting items.
105
+ If you have items in your selection property, they will still be reflected
106
+ visually.
107
+
108
+ @type Boolean
109
+ */
110
+ isSelectable: true,
111
+
112
+ /** @private */
113
+ isSelectableBindingDefault: SC.Binding.Bool,
114
+
115
+ /**
116
+ Enable or disable the view.
117
+
118
+ The collection view will set the isEnabled property of its item views to
119
+ reflect the same view of this property. Whenever isEnabled is false,
120
+ the collection view will also be not selectable or editable, regardless of the
121
+ settings for isEditable & isSelectable.
122
+
123
+ @type Boolean
124
+ */
125
+ isEnabled: true,
126
+
127
+ /** @private */
128
+ isEnabledBindingDefault: SC.Binding.Bool,
129
+
130
+ /**
131
+ Allow user to edit content views.
132
+
133
+ The collection view will set the isEditable property on its item views to
134
+ reflect the same value of this property. Whenever isEditable is false, the
135
+ user will not be able to reorder, add, or delete items regardless of the
136
+ canReorderContent and canDeleteContent and isDropTarget properties.
137
+ */
138
+ isEditable: true,
139
+
140
+ /** @private */
141
+ isEditableBindingDefault: SC.Binding.Bool,
142
+
143
+ /**
144
+ Allow user to reorder items using drag and drop.
145
+
146
+ If true, the user will can use drag and drop to reorder items in the list.
147
+ If you also accept drops, this will allow the user to drop items into
148
+ specific points in the list. Otherwise items will be added to the end.
149
+ */
150
+ canReorderContent: false,
151
+
152
+ /** @private */
153
+ canReorderContentBindingDefault: SC.Binding.Bool,
154
+
155
+ /**
156
+ Allow the user to delete items using the delete key
157
+
158
+ If true the user will be allowed to delete selected items using the delete
159
+ key. Otherwise deletes will not be permitted.
160
+ */
161
+ canDeleteContent: false,
162
+
163
+ /** @private */
164
+ canDeleteContentBindingDefault: SC.Binding.Bool,
165
+
166
+ /**
167
+ Accept drops for data other than reordering.
168
+
169
+ Setting this property to return true when the view is instantiated will cause
170
+ it to be registered as a drop target, activating the other drop machinery.
171
+ */
172
+ isDropTarget: false,
173
+
174
+ /**
175
+ Use toggle selection instead of normal click behavior.
176
+
177
+ If set to true, then selection will use a toggle instead of the normal
178
+ click behavior. Command modifiers will be ignored and instead clicking
179
+ once will enable an item and clicking on it again will disable it.
180
+
181
+ @type Boolean
182
+ */
183
+ useToggleSelection: false,
184
+
185
+ /**
186
+ Delete views when the content object is removed from the content array.
187
+
188
+ Whenever you remove a content object from the content array, the collection view
189
+ will automatically remove the corresponding item view from the display. If this
190
+ property is set to true, that view will be subsequently deleted as well.
191
+
192
+ If you set this property to false, then the collection view will store these
193
+ unused views in a cache and reuse them later should the content object they
194
+ represent reappear in the content array.
195
+
196
+ In general, you want to leave this property to true in order to keep your
197
+ memory usage under control. However, if you are rendering a collection of
198
+ views that will change often, adding and removing the same content objects,
199
+ then your collection view will be much faster if you set this to false.
200
+
201
+ Most of the time, you will set this to false if you are rendering a collection
202
+ of objects that may be filtered based on search criteria and you want to update
203
+ the display very quickly.
204
+
205
+ @type Boolean
206
+ */
207
+ flushUnusedViews: true,
208
+
209
+ /**
210
+ Trigger the action method on a single click.
211
+
212
+ Normally, clicking on an item view in a collection will select the content
213
+ object and double clicking will trigger the action method on the collection
214
+ view.
215
+
216
+ If you set this property to true, then clicking on a view will both select it
217
+ (if isSelected is true) and trigger the action method.
218
+
219
+ Use this if you are using the collection view as a menu of items.
220
+
221
+ @type {Boolean}
222
+ */
223
+ actOnSelect: false,
224
+
225
+ /**
226
+ Property key to use to group objects.
227
+
228
+ If groupBy is set to a non-null value, then the collection view will
229
+ automatically display item views in groups based on the value of the
230
+ passed property key. The exampleGroupView will be used to display the
231
+ items in groups.
232
+
233
+ If this property is set, you MUST ensure the items in the content array are
234
+ already sorted by the group key. Otherwise item view groups might appear more
235
+ than once.
236
+
237
+ @type {String}
238
+ */
239
+ groupBy: null,
240
+
241
+ /**
242
+ The view class to use when creating new item views.
243
+
244
+ The collection view will automatically create an instance of the view class
245
+ you set here for each item in its content array. You should provide your own
246
+ subclass for this property to display the type of content you want.
247
+
248
+ For best results, the view you set here should understand the following
249
+ properties:
250
+
251
+ {{{
252
+ content: The content object from the content array your view should display
253
+ isEnabled: True if the view should appear enabled
254
+ isSelected: True if the view should appear selected
255
+ }}}
256
+
257
+ In general you do not want your child views to actually respond to mouse and
258
+ keyboard events themselves. It is better to let the collection view do that.
259
+
260
+ If you do implement your own event handlers such as mouseDown or mouseUp, you
261
+ should be sure to actually call the same method on the collection view to
262
+ give it the chance to perform its own selection housekeeping.
263
+
264
+ @type {SC.View}
265
+ */
266
+ exampleView: SC.View,
267
+
268
+ /**
269
+ The view class to use when displaying item views in groups.
270
+
271
+ If the groupBy property is not null, then the collection view will create
272
+ an instance of this view class with the item views that belong to the group
273
+ as child nodes for each distinct group value it encounters.
274
+
275
+ Your groupView should have two outlets:
276
+
277
+ {{{
278
+ labelView: The view to display the group label. The group value will be set
279
+ as the content property of this view.
280
+
281
+ itemView: This is the view the item views will be added to as children to
282
+ this view.
283
+ }}}
284
+
285
+ If groupBy is null, then this property will not be used. The default class
286
+ provided here simply displays the group value in an H1 tag.
287
+
288
+ @type {SC.View}
289
+ */
290
+ exampleGroupView: SC.View.extend({
291
+ emptyElement: '<div><h1></h1><div class="well"></div></div>',
292
+ outlets: ['labelView','itemView'],
293
+ labelView: SC.LabelView.outletFor('h1?'),
294
+ itemView: SC.View.outletFor('.well?')
295
+ }),
296
+
297
+ /**
298
+ Invoked when the user double clicks on an item (or single clicks of actOnSelect is true)
299
+
300
+ Set this to the name of the action you want to send down the
301
+ responder chain when the user double clicks on an item (or single clicks if
302
+ actOnSelect is true). You can optionally specify a specific target as well
303
+ using the target property.
304
+
305
+ If you do not specify an action, then the collection view will also try to
306
+ invoke the action named on the target item view.
307
+
308
+ Older versions of SproutCore expected the action property to contain an actual
309
+ function that would be run. This format is still supported but is deprecated
310
+ for future use. You should generally use the responder chain to handle your
311
+ action for you.
312
+
313
+ @type {String}
314
+ */
315
+ action: null,
316
+
317
+ /**
318
+ Optional target to send the action to when the user double clicks.
319
+
320
+ If you set the action property to the name of an action, you can optionally
321
+ specify the target object you want the action to be sent to. This can be
322
+ either an actual object or a property path that will resolve to an object at
323
+ the time that the action is invoked.
324
+
325
+ This property is ignored if you use the deprecated approach of making the
326
+ action property a function.
327
+
328
+ @type {String|Object}
329
+ */
330
+ target: null,
331
+
332
+ /**
333
+ Set to true whenever the content changes and remains true until
334
+ the content has been rerendered.
335
+
336
+ You can also set this to true yourself to be notified when it is completed.
337
+ */
338
+ isDirty: true,
339
+
340
+ /**
341
+ The maximum time the collection view will spend updating its
342
+ views before it takes a break from the update.
343
+
344
+ This keeps your browser from freezing or displaying a slow script
345
+ warning while the render code works. Number is in msec.
346
+
347
+ Future versions of CollectionView may ignore this property as newer
348
+ rendering techniques make it no longer necessary.
349
+ */
350
+ maxRenderTime: 0,
351
+
352
+ /**
353
+ Property returns all of the item views, regardless of group view.
354
+
355
+ @returns {Array} the item views.
356
+ */
357
+ itemViews: function() {
358
+ var ret = [] ;
359
+ if (!this._itemViews) return ret ;
360
+ for(var key in this._itemViews) {
361
+ if (this._itemViews.hasOwnProperty(key)) ret.push(this._itemViews[key]);
362
+ }
363
+ return ret;
364
+ }.property(),
365
+
366
+ /**
367
+ Returns true if the passed view belongs to the collection.
368
+
369
+ This method uses the internal hash of item views and works even if
370
+ your items are stored in group views. This is faster than searching
371
+ the child view hierarchy yourself.
372
+
373
+ @param {SC.View} view The view to search for.
374
+
375
+ @returns {Boolean} True if the view is an item view in the receiver.
376
+ */
377
+ hasItemView: function(view) {
378
+ if (!this._itemViews) this._itemViews = {};
379
+ return !!this._itemViews[SC.getGUID(view)];
380
+ },
381
+
382
+ // ......................................
383
+ // DRAG AND DROP SUPPORT
384
+ //
385
+
386
+ /**
387
+ The insertion orientation. This is used to determine which
388
+ dimension we should pay attention to when determining insertion point for
389
+ a mouse click.
390
+
391
+ {{{
392
+ SC.HORIZONTAL_ORIENTATION: look at the X dimension only
393
+ SC.VERTICAL_ORIENTATION: look at the Y dimension only
394
+ }}}
395
+ */
396
+ insertionOrientation: SC.HORIZONTAL_ORIENTATION,
397
+
398
+ /**
399
+ Get the preferred insertion point for the given location, including
400
+ an insertion preference of before or after the named index.
401
+
402
+ The default implementation will loop through the item views looking for
403
+ the first view to "switch sides" in the orientation you specify.
404
+ */
405
+ insertionIndexForLocation: function(loc) {
406
+ var content = this.get('content') ;
407
+ var f, itemView, curSide, lastSide = null ;
408
+ var orient = this.get('insertionOrientation') ;
409
+ var ret= null ;
410
+ for(var idx=0; ((ret == null) && (idx<content.length)); idx++) {
411
+ itemView = this.itemViewForContent(content.objectAt(idx));
412
+ f = this.convertFrameFromView(itemView.get('frame'), itemView) ;
413
+
414
+ // if we are a horizontal orientation, look for the first item that
415
+ // will "switch sides" on the x path an the maxY is greater than Y.
416
+ // This assumes you will flow top to bottom, but it should work if you
417
+ // flow LTR or RTL.
418
+ if (orient == SC.HORIZONTAL_ORIENTATION) {
419
+ if (SC.maxY(f) > loc.y) {
420
+ curSide = (SC.maxX(f) < loc.x) ? -1 : 1 ;
421
+ } else curSide = null ;
422
+
423
+ // if we are a vertical orientation, look for the first item that
424
+ // will "swithc sides" on the y path and the maxX is greater than X.
425
+ // This assumes you will flow LTR, but it should work if you flow
426
+ // bottom to top or top to bottom.
427
+ } else {
428
+ if (SC.maxX(f) > loc.x) {
429
+ curSide = (SC.maxY(f) < loc.y) ? -1 : 1 ;
430
+ } else curSide = null ;
431
+ }
432
+
433
+ // if we "switched" sides then return this item view.
434
+ if (curSide !== null) {
435
+
436
+ // OK, we found an item view, while we have this data, decide if
437
+ // we should insert before or after the view
438
+ if ((lastSide !== null) && (curSide != lastSide)) {
439
+ ret = idx ;
440
+ if (orient == SC.HORIZONTAL_ORIENTATION) {
441
+ if (SC.midX(f) < loc.x) ret++ ;
442
+ } else {
443
+ if (SC.midY(f) < loc.y) ret++ ;
444
+ }
445
+ }
446
+ lastSide =curSide ;
447
+ }
448
+ }
449
+
450
+ // Handle some edge cases
451
+ if ((ret == null) || (ret < 0)) ret = 0 ;
452
+ if (ret > content.length) ret = content.length ;
453
+
454
+ // Done. Phew. Return.
455
+ return ret;
456
+ },
457
+
458
+ /**
459
+ Override to show the insertion point during a drag.
460
+
461
+ Called during a drag to show the insertion point. Passed value is the
462
+ item view that you should display the insertion point before. If the
463
+ passed value is null, then you should show the insertion point AFTER that
464
+ last item view returned by the itemViews property.
465
+
466
+ Once this method is called, you are guaranteed to also recieve a call to
467
+ hideInsertionPoint() at some point in the future.
468
+
469
+ The default implementation of this method does nothing.
470
+
471
+ @param {SC.View} itemView view the insertion point should appear directly before. If null, show insertion point at end.
472
+
473
+ @returns {void}
474
+ */
475
+ showInsertionPointBefore: function(itemView) {},
476
+
477
+ /**
478
+ Override to hide the insertion point when a drag ends.
479
+
480
+ Called during a drag to hide the insertion point. This will be called when the
481
+ user exits the view, cancels the drag or completes the drag. It will not be
482
+ called when the insertion point changes during a drag.
483
+
484
+ You should expect to receive one or more calls to showInsertionPointBefore()
485
+ during a drag followed by at least one call to this method at the end. Your
486
+ method should not raise an error if it is called more than once.
487
+
488
+ @returns {void}
489
+ */
490
+ hideInsertionPoint: function() {},
491
+
492
+ // handle mouse drags. If the canReorderContent is enabled, allow the
493
+ // user to start a reorder.
494
+ mouseDragged: function(ev) {
495
+ // Don't do anything unless the user has been dragging for 123msec
496
+ if ((Date.now() - this._mouseDownAt) < 123) return true ;
497
+
498
+ // OK, they must be serious, start a drag if possible.
499
+ // Also use this opportunity to clean up since mouseUp won't
500
+ // get called.
501
+ if (this.get('canReorderContent')) {
502
+ SC.Drag.start({
503
+ event: this._mouseDownEvent,
504
+ source: this,
505
+ dragView: this._mouseDownView,
506
+ ghost: NO,
507
+ slideBack: YES,
508
+ data: { "_mouseDownContent": this._mouseDownContent }
509
+ }) ;
510
+ this._cleanupMouseDown() ;
511
+ }
512
+ },
513
+
514
+ // Drop Source.
515
+ dragEntered: function(drag, evt) {
516
+ if ((drag.get('source') == this) && this.get('canReorderContent')) {
517
+ return SC.DRAG_MOVE ;
518
+ } else {
519
+ return SC.DRAG_NONE ;
520
+ }
521
+ },
522
+
523
+ // If reordering is allowed, then show insertion point
524
+ dragUpdated: function(drag, evt) {
525
+ if (this.get('canReorderContent')) {
526
+ var loc = drag.get('location') ;
527
+ loc = this.convertFrameFromView(loc, null) ;
528
+ var ret = this.insertionIndexForLocation(loc) ;
529
+ if (this._lastInsertionIndex != ret) {
530
+ console.log("--itemView: %@".fmt(ret)) ;
531
+ var itemView = this.itemViewForContent(this.get('content').objectAt(ret));
532
+ this.showInsertionPointBefore(itemView) ;
533
+ }
534
+ this._lastInsertionIndex = ret ;
535
+
536
+ }
537
+ return SC.DRAG_MOVE;
538
+ },
539
+
540
+ dragExited: function() {
541
+ this.hideInsertionPoint() ;
542
+ this._lastInsertionIndex = null ;
543
+ },
544
+
545
+ dragEnded: function() {
546
+ this.hideInsertionPoint() ;
547
+ this._lastInsertionIndex = null ;
548
+ },
549
+
550
+ prepareForDragOperation: function(op, drag) {
551
+ return SC.DRAG_ANY;
552
+ },
553
+
554
+ performDragOperation: function(op, drag) {
555
+
556
+ var loc = drag.get('location') ;
557
+ loc = this.convertFrameFromView(loc, null) ;
558
+
559
+ // if op is MOVE or COPY, add item to view.
560
+ var obj = drag.dataForType('_mouseDownContent') ;
561
+ if (obj && (op == SC.DRAG_MOVE)) {
562
+
563
+ // find the index to for the new insertion
564
+ var idx = this.insertionIndexForLocation(loc) ;
565
+
566
+ var content = this.get('content') ;
567
+ content.beginPropertyChanges(); // suspend notifications
568
+
569
+ // find the old index and remove it.
570
+ var old = content.indexOf(obj) ;
571
+ if (old >= 0) content.removeAt(old) ;
572
+ if ((old >= 0) && (old <= idx)) idx--; //adjust idx
573
+
574
+ // now insert object at new location
575
+ content.insertAt(idx, obj) ;
576
+ content.endPropertyChanges(); // restart notifications
577
+ }
578
+
579
+ return SC.DRAG_MOVE;
580
+ },
581
+
582
+ concludeDragOperation: function(op, drag) {
583
+ this.hideInsertionPoint() ;
584
+ this._lastInsertionIndex = null ;
585
+ },
586
+
587
+
588
+
589
+ // ......................................
590
+ // GENERATING CHILDREN
591
+ //
592
+
593
+ /**
594
+ Ensure that the displayed item views match the current set of content objects.
595
+
596
+ This is the main entry point to the Collection View layout system. It
597
+ compares the current set of item views to the content objects, adding, removing,
598
+ and reordering views as necessary to bring them in sync with the set of content
599
+ objects.
600
+
601
+ Once it has finished running, this method will also call your layoutChildViewsFor()
602
+ method if you have implemented it.
603
+
604
+ This method is called automatically whenever the content array changes. You will
605
+ not usually need to call it yourself. If you want to refresh the item views,
606
+ called rebuildChildren() instead.
607
+ */
608
+ updateChildren: function()
609
+ {
610
+ var el = this.containerElement || this.rootElement;
611
+
612
+ // initial setup
613
+ if (this._firstUpdate)
614
+ {
615
+ el.innerHTML = '';
616
+ this._firstUpdate = false;
617
+ }
618
+
619
+ // before removing from parent, make sure we have retrieved the frame
620
+ // size so that layout can happen.
621
+ this.cacheFrame();
622
+
623
+ // viewsForContent will hold all the item views we currently have rendered
624
+ // keyed by content._guid. We use this to quickly determine if a view can
625
+ // be reused.
626
+ if (!this._viewsForContent) this._viewsForContent = {};
627
+
628
+ // handle grouped items. If items are grouped, then each childNode is
629
+ // a group, which contains a label and a div with the items themselves.
630
+ var groupBy = this.get('groupBy');
631
+ var content = this.get('content') || [];
632
+
633
+ // If the number of childViews differs from the content size, remove from
634
+ // DOM to improve performance while updating.
635
+ this._cachedParent = null;
636
+ this._cachedSibling = null;
637
+ if (content.get('length') != this.childNodes.get('length'))
638
+ {
639
+ this._cachedParent = el.parentNode;
640
+ this._cachedSibling = el.nextSibling;
641
+ if (this._cachedParent) {
642
+ this._cachedParent.removeChild(el);
643
+ } else {
644
+ //debugger;
645
+ }
646
+ }
647
+
648
+ // this code path will render the collection of groups. This creates
649
+ // group views for each distinct group it encounters and then has it
650
+ // render child views in each item.
651
+ if (groupBy)
652
+ {
653
+ var loc = 0;
654
+ var group = this.firstChild;
655
+ while (group || (loc < content.get('length')))
656
+ {
657
+ var groupValue = (loc < content.get('length')) ? content.objectAt(loc).get(groupBy) : null;
658
+
659
+ // we are out of content, just remove any remaining groups (including
660
+ // child nodes)
661
+ if (loc >= content.get('length')) {
662
+ if (group) {
663
+ // this will clear out the item views in the group.
664
+ loc = this.updateChildrenInGroup(group.itemView, content, loc, groupBy, null);
665
+ // now remove the group.
666
+ var prev = group.previousSibling ;
667
+ this.removeChild(group) ;
668
+ group = prev ;
669
+ }
670
+
671
+ // otherwise, make sure the current group matches the next group. If
672
+ // it doesn't, then add a new group.
673
+ } else if (!group || (group.get('groupValue') != groupValue)) {
674
+
675
+ // create group view.
676
+ var newGroup = this.exampleGroupView.viewFor(null) ;
677
+ newGroup.owner = this ;
678
+ newGroup.set('groupValue',groupValue) ;
679
+
680
+ // add group label view.
681
+ if (newGroup.labelView) newGroup.labelView.set('content',groupValue);
682
+
683
+ // add item views to group.
684
+ loc = this.updateChildrenInGroup(newGroup.itemView,content,loc,
685
+ groupBy, groupValue) ;
686
+
687
+ // add the new group at this point
688
+ this.insertBefore(newGroup,group) ;
689
+ group = newGroup ;
690
+
691
+ // otherwise, if the current group does match the next group, just
692
+ // update its child nodes.
693
+ } else {
694
+ loc = this.updateChildrenInGroup(group.itemView,content,loc,
695
+ groupBy, groupValue) ;
696
+ }
697
+
698
+ // go to the next group. group will be nil if the first group was
699
+ // removed.
700
+ group = (group) ? group.nextSibling : this.firstChild ;
701
+ }
702
+
703
+ // grouping is not turned on.
704
+ } else {
705
+ this.updateChildrenInGroup(this, content, 0, null, null) ;
706
+ }
707
+
708
+ // Add back into DOM if optimization was used.
709
+ if (this._cachedParent) {
710
+ this._cachedParent.insertBefore(el,this._cachedSibling) ;
711
+ }
712
+
713
+ this.updateSelectionStates() ;
714
+ this.flushFrameCache() ;
715
+ this.set('isDirty',false);
716
+ },
717
+
718
+ /**
719
+ @private
720
+
721
+ Step through the child nodes in the parent to match them to
722
+ the content array, starting at the passed location. It will go until it
723
+ runs out of content objects or until the content no longer belong to the
724
+ group indicated.
725
+ */
726
+ updateChildrenInGroup: function(parent,content,loc,groupBy,groupValue) {
727
+ // cacheing content.get('length') for optimization.
728
+ var contentCount = content.get('length');
729
+ var child = parent.firstChild;
730
+ var inGroup = true ;
731
+
732
+ if (!this._itemViews) this._itemViews = {};
733
+ var itemViewsDidChange = false;
734
+
735
+ this.updateComputedViewHeight(parent);
736
+
737
+ // if we aren't rendering groups, then this can expire.
738
+ var expired = false;
739
+ var canExpire = !groupBy && loc == 0 ;
740
+ if (canExpire)
741
+ {
742
+ loc = this._lastRenderLoc ;
743
+ child = this._lastRenderChild || child;
744
+ this._resetRenderClock();
745
+ };
746
+
747
+ var firstChild = null ;
748
+
749
+ while (child || (inGroup && (loc < contentCount) && !expired)) {
750
+
751
+ // get the content object.
752
+ var cur = (inGroup && (loc < contentCount)) ? content.objectAt(loc) : null;
753
+
754
+ // verify the new cur is still in the group.
755
+ if (cur && groupBy && (cur.get(groupBy) != groupValue))
756
+ {
757
+ inGroup = false;
758
+ cur = null;
759
+ }
760
+
761
+ // we are out of content for this group, remaining children simply need
762
+ // to be removed.
763
+ if (cur == null) {
764
+ if (child) {
765
+ if (this.flushUnusedViews) {
766
+ var viewContent = child.get('content') ;
767
+ if (viewContent) delete this._viewsForContent[SC.getGUID(viewContent)];
768
+ child.set('content',null) ;
769
+ }
770
+ var prev = child.previousSibling ;
771
+ parent.removeChild(child) ;
772
+ if (this._itemViews[SC.getGUID(child)]) {
773
+ itemViewsDidChange = true ;
774
+ delete this._itemViews[SC.getGUID(child)];
775
+ }
776
+
777
+ child = prev;
778
+ }
779
+
780
+ // otherwise, make sure the current child matches the content object.
781
+ // if it doesn't, get the right view (or create it) and insert it here.
782
+ } else if (!child || (child.get('content') != cur)) {
783
+
784
+ // find the correct view. If it doesn't exist, create it.
785
+ var newChild = this._viewsForContent[SC.getGUID(cur)] ;
786
+ if (!newChild) {
787
+ newChild = this.exampleView.viewFor(null) ;
788
+ newChild.owner = this ;
789
+ newChild._isChildView = true ;
790
+ newChild.set('content',cur) ;
791
+ this._viewsForContent[SC.getGUID(cur)] = newChild ;
792
+ }
793
+
794
+ // add the view at this point in the hierarchy and make the new child
795
+ // the current child.
796
+ parent.insertBefore(newChild,child);
797
+ this._itemViews[SC.getGUID(newChild)] = newChild;
798
+ itemViewsDidChange = true;
799
+ child = newChild;
800
+ }
801
+
802
+ // go to next child and content object
803
+ // child would only be nil if the current child was first and was
804
+ // removed
805
+ if (!firstChild) firstChild = child;
806
+ child = (child) ? child.nextSibling : ((inGroup) ? parent.firstChild : null);
807
+
808
+ // go to the next loc only if cur was used last time.
809
+ if (cur) loc++;
810
+
811
+ expired = this._renderExpired();
812
+ }
813
+
814
+
815
+ // maybe save the current render loc and reschedule.
816
+ if (expired && (loc < contentCount))
817
+ {
818
+ this._lastRenderLoc = loc ;
819
+ this._lastRenderChild = child ;
820
+ setTimeout(this.updateChildren.bind(this),1) ; // do more later.
821
+ }
822
+ else
823
+ {
824
+ this._resetExpiredRender();
825
+ }
826
+
827
+ // now let the collection view layout the views that changed (if
828
+ // it is implemented.)
829
+ if (this.layoutChildViewsFor)
830
+ {
831
+ var el = this.containerElement || this.rootElement;
832
+ if (this._cachedParent) {
833
+ this._cachedParent.insertBefore(el,this._cachedSibling);
834
+ }
835
+ this.layoutChildViewsFor(parent, firstChild);
836
+ if (this._cachedParent) {
837
+ this._cachedParent.removeChild(el);
838
+ }
839
+ }
840
+
841
+ // notify itemViews change if applicable.
842
+ if (itemViewsDidChange) this.propertyDidChange('itemViews');
843
+
844
+ return loc;
845
+ },
846
+
847
+
848
+ /**
849
+ Returns the itemView that represents the passed content object.
850
+
851
+ If no item view is currently rendered for the object, this method will
852
+ return null.
853
+
854
+ @param {Object} obj The content object. Should be a member of the content array.
855
+ @returns {SC.View} The item view for this object or null if no match could be found.
856
+ */
857
+ itemViewForContent: function( obj )
858
+ {
859
+ return this._viewsForContent[SC.getGUID(obj)];
860
+ },
861
+
862
+ /**
863
+ Rebuild all the child item views in the collection view.
864
+
865
+ This will remove all the child views from the collection view and rebuild them
866
+ from scratch. This method is generally expensive, but if you have made a
867
+ substantial number of changes to the content array and need to bring everything
868
+ up to date, this is the best way to do it.
869
+
870
+ In general the collection view will automatically keep the item views in sync
871
+ with the content objects for you. You should not need to call this method
872
+ very often.
873
+
874
+ @returns {void}
875
+ */
876
+ rebuildChildren: function() {
877
+ this.clear();
878
+ this._viewsForContent = {};
879
+ this._resetExpiredRender();
880
+ this.updateChildren();
881
+ },
882
+
883
+ /**
884
+ Update the selection state for the item views to reflect the selection array.
885
+
886
+ This will update the isSelected property of all item views so that only those
887
+ representing content objects found in the selection array are selected.
888
+
889
+ This method is called automatically whenever your content or selection properties
890
+ changed. You should not need to call or override it often.
891
+ */
892
+ updateSelectionStates: function() {
893
+ if (!this._itemViews) return ;
894
+ var selection = this.get('selection') || [];
895
+
896
+ // First, for efficiency, turn the selection into a hash by GUID. This
897
+ // way, we'll only have to perform a linear search over the children.
898
+ var selectionHash = {};
899
+ var numberOfSelectedItems = selection.get('length');
900
+ for( var i = 0; i < numberOfSelectedItems; i++ ) {
901
+ var item = selection.objectAt(i);
902
+ selectionHash[SC.getGUID(item)] = true;
903
+ }
904
+
905
+ for(var key in this._itemViews) {
906
+ if (!this._itemViews.hasOwnProperty(key)) continue ;
907
+ var child = this._itemViews[key] ;
908
+ var content = (child.get) ? child.get('content') : null;
909
+ var guid = (content) ? SC.getGUID(content) : null;
910
+
911
+ if( !guid ) continue;
912
+ var childIsSelected = selectionHash[guid] ? true : false;
913
+
914
+ // If the child's state has changed from before, set it to the new
915
+ // state. Otherwise, don't bother setting the state to the same value
916
+ // it used to have.
917
+ if( childIsSelected != child.get('isSelected') ) {
918
+ if (child.set) child.set('isSelected', childIsSelected);
919
+ }
920
+ }
921
+ },
922
+
923
+ // layoutChildViewsFor: function(parentView, startingView) { return false; },
924
+
925
+ resizeChildrenWithOldSize: function(oldSize) {
926
+ if (this.layoutChildViewsFor && (this.layoutChildViewsFor(this, null))) {
927
+ this.updateComputedViewHeight(this) ;
928
+ } else {
929
+ arguments.callee.base.apply(this,arguments) ;
930
+ }
931
+ },
932
+
933
+ _firstUpdate: true,
934
+
935
+ _lastRenderLoc: 0,
936
+ _renderStart: null,
937
+ _resetRenderClock: function() { this._renderStart = new Date().getTime(); },
938
+
939
+ _resetExpiredRender: function() {
940
+ this._lastRenderLoc = 0; this._lastRenderChild = null;
941
+ },
942
+
943
+ _renderExpired: function() {
944
+ var max = this.maxRenderTime ;
945
+ if ((this._renderStart == null) || (max == 0)) return false ;
946
+ return ((new Date().getTime()) - this._renderStart) > max ;
947
+ },
948
+
949
+ /**
950
+ Override to return the range of items to render for a given frame.
951
+
952
+ The range you return will be used to limit the number of actual views that are
953
+ created for the collection view. The passed frame is relative to the total frame
954
+ of the groupView.
955
+
956
+ You should override this method if you want to support incremental rendering.
957
+ The default implementation does nothing.
958
+
959
+ @param {SC.View} groupView The group view the requested items belong to. If
960
+ grouping is not used, this will always be null.
961
+
962
+ @param {Frame} frame The frame you should use to determine the range.
963
+
964
+ @returns {Range} A hash that indicates the range of content objects to render. ({ start: X, length: Y })
965
+ */
966
+ itemRangeInFrame: function(groupView, frame) { return null; },
967
+
968
+ /**
969
+ Override to return a computed height of the collection.
970
+
971
+ This will be used to set a dynamic scrollbar height if you support incremental
972
+ rendering. The default implementation does nothing.
973
+
974
+ @param {SC.View} groupView The group view this request relates to. If grouping is
975
+ turned off, this parameter will be null.
976
+
977
+ @returns {Number} The view height in pixels.
978
+ */
979
+ computedViewHeight: function(groupView) { return -1; },
980
+
981
+ // This will set the collection height.
982
+ updateComputedViewHeight: function(groupView) {
983
+ var height = this.computedViewHeight(groupView) ;
984
+ if (height <= 0) {
985
+ if (groupView._heightView) {
986
+ groupView.rootElement.removeChild(this._heightView) ;
987
+ groupView._heightView = null ;
988
+ }
989
+ } else {
990
+ if (!groupView._heightView) {
991
+ groupView._heightView = document.createElement('div') ;
992
+ groupView.rootElement.appendChild(groupView._heightView) ;
993
+ Element.setStyle(groupView._heightView,{
994
+ position: 'absolute', left: '0px', display: 'block',
995
+ width: '1px', height: '1px'
996
+ }) ;
997
+ }
998
+
999
+ if (height != groupView._lastComputedHeight) {
1000
+ Element.setStyle(groupView._heightView,{ top: height + 'px' }) ;
1001
+ groupView._lastComputedHeight = height ;
1002
+ }
1003
+ }
1004
+ },
1005
+
1006
+ // ......................................
1007
+ // SELECTION
1008
+ //
1009
+
1010
+ selectPreviousItem: function()
1011
+ {
1012
+ var extend = arguments[0] || false;
1013
+ var content = this.get('content');
1014
+ var selected = this.get('selection').first();
1015
+ var indexOfFirst = 0;
1016
+ var indexOfSelected = content.indexOf( selected );
1017
+ var indexOfPrevious = indexOfSelected - 1;
1018
+ // error check to make sure we're not out of bounds...
1019
+ if ( indexOfPrevious < indexOfFirst ) indexOfPrevious = indexOfFirst;
1020
+ // ensure that the item is visible
1021
+ this.scrollToItemRecord(content.objectAt(indexOfPrevious));
1022
+ // set the selection
1023
+ this.selectItems(content.objectAt(indexOfPrevious), extend);
1024
+ },
1025
+
1026
+ selectNextItem: function()
1027
+ {
1028
+ var extend = arguments[0] || false;
1029
+ var content = this.get('content');
1030
+ var selected = this.get('selection').last();
1031
+ var indexOfLast = (content.get('length') - 1) || 0;
1032
+ var indexOfSelected = content.indexOf( selected );
1033
+ var indexOfNext = indexOfSelected + 1;
1034
+ // error check to make sure we're not out of bounds...
1035
+ if ( indexOfNext > indexOfLast ) indexOfNext = indexOfLast;
1036
+ // ensure that the item is visible
1037
+ this.scrollToItemRecord(content.objectAt(indexOfNext));
1038
+ // set the selection
1039
+ this.selectItems(content.objectAt(indexOfNext), extend);
1040
+ },
1041
+
1042
+ /**
1043
+ * Scroll the rootElement (if needed) to ensure that the item is visible.
1044
+ * @param {SC.Record} record The record to scroll to
1045
+ * @returns {void}
1046
+ */
1047
+ scrollToItemRecord: function( record )
1048
+ {
1049
+ this.scrollToItemView( this.itemViewForContent(record) );
1050
+ },
1051
+ /**
1052
+ * Scroll the rootElement (if needed) to ensure that the item is visible.
1053
+ * @param {SC.View} view The item view to scroll to
1054
+ * @returns {void}
1055
+ */
1056
+ scrollToItemView: function( view )
1057
+ {
1058
+ var visible = Element.extend(this.get('rootElement'));
1059
+ var visibleTop = visible.scrollTop;
1060
+ var visibleBottom = visibleTop + visible.getHeight();
1061
+
1062
+ visible.makePositioned();
1063
+
1064
+ var item = Element.extend(view.get('rootElement'));
1065
+ var itemTop = item.positionedOffset().top;
1066
+ var itemBottom = itemTop + item.getHeight();
1067
+
1068
+ visible.undoPositioned();
1069
+
1070
+ if (itemTop < visibleTop) {
1071
+ visible.scrollTop = itemTop;
1072
+ }
1073
+ if (itemBottom > visibleBottom) {
1074
+ visible.scrollTop += (itemBottom - visibleBottom);
1075
+ }
1076
+ },
1077
+
1078
+ selectItems: function(items, extendSelection) {
1079
+ var base = (extendSelection) ? this.get('selection') : [] ;
1080
+ var sel = [items].concat(base).flatten().uniq() ;
1081
+ this.set('selection',sel) ;
1082
+ },
1083
+
1084
+ deselectItems: function(items) {
1085
+ items = [items].flatten() ;
1086
+ var base = this.get('selection') || [] ;
1087
+ var sel = base.map(function(i) { return (items.include(i)) ? null : i; });
1088
+ sel = sel.compact() ;
1089
+ this.set('selection',sel) ;
1090
+ },
1091
+
1092
+ // ......................................
1093
+ // EVENT HANDLING
1094
+ //
1095
+
1096
+ /**
1097
+ Find the item view underneath the passed mouse location.
1098
+
1099
+ The default implementation of this method simply searches each item view's
1100
+ frame to find one that includes the location. If you are doing your own
1101
+ layout, you may be able to perform this calculation more quickly. If so,
1102
+ consider overriding this method for better performance during drag operations.
1103
+
1104
+ @param {Point} loc The current mouse location in the coordinate of the
1105
+ collection view
1106
+
1107
+ @returns {SC.View} The item view under the collection
1108
+ */
1109
+ itemViewAtLocation: function(loc) {
1110
+ var content = this.get('content') ;
1111
+ var idx = content.length;
1112
+ while(--idx >= 0) {
1113
+ var itemView = this.itemViewForContent(content.objectAt(idx));
1114
+ var frame = itemView.get('frame');
1115
+ if (SC.pointInRect(loc, frame)) return itemView ;
1116
+ }
1117
+ return null; // not in an itemView right now.
1118
+ },
1119
+
1120
+
1121
+
1122
+ /**
1123
+ Find the first content item view for the passed event.
1124
+
1125
+ This method will go up the view chain, starting with the view that was the target
1126
+ of the passed event, looking for a child item. This will become the view that
1127
+ is selected by the mouse event.
1128
+
1129
+ This method only works for mouseDown & mouseUp events. mouseMoved events do
1130
+ not have a target.
1131
+
1132
+ @param {Event} evt An event
1133
+
1134
+ */
1135
+ itemViewForEvent: function(evt)
1136
+ {
1137
+ var view = SC.window.firstViewForEvent( evt );
1138
+ // work up the view hierarchy to find a match...
1139
+ do {
1140
+ // item clicked was the ContainerView itself... i.e. the user clicked outside the child items
1141
+ // nothing to return...
1142
+ if ( view == this ) return null;
1143
+
1144
+ // sweet!... the view is not only in the collection, but it says we can hit it.
1145
+ // hit it and quit it...
1146
+ if ( this.hasItemView(view) && (!view.hitTest || view.hitTest(evt)) ) return view;
1147
+ } while ( view = view.get('parentNode') );
1148
+
1149
+ // nothing was found...
1150
+ return null;
1151
+ },
1152
+
1153
+
1154
+ didMouseDown: function(ev) {
1155
+ console.warn("didMouseDown will be removed from CollectionView in the near future. Use mouseDown instead");
1156
+ return this._mouseDown(ev, true);
1157
+ },
1158
+
1159
+ mouseDown: function(ev) {
1160
+ // older code might still use didMouseDown. Warn to give people some time to transition.
1161
+ if (this.didMouseDown != SC.CollectionView.prototype.didMouseDown) {
1162
+ return this.didMouseDown(ev) ;
1163
+ } else return this._mouseDown(ev);
1164
+ },
1165
+
1166
+ _mouseDown: function(ev) {
1167
+ // save for drag opt
1168
+ this._mouseDownEvent = ev ;
1169
+
1170
+ // Toggle selection only triggers on mouse up. Do nothing.
1171
+ if (this.useToggleSelection) return true;
1172
+
1173
+ // Make sure that saved mouseDown state is always reset in case we do
1174
+ // not get a paired mouseUp. (Only happens if subclass does not call us like it should)
1175
+ this._mouseDownAt = this._shouldDeselect = this._shouldReselect = this._refreshSelection = false;
1176
+
1177
+ var mouseDownView = this._mouseDownView = this.itemViewForEvent(ev);
1178
+ var mouseDownContent = this._mouseDownContent = (mouseDownView) ? mouseDownView.get('content') : null;
1179
+
1180
+ // recieved a mouseDown on the collection element, but not on one of the childItems... bail
1181
+ if (!mouseDownView) {
1182
+ if (this.get('allowDeselectAll')) this.selectItems([], false);
1183
+ return true ;
1184
+ }
1185
+
1186
+ // collection some basic setup info
1187
+ var selection = this.get('selection') || [];
1188
+ var isSelected = selection.include(mouseDownContent);
1189
+ var modifierKeyPressed = ev.ctrlKey || ev.altKey || ev.metaKey;
1190
+ if (mouseDownView.checkboxView && (Event.element(ev) == el.checkboxView.rootElement)) {
1191
+ modifierKeyPressed = true ;
1192
+ }
1193
+
1194
+ this._mouseDownAt = Date.now();
1195
+
1196
+ // holding down a modifier key while clicking a selected item should deselect that item...
1197
+ // deselect and bail.
1198
+ if (modifierKeyPressed && isSelected) {
1199
+ this._shouldDeselect = mouseDownContent;
1200
+
1201
+ // if the shiftKey was pressed, then we want to extend the selection
1202
+ // from the last selected item
1203
+ } else if (ev.shiftKey && selection.get('length') > 0) {
1204
+ selection = this._findSelectionExtendedByShift(selection, mouseDownContent) ;
1205
+ this.selectItems(selection) ;
1206
+
1207
+ // If no modifier key was pressed, then clicking on the selected item should clear
1208
+ // the selection and reselect only the clicked on item.
1209
+ } else if (!modifierKeyPressed && isSelected) {
1210
+ this._shouldReselect = mouseDownContent;
1211
+
1212
+ // Otherwise, simply select the clicked on item, adding it to the current
1213
+ // selection if a modifier key was pressed.
1214
+ } else {
1215
+ this.selectItems(mouseDownContent, modifierKeyPressed);
1216
+ }
1217
+
1218
+ // saved for extend by shift ops.
1219
+ this._previousMouseDownContent = mouseDownContent;
1220
+ return true;
1221
+ },
1222
+
1223
+ // invoked when the user releases the mouse. based on the information saved
1224
+ // during mouse down, we decide what to do.
1225
+ didMouseUp: function(ev) {
1226
+ console.warn("didMouseUp will be removed from CollectionView in the near future. Use mouseUp instead");
1227
+ return this._mouseUp(ev);
1228
+ },
1229
+
1230
+ mouseUp: function(ev) {
1231
+ if (this.didMouseUp != SC.CollectionView.prototype.didMouseUp) {
1232
+ return this.didMouseUp(ev) ;
1233
+ } else return this._mouseUp(ev) ;
1234
+ },
1235
+
1236
+ _mouseUp: function(ev) {
1237
+
1238
+ console.info('_mouseUp!');
1239
+
1240
+ var canAct = this.get('actOnSelect') ;
1241
+ var view = this.itemViewForEvent(ev) ;
1242
+
1243
+ if (this.useToggleSelection) {
1244
+ if (!view) return ; // do nothing when clicked outside of elements
1245
+
1246
+ // determine if item is selected. If so, then go on.
1247
+ var selection = this.get('selection') || [] ;
1248
+ var content = (view) ? view.get('content') : null ;
1249
+ var isSelected = selection.include(content) ;
1250
+ if (isSelected) {
1251
+ this.deselectItems([content]) ;
1252
+ } else this.selectItems([content],true) ;
1253
+
1254
+ } else {
1255
+ if (this._shouldDeselect) this.deselectItems(this._shouldDeselect);
1256
+ if (this._shouldReselect) this.selectItems(this._shouldReselect,false) ;
1257
+
1258
+ // this is invoked if the user clicked on a checkbox. If this is not
1259
+ // done then the checkbox might not update properly.
1260
+ if (this._refreshSelection) {
1261
+ }
1262
+ this._cleanupMouseDown() ;
1263
+ }
1264
+
1265
+ this._mouseDownEvent = null ;
1266
+ if (canAct) this._action(ev, view) ;
1267
+
1268
+ return false; // bubble event to allow didDoubleClick to be called...
1269
+ },
1270
+
1271
+ _cleanupMouseDown: function() {
1272
+ this._mouseDownAt = this._shouldDeselect = this._shouldReselect = this._refreshSelection = false;
1273
+ this._mouseDownEvent = this._mouseDownContent = this._mouseDownView = null ;
1274
+ },
1275
+
1276
+ // this can be used to initiate a drag. Only drags 100ms after mouseDown
1277
+ // to avoid responding to clicks.
1278
+ mouseDidMove: function(ev) {
1279
+ console.warn("mouseDidMove will be removed from CollectionView in the near future. Use mouseMoved instead");
1280
+ return this._mouseMoved(ev) ;
1281
+ },
1282
+
1283
+ mouseMoved: function(ev) {
1284
+ if (this.mouseDidMove != SC.CollectionView.prototype.mouseDidMove) {
1285
+ return this.mouseDidMove(ev) ;
1286
+ } else return this._mouseMoved(ev) ;
1287
+ },
1288
+
1289
+ _mouseMoved: function(ev) {
1290
+ var view = this.itemViewForEvent(ev) ;
1291
+ // handle hover events.
1292
+ if(this._lastHoveredItem && ((view === null) || (view != this._lastHoveredItem)) && this._lastHoveredItem.didMouseOut) {
1293
+ this._lastHoveredItem.didMouseOut(ev);
1294
+ }
1295
+ this._lastHoveredItem = view ;
1296
+ if (view && view.didMouseOver) view.didMouseOver(ev) ;
1297
+ },
1298
+
1299
+ didMouseOut: function(ev) {
1300
+ console.warn("didMouseOut will be removed from CollectionView in the near future. Use mouseOut instead");
1301
+ return this._mouseOut(ev) ;
1302
+ },
1303
+
1304
+ mouseOut: function(ev) {
1305
+ if (this.didMouseOut != SC.CollectionView.prototype.didMouseOut) {
1306
+ return this.didMouseOut(ev) ;
1307
+ } else return this._mouseOut(ev) ;
1308
+ },
1309
+
1310
+ _mouseOut: function(ev) {
1311
+
1312
+ var view = this._lastHoveredItem ;
1313
+ this._lastHoveredItem = null ;
1314
+ if (view && view.didMouseOut) view.didMouseOut(ev) ;
1315
+ },
1316
+
1317
+ // invoked when the user double clicks on an item.
1318
+ didDoubleClick: function(ev) {
1319
+ console.warn("didDoubleClick will be removed from CollectionView in the near future. Use mouseOut instead");
1320
+ return this._doubleClick(ev) ;
1321
+ },
1322
+
1323
+ doubleClick: function(ev) {
1324
+ if (this.didDoubleClick != SC.CollectionView.prototype.didDoubleClick) {
1325
+ return this.didDoubleClick(ev) ;
1326
+ } else return this._doubleClick(ev) ;
1327
+ },
1328
+
1329
+ _doubleClick: function(ev) {
1330
+ console.info('_doubleClick!') ;
1331
+ var view = this.itemViewForEvent(ev) ;
1332
+ if (view) {
1333
+ this._action(view, ev) ;
1334
+ return true ;
1335
+ } else return false ;
1336
+ },
1337
+
1338
+ _findSelectionExtendedByShift: function(selection, mouseDownContent) {
1339
+ var collection = this.get('content');
1340
+
1341
+ // bounds of the collection...
1342
+ var collectionLowerBounds = 0;
1343
+ var collectionUpperBounds = (collection.get('length') - 1);
1344
+
1345
+ var selectionBeginIndex = collection.indexOf(selection.first());
1346
+ var selectionEndIndex = collection.indexOf(selection.last());
1347
+
1348
+ var previousMouseDownIndex = collection.indexOf(this._previousMouseDownContent);
1349
+ // _previousMouseDownContent couldn't be found... either it hasn't been set yet or the record has been deleted by the user
1350
+ // fall back to the first selected item.
1351
+ if (previousMouseDownIndex == -1) previousMouseDownIndex = selectionBeginIndex;
1352
+
1353
+
1354
+ var currentMouseDownIndex = collection.indexOf(mouseDownContent);
1355
+ // sanity check...
1356
+ if (currentMouseDownIndex == -1) throw "Unable to extend selection to an item that's not in the collection!";
1357
+
1358
+ // clicked before the current selection set... extend it's beginning...
1359
+ if (currentMouseDownIndex < selectionBeginIndex) selectionBeginIndex = currentMouseDownIndex;
1360
+ // clicked after the current selection set... extend it's ending...
1361
+ if (currentMouseDownIndex > selectionEndIndex) selectionEndIndex = currentMouseDownIndex;
1362
+ // clicked inside the selection set... need to determine where the las
1363
+ if ((currentMouseDownIndex > selectionBeginIndex) && (currentMouseDownIndex < selectionEndIndex))
1364
+ {
1365
+ if (currentMouseDownIndex == previousMouseDownIndex) {
1366
+ selectionBeginIndex = currentMouseDownIndex;
1367
+ selectionEndIndex = currentMouseDownIndex;
1368
+ } else if (currentMouseDownIndex > previousMouseDownIndex) {
1369
+ selectionBeginIndex = previousMouseDownIndex;
1370
+ selectionEndIndex = currentMouseDownIndex;
1371
+ } else if (currentMouseDownIndex < previousMouseDownIndex){
1372
+ selectionBeginIndex = currentMouseDownIndex;
1373
+ selectionEndIndex = previousMouseDownIndex;
1374
+ }
1375
+ }
1376
+ // slice doesn't include the last index passed... silly..
1377
+ selectionEndIndex++;
1378
+
1379
+ // shouldn't need to sanity check that the selection is in bounds due to the indexOf checks above...
1380
+ // I'll have faith that indexOf hasn't lied to me...
1381
+ return collection.slice(selectionBeginIndex, selectionEndIndex);
1382
+ },
1383
+
1384
+
1385
+
1386
+ // ......................................
1387
+ // INTERNAL
1388
+ //
1389
+
1390
+ init: function() {
1391
+ arguments.callee.base.apply(this, arguments) ;
1392
+ this._dropTargetObserver();
1393
+ },
1394
+
1395
+ // When canReorderContent changes, add or remove drop target as necessary.
1396
+ _dropTargetObserver: function() {
1397
+ var canDrop = this.get('canReorderContent') || this.get('isDropTarget') ;
1398
+ if (canDrop) {
1399
+ SC.Drag.addDropTarget(this) ;
1400
+ } else {
1401
+ SC.Drag.removeDropTarget(this) ;
1402
+ }
1403
+ }.observes('canReorderContent', 'isDropTarget'),
1404
+
1405
+ // Perform the action. Supports legacy behavior as well as newer style
1406
+ // action dispatch.
1407
+ _action: function(view, evt) {
1408
+
1409
+ var action = this.get('action');
1410
+ var target = this.get('target') || null;
1411
+ if (action) {
1412
+ // if the action is a function, just call it
1413
+ if ($type(action) == T_FUNCTION) return this.action(view, evt) ;
1414
+
1415
+ // otherwise, use the new sendAction style
1416
+ SC.app.sendAction(action, target, this) ;
1417
+
1418
+ // if no action is specified, then trigger the support action,
1419
+ // if supported.
1420
+ } else if (!view) {
1421
+ return ; // nothing to do
1422
+
1423
+ // if the target view has its own internal action handler,
1424
+ // trigger that.
1425
+ } else if ($type(view._action) == T_FUNCTION) {
1426
+ return view._action(evt) ;
1427
+
1428
+ // otherwise call the action method to support older styles.
1429
+ } else if ($type(view.action) == T_FUNCTION) {
1430
+ return view.action(evt) ;
1431
+ }
1432
+ },
1433
+
1434
+ _viewsForContent: null,
1435
+ _content: [], // cached for changes.
1436
+ propertyObserver: function(observing,target,key,value)
1437
+ {
1438
+ if (target == this)
1439
+ {
1440
+ // update children when content changes.
1441
+ if (key == 'content')
1442
+ {
1443
+ // cache the observer binding
1444
+ if (!this._boundObserver)
1445
+ {
1446
+ this._boundObserver = this._contentPropertyObserver.bind(this);
1447
+ }
1448
+
1449
+ // don't update the content unless it has changed. Note that if we
1450
+ // get a new empty array, that doesn't count as a change from a prev
1451
+ // empty array.
1452
+ var isEqual = (
1453
+ ((value && this._content) && (value.get('length') == 0) && (this._content.get('length') == 0)) ||
1454
+ SC.isEqual( value, this._content)
1455
+ );
1456
+
1457
+ // remove and re-add the observer for "[]" before changing the content property
1458
+ // this triggers a render of the child item views whenever the array is modified.
1459
+ if (this._content && this._content.removeObserver) this._content.removeObserver('[]', this._boundObserver);
1460
+ this._content = value;
1461
+ if (this._content && this._content.addObserver) this._content.addObserver('[]', this._boundObserver);
1462
+
1463
+ // only re-render the collection if the content was actually changed to a new value.
1464
+ if (!isEqual)
1465
+ {
1466
+ this._contentPropertyObserver(target,key,value);
1467
+ }
1468
+
1469
+ // update selection when selection changes. set this as a timeout so
1470
+ // that a render can finish first.
1471
+ }
1472
+ else if (key == 'selection')
1473
+ {
1474
+ if (!this._updatingSel)
1475
+ {
1476
+ this._updatingSel = this.invokeLater('_updateSelectionState',1);
1477
+ }
1478
+ }
1479
+ }
1480
+ },
1481
+
1482
+ // called on content change *and* content.[] change...
1483
+ _contentPropertyObserver: function(target,key,value)
1484
+ {
1485
+ if (!this._updating)
1486
+ {
1487
+ this._updating = true;
1488
+ this.set('isDirty',true);
1489
+ this._resetExpiredRender();
1490
+ this.updateChildren();
1491
+ this._updating = false;
1492
+ }
1493
+ },
1494
+
1495
+ _updateSelectionState: function() {
1496
+ try {
1497
+ this.updateSelectionStates() ;
1498
+ } catch(e) {
1499
+ console.log('exception while updating selection states in %@: %@'.format(this,e)) ;
1500
+ }
1501
+ this._updatingSel = null ;
1502
+ },
1503
+
1504
+ // ======================================================================
1505
+ // DEPRECATED APIS (Still available for compatibility)
1506
+
1507
+ /** @private
1508
+ If set to false, this method will prevent you from deselecting all of
1509
+ the items in your view. This is better implemented using a controller
1510
+ that prohibits empty selection.
1511
+ */
1512
+ allowDeselectAll: true,
1513
+
1514
+ /** @private */
1515
+ itemExistsInCollection: function( view ) { return this.hasItemView(view); },
1516
+
1517
+ /** @private */
1518
+ viewForContentRecord: function(rec) { return this.itemViewForContent(rec); }
1519
+
1520
+
1521
+ }) ;