sproutcore 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +269 -0
- data/README.txt +67 -0
- data/Rakefile +4 -0
- data/app_generators/sproutcore/USAGE +5 -0
- data/app_generators/sproutcore/sproutcore_generator.rb +66 -0
- data/app_generators/sproutcore/templates/README +77 -0
- data/app_generators/sproutcore/templates/environment.yml +4 -0
- data/bin/sc-build +145 -0
- data/bin/sc-gen +24 -0
- data/bin/sc-server +63 -0
- data/bin/sproutcore +21 -0
- data/clients/sc_docs/controllers/docs.js +118 -0
- data/clients/sc_docs/core.js +19 -0
- data/clients/sc_docs/english.lproj/body.css +159 -0
- data/clients/sc_docs/english.lproj/body.rhtml +33 -0
- data/clients/sc_docs/english.lproj/controls.css +0 -0
- data/clients/sc_docs/english.lproj/icons/small/next.png +0 -0
- data/clients/sc_docs/english.lproj/icons/small/reset.png +0 -0
- data/clients/sc_docs/english.lproj/images/gradients.png +0 -0
- data/clients/sc_docs/english.lproj/images/indicator.gif +0 -0
- data/clients/sc_docs/english.lproj/images/toolbar.png +0 -0
- data/clients/sc_docs/english.lproj/no_docs.rhtml +7 -0
- data/clients/sc_docs/english.lproj/strings.js +14 -0
- data/clients/sc_docs/english.lproj/warning.rhtml +6 -0
- data/clients/sc_docs/fixtures/doc.js +11 -0
- data/clients/sc_docs/main.js +21 -0
- data/clients/sc_docs/models/doc.js +9 -0
- data/clients/sc_docs/tests/controllers/docs.rhtml +21 -0
- data/clients/sc_docs/tests/models/doc.rhtml +21 -0
- data/clients/sc_docs/tests/views/doc_frame.rhtml +21 -0
- data/clients/sc_docs/tests/views/doc_label_view.rhtml +21 -0
- data/clients/sc_docs/views/doc_frame.js +33 -0
- data/clients/sc_docs/views/doc_label.js +20 -0
- data/clients/sc_test_runner/controllers/runner.js +175 -0
- data/clients/sc_test_runner/core.js +19 -0
- data/clients/sc_test_runner/english.lproj/body.css +151 -0
- data/clients/sc_test_runner/english.lproj/body.rhtml +35 -0
- data/clients/sc_test_runner/english.lproj/controls.css +0 -0
- data/clients/sc_test_runner/english.lproj/icons/small/next.png +0 -0
- data/clients/sc_test_runner/english.lproj/icons/small/reset.png +0 -0
- data/clients/sc_test_runner/english.lproj/images/gradients.png +0 -0
- data/clients/sc_test_runner/english.lproj/images/indicator.gif +0 -0
- data/clients/sc_test_runner/english.lproj/images/toolbar.png +0 -0
- data/clients/sc_test_runner/english.lproj/no_tests.rhtml +6 -0
- data/clients/sc_test_runner/english.lproj/strings.js +14 -0
- data/clients/sc_test_runner/english.lproj/warning.rhtml +6 -0
- data/clients/sc_test_runner/fixtures/test.js +12 -0
- data/clients/sc_test_runner/main.js +26 -0
- data/clients/sc_test_runner/models/test.js +11 -0
- data/clients/sc_test_runner/views/runner_frame.js +72 -0
- data/clients/sc_test_runner/views/test_label.js +20 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +17 -0
- data/environment.yml +9 -0
- data/frameworks/prototype/prototype.js +4186 -0
- data/frameworks/sproutcore/Core.js +378 -0
- data/frameworks/sproutcore/README +3 -0
- data/frameworks/sproutcore/controllers/array.js +236 -0
- data/frameworks/sproutcore/controllers/collection.js +305 -0
- data/frameworks/sproutcore/controllers/controller.js +323 -0
- data/frameworks/sproutcore/controllers/object.js +372 -0
- data/frameworks/sproutcore/drag/drag.js +549 -0
- data/frameworks/sproutcore/drag/drag_data_source.js +32 -0
- data/frameworks/sproutcore/drag/drag_source.js +64 -0
- data/frameworks/sproutcore/drag/drop_target.js +153 -0
- data/frameworks/sproutcore/english.lproj/blank.gif +0 -0
- data/frameworks/sproutcore/english.lproj/buttons.css +589 -0
- data/frameworks/sproutcore/english.lproj/buttons.png +0 -0
- data/frameworks/sproutcore/english.lproj/inline_text_editor.css +21 -0
- data/frameworks/sproutcore/english.lproj/menu.css +121 -0
- data/frameworks/sproutcore/english.lproj/panels/background-fat.jpg +0 -0
- data/frameworks/sproutcore/english.lproj/panels/background-thin.jpg +0 -0
- data/frameworks/sproutcore/english.lproj/panels/bottom-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/bottom-left-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/bottom-right-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/left-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/overlay.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/right-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/top-edge.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/top-left-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panels/top-right-corner.png +0 -0
- data/frameworks/sproutcore/english.lproj/panes.css +155 -0
- data/frameworks/sproutcore/english.lproj/picker.css +22 -0
- data/frameworks/sproutcore/english.lproj/strings.js +15 -0
- data/frameworks/sproutcore/english.lproj/tab.css +23 -0
- data/frameworks/sproutcore/english.lproj/tests.css +67 -0
- data/frameworks/sproutcore/english.lproj/theme.css +77 -0
- data/frameworks/sproutcore/foundation/animator.js +670 -0
- data/frameworks/sproutcore/foundation/application.js +199 -0
- data/frameworks/sproutcore/foundation/array.js +348 -0
- data/frameworks/sproutcore/foundation/benchmark.js +211 -0
- data/frameworks/sproutcore/foundation/binding.js +384 -0
- data/frameworks/sproutcore/foundation/date.js +357 -0
- data/frameworks/sproutcore/foundation/error.js +39 -0
- data/frameworks/sproutcore/foundation/input_manager.js +153 -0
- data/frameworks/sproutcore/foundation/json.js +296 -0
- data/frameworks/sproutcore/foundation/mock.js +42 -0
- data/frameworks/sproutcore/foundation/node_descriptor.js +56 -0
- data/frameworks/sproutcore/foundation/object.js +777 -0
- data/frameworks/sproutcore/foundation/observable.js +451 -0
- data/frameworks/sproutcore/foundation/page.js +63 -0
- data/frameworks/sproutcore/foundation/path_module.js +413 -0
- data/frameworks/sproutcore/foundation/responder.js +310 -0
- data/frameworks/sproutcore/foundation/routes.js +371 -0
- data/frameworks/sproutcore/foundation/run_loop.js +21 -0
- data/frameworks/sproutcore/foundation/server.js +491 -0
- data/frameworks/sproutcore/foundation/set.js +96 -0
- data/frameworks/sproutcore/foundation/string.js +149 -0
- data/frameworks/sproutcore/foundation/undo_manager.js +186 -0
- data/frameworks/sproutcore/foundation/unittest.js +622 -0
- data/frameworks/sproutcore/foundation/utils.js +61 -0
- data/frameworks/sproutcore/globals/panels.js +182 -0
- data/frameworks/sproutcore/globals/popups.js +60 -0
- data/frameworks/sproutcore/globals/window.js +381 -0
- data/frameworks/sproutcore/lib/index.rhtml +66 -0
- data/frameworks/sproutcore/models/collection.js +395 -0
- data/frameworks/sproutcore/models/record.js +622 -0
- data/frameworks/sproutcore/models/store.js +295 -0
- data/frameworks/sproutcore/panes/dialog.js +16 -0
- data/frameworks/sproutcore/panes/manager.js +164 -0
- data/frameworks/sproutcore/panes/menu.js +45 -0
- data/frameworks/sproutcore/panes/overlay.js +231 -0
- data/frameworks/sproutcore/panes/pane.js +90 -0
- data/frameworks/sproutcore/panes/panel.js +19 -0
- data/frameworks/sproutcore/panes/picker.js +45 -0
- data/frameworks/sproutcore/tests/controllers/array.rhtml +86 -0
- data/frameworks/sproutcore/tests/controllers/controller.rhtml +273 -0
- data/frameworks/sproutcore/tests/controllers/object.rhtml +327 -0
- data/frameworks/sproutcore/tests/foundation/application.rhtml +125 -0
- data/frameworks/sproutcore/tests/foundation/array.rhtml +221 -0
- data/frameworks/sproutcore/tests/foundation/object.rhtml +69 -0
- data/frameworks/sproutcore/tests/globals/window.rhtml +45 -0
- data/frameworks/sproutcore/tests/panes/pane.rhtml +88 -0
- data/frameworks/sproutcore/tests/views/collection.rhtml +137 -0
- data/frameworks/sproutcore/tests/views/popup_button.rhtml +115 -0
- data/frameworks/sproutcore/tests/views/text_field.rhtml +37 -0
- data/frameworks/sproutcore/validators/credit_card.js +92 -0
- data/frameworks/sproutcore/validators/date.js +36 -0
- data/frameworks/sproutcore/validators/email.js +29 -0
- data/frameworks/sproutcore/validators/not_empty.js +24 -0
- data/frameworks/sproutcore/validators/number.js +55 -0
- data/frameworks/sproutcore/validators/password.js +78 -0
- data/frameworks/sproutcore/validators/validator.js +304 -0
- data/frameworks/sproutcore/views/button.js +425 -0
- data/frameworks/sproutcore/views/checkbox_field.js +30 -0
- data/frameworks/sproutcore/views/collection.js +1521 -0
- data/frameworks/sproutcore/views/container.js +62 -0
- data/frameworks/sproutcore/views/error_explanation.js +45 -0
- data/frameworks/sproutcore/views/field.js +214 -0
- data/frameworks/sproutcore/views/filter_button.js +29 -0
- data/frameworks/sproutcore/views/form.js +591 -0
- data/frameworks/sproutcore/views/image.js +141 -0
- data/frameworks/sproutcore/views/inline_text_editor.js +96 -0
- data/frameworks/sproutcore/views/label.js +176 -0
- data/frameworks/sproutcore/views/menu_item.js +90 -0
- data/frameworks/sproutcore/views/pagination.js +54 -0
- data/frameworks/sproutcore/views/popup_button.js +86 -0
- data/frameworks/sproutcore/views/popup_menu.js +137 -0
- data/frameworks/sproutcore/views/progress.js +100 -0
- data/frameworks/sproutcore/views/radio_field.js +107 -0
- data/frameworks/sproutcore/views/radio_group.js +48 -0
- data/frameworks/sproutcore/views/segmented.js +80 -0
- data/frameworks/sproutcore/views/select_field.js +272 -0
- data/frameworks/sproutcore/views/spinner.js +11 -0
- data/frameworks/sproutcore/views/tab.js +126 -0
- data/frameworks/sproutcore/views/text_field.js +179 -0
- data/frameworks/sproutcore/views/textarea_field.js +14 -0
- data/frameworks/sproutcore/views/toolbar.js +29 -0
- data/frameworks/sproutcore/views/view.js +1389 -0
- data/frameworks/sproutcore/views/workspace.js +170 -0
- data/generators/client/README +3 -0
- data/generators/client/USAGE +12 -0
- data/generators/client/client_generator.rb +53 -0
- data/generators/client/templates/core.js +19 -0
- data/generators/client/templates/english.lproj/body.css +0 -0
- data/generators/client/templates/english.lproj/body.rhtml +3 -0
- data/generators/client/templates/english.lproj/controls.css +0 -0
- data/generators/client/templates/english.lproj/strings.js +14 -0
- data/generators/client/templates/main.js +37 -0
- data/generators/controller/USAGE +16 -0
- data/generators/controller/controller_generator.rb +51 -0
- data/generators/controller/templates/controller.js +21 -0
- data/generators/controller/templates/test.rhtml +21 -0
- data/generators/framework/README +7 -0
- data/generators/framework/USAGE +12 -0
- data/generators/framework/framework_generator.rb +53 -0
- data/generators/framework/templates/core.js +20 -0
- data/generators/framework/templates/english.lproj/body.css +0 -0
- data/generators/framework/templates/english.lproj/body.rhtml +3 -0
- data/generators/framework/templates/english.lproj/controls.css +0 -0
- data/generators/framework/templates/english.lproj/strings.js +14 -0
- data/generators/language/USAGE +16 -0
- data/generators/language/language_generator.rb +47 -0
- data/generators/language/templates/strings.js +10 -0
- data/generators/model/USAGE +24 -0
- data/generators/model/model_generator.rb +55 -0
- data/generators/model/templates/fixture.js +11 -0
- data/generators/model/templates/model.js +20 -0
- data/generators/model/templates/test.rhtml +21 -0
- data/generators/test/USAGE +16 -0
- data/generators/test/templates/test.rhtml +21 -0
- data/generators/test/test_generator.rb +47 -0
- data/generators/view/USAGE +16 -0
- data/generators/view/templates/test.rhtml +21 -0
- data/generators/view/templates/view.js +20 -0
- data/generators/view/view_generator.rb +51 -0
- data/jsdoc/README.txt +119 -0
- data/jsdoc/app/DocFile.js +137 -0
- data/jsdoc/app/DocTag.js +110 -0
- data/jsdoc/app/Doclet.js +63 -0
- data/jsdoc/app/Dumper.js +143 -0
- data/jsdoc/app/JsDoc.js +103 -0
- data/jsdoc/app/JsHilite.js +45 -0
- data/jsdoc/app/JsIO.js +163 -0
- data/jsdoc/app/JsParse.js +385 -0
- data/jsdoc/app/JsPlate.js +130 -0
- data/jsdoc/app/JsTestrun.js +129 -0
- data/jsdoc/app/JsToke.js +564 -0
- data/jsdoc/app/Symbol.js +298 -0
- data/jsdoc/app/Transformer.js +14 -0
- data/jsdoc/app/Util.js +97 -0
- data/jsdoc/app/js.jar +0 -0
- data/jsdoc/app/run.js +144 -0
- data/jsdoc/plugins/min.js +316 -0
- data/jsdoc/plugins/strip.js +20 -0
- data/jsdoc/templates/sproutcore/class.tmpl +438 -0
- data/jsdoc/templates/sproutcore/default.css +241 -0
- data/jsdoc/templates/sproutcore/index.html +13 -0
- data/jsdoc/templates/sproutcore/index.tmpl +21 -0
- data/jsdoc/templates/sproutcore/prototype.js +4186 -0
- data/jsdoc/templates/sproutcore/publish.js +236 -0
- data/jsdoc/templates/sproutcore/splash.html +7 -0
- data/lib/sproutcore/build_tools/html_builder.rb +88 -0
- data/lib/sproutcore/build_tools/resource_builder.rb +194 -0
- data/lib/sproutcore/build_tools.rb +44 -0
- data/lib/sproutcore/bundle.rb +517 -0
- data/lib/sproutcore/bundle_manifest.rb +397 -0
- data/lib/sproutcore/generator_helper.rb +170 -0
- data/lib/sproutcore/helpers/capture_helper.rb +42 -0
- data/lib/sproutcore/helpers/static_helper.rb +80 -0
- data/lib/sproutcore/helpers/tag_helper.rb +110 -0
- data/lib/sproutcore/helpers/text_helper.rb +336 -0
- data/lib/sproutcore/helpers.rb +3 -0
- data/lib/sproutcore/jsdoc.rb +40 -0
- data/lib/sproutcore/jsmin.rb +247 -0
- data/lib/sproutcore/library.rb +258 -0
- data/lib/sproutcore/merb/bundle_controller.rb +179 -0
- data/lib/sproutcore/merb/router.rb +43 -0
- data/lib/sproutcore/merb.rb +27 -0
- data/lib/sproutcore/version.rb +9 -0
- data/lib/sproutcore/view_helpers/button_views.rb +302 -0
- data/lib/sproutcore/view_helpers/core_views.rb +284 -0
- data/lib/sproutcore/view_helpers/form_views.rb +258 -0
- data/lib/sproutcore/view_helpers/menu_views.rb +94 -0
- data/lib/sproutcore/view_helpers.rb +628 -0
- data/lib/sproutcore.rb +30 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/sproutcore_spec.rb +11 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +21 -0
- data/tasks/website.rake +17 -0
- 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
|
+
}) ;
|