sproutcore 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +233 -0
- data/Manifest.txt +67 -34
- data/bin/sc-build +12 -1
- data/bin/sc-gen +1 -1
- data/bin/sproutcore +14 -0
- data/clients/sc_docs/controllers/docs.js +38 -8
- data/clients/sc_docs/english.lproj/body.css +80 -127
- data/clients/sc_docs/english.lproj/body.rhtml +43 -23
- data/clients/sc_docs/english.lproj/no_docs.rhtml +2 -1
- data/clients/sc_docs/english.lproj/tabs.rhtml +16 -0
- data/clients/sc_docs/main.js +14 -9
- data/clients/sc_docs/models/doc.js +1 -1
- data/clients/sc_docs/tests/controllers/docs.rhtml +1 -2
- data/clients/sc_docs/tests/models/doc.rhtml +1 -2
- data/clients/sc_docs/tests/views/doc_frame.rhtml +1 -2
- data/clients/sc_docs/tests/views/doc_label_view.rhtml +1 -2
- data/clients/sc_docs/views/doc_frame.js +1 -1
- data/clients/sc_test_runner/controllers/runner.js +31 -8
- data/clients/sc_test_runner/english.lproj/body.css +62 -122
- data/clients/sc_test_runner/english.lproj/body.rhtml +62 -26
- data/clients/sc_test_runner/main.js +1 -6
- data/clients/sc_test_runner/models/test.js +14 -1
- data/clients/sc_test_runner/views/runner_frame.js +4 -2
- data/clients/view_builder/builders/builder.js +339 -0
- data/clients/view_builder/builders/button.js +81 -0
- data/clients/view_builder/controllers/document.js +21 -0
- data/clients/view_builder/core.js +19 -0
- data/clients/view_builder/english.lproj/body.css +77 -0
- data/clients/view_builder/english.lproj/body.rhtml +41 -0
- data/clients/{sc_docs → view_builder}/english.lproj/controls.css +0 -0
- data/clients/view_builder/english.lproj/strings.js +14 -0
- data/clients/view_builder/main.js +38 -0
- data/clients/view_builder/tests/controllers/document.rhtml +20 -0
- data/clients/view_builder/tests/views/builder.rhtml +20 -0
- data/clients/view_builder/views/builder.js +23 -0
- data/frameworks/prototype/prototype.js +1 -1
- data/frameworks/sproutcore/Core.js +32 -7
- data/frameworks/sproutcore/README +1 -1
- data/frameworks/sproutcore/animation/animation.js +411 -0
- data/frameworks/sproutcore/controllers/array.js +17 -9
- data/frameworks/sproutcore/controllers/collection.js +9 -110
- data/frameworks/sproutcore/controllers/controller.js +1 -1
- data/frameworks/sproutcore/controllers/object.js +2 -1
- data/frameworks/sproutcore/drag/drag.js +267 -56
- data/frameworks/sproutcore/drag/drag_data_source.js +24 -16
- data/frameworks/sproutcore/drag/drag_source.js +53 -42
- data/frameworks/sproutcore/drag/drop_target.js +2 -2
- data/frameworks/sproutcore/english.lproj/buttons.css +337 -236
- data/frameworks/sproutcore/english.lproj/core.css +115 -0
- data/frameworks/sproutcore/english.lproj/icons.css +227 -0
- data/{clients/sc_docs → frameworks/sproutcore}/english.lproj/images/indicator.gif +0 -0
- data/frameworks/sproutcore/english.lproj/images/sc-theme-sprite.png +0 -0
- data/frameworks/sproutcore/english.lproj/images/sc-theme-ysprite.png +0 -0
- data/frameworks/sproutcore/english.lproj/images/shared-icons.png +0 -0
- data/frameworks/sproutcore/english.lproj/menu.css +1 -1
- data/frameworks/sproutcore/english.lproj/strings.js +1 -1
- data/frameworks/sproutcore/english.lproj/theme.css +405 -31
- data/frameworks/sproutcore/foundation/application.js +15 -11
- data/frameworks/sproutcore/foundation/benchmark.js +1 -1
- data/frameworks/sproutcore/foundation/binding.js +2 -2
- data/frameworks/sproutcore/foundation/date.js +1 -1
- data/frameworks/sproutcore/foundation/error.js +1 -1
- data/frameworks/sproutcore/foundation/input_manager.js +32 -21
- data/frameworks/sproutcore/foundation/mock.js +1 -1
- data/frameworks/sproutcore/foundation/node_descriptor.js +9 -6
- data/frameworks/sproutcore/foundation/object.js +249 -177
- data/frameworks/sproutcore/foundation/page.js +5 -2
- data/frameworks/sproutcore/foundation/path_module.js +11 -10
- data/frameworks/sproutcore/foundation/responder.js +5 -2
- data/frameworks/sproutcore/foundation/routes.js +17 -13
- data/frameworks/sproutcore/foundation/run_loop.js +249 -11
- data/frameworks/sproutcore/foundation/server.js +1 -1
- data/frameworks/sproutcore/foundation/set.js +3 -3
- data/frameworks/sproutcore/foundation/string.js +5 -3
- data/frameworks/sproutcore/foundation/timer.js +371 -0
- data/frameworks/sproutcore/foundation/undo_manager.js +1 -1
- data/frameworks/sproutcore/foundation/unittest.js +3 -3
- data/frameworks/sproutcore/foundation/utils.js +161 -2
- data/frameworks/sproutcore/globals/panels.js +1 -1
- data/frameworks/sproutcore/globals/popups.js +4 -3
- data/frameworks/sproutcore/globals/window.js +44 -4
- data/frameworks/sproutcore/lib/button_views.rb +328 -0
- data/frameworks/sproutcore/lib/collection_view.rb +80 -0
- data/frameworks/sproutcore/lib/core_views.rb +281 -0
- data/frameworks/sproutcore/lib/form_views.rb +253 -0
- data/frameworks/sproutcore/lib/index.rhtml +2 -0
- data/frameworks/sproutcore/lib/menu_views.rb +88 -0
- data/frameworks/sproutcore/{foundation → mixins}/array.js +60 -29
- data/frameworks/sproutcore/mixins/control.js +265 -0
- data/frameworks/sproutcore/mixins/delegate_support.js +66 -0
- data/frameworks/sproutcore/{foundation → mixins}/observable.js +176 -6
- data/frameworks/sproutcore/mixins/scrollable.js +245 -0
- data/frameworks/sproutcore/mixins/selection_support.js +148 -0
- data/frameworks/sproutcore/mixins/validatable.js +152 -0
- data/frameworks/sproutcore/models/collection.js +5 -5
- data/frameworks/sproutcore/models/record.js +1 -1
- data/frameworks/sproutcore/models/store.js +1 -1
- data/frameworks/sproutcore/panes/dialog.js +1 -1
- data/frameworks/sproutcore/panes/manager.js +1 -1
- data/frameworks/sproutcore/panes/menu.js +1 -1
- data/frameworks/sproutcore/panes/overlay.js +2 -2
- data/frameworks/sproutcore/panes/panel.js +1 -1
- data/frameworks/sproutcore/panes/picker.js +1 -1
- data/frameworks/sproutcore/tests/controllers/array.rhtml +44 -4
- data/frameworks/sproutcore/tests/foundation/timer/invalidate.rhtml +33 -0
- data/frameworks/sproutcore/tests/foundation/timer/invokeLater.rhtml +145 -0
- data/frameworks/sproutcore/tests/foundation/timer/isPaused.rhtml +70 -0
- data/frameworks/sproutcore/tests/foundation/timer/schedule.rhtml +145 -0
- data/frameworks/sproutcore/tests/views/{scroll.rhtml → checkbox.rhtml} +3 -3
- data/frameworks/sproutcore/tests/views/{collection.rhtml → collection/base.rhtml} +33 -32
- data/frameworks/sproutcore/tests/views/collection/incremental_rendering.rhtml +260 -0
- data/frameworks/sproutcore/tests/views/image_cell.rhtml +19 -0
- data/frameworks/sproutcore/tests/views/label_item.rhtml +2 -4
- data/frameworks/sproutcore/tests/views/list.rhtml +2 -3
- data/frameworks/sproutcore/tests/views/list_item.rhtml +20 -0
- data/frameworks/sproutcore/tests/views/slider.rhtml +20 -0
- data/frameworks/sproutcore/tests/views/text_cell.rhtml +19 -0
- data/frameworks/sproutcore/tests/views/view/clippingFrame.rhtml +395 -0
- data/frameworks/sproutcore/tests/views/view/frame.rhtml +353 -0
- data/frameworks/sproutcore/tests/views/view/innerFrame.rhtml +347 -0
- data/frameworks/sproutcore/tests/views/view/isVisibleInWindow.rhtml +148 -0
- data/frameworks/sproutcore/tests/views/view/scrollFrame.rhtml +468 -0
- data/frameworks/sproutcore/validators/credit_card.js +33 -13
- data/frameworks/sproutcore/validators/date.js +26 -6
- data/frameworks/sproutcore/validators/email.js +21 -3
- data/frameworks/sproutcore/validators/not_empty.js +11 -1
- data/frameworks/sproutcore/validators/number.js +18 -4
- data/frameworks/sproutcore/validators/password.js +12 -1
- data/frameworks/sproutcore/validators/validator.js +204 -194
- data/frameworks/sproutcore/views/{button.js → button/button.js} +96 -94
- data/frameworks/sproutcore/views/button/checkbox.js +29 -0
- data/frameworks/sproutcore/views/button/disclosure.js +42 -0
- data/frameworks/sproutcore/views/button/radio.js +29 -0
- data/frameworks/sproutcore/views/{collection.js → collection/collection.js} +1373 -1024
- data/frameworks/sproutcore/views/collection/grid.js +124 -46
- data/frameworks/sproutcore/views/collection/image_cell.js +17 -46
- data/frameworks/sproutcore/views/collection/list.js +45 -35
- data/frameworks/sproutcore/views/collection/source_list.js +386 -0
- data/frameworks/sproutcore/views/collection/table.js +118 -0
- data/frameworks/sproutcore/views/container.js +7 -2
- data/frameworks/sproutcore/views/error_explanation.js +23 -10
- data/frameworks/sproutcore/views/{checkbox_field.js → field/checkbox_field.js} +16 -6
- data/frameworks/sproutcore/views/field/field.js +219 -0
- data/frameworks/sproutcore/views/{radio_field.js → field/radio_field.js} +27 -12
- data/frameworks/sproutcore/views/{select_field.js → field/select_field.js} +116 -90
- data/frameworks/sproutcore/views/{text_field.js → field/text_field.js} +57 -8
- data/frameworks/sproutcore/views/{textarea_field.js → field/textarea_field.js} +13 -3
- data/frameworks/sproutcore/views/filter_button.js +2 -2
- data/frameworks/sproutcore/views/form.js +3 -3
- data/frameworks/sproutcore/views/image.js +128 -21
- data/frameworks/sproutcore/views/inline_text_editor.js +1 -1
- data/frameworks/sproutcore/views/label.js +149 -92
- data/frameworks/sproutcore/views/list_item.js +225 -0
- data/frameworks/sproutcore/views/menu_item.js +10 -4
- data/frameworks/sproutcore/views/pagination.js +11 -4
- data/frameworks/sproutcore/views/popup_button.js +25 -21
- data/frameworks/sproutcore/views/popup_menu.js +10 -4
- data/frameworks/sproutcore/views/progress.js +29 -16
- data/frameworks/sproutcore/views/radio_group.js +1 -1
- data/frameworks/sproutcore/views/scroll.js +60 -20
- data/frameworks/sproutcore/views/segmented.js +1 -1
- data/frameworks/sproutcore/views/slider.js +132 -0
- data/frameworks/sproutcore/views/source_list_group.js +130 -0
- data/frameworks/sproutcore/views/spinner.js +1 -1
- data/frameworks/sproutcore/views/split.js +292 -0
- data/frameworks/sproutcore/views/split_divider.js +109 -0
- data/frameworks/sproutcore/views/tab.js +1 -1
- data/frameworks/sproutcore/views/toolbar.js +1 -1
- data/frameworks/sproutcore/views/view.js +1272 -591
- data/generators/client/templates/english.lproj/body.css +1 -1
- data/generators/controller/controller_generator.rb +1 -1
- data/generators/controller/templates/test.rhtml +2 -1
- data/generators/model/templates/test.rhtml +1 -1
- data/generators/test/templates/test.rhtml +1 -1
- data/generators/view/templates/test.rhtml +1 -1
- data/jsdoc/templates/sproutcore/class.tmpl +241 -338
- data/jsdoc/templates/sproutcore/default.css +105 -155
- data/jsdoc/templates/sproutcore/index.tmpl +43 -8
- data/jsdoc/templates/sproutcore/publish.js +9 -4
- data/lib/sproutcore/build_tools/html_builder.rb +29 -13
- data/lib/sproutcore/build_tools/resource_builder.rb +1 -1
- data/lib/sproutcore/bundle.rb +86 -25
- data/lib/sproutcore/jsdoc.rb +2 -0
- data/lib/sproutcore/version.rb +1 -1
- data/lib/sproutcore/view_helpers.rb +36 -3
- data/tasks/deployment.rake +1 -1
- metadata +69 -36
- 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/toolbar.png +0 -0
- data/clients/sc_docs/english.lproj/warning.rhtml +0 -6
- data/clients/sc_test_runner/english.lproj/warning.rhtml +0 -6
- data/frameworks/sproutcore/english.lproj/buttons.png +0 -0
- data/frameworks/sproutcore/english.lproj/collections.css +0 -82
- data/frameworks/sproutcore/english.lproj/images/buttons-sprite.png +0 -0
- data/frameworks/sproutcore/views/collection/collection_item.js +0 -36
- data/frameworks/sproutcore/views/collection/text_cell.js +0 -128
- data/frameworks/sproutcore/views/field.js +0 -214
- data/frameworks/sproutcore/views/workspace.js +0 -170
- data/generators/client/templates/english.lproj/controls.css +0 -0
- data/generators/framework/templates/english.lproj/body.css +0 -0
- data/generators/framework/templates/english.lproj/body.rhtml +0 -3
- data/generators/framework/templates/english.lproj/controls.css +0 -0
- data/lib/sproutcore/view_helpers/button_views.rb +0 -302
- data/lib/sproutcore/view_helpers/core_views.rb +0 -292
- data/lib/sproutcore/view_helpers/form_views.rb +0 -258
- data/lib/sproutcore/view_helpers/menu_views.rb +0 -94
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// ========================================================================
|
|
2
2
|
// SproutCore
|
|
3
|
-
// copyright 2006-
|
|
3
|
+
// copyright 2006-2008 Sprout Systems, Inc.
|
|
4
4
|
// ========================================================================
|
|
5
5
|
|
|
6
6
|
require('views/view') ;
|
|
7
7
|
require('views/label') ;
|
|
8
|
+
require('mixins/control') ;
|
|
8
9
|
|
|
9
10
|
// Constants
|
|
10
11
|
SC.TOGGLE_BEHAVIOR = 'toggle';
|
|
11
12
|
SC.PUSH_BEHAVIOR = 'push';
|
|
12
13
|
SC.TOGGLE_ON_BEHAVIOR = "on";
|
|
13
14
|
SC.TOGGLE_OFF_BEHAVIOR = "off" ;
|
|
14
|
-
SC.MIXED_STATE = '__MIXED__' ;
|
|
15
15
|
|
|
16
16
|
/** @class
|
|
17
17
|
|
|
@@ -19,12 +19,15 @@ SC.MIXED_STATE = '__MIXED__' ;
|
|
|
19
19
|
enabled or disabled state.
|
|
20
20
|
|
|
21
21
|
@extends SC.View
|
|
22
|
+
@extends SC.Control
|
|
23
|
+
@author Charles Jolley
|
|
24
|
+
@version 1.0
|
|
25
|
+
|
|
22
26
|
*/
|
|
23
|
-
SC.ButtonView = SC.View.extend(
|
|
24
|
-
|
|
25
|
-
{
|
|
27
|
+
SC.ButtonView = SC.View.extend(SC.Control,
|
|
28
|
+
/** @scope SC.ButtonView.prototype */ {
|
|
26
29
|
|
|
27
|
-
emptyElement: '<a href="javascript:;" class="regular"><span class="button-inner"><span class="label"></span></span></a>',
|
|
30
|
+
emptyElement: '<a href="javascript:;" class="sc-button-view regular"><span class="button-inner"><span class="label"></span></span></a>',
|
|
28
31
|
|
|
29
32
|
// PROPERTIES
|
|
30
33
|
|
|
@@ -81,8 +84,7 @@ SC.ButtonView = SC.View.extend(
|
|
|
81
84
|
should set a class name on the HTML with the same value to allow CSS
|
|
82
85
|
styling.
|
|
83
86
|
|
|
84
|
-
The default SproutCore theme supports "regular", "
|
|
85
|
-
"radio"
|
|
87
|
+
The default SproutCore theme supports "regular", "checkbox", and "radio"
|
|
86
88
|
*/
|
|
87
89
|
theme: 'regular',
|
|
88
90
|
|
|
@@ -113,71 +115,75 @@ SC.ButtonView = SC.View.extend(
|
|
|
113
115
|
buttonBehavior: SC.PUSH_BEHAVIOR,
|
|
114
116
|
|
|
115
117
|
/**
|
|
116
|
-
|
|
118
|
+
If NO the button will be disabled.
|
|
117
119
|
|
|
118
|
-
@type
|
|
120
|
+
@type Bool
|
|
119
121
|
*/
|
|
120
|
-
isEnabled:
|
|
121
|
-
isEnabledBindingDefault: SC.Binding.OneWayBool,
|
|
122
|
+
isEnabled: YES,
|
|
122
123
|
|
|
123
124
|
/**
|
|
124
|
-
|
|
125
|
+
button's selection state. Returns YES, NO, or SC.MIXED_STATE
|
|
125
126
|
*/
|
|
126
|
-
isSelected:
|
|
127
|
-
isSelectedBindingDefault: SC.Binding.OneWayBool,
|
|
127
|
+
isSelected: NO,
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
If YES, then this button will be triggered when you hit return.
|
|
131
|
+
|
|
132
|
+
This is the same as setting the keyEquivalent to 'return'. This will also
|
|
133
|
+
apply the "def" classname to the button.
|
|
133
134
|
*/
|
|
134
|
-
isDefault:
|
|
135
|
+
isDefault: NO,
|
|
135
136
|
isDefaultBindingDefault: SC.Binding.OneWayBool,
|
|
136
137
|
|
|
137
138
|
/**
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
If YES, then this button will be triggered when you hit escape.
|
|
140
|
+
|
|
141
|
+
This is the same as setting the keyEquivalent to 'escape'.
|
|
140
142
|
*/
|
|
141
|
-
isCancel:
|
|
143
|
+
isCancel: NO,
|
|
142
144
|
isCancelBindingDefault: SC.Binding.OneWayBool,
|
|
143
145
|
|
|
144
146
|
/**
|
|
145
|
-
|
|
146
|
-
|
|
147
|
+
If YES, then the title will be localized.
|
|
148
|
+
*/
|
|
149
|
+
localize: NO,
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
The selector path to the element that contains the button title.
|
|
153
|
+
|
|
154
|
+
This property is only used if you try to get or set the title property.
|
|
147
155
|
*/
|
|
148
|
-
|
|
156
|
+
titleSelector: '.label',
|
|
149
157
|
|
|
150
158
|
/**
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
The button title.
|
|
160
|
+
|
|
161
|
+
This property is observable and bindable.
|
|
162
|
+
|
|
163
|
+
@field {String}
|
|
153
164
|
*/
|
|
154
|
-
|
|
165
|
+
title: function(key, value) {
|
|
166
|
+
|
|
155
167
|
// set the value of the label text. Possibly localize and set innerHTML.
|
|
156
168
|
if (value !== undefined) {
|
|
157
|
-
if (this.
|
|
158
|
-
var text = this.
|
|
159
|
-
var lsel = this.get('
|
|
169
|
+
if (this._title != value) {
|
|
170
|
+
var text = this._title = value ;
|
|
171
|
+
var lsel = this.get('titleSelector') ;
|
|
160
172
|
var el = (lsel) ? this.$sel(lsel) : this.rootElement ;
|
|
161
173
|
|
|
162
174
|
if (this.get('localize')) text = text.loc() ;
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// lazily fetch the label text. This only happens if localization is
|
|
167
|
-
// turned off.
|
|
168
|
-
if (!this._labelText) {
|
|
169
|
-
var el = this.$sel(this.labelSelector) ;
|
|
170
|
-
this._labelText = (el) ? el.innerHTML : '' ;
|
|
175
|
+
el.innerHTML = text ;
|
|
171
176
|
}
|
|
172
|
-
return this._labelText ;
|
|
173
177
|
}
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
|
|
178
|
+
|
|
179
|
+
// lazily fetch the label text.
|
|
180
|
+
if (!this._title) {
|
|
181
|
+
var el = this.$sel(this.get('titleSelector')) ;
|
|
182
|
+
this._title = (el) ? el.innerHTML : '' ;
|
|
183
|
+
}
|
|
184
|
+
return this._title ;
|
|
177
185
|
}.property(),
|
|
178
186
|
|
|
179
|
-
labelSelector: '.label',
|
|
180
|
-
|
|
181
187
|
/**
|
|
182
188
|
The name of the action you want triggered when the button is pressed.
|
|
183
189
|
|
|
@@ -244,8 +250,7 @@ SC.ButtonView = SC.View.extend(
|
|
|
244
250
|
this.setClassName('active', true);
|
|
245
251
|
this.didTriggerAction();
|
|
246
252
|
this._action(evt);
|
|
247
|
-
|
|
248
|
-
setTimeout(function() { view.setClassName('active', false); }, 200);
|
|
253
|
+
this.invokeLater('setClassName', 200, 'active', false) ;
|
|
249
254
|
return true;
|
|
250
255
|
},
|
|
251
256
|
|
|
@@ -257,16 +262,20 @@ SC.ButtonView = SC.View.extend(
|
|
|
257
262
|
/** @private */
|
|
258
263
|
init: function() {
|
|
259
264
|
arguments.callee.base.call(this) ;
|
|
260
|
-
this._updateClassForState() ;
|
|
261
265
|
|
|
266
|
+
// setup initial CSS clases
|
|
267
|
+
this._isDefaultOrCancelObserver() ;
|
|
268
|
+
|
|
269
|
+
// If we need to localze, handle it...
|
|
262
270
|
var el ;
|
|
263
|
-
var
|
|
264
|
-
if (this.get('localize') && (el =
|
|
265
|
-
this.
|
|
266
|
-
|
|
271
|
+
var sel = this.get('titleSelector') ;
|
|
272
|
+
if (this.get('localize') && sel && (el = this.$sel(sel))) {
|
|
273
|
+
this._title = (el.innerHTML || '').strip() ;
|
|
274
|
+
el.innerHTML = this._title.loc() ;
|
|
267
275
|
}
|
|
268
276
|
},
|
|
269
277
|
|
|
278
|
+
// determines the target selected state
|
|
270
279
|
_selectedStateFromValue: function(value) {
|
|
271
280
|
var targetValue = this.get('toggleOnValue') ;
|
|
272
281
|
var state ;
|
|
@@ -275,7 +284,7 @@ SC.ButtonView = SC.View.extend(
|
|
|
275
284
|
if (value.length == 1) {
|
|
276
285
|
state = (value[0] == targetValue) ;
|
|
277
286
|
} else {
|
|
278
|
-
state = (value.
|
|
287
|
+
state = (value.indexOf(targetValue) >= 0) ? SC.MIXED_STATE : false ;
|
|
279
288
|
}
|
|
280
289
|
} else {
|
|
281
290
|
state = (value == targetValue) ;
|
|
@@ -288,53 +297,46 @@ SC.ButtonView = SC.View.extend(
|
|
|
288
297
|
if (target != this) return ;
|
|
289
298
|
|
|
290
299
|
// handle changes to the value
|
|
291
|
-
|
|
300
|
+
switch(key) {
|
|
301
|
+
|
|
292
302
|
// determine the new selection state.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// if the new selected state does not match the computed value, set it.
|
|
298
|
-
var state = this._selectedStateFromValue(value) ;
|
|
299
|
-
if (!(this.get('isSelected') == state)) {
|
|
300
|
-
this.set('isSelected', state) ;
|
|
301
|
-
this._updateClassForState() ;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// handle changes to the selected state
|
|
305
|
-
// forward to value if needed...
|
|
306
|
-
} else if (key == "isSelected") {
|
|
307
|
-
var newState = this.get('isSelected') ;
|
|
308
|
-
var curState = this._selectedStateFromValue(this.get('value')) ;
|
|
309
|
-
if (curState != newState) {
|
|
310
|
-
var valueKey = (newState) ? 'toggleOnValue' : 'toggleOffValue' ;
|
|
311
|
-
this.set('value', this.get(valueKey)) ;
|
|
312
|
-
}
|
|
313
|
-
this._updateClassForState() ;
|
|
303
|
+
case 'value':
|
|
304
|
+
value = this.get('value') ;
|
|
305
|
+
if (value == this._value) return ; // process value one time.
|
|
306
|
+
this._value = value ;
|
|
314
307
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
308
|
+
// set the new selected state if it does not match
|
|
309
|
+
var state = this._selectedStateFromValue(value) ;
|
|
310
|
+
this.setIfChanged('isSelected', state) ;
|
|
311
|
+
break ;
|
|
312
|
+
|
|
313
|
+
// forward to value if needed.
|
|
314
|
+
case 'isSelected':
|
|
315
|
+
var newState = this.get('isSelected') ;
|
|
316
|
+
var curState = this._selectedStateFromValue(this.get('value')) ;
|
|
317
|
+
if (curState != newState) {
|
|
318
|
+
var valueKey = (newState) ? 'toggleOnValue' : 'toggleOffValue' ;
|
|
319
|
+
this.set('value', this.get(valueKey)) ;
|
|
320
|
+
}
|
|
321
|
+
break ;
|
|
322
|
+
|
|
323
|
+
// otherwise do nothing
|
|
324
|
+
default:
|
|
325
|
+
break ;
|
|
318
326
|
}
|
|
319
327
|
},
|
|
320
328
|
|
|
321
|
-
|
|
322
|
-
var
|
|
323
|
-
var
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
this.
|
|
329
|
-
|
|
329
|
+
_isDefaultOrCancelObserver: function() {
|
|
330
|
+
var isDef = !!this.get('isDefault') ;
|
|
331
|
+
var isCancel = !isDef && this.get('isCancel') ;
|
|
332
|
+
|
|
333
|
+
this.setClassName('def', isDef) ;
|
|
334
|
+
|
|
335
|
+
var key = this.get('keyEquivalent') ;
|
|
336
|
+
if (isDef && key != 'return') this.set('keyEquivalent', 'return') ;
|
|
337
|
+
if (isCancel && key != 'escape') this.set('keyEquivalent', 'escape') ;
|
|
338
|
+
}.observes('isDefault', 'isCancel'),
|
|
330
339
|
|
|
331
|
-
// handle selected state.
|
|
332
|
-
var sel =this.get('isSelected') ;
|
|
333
|
-
var mixed = (sel == SC.MIXED_STATE) ;
|
|
334
|
-
this.setClassName('mixed', mixed) ;
|
|
335
|
-
this.setClassName('sel', ((mixed) ? false : sel)) ;
|
|
336
|
-
},
|
|
337
|
-
|
|
338
340
|
// on mouse down, set active only if enabled.
|
|
339
341
|
/** @private */
|
|
340
342
|
mouseDown: function(evt) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// SC.CheckboxView
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
require('views/button/button');
|
|
6
|
+
|
|
7
|
+
/** @class
|
|
8
|
+
|
|
9
|
+
Renders a checkbox button view specifically.
|
|
10
|
+
|
|
11
|
+
This view is basically a button view preconfigured to generate the correct
|
|
12
|
+
HTML and to set to use a TOGGLE_BEHAVIOR for its buttons.
|
|
13
|
+
|
|
14
|
+
This view renders a simulated checkbox that can display a mixed state and
|
|
15
|
+
has other features not found in platform-native controls. If you want to
|
|
16
|
+
use the platform native version instead, see SC.CheckboxFieldView.
|
|
17
|
+
|
|
18
|
+
@extends SC.ButtonView
|
|
19
|
+
@author Charles Jolley
|
|
20
|
+
@version 1.0
|
|
21
|
+
*/
|
|
22
|
+
SC.CheckboxView = SC.ButtonView.extend(
|
|
23
|
+
/** @scope SC.CheckboxView.prototype */ {
|
|
24
|
+
|
|
25
|
+
emptyElement: '<a href="javascript:;" class="sc-checkbox-view sc-button-view button checkbox"><img src="%@" class="button" /><span class="label"></span></a>'.fmt(static_url('blank')),
|
|
26
|
+
|
|
27
|
+
buttonBehavior: SC.TOGGLE_BEHAVIOR
|
|
28
|
+
|
|
29
|
+
}) ;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// SC.CheckboxView
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
require('views/button/button');
|
|
6
|
+
|
|
7
|
+
/** @class
|
|
8
|
+
|
|
9
|
+
Disclosure triangle button.
|
|
10
|
+
|
|
11
|
+
@extends SC.ButtonView
|
|
12
|
+
@author Charles Jolley
|
|
13
|
+
@version 1.0
|
|
14
|
+
*/
|
|
15
|
+
SC.DisclosureView = SC.ButtonView.extend(
|
|
16
|
+
/** @scope SC.DisclosureView.prototype */ {
|
|
17
|
+
|
|
18
|
+
emptyElement: '<a href="javascript:;" class="sc-disclosure-view sc-button-view button disclosure"><img src="%@" class="button" /><span class="label"></span></a>'.fmt(static_url('blank')),
|
|
19
|
+
|
|
20
|
+
buttonBehavior: SC.TOGGLE_BEHAVIOR,
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
This is the value that will be set when the disclosure triangle is toggled
|
|
24
|
+
open.
|
|
25
|
+
*/
|
|
26
|
+
toggleOnValue: YES,
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
The value that will be set when the disclosure triangle is toggled closed.
|
|
30
|
+
*/
|
|
31
|
+
toggleOffValue: NO,
|
|
32
|
+
|
|
33
|
+
valueBindingDefault: SC.Binding.Bool,
|
|
34
|
+
|
|
35
|
+
init: function() {
|
|
36
|
+
arguments.callee.base.apply(this,arguments) ;
|
|
37
|
+
if (this.get('value') == this.get('toggleOnValue')) {
|
|
38
|
+
this.set('isSelected', true) ;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
}) ;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// ==========================================================================
|
|
2
|
+
// SC.RadioView
|
|
3
|
+
// ==========================================================================
|
|
4
|
+
|
|
5
|
+
require('views/button/button');
|
|
6
|
+
|
|
7
|
+
/** @class
|
|
8
|
+
|
|
9
|
+
Renders a radio button view.
|
|
10
|
+
|
|
11
|
+
This view is basically a button view preconfigured to generate the correct
|
|
12
|
+
HTML and to set to use a TOGGLE_ON_BEHAVIOR.
|
|
13
|
+
|
|
14
|
+
This view renders a simulated checkbox that can display a mixed state and
|
|
15
|
+
has other features not found in platform-native controls. If you want to
|
|
16
|
+
use the platform native version instead, see SC.RadioFieldView.
|
|
17
|
+
|
|
18
|
+
@extends SC.ButtonView
|
|
19
|
+
@author Charles Jolley
|
|
20
|
+
@version 1.0
|
|
21
|
+
*/
|
|
22
|
+
SC.RadioView = SC.ButtonView.extend(
|
|
23
|
+
/** @scope SC.RadioView.prototype */ {
|
|
24
|
+
|
|
25
|
+
emptyElement: '<a href="javascript:;" class="sc-radio-view sc-button-view button radio"><img src="%@" class="button" /><span class="label"></span></a>'.fmt(static_url('blank')),
|
|
26
|
+
|
|
27
|
+
buttonBehavior: SC.TOGGLE_ON_BEHAVIOR
|
|
28
|
+
|
|
29
|
+
}) ;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// ========================================================================
|
|
2
2
|
// SproutCore
|
|
3
|
-
// copyright 2006-
|
|
3
|
+
// copyright 2006-2008 Sprout Systems, Inc.
|
|
4
4
|
// ========================================================================
|
|
5
5
|
|
|
6
6
|
require('views/view') ;
|
|
7
7
|
require('views/label') ;
|
|
8
8
|
|
|
9
|
+
SC.BENCHMARK_UPDATE_CHILDREN = NO ;
|
|
10
|
+
SC.VALIDATE_COLLECTION_CONSISTANCY = NO ;
|
|
11
|
+
|
|
9
12
|
/** Indicates that selection points should be selected using horizontal
|
|
10
13
|
orientation.
|
|
11
14
|
*/
|
|
@@ -14,6 +17,14 @@ SC.HORIZONTAL_ORIENTATION = 'horizontal';
|
|
|
14
17
|
/** Selection points should be selected using vertical orientation. */
|
|
15
18
|
SC.VERTICAL_ORIENTATION = 'vertical' ;
|
|
16
19
|
|
|
20
|
+
/** Enables an optimization using zombie group views. This option is configurable for perf testing purposes. You should not change it. */
|
|
21
|
+
SC.ZOMBIE_GROUPS_ENABLED = YES ;
|
|
22
|
+
|
|
23
|
+
/** Enables an optimization that removes the root element from the DOM during
|
|
24
|
+
a render and then readds it when complete. This option is configurable for
|
|
25
|
+
perf testing purposes. You should not change it. */
|
|
26
|
+
SC.REMOVE_COLLECTION_ROOT_ELEMENT_DURING_RENDER = NO ;
|
|
27
|
+
|
|
17
28
|
/**
|
|
18
29
|
@class
|
|
19
30
|
|
|
@@ -31,17 +42,6 @@ SC.VERTICAL_ORIENTATION = 'vertical' ;
|
|
|
31
42
|
property if you want to monitor selection. (be sure to set the isEnabled
|
|
32
43
|
property to allow selection.)
|
|
33
44
|
|
|
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
45
|
@extends SC.View
|
|
46
46
|
*/
|
|
47
47
|
SC.CollectionView = SC.View.extend(
|
|
@@ -57,8 +57,8 @@ SC.CollectionView = SC.View.extend(
|
|
|
57
57
|
|
|
58
58
|
This array should contain the content objects you want the collection view
|
|
59
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
|
|
61
|
-
this array.
|
|
60
|
+
created for each content object, in the order the content objects appear
|
|
61
|
+
in this array.
|
|
62
62
|
|
|
63
63
|
If you make the collection editable, the collection view will also modify
|
|
64
64
|
this array using the observable array methods of SC.Array.
|
|
@@ -76,20 +76,20 @@ SC.CollectionView = SC.View.extend(
|
|
|
76
76
|
/**
|
|
77
77
|
The array of currently selected objects.
|
|
78
78
|
|
|
79
|
-
This array should contain the currently selected content objects.
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
This array should contain the currently selected content objects. It is
|
|
80
|
+
modified automatically by the collection view when the user changes the
|
|
81
|
+
selection on the collection.
|
|
82
82
|
|
|
83
|
-
Any item views representing content objects in this array will
|
|
84
|
-
|
|
83
|
+
Any item views representing content objects in this array will have their
|
|
84
|
+
isSelected property set to YES automatically.
|
|
85
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
|
|
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
89
|
views.
|
|
90
90
|
|
|
91
|
-
Usually you will want to bind this property to a controller property
|
|
92
|
-
|
|
91
|
+
Usually you will want to bind this property to a controller property that
|
|
92
|
+
actually manages the selection for your display.
|
|
93
93
|
|
|
94
94
|
@type Array
|
|
95
95
|
*/
|
|
@@ -105,7 +105,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
105
105
|
If you have items in your selection property, they will still be reflected
|
|
106
106
|
visually.
|
|
107
107
|
|
|
108
|
-
@type
|
|
108
|
+
@type {Bool}
|
|
109
109
|
*/
|
|
110
110
|
isSelectable: true,
|
|
111
111
|
|
|
@@ -120,7 +120,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
120
120
|
the collection view will also be not selectable or editable, regardless of the
|
|
121
121
|
settings for isEditable & isSelectable.
|
|
122
122
|
|
|
123
|
-
@type
|
|
123
|
+
@type {Bool}
|
|
124
124
|
*/
|
|
125
125
|
isEnabled: true,
|
|
126
126
|
|
|
@@ -182,30 +182,6 @@ SC.CollectionView = SC.View.extend(
|
|
|
182
182
|
*/
|
|
183
183
|
useToggleSelection: false,
|
|
184
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
185
|
/**
|
|
210
186
|
Trigger the action method on a single click.
|
|
211
187
|
|
|
@@ -335,7 +311,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
335
311
|
|
|
336
312
|
You can also set this to true yourself to be notified when it is completed.
|
|
337
313
|
*/
|
|
338
|
-
isDirty:
|
|
314
|
+
isDirty: false,
|
|
339
315
|
|
|
340
316
|
/**
|
|
341
317
|
The maximum time the collection view will spend updating its
|
|
@@ -349,28 +325,23 @@ SC.CollectionView = SC.View.extend(
|
|
|
349
325
|
*/
|
|
350
326
|
maxRenderTime: 0,
|
|
351
327
|
|
|
352
|
-
/**
|
|
353
|
-
Property returns all of the item views, regardless of group view.
|
|
354
|
-
|
|
355
|
-
@property
|
|
356
|
-
@returns {Array} the item views.
|
|
357
|
-
*/
|
|
358
|
-
itemViews: function() {
|
|
359
|
-
var ret = [] ;
|
|
360
|
-
if (!this._itemViews) return ret ;
|
|
361
|
-
for(var key in this._itemViews) {
|
|
362
|
-
if (this._itemViews.hasOwnProperty(key)) ret.push(this._itemViews[key]);
|
|
363
|
-
}
|
|
364
|
-
return ret;
|
|
365
|
-
}.property(),
|
|
366
|
-
|
|
367
328
|
/**
|
|
368
|
-
|
|
329
|
+
Property to on content items to use for display.
|
|
330
|
+
|
|
331
|
+
Built-in item views such as the LabelViews and ImageViews will use the
|
|
332
|
+
value of this property as a key on the content object to determine the
|
|
333
|
+
value they should display.
|
|
334
|
+
|
|
335
|
+
For example, if you set contentValueKey to 'name' and set the
|
|
336
|
+
exampleView to an SC.LabelView, then the label views created by the
|
|
337
|
+
colleciton view will display the value of the content.name.
|
|
369
338
|
|
|
370
|
-
|
|
371
|
-
|
|
339
|
+
If you are writing your own custom item view for a collection, you can
|
|
340
|
+
get this behavior automatically by including the SC.Control mixin on your
|
|
341
|
+
view. You can also ignore this property if you like. The collection view
|
|
342
|
+
itself does not use this property to impact rendering.
|
|
372
343
|
*/
|
|
373
|
-
|
|
344
|
+
contentValueKey: null,
|
|
374
345
|
|
|
375
346
|
/**
|
|
376
347
|
Enables keyboard-based navigate if set to true.
|
|
@@ -388,6 +359,54 @@ SC.CollectionView = SC.View.extend(
|
|
|
388
359
|
to edit this property.
|
|
389
360
|
*/
|
|
390
361
|
itemsPerRow: 1,
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
Property returns all of the item views, regardless of group view.
|
|
365
|
+
|
|
366
|
+
@field
|
|
367
|
+
@returns {Array} the item views.
|
|
368
|
+
*/
|
|
369
|
+
itemViews: function() {
|
|
370
|
+
if (!this._itemViews) {
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
var range = this.get('nowShowingRange') ;
|
|
374
|
+
var content = this.get('content') || [] ;
|
|
375
|
+
this._itemViews = [] ;
|
|
376
|
+
for(var idx=0;idx<range.length;idx++) {
|
|
377
|
+
var cur = content.objectAt(idx) ;
|
|
378
|
+
this._itemViews.push(this.itemViewForContent(cur)) ;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return this._itemViews;
|
|
382
|
+
}.property(),
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
Property returns all of the rendered group views in order of their
|
|
386
|
+
appearance with the content.
|
|
387
|
+
*/
|
|
388
|
+
groupViews: function() {
|
|
389
|
+
if (!this._groupViews) {
|
|
390
|
+
var groupBy = this.get('groupBy') ;
|
|
391
|
+
if (groupBy) {
|
|
392
|
+
var range = this.get('nowShowingRange') ;
|
|
393
|
+
var content = this.get('content') || [] ;
|
|
394
|
+
var groupValue = undefined ;
|
|
395
|
+
this._groupViews = [] ;
|
|
396
|
+
|
|
397
|
+
for(var idx=0;idx<range.length;idx++) {
|
|
398
|
+
var cur = content.objectAt(idx) ;
|
|
399
|
+
var curGroupValue = (cur) ? cur.get(groupBy) : null ;
|
|
400
|
+
if (curGroupValue != groupValue) {
|
|
401
|
+
groupValue = curGroupValue ;
|
|
402
|
+
this._groupViews.push(this.groupViewForGroupValue(groupValue)) ;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return this._groupViews;
|
|
409
|
+
}.property(),
|
|
391
410
|
|
|
392
411
|
/**
|
|
393
412
|
Returns true if the passed view belongs to the collection.
|
|
@@ -401,624 +420,355 @@ SC.CollectionView = SC.View.extend(
|
|
|
401
420
|
@returns {Boolean} True if the view is an item view in the receiver.
|
|
402
421
|
*/
|
|
403
422
|
hasItemView: function(view) {
|
|
404
|
-
if (!this.
|
|
405
|
-
return !!this.
|
|
423
|
+
if (!this._itemViewsByGuid) this._itemViewsByGuid = {} ;
|
|
424
|
+
return !!this._itemViewsByGuid[SC.guidFor(view)] ;
|
|
406
425
|
},
|
|
407
426
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
427
|
+
/**
|
|
428
|
+
Find the item view underneath the passed mouse location.
|
|
429
|
+
|
|
430
|
+
The default implementation of this method simply searches each item view's
|
|
431
|
+
frame to find one that includes the location. If you are doing your own
|
|
432
|
+
layout, you may be able to perform this calculation more quickly. If so,
|
|
433
|
+
consider overriding this method for better performance during drag
|
|
434
|
+
operations.
|
|
435
|
+
|
|
436
|
+
@param {Point} loc The current mouse location in the coordinate of the
|
|
437
|
+
collection view
|
|
438
|
+
|
|
439
|
+
@returns {SC.View} The item view under the collection
|
|
415
440
|
*/
|
|
416
|
-
|
|
417
|
-
this.
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
441
|
+
itemViewAtLocation: function(loc) {
|
|
442
|
+
var itemView = this._itemViewRoot ;
|
|
443
|
+
while(itemView) {
|
|
444
|
+
var frame = itemView.get('frame');
|
|
445
|
+
if (SC.pointInRect(loc, frame)) return itemView ;
|
|
446
|
+
}
|
|
447
|
+
return null; // not in an itemView right now.
|
|
422
448
|
},
|
|
423
449
|
|
|
424
|
-
// ......................................
|
|
425
|
-
// DRAG AND DROP SUPPORT
|
|
426
|
-
//
|
|
427
450
|
|
|
451
|
+
|
|
428
452
|
/**
|
|
429
|
-
|
|
430
|
-
dimension we should pay attention to when determining insertion point for
|
|
431
|
-
a mouse click.
|
|
453
|
+
Find the first content item view for the passed event.
|
|
432
454
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
Get the preferred insertion point for the given location, including
|
|
442
|
-
an insertion preference of before or after the named index.
|
|
455
|
+
This method will go up the view chain, starting with the view that was the
|
|
456
|
+
target of the passed event, looking for a child item. This will become
|
|
457
|
+
the view that is selected by the mouse event.
|
|
458
|
+
|
|
459
|
+
This method only works for mouseDown & mouseUp events. mouseMoved events
|
|
460
|
+
do not have a target.
|
|
461
|
+
|
|
462
|
+
@param {Event} evt An event
|
|
443
463
|
|
|
444
|
-
The default implementation will loop through the item views looking for
|
|
445
|
-
the first view to "switch sides" in the orientation you specify.
|
|
446
464
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
var
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
// if we are a horizontal orientation, look for the first item that
|
|
457
|
-
// will "switch sides" on the x path an the maxY is greater than Y.
|
|
458
|
-
// This assumes you will flow top to bottom, but it should work if you
|
|
459
|
-
// flow LTR or RTL.
|
|
460
|
-
if (orient == SC.HORIZONTAL_ORIENTATION) {
|
|
461
|
-
if (SC.maxY(f) > loc.y) {
|
|
462
|
-
curSide = (SC.maxX(f) < loc.x) ? -1 : 1 ;
|
|
463
|
-
} else curSide = null ;
|
|
464
|
-
|
|
465
|
-
// if we are a vertical orientation, look for the first item that
|
|
466
|
-
// will "swithc sides" on the y path and the maxX is greater than X.
|
|
467
|
-
// This assumes you will flow LTR, but it should work if you flow
|
|
468
|
-
// bottom to top or top to bottom.
|
|
469
|
-
} else {
|
|
470
|
-
if (SC.minX(f) < loc.x) {
|
|
471
|
-
curSide = (SC.maxY(f) < loc.y) ? -1 : 1 ;
|
|
472
|
-
} else curSide = null ;
|
|
473
|
-
}
|
|
465
|
+
itemViewForEvent: function(evt)
|
|
466
|
+
{
|
|
467
|
+
var view = SC.window.firstViewForEvent( evt );
|
|
468
|
+
// work up the view hierarchy to find a match...
|
|
469
|
+
do {
|
|
470
|
+
// item clicked was the ContainerView itself... i.e. the user clicked outside the child items
|
|
471
|
+
// nothing to return...
|
|
472
|
+
if ( view == this ) return null;
|
|
474
473
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// we should insert before or after the view
|
|
480
|
-
if ((lastSide !== null) && (curSide != lastSide)) {
|
|
481
|
-
ret = idx ;
|
|
482
|
-
if (orient == SC.HORIZONTAL_ORIENTATION) {
|
|
483
|
-
if (SC.midX(f) < loc.x) ret++ ;
|
|
484
|
-
} else {
|
|
485
|
-
if (SC.midY(f) < loc.y) ret++ ;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
lastSide =curSide ;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Handle some edge cases
|
|
493
|
-
if ((ret == null) || (ret < 0)) ret = 0 ;
|
|
494
|
-
if (ret > content.length) ret = content.length ;
|
|
474
|
+
// sweet!... the view is not only in the collection, but it says we can hit it.
|
|
475
|
+
// hit it and quit it...
|
|
476
|
+
if ( this.hasItemView(view) && (!view.hitTest || view.hitTest(evt)) ) return view;
|
|
477
|
+
} while ( view = view.get('parentNode') );
|
|
495
478
|
|
|
496
|
-
//
|
|
497
|
-
return
|
|
479
|
+
// nothing was found...
|
|
480
|
+
return null;
|
|
498
481
|
},
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
Called during a drag to show the insertion point. Passed value is the
|
|
504
|
-
item view that you should display the insertion point before. If the
|
|
505
|
-
passed value is null, then you should show the insertion point AFTER that
|
|
506
|
-
last item view returned by the itemViews property.
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
Returns the itemView that represents the passed content object.
|
|
507
486
|
|
|
508
|
-
|
|
509
|
-
|
|
487
|
+
If no item view is currently rendered for the object, this method will
|
|
488
|
+
return null.
|
|
510
489
|
|
|
511
|
-
|
|
490
|
+
@param {Object} obj The content object.
|
|
491
|
+
@returns {SC.View} The item view or null
|
|
492
|
+
*/
|
|
493
|
+
itemViewForContent: function(obj) {
|
|
494
|
+
var key = (obj) ? SC.guidFor(obj) : '0';
|
|
495
|
+
return this._itemViewsByContent[key];
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
Returns the groupView that represents the passed group value.
|
|
512
500
|
|
|
513
|
-
|
|
501
|
+
If no group view is currently rendered for the gorup value, this method
|
|
502
|
+
will return null. If grouping is disabled, this method will also return
|
|
503
|
+
null.
|
|
514
504
|
|
|
515
|
-
@
|
|
505
|
+
@param {Object} value The group value.
|
|
506
|
+
@param {SC.View} The group view or null
|
|
516
507
|
*/
|
|
517
|
-
|
|
518
|
-
|
|
508
|
+
groupViewForGroupValue: function(groupValue) {
|
|
509
|
+
return this._groupViewsByValue[groupValue] ;
|
|
510
|
+
},
|
|
511
|
+
|
|
519
512
|
/**
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
Called during a drag to hide the insertion point. This will be called when the
|
|
523
|
-
user exits the view, cancels the drag or completes the drag. It will not be
|
|
524
|
-
called when the insertion point changes during a drag.
|
|
513
|
+
Returns the groupValue for the passed group view.
|
|
525
514
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
method
|
|
515
|
+
Older-style groupViews expect the group value to be set directly on
|
|
516
|
+
their labelView while newer groupViews expect their groupValue to be set.
|
|
517
|
+
This method takes into account both approaches.
|
|
529
518
|
|
|
530
|
-
@
|
|
519
|
+
@param {SC.View} groupView the group view.
|
|
520
|
+
@returns {Object} the value of the group view or null.
|
|
531
521
|
*/
|
|
532
|
-
|
|
533
|
-
|
|
522
|
+
groupValueForGroupView: function(groupView) {
|
|
523
|
+
if (!groupView) return null ;
|
|
524
|
+
var ret ;
|
|
525
|
+
if (groupView.groupValue === undefined) {
|
|
526
|
+
ret = groupView.get('content') ;
|
|
527
|
+
} else ret = groupView.get('groupValue') ;
|
|
528
|
+
return ret ;
|
|
529
|
+
},
|
|
530
|
+
|
|
534
531
|
/**
|
|
535
|
-
|
|
532
|
+
Expands the index into a range of content objects that have the same
|
|
533
|
+
group value.
|
|
536
534
|
|
|
537
|
-
|
|
538
|
-
|
|
535
|
+
This method searches backward and forward through your content array for
|
|
536
|
+
objects that have the same group value as the object at the index you
|
|
537
|
+
pass in. You can use this method when implementing layoutGroupView to
|
|
538
|
+
determine the range of the content that belongs to the group.
|
|
539
539
|
|
|
540
|
-
|
|
540
|
+
Since this method simply searches through the content array, it is really
|
|
541
|
+
only suitable for content arrays of a few hundred items or less. If you
|
|
542
|
+
expect to have a larger size of content array, then you may need to do
|
|
543
|
+
something custom in your data model to calculate this range in less time.
|
|
544
|
+
|
|
545
|
+
@param {Number} contentIndex index of a content object
|
|
546
|
+
@returns {Range} a range of objects
|
|
541
547
|
*/
|
|
542
|
-
|
|
543
|
-
var
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
var
|
|
547
|
-
var
|
|
548
|
+
groupRangeForContentIndex: function(contentIndex) {
|
|
549
|
+
var groupBy = this.get('groupBy') ;
|
|
550
|
+
if (!groupBy) return { start: contentIndex, length: 1 } ;
|
|
551
|
+
|
|
552
|
+
var min = contentIndex, max = contentIndex ;
|
|
553
|
+
var content = Array.from(this.get('content')) ;
|
|
554
|
+
var len = content.get('length') ;
|
|
555
|
+
var cur = content.objectAt(contentIndex) ;
|
|
556
|
+
var groupValue = (cur) ? cur.get(groupBy) : null ;
|
|
557
|
+
|
|
558
|
+
// find first item at bottom that does not match. add one to get start
|
|
559
|
+
while(--min >= 0) {
|
|
560
|
+
var cur = content.objectAt(min) ;
|
|
561
|
+
var curGroupValue = (cur) ? cur.get(groupBy) : null ;
|
|
562
|
+
if (curGroupValue !== groupValue) break ;
|
|
563
|
+
}
|
|
564
|
+
min++ ;
|
|
548
565
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
var
|
|
553
|
-
|
|
554
|
-
if (!dom) continue ;
|
|
555
|
-
|
|
556
|
-
// save the maxX & maxY. This will be used to trim the size
|
|
557
|
-
// of the ghost view later.
|
|
558
|
-
if (SC.maxX(f) > maxX) maxX = SC.maxX(f) ;
|
|
559
|
-
if (SC.maxY(f) > maxY) maxY = SC.maxY(f) ;
|
|
560
|
-
|
|
561
|
-
// Clone the contents of this node. We should probably apply the
|
|
562
|
-
// computed style to the cloned nodes in order to make sure they match even if the
|
|
563
|
-
// CSS styles do not match. Make sure the items are properly
|
|
564
|
-
// positioned.
|
|
565
|
-
dom = dom.cloneNode(true) ;
|
|
566
|
-
Element.setStyle(dom, { position: "absolute", left: "%@px".fmt(f.x), top: "%@px".fmt(f.y), width: "%@px".fmt(f.width), height: "%@px".fmt(f.height) }) ;
|
|
567
|
-
view.rootElement.appendChild(dom) ;
|
|
566
|
+
// find first item at top that does not match. keep value to calc range
|
|
567
|
+
while(++max < len) {
|
|
568
|
+
var cur = content.objectAt(max) ;
|
|
569
|
+
var curGroupValue = (cur) ? cur.get(groupBy) : null ;
|
|
570
|
+
if (curGroupValue !== groupValue) break ;
|
|
568
571
|
}
|
|
569
572
|
|
|
570
|
-
|
|
571
|
-
|
|
573
|
+
return { start: min, length: max-min } ;
|
|
574
|
+
},
|
|
572
575
|
|
|
573
|
-
|
|
576
|
+
// Determines the group value at a specified index.
|
|
577
|
+
groupValueAtContentIndex: function(contentIndex) {
|
|
578
|
+
var groupBy = this.get('groupBy') ;
|
|
579
|
+
var content = Array.from(this.get('content')).objectAt(contentIndex) ;
|
|
580
|
+
return (groupBy && content && content.get) ? content.get(groupBy) : null;
|
|
574
581
|
},
|
|
582
|
+
|
|
583
|
+
// ......................................
|
|
584
|
+
// GENERATING CHILDREN
|
|
585
|
+
//
|
|
575
586
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
587
|
+
/**
|
|
588
|
+
Update the itemViews in the receiver to match the currently visible
|
|
589
|
+
content objects. Normally this method assumes the content objects
|
|
590
|
+
themselves have not changed and only updates the views if the range of
|
|
591
|
+
visible content has changed. If you pass true to the fullUpdate property,
|
|
592
|
+
then the entire set of itemViews will be revalidated in case any content
|
|
593
|
+
objects have changed.
|
|
579
594
|
|
|
580
|
-
|
|
581
|
-
|
|
595
|
+
@param {Bool} fullUpdate (Optional) if set to true, assumes content has
|
|
596
|
+
changed and will perform a full update.
|
|
597
|
+
|
|
598
|
+
*/
|
|
599
|
+
updateChildren: function(fullUpdate) {
|
|
582
600
|
|
|
583
|
-
|
|
584
|
-
this.flushFrameCache();
|
|
585
|
-
|
|
586
|
-
// First, get the selection to drag. Drag an array of selected
|
|
587
|
-
// items appearing in this collection, in the order of the
|
|
588
|
-
// collection.
|
|
589
|
-
var content = this.get('content') || [] ;
|
|
590
|
-
var dragContent = this.get('selection').sort(function(a,b) {
|
|
591
|
-
a = content.indexOf(a) ; b = content.indexOf(b) ;
|
|
592
|
-
return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
|
|
593
|
-
});
|
|
601
|
+
var f ;
|
|
594
602
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
event: this._mouseDownEvent,
|
|
602
|
-
source: this,
|
|
603
|
-
dragView: view,
|
|
604
|
-
ghost: NO,
|
|
605
|
-
slideBack: YES,
|
|
606
|
-
data: { "_mouseDownContent": dragContent }
|
|
607
|
-
}) ;
|
|
608
|
-
|
|
609
|
-
// Also use this opportunity to clean up since mouseUp won't
|
|
610
|
-
// get called.
|
|
611
|
-
this._cleanupMouseDown() ;
|
|
612
|
-
this._lastInsertionIndex = null ;
|
|
613
|
-
}
|
|
614
|
-
},
|
|
615
|
-
|
|
616
|
-
// Drop Source.
|
|
617
|
-
dragEntered: function(drag, evt) {
|
|
618
|
-
if ((drag.get('source') == this) && this.get('canReorderContent')) {
|
|
619
|
-
return SC.DRAG_MOVE ;
|
|
620
|
-
} else {
|
|
621
|
-
return SC.DRAG_NONE ;
|
|
603
|
+
// if the collection is not presently visible in the window, then there is
|
|
604
|
+
// really nothing to do here. Just mark the view as dirty and return.
|
|
605
|
+
if (!this.get('isVisibleInWindow')) {
|
|
606
|
+
this.set('isDirty', true) ;
|
|
607
|
+
this._needsFullUpdate = this._needsFullUpdate || fullUpdate ;
|
|
608
|
+
return;
|
|
622
609
|
}
|
|
623
|
-
},
|
|
624
|
-
|
|
625
|
-
// If reordering is allowed, then show insertion point
|
|
626
|
-
dragUpdated: function(drag, evt) {
|
|
627
|
-
if (this.get('canReorderContent')) {
|
|
628
|
-
var loc = drag.get('location') ;
|
|
629
|
-
loc = this.convertFrameFromView(loc, null) ;
|
|
630
|
-
|
|
631
|
-
// get the insertion index for this location. This can be computed
|
|
632
|
-
// by a subclass using whatever method. This method is not expected to
|
|
633
|
-
// do any data valdidation, just to map the location to an insertion index.
|
|
634
|
-
var ret = this.insertionIndexForLocation(loc) ;
|
|
635
|
-
|
|
636
|
-
// now that we have an index, find the nearest index that we can actually
|
|
637
|
-
// insert at, or do not allow.
|
|
638
|
-
var objects = (drag.source == this) ? (drag.dataForType('_mouseDownContent') || []) : [];
|
|
639
|
-
var content = this.get('content') || [] ;
|
|
640
|
-
|
|
641
|
-
// if the insertion index is in between two items in the drag itself, then this is
|
|
642
|
-
// not allowed. Either use the last insertion index or find the first index that is not
|
|
643
|
-
// in between selections.
|
|
644
|
-
var isPreviousInDrag = (ret > 0) ? objects.indexOf(content.objectAt(ret-1)) : -1 ;
|
|
645
|
-
var isNextInDrag = (ret < content.get('length')-1) ? objects.indexOf(content.objectAt(ret)) : -1 ;
|
|
646
|
-
if (isPreviousInDrag>=0 && isNextInDrag>=0) {
|
|
647
|
-
if (this._lastInsertionIndex == null) {
|
|
648
|
-
while((ret > 0) && (objects.indexOf(content.objectAt(ret)) >= 0)) ret-- ;
|
|
649
|
-
} else ret = this._lastInsertionIndex ;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Now that we have verified that, check to see if a drop is allowed in the
|
|
653
|
-
// insertion index with the delegate.
|
|
654
|
-
// TODO
|
|
655
610
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
this._lastInsertionIndex = ret ;
|
|
661
|
-
|
|
611
|
+
if (SC.BENCHMARK_UPDATE_CHILDREN) {
|
|
612
|
+
var bkey = '%@.updateChildren(%@)'.fmt(this, (fullUpdate) ? 'FULL' : 'FAST') ;
|
|
613
|
+
SC.Benchmark.start(bkey);
|
|
662
614
|
}
|
|
663
|
-
return SC.DRAG_MOVE;
|
|
664
|
-
},
|
|
665
|
-
|
|
666
|
-
dragExited: function() {
|
|
667
|
-
this.hideInsertionPoint() ;
|
|
668
|
-
this._lastInsertionIndex = null ;
|
|
669
|
-
},
|
|
670
|
-
|
|
671
|
-
dragEnded: function() {
|
|
672
|
-
this.hideInsertionPoint() ;
|
|
673
|
-
this._lastInsertionIndex = null ;
|
|
674
|
-
},
|
|
675
|
-
|
|
676
|
-
prepareForDragOperation: function(op, drag) {
|
|
677
|
-
return SC.DRAG_ANY;
|
|
678
|
-
},
|
|
679
|
-
|
|
680
|
-
performDragOperation: function(op, drag) {
|
|
681
|
-
|
|
682
|
-
SC.Benchmark.start('%@ performDragOperation'.fmt(this._guid)) ;
|
|
683
|
-
|
|
684
|
-
var loc = drag.get('location') ;
|
|
685
|
-
loc = this.convertFrameFromView(loc, null) ;
|
|
686
|
-
|
|
687
|
-
// if op is MOVE or COPY, add item to view.
|
|
688
|
-
var objects = drag.dataForType('_mouseDownContent') ;
|
|
689
|
-
if (objects && (op == SC.DRAG_MOVE)) {
|
|
690
|
-
|
|
691
|
-
// find the index to for the new insertion
|
|
692
|
-
var idx = this.insertionIndexForLocation(loc) ;
|
|
693
615
|
|
|
694
|
-
|
|
695
|
-
content.beginPropertyChanges(); // suspend notifications
|
|
696
|
-
|
|
697
|
-
// debugger ;
|
|
698
|
-
// find the old index and remove it.
|
|
699
|
-
var objectsIdx = objects.get('length') ;
|
|
700
|
-
while(--objectsIdx >= 0) {
|
|
701
|
-
var obj = objects.objectAt(objectsIdx) ;
|
|
702
|
-
var old = content.indexOf(obj) ;
|
|
703
|
-
if (old >= 0) content.removeAt(old) ;
|
|
704
|
-
if ((old >= 0) && (old < idx)) idx--; //adjust idx
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// now insert objects at new location
|
|
708
|
-
content.replace(idx, 0, objects) ;
|
|
709
|
-
content.endPropertyChanges(); // restart notifications
|
|
710
|
-
}
|
|
616
|
+
//console.log('updateChildren') ;
|
|
711
617
|
|
|
712
|
-
|
|
713
|
-
console.log(SC.Benchmark.report()) ;
|
|
714
|
-
|
|
715
|
-
return SC.DRAG_MOVE;
|
|
716
|
-
},
|
|
717
|
-
|
|
718
|
-
concludeDragOperation: function(op, drag) {
|
|
719
|
-
this.hideInsertionPoint() ;
|
|
720
|
-
this._lastInsertionIndex = null ;
|
|
721
|
-
},
|
|
618
|
+
this.beginPropertyChanges() ; // avoid sending notifications
|
|
722
619
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
Once it has finished running, this method will also call your layoutChildViewsFor()
|
|
738
|
-
method if you have implemented it.
|
|
620
|
+
// STEP 1: Update frame size if needed. Required to compute the
|
|
621
|
+
// clippingFrame.
|
|
622
|
+
var f ;
|
|
623
|
+
if ((f = this.computeFrame()) && !SC.rectsEqual(f, this.get('frame'))) {
|
|
624
|
+
var parent = this.get('parentNode') ;
|
|
625
|
+
if (parent) parent.viewFrameWillChange() ;
|
|
626
|
+
this.set('frame', f) ;
|
|
627
|
+
if (parent) parent.viewFrameDidChange() ;
|
|
628
|
+
|
|
629
|
+
if ((f = this.computeFrame()) && !SC.rectsEqual(f, this.get('frame'))) {
|
|
630
|
+
this.set('frame', f) ;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
739
633
|
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
updateChildren: function()
|
|
745
|
-
{
|
|
746
|
-
var el = this.containerElement || this.rootElement;
|
|
634
|
+
// Save the current clipping frame. If the frame methods are called again
|
|
635
|
+
// later but the frame has not actually changed, we don't want to run
|
|
636
|
+
// updateChildren again.
|
|
637
|
+
var clippingFrame = this._lastClippingFrame = this.get('clippingFrame') ;
|
|
747
638
|
|
|
748
|
-
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
//
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
// group views for each distinct group it encounters and then has it
|
|
787
|
-
// render child views in each item.
|
|
788
|
-
if (groupBy)
|
|
789
|
-
{
|
|
790
|
-
var loc = 0;
|
|
791
|
-
var group = this.firstChild;
|
|
792
|
-
while (group || (loc < content.get('length')))
|
|
793
|
-
{
|
|
794
|
-
var groupValue = (loc < content.get('length')) ? content.objectAt(loc).get(groupBy) : null;
|
|
795
|
-
|
|
796
|
-
// we are out of content, just remove any remaining groups (including
|
|
797
|
-
// child nodes)
|
|
798
|
-
if (loc >= content.get('length')) {
|
|
799
|
-
if (group) {
|
|
800
|
-
// this will clear out the item views in the group.
|
|
801
|
-
loc = this.updateChildrenInGroup(group.itemView, content, loc, groupBy, null);
|
|
802
|
-
// now remove the group.
|
|
803
|
-
var prev = group.previousSibling ;
|
|
804
|
-
this.removeChild(group) ;
|
|
805
|
-
group = prev ;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// otherwise, make sure the current group matches the next group. If
|
|
809
|
-
// it doesn't, then add a new group.
|
|
810
|
-
} else if (!group || (group.get('groupValue') != groupValue)) {
|
|
811
|
-
|
|
812
|
-
// create group view.
|
|
813
|
-
var newGroup = this.exampleGroupView.viewFor(null) ;
|
|
814
|
-
newGroup.owner = this ;
|
|
815
|
-
newGroup.set('groupValue',groupValue) ;
|
|
816
|
-
|
|
817
|
-
// add group label view.
|
|
818
|
-
if (newGroup.labelView) newGroup.labelView.set('content',groupValue);
|
|
819
|
-
|
|
820
|
-
// add item views to group.
|
|
821
|
-
loc = this.updateChildrenInGroup(newGroup.itemView,content,loc,
|
|
822
|
-
groupBy, groupValue) ;
|
|
823
|
-
|
|
824
|
-
// add the new group at this point
|
|
825
|
-
this.insertBefore(newGroup,group) ;
|
|
826
|
-
group = newGroup ;
|
|
827
|
-
|
|
828
|
-
// otherwise, if the current group does match the next group, just
|
|
829
|
-
// update its child nodes.
|
|
830
|
-
} else {
|
|
831
|
-
loc = this.updateChildrenInGroup(group.itemView,content,loc,
|
|
832
|
-
groupBy, groupValue) ;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// go to the next group. group will be nil if the first group was
|
|
836
|
-
// removed.
|
|
837
|
-
group = (group) ? group.nextSibling : this.firstChild ;
|
|
639
|
+
// STEP 2: Calculate the new range of content to display in
|
|
640
|
+
// the clipping frame. Determine if we need to do a full update or
|
|
641
|
+
// not.
|
|
642
|
+
|
|
643
|
+
var range = this.contentRangeInFrame(clippingFrame) ;
|
|
644
|
+
var content = Array.from(this.get('content'));
|
|
645
|
+
|
|
646
|
+
//make sure the range isn't greater than the content length
|
|
647
|
+
//this will prevent trying to render items that aren't really there.
|
|
648
|
+
range.length = Math.min(SC.maxRange(range), content.get('length')) - range.start ;
|
|
649
|
+
|
|
650
|
+
var nowShowingRange = this.get('nowShowingRange') ;
|
|
651
|
+
fullUpdate = fullUpdate || (SC.intersectRanges(range, nowShowingRange).length <= 0) ;
|
|
652
|
+
this.set('nowShowingRange', range) ;
|
|
653
|
+
|
|
654
|
+
// STEP 3: Update item views.
|
|
655
|
+
var groupBy = this.get('groupBy') ;
|
|
656
|
+
var didChange = false ;
|
|
657
|
+
|
|
658
|
+
// If this is a fullUpdate, then rebuild the itemViewsByContent hash
|
|
659
|
+
// from scratch. This is necessary of the content of the visible range
|
|
660
|
+
// might have changed.
|
|
661
|
+
if (fullUpdate) {
|
|
662
|
+
|
|
663
|
+
var itemViewsByContent = {} ; // this will replace the current hash.
|
|
664
|
+
|
|
665
|
+
// iterate through all of the views and insert them. If the view
|
|
666
|
+
// already exists, it will simply be reused.
|
|
667
|
+
var idx = SC.maxRange(range) ;
|
|
668
|
+
while(--idx >= range.start) {
|
|
669
|
+
var c = content.objectAt(idx) ;
|
|
670
|
+
var key = SC.guidFor(c) ;
|
|
671
|
+
var itemView = this._insertItemViewFor(c, groupBy, idx) ;
|
|
672
|
+
|
|
673
|
+
// add item view to new hash and remove from old hash.
|
|
674
|
+
itemViewsByContent[key] = itemView;
|
|
675
|
+
|
|
676
|
+
delete this._itemViewsByContent[key];
|
|
838
677
|
}
|
|
839
678
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
this._cachedParent.insertBefore(el,this._cachedSibling) ;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
this.updateSelectionStates() ;
|
|
851
|
-
this.flushFrameCache() ;
|
|
852
|
-
this.set('isDirty',false);
|
|
853
|
-
SC.Benchmark.end('%@: updateChildren'.fmt(this._guid)) ;
|
|
854
|
-
},
|
|
855
|
-
|
|
856
|
-
/**
|
|
857
|
-
@private
|
|
858
|
-
|
|
859
|
-
Step through the child nodes in the parent to match them to
|
|
860
|
-
the content array, starting at the passed location. It will go until it
|
|
861
|
-
runs out of content objects or until the content no longer belong to the
|
|
862
|
-
group indicated.
|
|
863
|
-
*/
|
|
864
|
-
updateChildrenInGroup: function(parent,content,loc,groupBy,groupValue) {
|
|
865
|
-
// cacheing content.get('length') for optimization.
|
|
866
|
-
var contentCount = content.get('length');
|
|
867
|
-
var child = parent.firstChild;
|
|
868
|
-
var inGroup = true ;
|
|
869
|
-
|
|
870
|
-
if (!this._itemViews) this._itemViews = {};
|
|
871
|
-
var itemViewsDidChange = false;
|
|
872
|
-
|
|
873
|
-
this.updateComputedViewHeight(parent);
|
|
874
|
-
|
|
875
|
-
// if we aren't rendering groups, then this can expire.
|
|
876
|
-
var expired = false;
|
|
877
|
-
var canExpire = !groupBy && loc == 0 ;
|
|
878
|
-
if (canExpire)
|
|
879
|
-
{
|
|
880
|
-
loc = this._lastRenderLoc ;
|
|
881
|
-
child = this._lastRenderChild || child;
|
|
882
|
-
this._resetRenderClock();
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
var firstChild = null ;
|
|
886
|
-
|
|
887
|
-
// save the first child to be modified. This will be
|
|
888
|
-
// passed to the layout method.
|
|
889
|
-
var firstModifiedChild = null;
|
|
890
|
-
|
|
891
|
-
while (child || (inGroup && (loc < contentCount) && !expired)) {
|
|
679
|
+
// Now iterate through the old hash. Any left over item views should
|
|
680
|
+
// be removed.
|
|
681
|
+
for(var key in this._itemViewsByContent) {
|
|
682
|
+
if (!this._itemViewsByContent.hasOwnProperty(key)) continue ;
|
|
683
|
+
var itemView = this._itemViewsByContent[key] ;
|
|
684
|
+
this._removeItemView(itemView, groupBy) ;
|
|
685
|
+
} ;
|
|
892
686
|
|
|
893
|
-
//
|
|
894
|
-
|
|
687
|
+
// Swap out remaining content items.
|
|
688
|
+
this._itemViewsByContent = itemViewsByContent ;
|
|
689
|
+
didChange = true;
|
|
895
690
|
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
691
|
+
// If a fullUpdate is not required, then we assume no content has changed
|
|
692
|
+
// and we just need to add or remove some views to bring the ranges up
|
|
693
|
+
// to date.
|
|
694
|
+
} else {
|
|
695
|
+
// Find changed range at the top. Note that the length here may be
|
|
696
|
+
// negative. Negative means views should be removed.
|
|
697
|
+
var start = range.start ;
|
|
698
|
+
var length = (nowShowingRange.start - start) ;
|
|
699
|
+
if (length != 0) {
|
|
700
|
+
this._insertOrRemoveItemViewsInRange(start, length, groupBy) ;
|
|
701
|
+
didChange = true ;
|
|
901
702
|
}
|
|
902
703
|
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
child.set('content',null) ;
|
|
911
|
-
}
|
|
912
|
-
var prev = child.previousSibling ;
|
|
913
|
-
parent.removeChild(child) ;
|
|
914
|
-
if (this._itemViews[SC.getGUID(child)]) {
|
|
915
|
-
itemViewsDidChange = true ;
|
|
916
|
-
delete this._itemViews[SC.getGUID(child)];
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
child = prev;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
// otherwise, make sure the current child matches the content object.
|
|
923
|
-
// if it doesn't, get the right view (or create it) and insert it here.
|
|
924
|
-
} else if (!child || (child.get('content') != cur)) {
|
|
925
|
-
|
|
926
|
-
// find the correct view. If it doesn't exist, create it.
|
|
927
|
-
var newChild = this._viewsForContent[SC.getGUID(cur)] ;
|
|
928
|
-
if (!newChild) {
|
|
929
|
-
newChild = this.exampleView.viewFor(null) ;
|
|
930
|
-
newChild.owner = this ;
|
|
931
|
-
newChild._isChildView = true ;
|
|
932
|
-
newChild.set('content',cur) ;
|
|
933
|
-
this._viewsForContent[SC.getGUID(cur)] = newChild ;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// add the view at this point in the hierarchy and make the new child
|
|
937
|
-
// the current child.
|
|
938
|
-
parent.insertBefore(newChild,child);
|
|
939
|
-
this._itemViews[SC.getGUID(newChild)] = newChild;
|
|
940
|
-
itemViewsDidChange = true;
|
|
941
|
-
if (!firstModifiedChild) firstModifiedChild = newChild ;
|
|
942
|
-
child = newChild;
|
|
704
|
+
// Find the changed range at the bottom. Note that the length here may
|
|
705
|
+
// also be negative. Negative means views should be removed.
|
|
706
|
+
var start = SC.maxRange(nowShowingRange) ;
|
|
707
|
+
var length = SC.maxRange(range) - start ;
|
|
708
|
+
if (length != 0) {
|
|
709
|
+
this._insertOrRemoveItemViewsInRange(start, length, groupBy) ;
|
|
710
|
+
didChange = true ;
|
|
943
711
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Clean out some cached items and notify their changes.
|
|
715
|
+
if (didChange) {
|
|
716
|
+
this._flushZombieGroupViews() ;
|
|
717
|
+
this.updateSelectionStates() ;
|
|
950
718
|
|
|
951
|
-
|
|
952
|
-
|
|
719
|
+
this._itemViews = null ;
|
|
720
|
+
this.notifyPropertyChange('itemViews') ;
|
|
953
721
|
|
|
954
|
-
|
|
722
|
+
this._groupViews = null ;
|
|
723
|
+
this.notifyPropertyChange('groupViews') ;
|
|
955
724
|
}
|
|
956
725
|
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (expired && (loc < contentCount)) {
|
|
960
|
-
this._lastRenderLoc = loc ;
|
|
961
|
-
this._lastRenderChild = child ;
|
|
962
|
-
setTimeout(this.updateChildren.bind(this),1) ; // do more later.
|
|
963
|
-
} else {
|
|
964
|
-
this._resetExpiredRender();
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// now let the collection view layout the views that changed (if
|
|
968
|
-
// it is implemented.)
|
|
969
|
-
if (firstModifiedChild && this.layoutChildViewsFor) {
|
|
970
|
-
var el = this.containerElement || this.rootElement;
|
|
971
|
-
if (this._cachedParent) {
|
|
972
|
-
this._cachedParent.insertBefore(el,this._cachedSibling);
|
|
973
|
-
}
|
|
974
|
-
this.layoutChildViewsFor(parent, firstModifiedChild);
|
|
975
|
-
if (this._cachedParent) {
|
|
976
|
-
this._cachedParent.removeChild(el);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
// notify itemViews change if applicable.
|
|
981
|
-
if (itemViewsDidChange) this.propertyDidChange('itemViews');
|
|
726
|
+
// Recache frames just in case this changed the scroll height.
|
|
727
|
+
this.recacheFrames() ;
|
|
982
728
|
|
|
983
|
-
return loc;
|
|
984
|
-
},
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
/**
|
|
988
|
-
Returns the itemView that represents the passed content object.
|
|
989
729
|
|
|
990
|
-
|
|
991
|
-
|
|
730
|
+
// Set this to true once children have been rendered. Whenever the
|
|
731
|
+
// content changes, we don't want resize or clipping frame changes to
|
|
732
|
+
// cause a refresh until the content has been rendered for the first time.
|
|
733
|
+
this._hasChildren = range.length>0 ;
|
|
992
734
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
itemViewForContent: function( obj )
|
|
997
|
-
{
|
|
998
|
-
return this._viewsForContent[SC.getGUID(obj)];
|
|
735
|
+
this.set('isDirty',false);
|
|
736
|
+
this.endPropertyChanges() ;
|
|
737
|
+
if (SC.BENCHMARK_UPDATE_CHILDREN) SC.Benchmark.end(bkey);
|
|
999
738
|
},
|
|
1000
739
|
|
|
1001
740
|
/**
|
|
1002
741
|
Rebuild all the child item views in the collection view.
|
|
1003
742
|
|
|
1004
|
-
This will remove all the child views from the collection view and rebuild
|
|
1005
|
-
from scratch. This method is generally expensive, but if you have
|
|
1006
|
-
substantial number of changes to the content array
|
|
1007
|
-
|
|
743
|
+
This will remove all the child views from the collection view and rebuild
|
|
744
|
+
them from scratch. This method is generally expensive, but if you have
|
|
745
|
+
made a substantial number of changes to the content array, this may be the
|
|
746
|
+
most efficient way to perform the update.
|
|
1008
747
|
|
|
1009
|
-
In general the collection view will automatically keep the item views in
|
|
1010
|
-
with the content objects for you. You should not need to call this
|
|
1011
|
-
very often.
|
|
748
|
+
In general the collection view will automatically keep the item views in
|
|
749
|
+
sync with the content objects for you. You should not need to call this
|
|
750
|
+
method very often.
|
|
1012
751
|
|
|
1013
752
|
@returns {void}
|
|
1014
753
|
*/
|
|
1015
754
|
rebuildChildren: function() {
|
|
1016
|
-
|
|
1017
|
-
this.
|
|
1018
|
-
|
|
1019
|
-
|
|
755
|
+
|
|
756
|
+
this.beginPropertyChanges() ;
|
|
757
|
+
|
|
758
|
+
// iterate through itemViews and remove them
|
|
759
|
+
while(this._itemViewRoot) this._removeItemViewFromChain(this._itemViewRoot) ;
|
|
760
|
+
|
|
761
|
+
// iterate through groupViews and remove them .. if grouping is disabled,
|
|
762
|
+
// _groupViewRoot will be null anyway.
|
|
763
|
+
while(this._groupViewRoot) this._removeGroupView(this._groupViewRoot) ;
|
|
764
|
+
|
|
765
|
+
// now updateChildren.
|
|
766
|
+
this._hasChildren = false ;
|
|
767
|
+
this.updateChildren() ;
|
|
768
|
+
|
|
769
|
+
this.endPropertyChanges() ;
|
|
1020
770
|
},
|
|
1021
|
-
|
|
771
|
+
|
|
1022
772
|
/**
|
|
1023
773
|
Update the selection state for the item views to reflect the selection array.
|
|
1024
774
|
|
|
@@ -1031,120 +781,480 @@ SC.CollectionView = SC.View.extend(
|
|
|
1031
781
|
updateSelectionStates: function() {
|
|
1032
782
|
if (!this._itemViews) return ;
|
|
1033
783
|
var selection = this.get('selection') || [];
|
|
1034
|
-
|
|
784
|
+
|
|
1035
785
|
// First, for efficiency, turn the selection into a hash by GUID. This
|
|
1036
786
|
// way, we'll only have to perform a linear search over the children.
|
|
1037
|
-
|
|
1038
|
-
var
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
787
|
+
// This hash is cached and flushed each time the selection changes.
|
|
788
|
+
var selectionHash = this._selectionHash ;
|
|
789
|
+
if (!selectionHash) {
|
|
790
|
+
selectionHash = {} ;
|
|
791
|
+
var idx = selection.get('length') ;
|
|
792
|
+
while(--idx >= 0) {
|
|
793
|
+
var cur = selection.objectAt(idx) ;
|
|
794
|
+
var key = SC.guidFor(cur) ;
|
|
795
|
+
selectionHash[key] = true ;
|
|
796
|
+
}
|
|
797
|
+
this._selectionHash = selectionHash ;
|
|
1042
798
|
}
|
|
1043
799
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
var
|
|
1048
|
-
var
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
var childIsSelected = selectionHash[guid] ? true : false;
|
|
1052
|
-
|
|
1053
|
-
// If the child's state has changed from before, set it to the new
|
|
1054
|
-
// state. Otherwise, don't bother setting the state to the same value
|
|
1055
|
-
// it used to have.
|
|
1056
|
-
if( childIsSelected != child.get('isSelected') ) {
|
|
1057
|
-
if (child.set) child.set('isSelected', childIsSelected);
|
|
800
|
+
// Iterate over the item views and set their selection property.
|
|
801
|
+
for(var key in this._itemViewsByContent) {
|
|
802
|
+
if (!this._itemViewsByContent.hasOwnProperty(key)) continue ;
|
|
803
|
+
var itemView = this._itemViewsByContent[key] ;
|
|
804
|
+
var isSelected = (key) ? selectionHash[key] : false ;
|
|
805
|
+
if (itemView.get('isSelected') != isSelected) {
|
|
806
|
+
itemView.set('isSelected', isSelected) ;
|
|
1058
807
|
}
|
|
1059
808
|
}
|
|
1060
809
|
},
|
|
1061
810
|
|
|
1062
|
-
// layoutChildViewsFor: function(parentView, startingView) { return false; },
|
|
1063
811
|
|
|
812
|
+
/**
|
|
813
|
+
Calls updateChildren whenever the view is resized, unless you have not
|
|
814
|
+
implemented custom layout or incremental rendering.
|
|
815
|
+
|
|
816
|
+
UPDATE:
|
|
817
|
+
-- add/remove any children as needed
|
|
818
|
+
-- update layout on all itemViews unless you have a more efficient
|
|
819
|
+
*/
|
|
1064
820
|
resizeChildrenWithOldSize: function(oldSize) {
|
|
1065
|
-
if (this.
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
arguments.callee.base.apply(this,arguments) ;
|
|
1069
|
-
}
|
|
821
|
+
if (!this._hasChildren) return ;
|
|
822
|
+
this.updateChildren() ; // add/remove any new views.
|
|
823
|
+
this.layoutResize() ; // perform layout on all of the views if needed.
|
|
1070
824
|
},
|
|
1071
|
-
|
|
1072
|
-
_firstUpdate: true,
|
|
1073
|
-
|
|
1074
|
-
_lastRenderLoc: 0,
|
|
1075
|
-
_renderStart: null,
|
|
1076
|
-
_resetRenderClock: function() { this._renderStart = new Date().getTime(); },
|
|
1077
825
|
|
|
1078
|
-
|
|
1079
|
-
|
|
826
|
+
/**
|
|
827
|
+
Whenever your clipping frame changes, determine new range to display. If
|
|
828
|
+
new range is a change, then it will update the children and relayout.
|
|
829
|
+
|
|
830
|
+
UPDATE:
|
|
831
|
+
-- add/remove any children as needed
|
|
832
|
+
-- update layout on added children only
|
|
833
|
+
*/
|
|
834
|
+
clippingFrameDidChange: function() {
|
|
835
|
+
if (!this._hasChildren) return ;
|
|
836
|
+
SC.Benchmark.start('%@.clippingFrameDidChange'.fmt(this.toString())) ;
|
|
837
|
+
if (!SC.rectsEqual(this._lastClippingFrame, this.get('clippingFrame'))) {
|
|
838
|
+
if (this._hasChildren) this.updateChildren() ;
|
|
839
|
+
}
|
|
840
|
+
SC.Benchmark.end('%@.clippingFrameDidChange'.fmt(this.toString())) ;
|
|
1080
841
|
},
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
Override to return the computed frame dimensions of the collection view.
|
|
845
|
+
|
|
846
|
+
These dimensions are automatically applied at the end of a call to
|
|
847
|
+
updateChildren() if they change at all. This method is critical for
|
|
848
|
+
support of incremental rendering.
|
|
1081
849
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
return ((new Date().getTime()) - this._renderStart) > max ;
|
|
1086
|
-
},
|
|
850
|
+
@returns {Rect} width and/or height you want this collection view to have.
|
|
851
|
+
*/
|
|
852
|
+
computeFrame: function() { return null; },
|
|
1087
853
|
|
|
1088
854
|
/**
|
|
1089
855
|
Override to return the range of items to render for a given frame.
|
|
856
|
+
|
|
857
|
+
You can override this method to implement support for incremenetal
|
|
858
|
+
rendering. The range you return here will be used to limit the number of
|
|
859
|
+
actual item views that are created by the collection view.
|
|
1090
860
|
|
|
1091
|
-
The
|
|
1092
|
-
created for the collection view. The passed frame is relative to the total frame
|
|
1093
|
-
of the groupView.
|
|
1094
|
-
|
|
1095
|
-
You should override this method if you want to support incremental rendering.
|
|
1096
|
-
The default implementation does nothing.
|
|
1097
|
-
|
|
1098
|
-
@param {SC.View} groupView The group view the requested items belong to. If
|
|
1099
|
-
grouping is not used, this will always be null.
|
|
1100
|
-
|
|
1101
|
-
@param {Frame} frame The frame you should use to determine the range.
|
|
861
|
+
@param {Rect} frame The frame you should use to determine the range.
|
|
1102
862
|
|
|
1103
|
-
@returns {Range} A hash that indicates the range of content objects to
|
|
863
|
+
@returns {Range} A hash that indicates the range of content objects to
|
|
864
|
+
render. ({ start: X, length: Y })
|
|
1104
865
|
*/
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
866
|
+
contentRangeInFrame: function(frame) {
|
|
867
|
+
var content = this.get('content') ;
|
|
868
|
+
var len = ((content && content.get) ? content.get('length') : 0) || 0 ;
|
|
869
|
+
return { start: 0, length: len };
|
|
870
|
+
},
|
|
1109
871
|
|
|
1110
|
-
|
|
1111
|
-
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
This method is called whenever a group view is added or during the
|
|
875
|
+
layoutResize() method. You should use this method to size and position
|
|
876
|
+
the group view.
|
|
877
|
+
|
|
878
|
+
The included contentIndexHint can be used to help you determine the range
|
|
879
|
+
of content that should be included in the group. If you are renderings a
|
|
880
|
+
list of items 100 or less, you can get the range of content belonging to
|
|
881
|
+
the group using the contentRangeForGroup() method. If you are managing
|
|
882
|
+
a much larger set of content, you should probably implement your own
|
|
883
|
+
data model.
|
|
884
|
+
|
|
885
|
+
Your layout method should can optionally also use the firstLayout to
|
|
886
|
+
further optimize itself. Normally, you will want to only change a view's
|
|
887
|
+
actual frame if it does not match your calculated size. However, if
|
|
888
|
+
firstLayout is true, you can simply set the new layout without checking
|
|
889
|
+
first.
|
|
890
|
+
|
|
891
|
+
@param {SC.View} groupView the view to size and position.
|
|
892
|
+
@param {Object} groupValue the value the groupView represents.
|
|
893
|
+
@param {Number} contentIndexHint the index of a content object.
|
|
894
|
+
@param {Bool} firstLayout True if this is the first the view has been laid out.
|
|
895
|
+
|
|
896
|
+
*/
|
|
897
|
+
layoutGroupView: function(groupView, groupValue, contentIndexHint, firstLayout) {
|
|
898
|
+
|
|
899
|
+
},
|
|
1112
900
|
|
|
1113
|
-
|
|
1114
|
-
|
|
901
|
+
/**
|
|
902
|
+
This method is called whenever an itemView is added or during the
|
|
903
|
+
layoutResize() method. You should use this method to size and position
|
|
904
|
+
the itemView.
|
|
1115
905
|
|
|
1116
|
-
@
|
|
906
|
+
@param {SC.View} itemViewthe item view to layout
|
|
907
|
+
@param {Number} contentIndex the index of the content this layout represents.
|
|
908
|
+
@param {Bool} firstLayout true if this is the first time it has been laid out.
|
|
1117
909
|
*/
|
|
1118
|
-
|
|
910
|
+
layoutItemView: function(itemView, contentIndex, firstLayout) {
|
|
911
|
+
|
|
912
|
+
},
|
|
1119
913
|
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
914
|
+
/**
|
|
915
|
+
This method is called whenever the view is resized. The default
|
|
916
|
+
implementation will simply iterate through the visible content range and
|
|
917
|
+
call layoutItemView() and layoutGroupView() on all the views.
|
|
918
|
+
|
|
919
|
+
If you would like to provide a more efficient method for updating the
|
|
920
|
+
layout on a resize, you could override this method and do the iterating
|
|
921
|
+
yourself.
|
|
922
|
+
*/
|
|
923
|
+
layoutResize: function() {
|
|
924
|
+
if (!this._hasChildren) return ; // ignore calls before first render
|
|
925
|
+
var nowShowingRange = this.get('nowShowingRange') ;
|
|
926
|
+
var groupBy = this.get('groupBy') ;
|
|
927
|
+
var groupValue = undefined ;
|
|
928
|
+
var content = this.get('content') || [] ;
|
|
929
|
+
|
|
930
|
+
var idx = SC.maxRange(nowShowingRange) ;
|
|
931
|
+
while(--idx >= nowShowingRange.start) {
|
|
932
|
+
var cur = content.objectAt(idx) ;
|
|
933
|
+
var itemView = this.itemViewForContent(cur) ;
|
|
934
|
+
|
|
935
|
+
// should never happen, but recover just in case.
|
|
936
|
+
if (!itemView) continue ;
|
|
937
|
+
|
|
938
|
+
// if grouping is enabled, get the group value and layout based on that.
|
|
939
|
+
if (groupBy && ((curGroupValue = (cur) ? cur.get(groupBy) : null) !== groupValue)) {
|
|
940
|
+
var groupView = this.groupViewForGroupValue(groupValue) ;
|
|
941
|
+
if (groupView) {
|
|
942
|
+
this.layoutGroupView(groupView, groupValue, idx, false) ;
|
|
943
|
+
}
|
|
1128
944
|
}
|
|
945
|
+
|
|
946
|
+
// now layout the itemView itself.
|
|
947
|
+
this.layoutItemView(itemView, idx, false) ;
|
|
1129
948
|
}
|
|
1130
949
|
},
|
|
950
|
+
|
|
1131
951
|
|
|
1132
|
-
//
|
|
1133
|
-
//
|
|
1134
|
-
|
|
952
|
+
// Ordered array of item views currently on display. This array
|
|
953
|
+
// is reset whenever the item views are regenerated.
|
|
954
|
+
_itemViews: null,
|
|
1135
955
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
956
|
+
// Ordered array of group views currently in the display. This array is
|
|
957
|
+
// reset whenever the group views are regenerated.
|
|
958
|
+
_groupViews: null,
|
|
959
|
+
|
|
960
|
+
// Most recent content range on display.
|
|
961
|
+
_visibleContentRange: null,
|
|
962
|
+
|
|
963
|
+
// Hash of itemViews to the content guids they current represent. This
|
|
964
|
+
// only matches views in currently in the _visibleContentRange.
|
|
965
|
+
_itemViewsByContent: null,
|
|
1140
966
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
967
|
+
// Hash of groupViews to the group key they currently represent.
|
|
968
|
+
_groupViewsByValue: null,
|
|
969
|
+
|
|
970
|
+
// Hash of counts of item views contained in a group view. When the count
|
|
971
|
+
// of a group reaches zero, it will be removed.
|
|
972
|
+
_groupViewCounts: null,
|
|
973
|
+
|
|
974
|
+
// Array of unused itemViews. Push/pop only.
|
|
975
|
+
_itemViewPool: null,
|
|
976
|
+
|
|
977
|
+
// Array of unused groupViews. Push/pop only.
|
|
978
|
+
_groupViewPool: null,
|
|
979
|
+
|
|
980
|
+
// When a group view's item view count reaches zero, it is moved to this
|
|
981
|
+
// hash until updateChildren() completes. During that time, if the group
|
|
982
|
+
// is needed again, it can be reused. At the end of updateChildren() this
|
|
983
|
+
// hash will be flushed and its members returned to the groupView pool.
|
|
984
|
+
//
|
|
985
|
+
_zombieGroupViews: null,
|
|
986
|
+
|
|
987
|
+
/** @private
|
|
988
|
+
Finds or creates the itemView for the named content and inserts it into
|
|
989
|
+
view under the correct group if needed. Note that this method does not
|
|
990
|
+
take into account the actual ORDER of item views in the hierarchy. It
|
|
991
|
+
assumes that manual layout will ensure the items appear visually in the
|
|
992
|
+
proper order anyway.
|
|
993
|
+
|
|
994
|
+
@param {SC.View} itemView The item view to remove
|
|
995
|
+
@param {String} groupBy the value used for grouping or null if grouping is
|
|
996
|
+
disabled.
|
|
997
|
+
|
|
998
|
+
@returns {SC.View} the new itemView.
|
|
999
|
+
*/
|
|
1000
|
+
_insertItemViewFor: function(content, groupBy, contentIndex) {
|
|
1001
|
+
|
|
1002
|
+
// first look for a matching record.
|
|
1003
|
+
var key = SC.guidFor(content) ;
|
|
1004
|
+
var ret = this._itemViewsByContent[key];
|
|
1005
|
+
var firstLayout = false ;
|
|
1006
|
+
|
|
1007
|
+
// if no record was found, pull an item view from the pool or create one.
|
|
1008
|
+
// set the content.
|
|
1009
|
+
if (!ret) {
|
|
1010
|
+
ret = this._itemViewPool.pop() || this.get('exampleView').create({
|
|
1011
|
+
owner: this, displayDelegate: this
|
|
1012
|
+
}) ;
|
|
1013
|
+
ret.addClassName('sc-collection-item') ; // add class name for display
|
|
1014
|
+
|
|
1015
|
+
// set content and add to content hash
|
|
1016
|
+
ret.set('content', content) ;
|
|
1017
|
+
this._itemViewsByContent[key] = ret ;
|
|
1018
|
+
this._itemViewsByGuid[SC.guidFor(ret)] = ret ;
|
|
1019
|
+
firstLayout = true ;
|
|
1020
|
+
}
|
|
1021
|
+
if (!ret) throw "Could not create itemView for content: %@".fmt(content);
|
|
1022
|
+
|
|
1023
|
+
// Determine proper parent view and insert itemView if needed.
|
|
1024
|
+
// Also update count of itemViews.
|
|
1025
|
+
var parentView = (groupBy && content) ? this._insertGroupViewFor(content.get(groupBy), contentIndex) : this ;
|
|
1026
|
+
if (ret.get('parentNode') != parentView) {
|
|
1027
|
+
parentView.appendChild(ret) ;
|
|
1028
|
+
if (groupBy) this._groupViewCounts[SC.guidFor(parentView)]++ ;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// Layout itemView.
|
|
1032
|
+
this.layoutItemView(ret, contentIndex, firstLayout) ;
|
|
1033
|
+
return ret ;
|
|
1034
|
+
},
|
|
1035
|
+
|
|
1036
|
+
/** @private
|
|
1037
|
+
Removes the itemView from the receiver and returns it to the itemView pool
|
|
1038
|
+
for later reuse.
|
|
1039
|
+
|
|
1040
|
+
If the itemView belongs to a groupView and this leaves the groupView empty
|
|
1041
|
+
as well, then the groupView will be moved to the zombieGroupViews hash.
|
|
1042
|
+
|
|
1043
|
+
@param {SC.View} itemView The item view to remove
|
|
1044
|
+
@param {String} groupBy the value used for grouping or null if grouping is
|
|
1045
|
+
disabled.
|
|
1046
|
+
|
|
1047
|
+
@returns {SC.View} The itemView that was removed.
|
|
1048
|
+
*/
|
|
1049
|
+
_removeItemView: function(itemView, groupBy) {
|
|
1050
|
+
|
|
1051
|
+
// If we are grouping, then decrement the groupViewCount. If the new
|
|
1052
|
+
// count is zero, save groupView for later removal.
|
|
1053
|
+
var groupView = null ; var groupValue ;
|
|
1054
|
+
if (groupBy && (groupView = itemView.get('parentNode'))) {
|
|
1055
|
+
if (--this._groupViewCounts[SC.guidFor(groupView)] > 0) groupView = null ;
|
|
1056
|
+
if (groupView) {
|
|
1057
|
+
var content = itemView.get('content') ;
|
|
1058
|
+
groupValue = (content) ? content.get(groupBy) : null ;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Remove itemView from parent and remove from content hash.
|
|
1063
|
+
var content = itemView.get('content') ;
|
|
1064
|
+
var key = SC.guidFor(content) ;
|
|
1065
|
+
delete this._itemViewsByContent[key] ;
|
|
1066
|
+
delete this._itemViewsByGuid[SC.guidFor(itemView)] ;
|
|
1067
|
+
itemView.removeFromParent() ;
|
|
1068
|
+
|
|
1069
|
+
// Clear content and return itemView to pool
|
|
1070
|
+
itemView.set('content', null) ;
|
|
1071
|
+
this._itemViewPool.push(itemView) ;
|
|
1072
|
+
|
|
1073
|
+
// if a groupView is set, then it also needs to be returned to the pool
|
|
1074
|
+
if (groupView) this._removeGroupView(groupView, groupValue) ;
|
|
1075
|
+
|
|
1076
|
+
return itemView;
|
|
1077
|
+
},
|
|
1078
|
+
|
|
1079
|
+
/** @private
|
|
1080
|
+
Adds or removes itemViews for the content in the specified range.
|
|
1081
|
+
Note that this is not passed as a formal range because the length
|
|
1082
|
+
could be negative.
|
|
1083
|
+
|
|
1084
|
+
A negative length means views should be removed.
|
|
1085
|
+
*/
|
|
1086
|
+
_insertOrRemoveItemViewsInRange: function(start, length, groupBy) {
|
|
1087
|
+
// zero length means do nothing.
|
|
1088
|
+
if (length == 0) return ;
|
|
1089
|
+
|
|
1090
|
+
var content = this.get('content') || [] ;
|
|
1091
|
+
|
|
1092
|
+
// negative length == remove item views
|
|
1093
|
+
if (length < 0) {
|
|
1094
|
+
while(++length < 0) {
|
|
1095
|
+
var c = content.objectAt(start + length) ;
|
|
1096
|
+
var itemView = this.itemViewForContent(c) ;
|
|
1097
|
+
if (itemView) this._removeItemView(itemView, groupBy) ;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// positive length == add item views.
|
|
1101
|
+
} else if (length > 0) {
|
|
1102
|
+
while(--length >= 0) {
|
|
1103
|
+
var idx = start + length ;
|
|
1104
|
+
var c = content.objectAt(idx) ;
|
|
1105
|
+
this._insertItemViewFor(c, groupBy, idx) ;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
|
|
1110
|
+
/** @private
|
|
1111
|
+
Finds or creates a groupView for the named group value and inserts it into
|
|
1112
|
+
the receiver. This method does not take into account the actual ORDER of
|
|
1113
|
+
the groupViews in the hierarchy. It assumes that manual layout will
|
|
1114
|
+
ensure the items appear visually in the proper order anyway.
|
|
1115
|
+
|
|
1116
|
+
@returns {SC.View} the new groupView.
|
|
1117
|
+
*/
|
|
1118
|
+
_insertGroupViewFor: function(groupValue, contentIndex) {
|
|
1119
|
+
var ret = this._groupViewsByValue[groupValue] ;
|
|
1120
|
+
// if (ret) return ret ; // nothing to do
|
|
1121
|
+
|
|
1122
|
+
var firstLayout = false ;
|
|
1123
|
+
|
|
1124
|
+
// if the group was not found, check the zombie pool. If found in zombie
|
|
1125
|
+
// pool, restore it to the regular group view hash.
|
|
1126
|
+
if (!ret && this._zombieGroupViews) {
|
|
1127
|
+
ret = this._zombieGroupViews[groupValue] ;
|
|
1128
|
+
if (ret) {
|
|
1129
|
+
delete this._zombieGroupViews[groupValue] ;
|
|
1130
|
+
this._groupViewsByValue[groupValue] = ret ;
|
|
1131
|
+
this._groupViewCounts[SC.guidFor(ret)] = 0 ;
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// If groupValue still not found, create one.
|
|
1136
|
+
if (!ret) {
|
|
1137
|
+
ret = this._groupViewPool.pop() || this.get('exampleGroupView').create({
|
|
1138
|
+
owner: this, displayDelegate: this
|
|
1139
|
+
});
|
|
1140
|
+
ret.addClassName('sc-collection-group') ;
|
|
1141
|
+
|
|
1142
|
+
// set the groupValue on the groupView. Older groupViews expect us to
|
|
1143
|
+
// set this directly on the labelView. Newer groupViews should have a
|
|
1144
|
+
// groupValue property.
|
|
1145
|
+
if (ret.groupValue !== undefined) {
|
|
1146
|
+
ret.set('groupValue', groupValue) ;
|
|
1147
|
+
} else ret.set('content', groupValue) ;
|
|
1148
|
+
|
|
1149
|
+
// save in cache
|
|
1150
|
+
this._groupViewsByValue[groupValue] = ret ;
|
|
1151
|
+
this._groupViewCounts[SC.guidFor(ret)] = 0 ;
|
|
1152
|
+
firstLayout = true;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// If the group view does not already belong to the receiver, add it.
|
|
1156
|
+
if (!ret) throw "Could not create a groupView for value: %@".fmt(groupValue) ;
|
|
1157
|
+
if (ret.get('parentNode') != this) this.appendChild(ret) ;
|
|
1158
|
+
|
|
1159
|
+
// Layout the group View
|
|
1160
|
+
this.layoutGroupView(ret, groupValue, contentIndex, firstLayout) ;
|
|
1161
|
+
|
|
1162
|
+
return ret ;
|
|
1163
|
+
},
|
|
1164
|
+
|
|
1165
|
+
/** @private
|
|
1166
|
+
Called whenever a groupView is no longer being used.
|
|
1167
|
+
|
|
1168
|
+
Theoretically, this method removes a group view from the receiver and
|
|
1169
|
+
stores it in the pool for later use. In actuality, this will just moved
|
|
1170
|
+
the view to the zombieGroupView pool. You must call
|
|
1171
|
+
_flushZombieGroupViews() to actually remove them from the receiver.
|
|
1172
|
+
*/
|
|
1173
|
+
_removeGroupView: function(groupView, groupValue) {
|
|
1174
|
+
if (SC.ZOMBIE_GROUPS_ENABLED) {
|
|
1175
|
+
this._zombieGroupViews[groupValue] = groupView ;
|
|
1176
|
+
} else {
|
|
1177
|
+
this._finalRemoveGroupView(groupView) ;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
delete this._groupViewsByValue[groupValue] ;
|
|
1181
|
+
delete this._groupViewCounts[SC.guidFor(groupView)] ;
|
|
1182
|
+
return groupView ;
|
|
1183
|
+
},
|
|
1184
|
+
|
|
1185
|
+
/** @private
|
|
1186
|
+
Flushes any zombie group views, removing them from their parent view and
|
|
1187
|
+
returning them to the groupView pool for later consumption.
|
|
1188
|
+
*/
|
|
1189
|
+
_flushZombieGroupViews: function() {
|
|
1190
|
+
if (!SC.ZOMBIE_GROUPS_ENABLED) return ; // nothing to do
|
|
1191
|
+
|
|
1192
|
+
for(var key in this._zombieGroupViews) {
|
|
1193
|
+
if (!this._zombieGroupViews.hasOwnProperty(key)) continue ;
|
|
1194
|
+
var groupView = this._zombieGroupViews[key] ;
|
|
1195
|
+
this._finalRemoveGroupView(groupView) ;
|
|
1196
|
+
}
|
|
1197
|
+
this._zombieGroupViews = {} ; // reset
|
|
1198
|
+
},
|
|
1199
|
+
|
|
1200
|
+
/** @private
|
|
1201
|
+
Final method to actually remove a groupView from its parent view and
|
|
1202
|
+
return it to the groupView pool.
|
|
1203
|
+
*/
|
|
1204
|
+
_finalRemoveGroupView: function(groupView) {
|
|
1205
|
+
groupView.removeFromParent() ;
|
|
1206
|
+
|
|
1207
|
+
// set the groupValue on the groupView. Older groupViews expect us to set
|
|
1208
|
+
// this directly on the labelView. Newer groupViews should have a
|
|
1209
|
+
// groupValue property.
|
|
1210
|
+
if (groupView.groupValue !== undefined) {
|
|
1211
|
+
groupView.set('groupValue', null) ;
|
|
1212
|
+
} else groupView.set('content', null) ;
|
|
1213
|
+
|
|
1214
|
+
this._groupViewPool.push(groupView) ;
|
|
1215
|
+
return groupView ;
|
|
1216
|
+
},
|
|
1217
|
+
|
|
1218
|
+
/** @private
|
|
1219
|
+
Removes the rootElement from the DOM temporarily if needed to optimize performance.
|
|
1220
|
+
*/
|
|
1221
|
+
_removeRootElementFromDom: function() {
|
|
1222
|
+
if (!SC.REMOVE_COLLECTION_ROOT_ELEMENT_DURING_RENDER) return ;
|
|
1223
|
+
if (this._cachedRootElementParent === undefined) {
|
|
1224
|
+
var parent = this._cachedRootElementParent = this.rootElement.parentNode ;
|
|
1225
|
+
this._cachedRootElementNextSibling = this.rootElement.nextSibling ;
|
|
1226
|
+
if (parent) parent.removeChild(this.rootElement) ;
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
|
|
1230
|
+
/** @private
|
|
1231
|
+
Re-adds root element into DOM if necessary. Inverts _removeRootElementFromDom().
|
|
1232
|
+
*/
|
|
1233
|
+
_restoreRootElementInDom: function() {
|
|
1234
|
+
if (!SC.REMOVE_COLLECTION_ROOT_ELEMENT_DURING_RENDER) return ;
|
|
1235
|
+
if (this._cachedRootElementParent) {
|
|
1236
|
+
this._cachedRootElementParent.insertBefore(this.rootElement, this._cachedRootElementNextSibling);
|
|
1237
|
+
}
|
|
1238
|
+
this._cachedRootElementParent = this._cachedRootElementNextSibling = null ;
|
|
1239
|
+
},
|
|
1240
|
+
|
|
1241
|
+
|
|
1242
|
+
// ......................................
|
|
1243
|
+
// SELECTION
|
|
1244
|
+
//
|
|
1245
|
+
|
|
1246
|
+
_indexOfSelectionTop: function() {
|
|
1247
|
+
var content = this.get('content');
|
|
1248
|
+
var sel = this.get('selection');
|
|
1249
|
+
if (!content || !sel) return - 1;
|
|
1250
|
+
|
|
1251
|
+
// find the first item in the selection
|
|
1252
|
+
var contentLength = content.get('length') ;
|
|
1253
|
+
var indexOfSelected = contentLength ; var idx = sel.length ;
|
|
1254
|
+
while(--idx >= 0) {
|
|
1255
|
+
var curIndex = content.indexOf(sel[idx]) ;
|
|
1256
|
+
if ((curIndex >= 0) && (curIndex < indexOfSelected)) indexOfSelected = curIndex ;
|
|
1257
|
+
}
|
|
1148
1258
|
|
|
1149
1259
|
return (indexOfSelected >= contentLength) ? -1 : indexOfSelected ;
|
|
1150
1260
|
},
|
|
@@ -1191,11 +1301,11 @@ SC.CollectionView = SC.View.extend(
|
|
|
1191
1301
|
|
|
1192
1302
|
// If the selBottom is after the anchor, then reduce the selection
|
|
1193
1303
|
if (selBottom > anchor) {
|
|
1194
|
-
selBottom
|
|
1304
|
+
selBottom = selBottom - numberOfItems ;
|
|
1195
1305
|
|
|
1196
1306
|
// otherwise, select the previous item from the top
|
|
1197
1307
|
} else {
|
|
1198
|
-
selTop
|
|
1308
|
+
selTop = selTop - numberOfItems ;
|
|
1199
1309
|
}
|
|
1200
1310
|
|
|
1201
1311
|
// Ensure we are not out of bounds
|
|
@@ -1204,7 +1314,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
1204
1314
|
|
|
1205
1315
|
// if not extending, just select the item previous to the selTop
|
|
1206
1316
|
} else {
|
|
1207
|
-
selTop = this._indexOfSelectionTop() -
|
|
1317
|
+
selTop = this._indexOfSelectionTop() - numberOfItems;
|
|
1208
1318
|
if (selTop < 0) selTop = 0 ;
|
|
1209
1319
|
selBottom = selTop ;
|
|
1210
1320
|
anchor = null ;
|
|
@@ -1218,7 +1328,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
1218
1328
|
|
|
1219
1329
|
// ensure that the item is visible and set the selection
|
|
1220
1330
|
if (items.length > 0) {
|
|
1221
|
-
this.
|
|
1331
|
+
this.scrollToContent(items.first());
|
|
1222
1332
|
this.selectItems(items);
|
|
1223
1333
|
}
|
|
1224
1334
|
|
|
@@ -1253,11 +1363,11 @@ SC.CollectionView = SC.View.extend(
|
|
|
1253
1363
|
|
|
1254
1364
|
// If the selTop is before the anchor, then reduce the selection
|
|
1255
1365
|
if (selTop < anchor) {
|
|
1256
|
-
selTop
|
|
1366
|
+
selTop = selTop + numberOfItems ;
|
|
1257
1367
|
|
|
1258
1368
|
// otherwise, select the next item after the top
|
|
1259
1369
|
} else {
|
|
1260
|
-
selBottom
|
|
1370
|
+
selBottom = selBottom + numberOfItems ;
|
|
1261
1371
|
}
|
|
1262
1372
|
|
|
1263
1373
|
// Ensure we are not out of bounds
|
|
@@ -1266,7 +1376,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
1266
1376
|
|
|
1267
1377
|
// if not extending, just select the item next to the selBottom
|
|
1268
1378
|
} else {
|
|
1269
|
-
selBottom = this._indexOfSelectionBottom() +
|
|
1379
|
+
selBottom = this._indexOfSelectionBottom() + numberOfItems;
|
|
1270
1380
|
if (selBottom >= contentLength) selBottom = contentLength-1;
|
|
1271
1381
|
selTop = selBottom ;
|
|
1272
1382
|
anchor = null ;
|
|
@@ -1280,7 +1390,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
1280
1390
|
|
|
1281
1391
|
// ensure that the item is visible and set the selection
|
|
1282
1392
|
if (items.length > 0) {
|
|
1283
|
-
this.
|
|
1393
|
+
this.scrollToContent(items.first());
|
|
1284
1394
|
this.selectItems(items);
|
|
1285
1395
|
}
|
|
1286
1396
|
|
|
@@ -1292,9 +1402,16 @@ SC.CollectionView = SC.View.extend(
|
|
|
1292
1402
|
* @param {SC.Record} record The record to scroll to
|
|
1293
1403
|
* @returns {void}
|
|
1294
1404
|
*/
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1405
|
+
scrollToContent: function(record) {
|
|
1406
|
+
// find the itemView. if not present, add one.
|
|
1407
|
+
var itemView = this.itemViewForContent(record) ;
|
|
1408
|
+
if (!itemView) {
|
|
1409
|
+
var content = Array.from(this.get('content')) ;
|
|
1410
|
+
var contentIndex = content.indexOf(record) ;
|
|
1411
|
+
var groupBy = this.get('groupBy');
|
|
1412
|
+
itemView = this._insertItemViewFor(itemView, groupBy, contentIndex);
|
|
1413
|
+
}
|
|
1414
|
+
if (itemView) this.scrollToItemView(itemView);
|
|
1298
1415
|
},
|
|
1299
1416
|
/**
|
|
1300
1417
|
* Scroll the rootElement (if needed) to ensure that the item is visible.
|
|
@@ -1303,24 +1420,13 @@ SC.CollectionView = SC.View.extend(
|
|
|
1303
1420
|
*/
|
|
1304
1421
|
scrollToItemView: function( view )
|
|
1305
1422
|
{
|
|
1306
|
-
|
|
1307
|
-
var
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
visible.makePositioned();
|
|
1311
|
-
|
|
1312
|
-
var item = Element.extend(view.get('rootElement'));
|
|
1313
|
-
var itemTop = item.positionedOffset().top;
|
|
1314
|
-
var itemBottom = itemTop + item.getHeight();
|
|
1315
|
-
|
|
1316
|
-
visible.undoPositioned();
|
|
1317
|
-
|
|
1318
|
-
if (itemTop < visibleTop) {
|
|
1319
|
-
visible.scrollTop = itemTop;
|
|
1320
|
-
}
|
|
1321
|
-
if (itemBottom > visibleBottom) {
|
|
1322
|
-
visible.scrollTop += (itemBottom - visibleBottom);
|
|
1423
|
+
// find first scrollable view.
|
|
1424
|
+
var scrollable = this ;
|
|
1425
|
+
while(scrollable && (scrollable != SC.window) && (!scrollable.get('isScrollable'))) {
|
|
1426
|
+
scrollable = scrollable.get('parentNode') ;
|
|
1323
1427
|
}
|
|
1428
|
+
if (!scrollable || (scrollable == SC.window)) return ; // no scrollable!
|
|
1429
|
+
scrollable.scrollToVisible(view) ;
|
|
1324
1430
|
},
|
|
1325
1431
|
|
|
1326
1432
|
/**
|
|
@@ -1382,7 +1488,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
1382
1488
|
Selects the previous item if itemsPerRow > 1. Otherwise does nothing.
|
|
1383
1489
|
*/
|
|
1384
1490
|
moveLeft: function(sender, evt) {
|
|
1385
|
-
if ((this.get('itemsPerRow') || 1) > 1) this.
|
|
1491
|
+
if ((this.get('itemsPerRow') || 1) > 1) this.selectPreviousItem(false, 1) ;
|
|
1386
1492
|
return true ;
|
|
1387
1493
|
},
|
|
1388
1494
|
|
|
@@ -1390,7 +1496,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
1390
1496
|
Selects the next item if itemsPerRow > 1. Otherwise does nothing.
|
|
1391
1497
|
*/
|
|
1392
1498
|
moveRight: function(sender, evt) {
|
|
1393
|
-
if ((this.get('itemsPerRow') || 1) > 1) this.
|
|
1499
|
+
if ((this.get('itemsPerRow') || 1) > 1) this.selectNextItem(false, 1) ;
|
|
1394
1500
|
return true ;
|
|
1395
1501
|
},
|
|
1396
1502
|
|
|
@@ -1408,7 +1514,7 @@ SC.CollectionView = SC.View.extend(
|
|
|
1408
1514
|
Selects the previous item if itemsPerRow > 1. Otherwise does nothing.
|
|
1409
1515
|
*/
|
|
1410
1516
|
moveLeftAndModifySelection: function(sender, evt) {
|
|
1411
|
-
if ((this.get('itemsPerRow') || 1) > 1) this.
|
|
1517
|
+
if ((this.get('itemsPerRow') || 1) > 1) this.selectPreviousItem(true, 1) ;
|
|
1412
1518
|
return true ;
|
|
1413
1519
|
},
|
|
1414
1520
|
|
|
@@ -1416,82 +1522,12 @@ SC.CollectionView = SC.View.extend(
|
|
|
1416
1522
|
Selects the next item if itemsPerRow > 1. Otherwise does nothing.
|
|
1417
1523
|
*/
|
|
1418
1524
|
moveRightAndModifySelection: function(sender, evt) {
|
|
1419
|
-
if ((this.get('itemsPerRow') || 1) > 1) this.
|
|
1525
|
+
if ((this.get('itemsPerRow') || 1) > 1) this.selectNextItem(true, 1) ;
|
|
1420
1526
|
return true ;
|
|
1421
1527
|
},
|
|
1422
1528
|
|
|
1423
|
-
|
|
1424
|
-
/**
|
|
1425
|
-
Find the item view underneath the passed mouse location.
|
|
1426
|
-
|
|
1427
|
-
The default implementation of this method simply searches each item view's
|
|
1428
|
-
frame to find one that includes the location. If you are doing your own
|
|
1429
|
-
layout, you may be able to perform this calculation more quickly. If so,
|
|
1430
|
-
consider overriding this method for better performance during drag operations.
|
|
1431
|
-
|
|
1432
|
-
@param {Point} loc The current mouse location in the coordinate of the
|
|
1433
|
-
collection view
|
|
1434
|
-
|
|
1435
|
-
@returns {SC.View} The item view under the collection
|
|
1436
|
-
*/
|
|
1437
|
-
itemViewAtLocation: function(loc) {
|
|
1438
|
-
var content = this.get('content') ;
|
|
1439
|
-
var idx = content.length;
|
|
1440
|
-
while(--idx >= 0) {
|
|
1441
|
-
var itemView = this.itemViewForContent(content.objectAt(idx));
|
|
1442
|
-
var frame = itemView.get('frame');
|
|
1443
|
-
if (SC.pointInRect(loc, frame)) return itemView ;
|
|
1444
|
-
}
|
|
1445
|
-
return null; // not in an itemView right now.
|
|
1446
|
-
},
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
/**
|
|
1451
|
-
Find the first content item view for the passed event.
|
|
1452
|
-
|
|
1453
|
-
This method will go up the view chain, starting with the view that was the target
|
|
1454
|
-
of the passed event, looking for a child item. This will become the view that
|
|
1455
|
-
is selected by the mouse event.
|
|
1456
|
-
|
|
1457
|
-
This method only works for mouseDown & mouseUp events. mouseMoved events do
|
|
1458
|
-
not have a target.
|
|
1459
|
-
|
|
1460
|
-
@param {Event} evt An event
|
|
1461
|
-
|
|
1462
|
-
*/
|
|
1463
|
-
itemViewForEvent: function(evt)
|
|
1464
|
-
{
|
|
1465
|
-
var view = SC.window.firstViewForEvent( evt );
|
|
1466
|
-
// work up the view hierarchy to find a match...
|
|
1467
|
-
do {
|
|
1468
|
-
// item clicked was the ContainerView itself... i.e. the user clicked outside the child items
|
|
1469
|
-
// nothing to return...
|
|
1470
|
-
if ( view == this ) return null;
|
|
1471
|
-
|
|
1472
|
-
// sweet!... the view is not only in the collection, but it says we can hit it.
|
|
1473
|
-
// hit it and quit it...
|
|
1474
|
-
if ( this.hasItemView(view) && (!view.hitTest || view.hitTest(evt)) ) return view;
|
|
1475
|
-
} while ( view = view.get('parentNode') );
|
|
1476
|
-
|
|
1477
|
-
// nothing was found...
|
|
1478
|
-
return null;
|
|
1479
|
-
},
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
didMouseDown: function(ev) {
|
|
1483
|
-
console.warn("didMouseDown will be removed from CollectionView in the near future. Use mouseDown instead");
|
|
1484
|
-
return this._mouseDown(ev, true);
|
|
1485
|
-
},
|
|
1486
|
-
|
|
1487
1529
|
mouseDown: function(ev) {
|
|
1488
|
-
|
|
1489
|
-
if (this.didMouseDown != SC.CollectionView.prototype.didMouseDown) {
|
|
1490
|
-
return this.didMouseDown(ev) ;
|
|
1491
|
-
} else return this._mouseDown(ev);
|
|
1492
|
-
},
|
|
1493
|
-
|
|
1494
|
-
_mouseDown: function(ev) {
|
|
1530
|
+
|
|
1495
1531
|
// save for drag opt
|
|
1496
1532
|
this._mouseDownEvent = ev ;
|
|
1497
1533
|
|
|
@@ -1499,11 +1535,14 @@ SC.CollectionView = SC.View.extend(
|
|
|
1499
1535
|
if (this.useToggleSelection) return true;
|
|
1500
1536
|
|
|
1501
1537
|
// Make sure that saved mouseDown state is always reset in case we do
|
|
1502
|
-
// not get a paired mouseUp. (Only happens if subclass does not call us
|
|
1503
|
-
|
|
1538
|
+
// not get a paired mouseUp. (Only happens if subclass does not call us
|
|
1539
|
+
// like it should)
|
|
1540
|
+
this._mouseDownAt = this._shouldDeselect =
|
|
1541
|
+
this._shouldReselect = this._refreshSelection = false;
|
|
1504
1542
|
|
|
1505
1543
|
var mouseDownView = this._mouseDownView = this.itemViewForEvent(ev);
|
|
1506
|
-
var mouseDownContent =
|
|
1544
|
+
var mouseDownContent =
|
|
1545
|
+
this._mouseDownContent = (mouseDownView) ? mouseDownView.get('content') : null;
|
|
1507
1546
|
|
|
1508
1547
|
// become first responder if possible.
|
|
1509
1548
|
this.becomeFirstResponder() ;
|
|
@@ -1540,176 +1579,463 @@ SC.CollectionView = SC.View.extend(
|
|
|
1540
1579
|
} else if (!modifierKeyPressed && isSelected) {
|
|
1541
1580
|
this._shouldReselect = mouseDownContent;
|
|
1542
1581
|
|
|
1543
|
-
// Otherwise, simply select the clicked on item, adding it to the current
|
|
1544
|
-
// selection if a modifier key was pressed.
|
|
1545
|
-
} else {
|
|
1546
|
-
this.selectItems(mouseDownContent, modifierKeyPressed);
|
|
1582
|
+
// Otherwise, simply select the clicked on item, adding it to the current
|
|
1583
|
+
// selection if a modifier key was pressed.
|
|
1584
|
+
} else {
|
|
1585
|
+
this.selectItems(mouseDownContent, modifierKeyPressed);
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// saved for extend by shift ops.
|
|
1589
|
+
this._previousMouseDownContent = mouseDownContent;
|
|
1590
|
+
return true;
|
|
1591
|
+
},
|
|
1592
|
+
|
|
1593
|
+
mouseUp: function(ev) {
|
|
1594
|
+
|
|
1595
|
+
var canAct = this.get('actOnSelect') ;
|
|
1596
|
+
var view = this.itemViewForEvent(ev) ;
|
|
1597
|
+
|
|
1598
|
+
if (this.useToggleSelection) {
|
|
1599
|
+
if (!view) return ; // do nothing when clicked outside of elements
|
|
1600
|
+
|
|
1601
|
+
// determine if item is selected. If so, then go on.
|
|
1602
|
+
var selection = this.get('selection') || [] ;
|
|
1603
|
+
var content = (view) ? view.get('content') : null ;
|
|
1604
|
+
var isSelected = selection.include(content) ;
|
|
1605
|
+
if (isSelected) {
|
|
1606
|
+
this.deselectItems([content]) ;
|
|
1607
|
+
} else this.selectItems([content],true) ;
|
|
1608
|
+
|
|
1609
|
+
} else {
|
|
1610
|
+
if (this._shouldDeselect) this.deselectItems(this._shouldDeselect);
|
|
1611
|
+
if (this._shouldReselect) this.selectItems(this._shouldReselect,false) ;
|
|
1612
|
+
|
|
1613
|
+
// this is invoked if the user clicked on a checkbox. If this is not
|
|
1614
|
+
// done then the checkbox might not update properly.
|
|
1615
|
+
if (this._refreshSelection) {
|
|
1616
|
+
}
|
|
1617
|
+
this._cleanupMouseDown() ;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
this._mouseDownEvent = null ;
|
|
1621
|
+
if (canAct) this._action(ev, view) ;
|
|
1622
|
+
|
|
1623
|
+
return false; // bubble event to allow didDoubleClick to be called...
|
|
1624
|
+
},
|
|
1625
|
+
|
|
1626
|
+
_cleanupMouseDown: function() {
|
|
1627
|
+
this._mouseDownAt = this._shouldDeselect = this._shouldReselect = this._refreshSelection = false;
|
|
1628
|
+
this._mouseDownEvent = this._mouseDownContent = this._mouseDownView = null ;
|
|
1629
|
+
},
|
|
1630
|
+
|
|
1631
|
+
mouseMoved: function(ev) {
|
|
1632
|
+
var view = this.itemViewForEvent(ev) ;
|
|
1633
|
+
// handle hover events.
|
|
1634
|
+
if(this._lastHoveredItem && ((view === null) || (view != this._lastHoveredItem)) && this._lastHoveredItem.mouseOut) {
|
|
1635
|
+
this._lastHoveredItem.mouseOut(ev);
|
|
1636
|
+
}
|
|
1637
|
+
this._lastHoveredItem = view ;
|
|
1638
|
+
if (view && view.mouseOver) view.mouseOver(ev) ;
|
|
1639
|
+
},
|
|
1640
|
+
|
|
1641
|
+
mouseOut: function(ev) {
|
|
1642
|
+
|
|
1643
|
+
var view = this._lastHoveredItem ;
|
|
1644
|
+
this._lastHoveredItem = null ;
|
|
1645
|
+
if (view && view.didMouseOut) view.didMouseOut(ev) ;
|
|
1646
|
+
},
|
|
1647
|
+
|
|
1648
|
+
// invoked when the user double clicks on an item.
|
|
1649
|
+
didDoubleClick: function(ev) {
|
|
1650
|
+
console.warn("didDoubleClick will be removed from CollectionView in the near future. Use mouseOut instead");
|
|
1651
|
+
return this._doubleClick(ev) ;
|
|
1652
|
+
},
|
|
1653
|
+
|
|
1654
|
+
doubleClick: function(ev) {
|
|
1655
|
+
if (this.didDoubleClick != SC.CollectionView.prototype.didDoubleClick) {
|
|
1656
|
+
return this.didDoubleClick(ev) ;
|
|
1657
|
+
} else return this._doubleClick(ev) ;
|
|
1658
|
+
},
|
|
1659
|
+
|
|
1660
|
+
_doubleClick: function(ev) {
|
|
1661
|
+
console.info('_doubleClick!') ;
|
|
1662
|
+
var view = this.itemViewForEvent(ev) ;
|
|
1663
|
+
if (view) {
|
|
1664
|
+
this._action(view, ev) ;
|
|
1665
|
+
return true ;
|
|
1666
|
+
} else return false ;
|
|
1667
|
+
},
|
|
1668
|
+
|
|
1669
|
+
_findSelectionExtendedByShift: function(selection, mouseDownContent) {
|
|
1670
|
+
var collection = this.get('content');
|
|
1671
|
+
|
|
1672
|
+
// bounds of the collection...
|
|
1673
|
+
var collectionLowerBounds = 0;
|
|
1674
|
+
var collectionUpperBounds = (collection.get('length') - 1);
|
|
1675
|
+
|
|
1676
|
+
var selectionBeginIndex = collection.indexOf(selection.first());
|
|
1677
|
+
var selectionEndIndex = collection.indexOf(selection.last());
|
|
1678
|
+
|
|
1679
|
+
var previousMouseDownIndex = collection.indexOf(this._previousMouseDownContent);
|
|
1680
|
+
// _previousMouseDownContent couldn't be found... either it hasn't been set yet or the record has been deleted by the user
|
|
1681
|
+
// fall back to the first selected item.
|
|
1682
|
+
if (previousMouseDownIndex == -1) previousMouseDownIndex = selectionBeginIndex;
|
|
1683
|
+
|
|
1684
|
+
|
|
1685
|
+
var currentMouseDownIndex = collection.indexOf(mouseDownContent);
|
|
1686
|
+
// sanity check...
|
|
1687
|
+
if (currentMouseDownIndex == -1) throw "Unable to extend selection to an item that's not in the collection!";
|
|
1688
|
+
|
|
1689
|
+
// clicked before the current selection set... extend it's beginning...
|
|
1690
|
+
if (currentMouseDownIndex < selectionBeginIndex) selectionBeginIndex = currentMouseDownIndex;
|
|
1691
|
+
// clicked after the current selection set... extend it's ending...
|
|
1692
|
+
if (currentMouseDownIndex > selectionEndIndex) selectionEndIndex = currentMouseDownIndex;
|
|
1693
|
+
// clicked inside the selection set... need to determine where the las
|
|
1694
|
+
if ((currentMouseDownIndex > selectionBeginIndex) && (currentMouseDownIndex < selectionEndIndex))
|
|
1695
|
+
{
|
|
1696
|
+
if (currentMouseDownIndex == previousMouseDownIndex) {
|
|
1697
|
+
selectionBeginIndex = currentMouseDownIndex;
|
|
1698
|
+
selectionEndIndex = currentMouseDownIndex;
|
|
1699
|
+
} else if (currentMouseDownIndex > previousMouseDownIndex) {
|
|
1700
|
+
selectionBeginIndex = previousMouseDownIndex;
|
|
1701
|
+
selectionEndIndex = currentMouseDownIndex;
|
|
1702
|
+
} else if (currentMouseDownIndex < previousMouseDownIndex){
|
|
1703
|
+
selectionBeginIndex = currentMouseDownIndex;
|
|
1704
|
+
selectionEndIndex = previousMouseDownIndex;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
// slice doesn't include the last index passed... silly..
|
|
1708
|
+
selectionEndIndex++;
|
|
1709
|
+
|
|
1710
|
+
// shouldn't need to sanity check that the selection is in bounds due to the indexOf checks above...
|
|
1711
|
+
// I'll have faith that indexOf hasn't lied to me...
|
|
1712
|
+
return collection.slice(selectionBeginIndex, selectionEndIndex);
|
|
1713
|
+
},
|
|
1714
|
+
|
|
1715
|
+
|
|
1716
|
+
// ......................................
|
|
1717
|
+
// FIRST RESPONDER
|
|
1718
|
+
//
|
|
1719
|
+
|
|
1720
|
+
/**
|
|
1721
|
+
Called whenever the collection becomes first responder.
|
|
1722
|
+
Adds the focused class to the element.
|
|
1723
|
+
*/
|
|
1724
|
+
didBecomeFirstResponder: function() {
|
|
1725
|
+
this.addClassName('focus') ;
|
|
1726
|
+
},
|
|
1727
|
+
|
|
1728
|
+
willLoseFirstResponder: function() {
|
|
1729
|
+
this.removeClassName('focus');
|
|
1730
|
+
},
|
|
1731
|
+
|
|
1732
|
+
// ......................................
|
|
1733
|
+
// DRAG AND DROP SUPPORT
|
|
1734
|
+
//
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
The insertion orientation. This is used to determine which
|
|
1738
|
+
dimension we should pay attention to when determining insertion point for
|
|
1739
|
+
a mouse click.
|
|
1740
|
+
|
|
1741
|
+
{{{
|
|
1742
|
+
SC.HORIZONTAL_ORIENTATION: look at the X dimension only
|
|
1743
|
+
SC.VERTICAL_ORIENTATION: look at the Y dimension only
|
|
1744
|
+
}}}
|
|
1745
|
+
*/
|
|
1746
|
+
insertionOrientation: SC.HORIZONTAL_ORIENTATION,
|
|
1747
|
+
|
|
1748
|
+
/**
|
|
1749
|
+
Get the preferred insertion point for the given location, including
|
|
1750
|
+
an insertion preference of before or after the named index.
|
|
1751
|
+
|
|
1752
|
+
The default implementation will loop through the item views looking for
|
|
1753
|
+
the first view to "switch sides" in the orientation you specify.
|
|
1754
|
+
*/
|
|
1755
|
+
insertionIndexForLocation: function(loc) {
|
|
1756
|
+
var content = this.get('content') ;
|
|
1757
|
+
var f, itemView, curSide, lastSide = null ;
|
|
1758
|
+
var orient = this.get('insertionOrientation') ;
|
|
1759
|
+
var ret= null ;
|
|
1760
|
+
for(var idx=0; ((ret == null) && (idx<content.length)); idx++) {
|
|
1761
|
+
itemView = this.itemViewForContent(content.objectAt(idx));
|
|
1762
|
+
f = this.convertFrameFromView(itemView.get('frame'), itemView) ;
|
|
1763
|
+
|
|
1764
|
+
// if we are a horizontal orientation, look for the first item that
|
|
1765
|
+
// will "switch sides" on the x path an the maxY is greater than Y.
|
|
1766
|
+
// This assumes you will flow top to bottom, but it should work if you
|
|
1767
|
+
// flow LTR or RTL.
|
|
1768
|
+
if (orient == SC.HORIZONTAL_ORIENTATION) {
|
|
1769
|
+
if (SC.maxY(f) > loc.y) {
|
|
1770
|
+
curSide = (SC.maxX(f) < loc.x) ? -1 : 1 ;
|
|
1771
|
+
} else curSide = null ;
|
|
1772
|
+
|
|
1773
|
+
// if we are a vertical orientation, look for the first item that
|
|
1774
|
+
// will "swithc sides" on the y path and the maxX is greater than X.
|
|
1775
|
+
// This assumes you will flow LTR, but it should work if you flow
|
|
1776
|
+
// bottom to top or top to bottom.
|
|
1777
|
+
} else {
|
|
1778
|
+
if (SC.minX(f) < loc.x) {
|
|
1779
|
+
curSide = (SC.maxY(f) < loc.y) ? -1 : 1 ;
|
|
1780
|
+
} else curSide = null ;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// if we "switched" sides then return this item view.
|
|
1784
|
+
if (curSide !== null) {
|
|
1785
|
+
|
|
1786
|
+
// OK, we found an item view, while we have this data, decide if
|
|
1787
|
+
// we should insert before or after the view
|
|
1788
|
+
if ((lastSide !== null) && (curSide != lastSide)) {
|
|
1789
|
+
ret = idx ;
|
|
1790
|
+
if (orient == SC.HORIZONTAL_ORIENTATION) {
|
|
1791
|
+
if (SC.midX(f) < loc.x) ret++ ;
|
|
1792
|
+
} else {
|
|
1793
|
+
if (SC.midY(f) < loc.y) ret++ ;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
lastSide =curSide ;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// Handle some edge cases
|
|
1801
|
+
if ((ret == null) || (ret < 0)) ret = 0 ;
|
|
1802
|
+
if (ret > content.length) ret = content.length ;
|
|
1803
|
+
|
|
1804
|
+
// Done. Phew. Return.
|
|
1805
|
+
return ret;
|
|
1806
|
+
},
|
|
1807
|
+
|
|
1808
|
+
/**
|
|
1809
|
+
Override to show the insertion point during a drag.
|
|
1810
|
+
|
|
1811
|
+
Called during a drag to show the insertion point. Passed value is the
|
|
1812
|
+
item view that you should display the insertion point before. If the
|
|
1813
|
+
passed value is null, then you should show the insertion point AFTER that
|
|
1814
|
+
last item view returned by the itemViews property.
|
|
1815
|
+
|
|
1816
|
+
Once this method is called, you are guaranteed to also recieve a call to
|
|
1817
|
+
hideInsertionPoint() at some point in the future.
|
|
1818
|
+
|
|
1819
|
+
The default implementation of this method does nothing.
|
|
1820
|
+
|
|
1821
|
+
@param {SC.View} itemView view the insertion point should appear directly before. If null, show insertion point at end.
|
|
1822
|
+
|
|
1823
|
+
@returns {void}
|
|
1824
|
+
*/
|
|
1825
|
+
showInsertionPointBefore: function(itemView) {},
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
Override to hide the insertion point when a drag ends.
|
|
1829
|
+
|
|
1830
|
+
Called during a drag to hide the insertion point. This will be called when the
|
|
1831
|
+
user exits the view, cancels the drag or completes the drag. It will not be
|
|
1832
|
+
called when the insertion point changes during a drag.
|
|
1833
|
+
|
|
1834
|
+
You should expect to receive one or more calls to showInsertionPointBefore()
|
|
1835
|
+
during a drag followed by at least one call to this method at the end. Your
|
|
1836
|
+
method should not raise an error if it is called more than once.
|
|
1837
|
+
|
|
1838
|
+
@returns {void}
|
|
1839
|
+
*/
|
|
1840
|
+
hideInsertionPoint: function() {},
|
|
1841
|
+
|
|
1842
|
+
/**
|
|
1843
|
+
Override this method to provide your own ghost image for a drag.
|
|
1844
|
+
|
|
1845
|
+
Note that the only purpose of this view is to render a visible drag element. It is
|
|
1846
|
+
not critical that you make this element bindable, etc.
|
|
1847
|
+
|
|
1848
|
+
@param dragContent {Array} Array of content objects that will be used in the drag.
|
|
1849
|
+
*/
|
|
1850
|
+
ghostViewFor: function(dragContent) {
|
|
1851
|
+
var view = SC.View.create() ;
|
|
1852
|
+
view.setStyle({ position: 'absolute', overflow: 'hidden' });
|
|
1853
|
+
|
|
1854
|
+
var viewFrame = this.convertFrameToView(this.get('frame'), null) ;
|
|
1855
|
+
view.set('frame', viewFrame) ;
|
|
1856
|
+
|
|
1857
|
+
var idx = dragContent.length ;
|
|
1858
|
+
var maxX = 0; var maxY = 0 ; var minX =100000; var minY = 100000 ;
|
|
1859
|
+
|
|
1860
|
+
while(--idx >= 0) {
|
|
1861
|
+
var itemView = this.itemViewForContent(dragContent[idx]) ;
|
|
1862
|
+
if (!itemView) continue ;
|
|
1863
|
+
var f = itemView.get('frame') ;
|
|
1864
|
+
var dom = itemView.rootElement ;
|
|
1865
|
+
if (!dom) continue ;
|
|
1866
|
+
|
|
1867
|
+
// save the maxX & maxY. This will be used to trim the size
|
|
1868
|
+
// of the ghost view later.
|
|
1869
|
+
if (SC.maxX(f) > maxX) maxX = SC.maxX(f) ;
|
|
1870
|
+
if (SC.maxY(f) > maxY) maxY = SC.maxY(f) ;
|
|
1871
|
+
if (SC.minX(f) < minX) minX = SC.minX(f) ;
|
|
1872
|
+
if (SC.minY(f) < minY) minY = SC.minY(f) ;
|
|
1873
|
+
|
|
1874
|
+
// Clone the contents of this node. We should probably apply the
|
|
1875
|
+
// computed style to the cloned nodes in order to make sure they match even if the
|
|
1876
|
+
// CSS styles do not match. Make sure the items are properly
|
|
1877
|
+
// positioned.
|
|
1878
|
+
dom = dom.cloneNode(true) ;
|
|
1879
|
+
Element.setStyle(dom, { position: "absolute", left: "%@px".fmt(f.x), top: "%@px".fmt(f.y), width: "%@px".fmt(f.width), height: "%@px".fmt(f.height) }) ;
|
|
1880
|
+
view.rootElement.appendChild(dom) ;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// Now we have a view, create another view that will wrap the other view and position it
|
|
1884
|
+
// inside.
|
|
1885
|
+
var wrapper = SC.View.create() ;
|
|
1886
|
+
wrapper.setStyle({ position: 'absolute', overflow: 'hidden' }) ;
|
|
1887
|
+
wrapper.set('frame', {
|
|
1888
|
+
x: viewFrame.x+minX, y: viewFrame.y+minY,
|
|
1889
|
+
width: (maxX-minX+1), height: (maxY-minY+1)
|
|
1890
|
+
}) ;
|
|
1891
|
+
wrapper.appendChild(view) ;
|
|
1892
|
+
view.set('frame', { x: 0-minX, y: 0-minY }) ;
|
|
1893
|
+
return wrapper ;
|
|
1894
|
+
},
|
|
1895
|
+
|
|
1896
|
+
mouseDragged: function(ev) {
|
|
1897
|
+
// Don't do anything unless the user has been dragging for 123msec
|
|
1898
|
+
if ((Date.now() - this._mouseDownAt) < 123) return true ;
|
|
1899
|
+
|
|
1900
|
+
// OK, they must be serious, start a drag if possible.
|
|
1901
|
+
if (this.get('canReorderContent')) {
|
|
1902
|
+
|
|
1903
|
+
// First, get the selection to drag. Drag an array of selected
|
|
1904
|
+
// items appearing in this collection, in the order of the
|
|
1905
|
+
// collection.
|
|
1906
|
+
var content = this.get('content') || [] ;
|
|
1907
|
+
var dragContent = this.get('selection').sort(function(a,b) {
|
|
1908
|
+
a = content.indexOf(a) ; b = content.indexOf(b) ;
|
|
1909
|
+
return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
|
|
1910
|
+
});
|
|
1911
|
+
|
|
1912
|
+
// Build the drag view to use for the ghost drag. This
|
|
1913
|
+
// should essentially contain any visible drag items.
|
|
1914
|
+
var view = this.ghostViewFor(dragContent) ;
|
|
1915
|
+
|
|
1916
|
+
// Initiate the drag
|
|
1917
|
+
SC.Drag.start({
|
|
1918
|
+
event: this._mouseDownEvent,
|
|
1919
|
+
source: this,
|
|
1920
|
+
dragView: view,
|
|
1921
|
+
ghost: NO,
|
|
1922
|
+
slideBack: YES,
|
|
1923
|
+
data: { "_mouseDownContent": dragContent }
|
|
1924
|
+
}) ;
|
|
1925
|
+
|
|
1926
|
+
// Also use this opportunity to clean up since mouseUp won't
|
|
1927
|
+
// get called.
|
|
1928
|
+
this._cleanupMouseDown() ;
|
|
1929
|
+
this._lastInsertionIndex = null ;
|
|
1547
1930
|
}
|
|
1548
|
-
|
|
1549
|
-
// saved for extend by shift ops.
|
|
1550
|
-
this._previousMouseDownContent = mouseDownContent;
|
|
1551
|
-
return true;
|
|
1552
1931
|
},
|
|
1553
1932
|
|
|
1554
|
-
//
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
mouseUp: function(ev) {
|
|
1562
|
-
if (this.didMouseUp != SC.CollectionView.prototype.didMouseUp) {
|
|
1563
|
-
return this.didMouseUp(ev) ;
|
|
1564
|
-
} else return this._mouseUp(ev) ;
|
|
1933
|
+
// Drop Source.
|
|
1934
|
+
dragEntered: function(drag, evt) {
|
|
1935
|
+
if ((drag.get('source') == this) && this.get('canReorderContent')) {
|
|
1936
|
+
return SC.DRAG_MOVE ;
|
|
1937
|
+
} else {
|
|
1938
|
+
return SC.DRAG_NONE ;
|
|
1939
|
+
}
|
|
1565
1940
|
},
|
|
1566
1941
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
if (this.useToggleSelection) {
|
|
1573
|
-
if (!view) return ; // do nothing when clicked outside of elements
|
|
1574
|
-
|
|
1575
|
-
// determine if item is selected. If so, then go on.
|
|
1576
|
-
var selection = this.get('selection') || [] ;
|
|
1577
|
-
var content = (view) ? view.get('content') : null ;
|
|
1578
|
-
var isSelected = selection.include(content) ;
|
|
1579
|
-
if (isSelected) {
|
|
1580
|
-
this.deselectItems([content]) ;
|
|
1581
|
-
} else this.selectItems([content],true) ;
|
|
1942
|
+
// If reordering is allowed, then show insertion point
|
|
1943
|
+
dragUpdated: function(drag, evt) {
|
|
1944
|
+
if (this.get('canReorderContent')) {
|
|
1945
|
+
var loc = drag.get('location') ;
|
|
1946
|
+
loc = this.convertFrameFromView(loc, null) ;
|
|
1582
1947
|
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1948
|
+
// get the insertion index for this location. This can be computed
|
|
1949
|
+
// by a subclass using whatever method. This method is not expected to
|
|
1950
|
+
// do any data valdidation, just to map the location to an insertion index.
|
|
1951
|
+
var ret = this.insertionIndexForLocation(loc) ;
|
|
1586
1952
|
|
|
1587
|
-
//
|
|
1588
|
-
//
|
|
1589
|
-
|
|
1953
|
+
// now that we have an index, find the nearest index that we can
|
|
1954
|
+
// actually insert at, or do not allow.
|
|
1955
|
+
var objects = (drag.source == this) ? (drag.dataForType('_mouseDownContent') || []) : [];
|
|
1956
|
+
var content = this.get('content') || [] ;
|
|
1957
|
+
|
|
1958
|
+
// if the insertion index is in between two items in the drag itself,
|
|
1959
|
+
// then this is not allowed. Either use the last insertion index or
|
|
1960
|
+
// find the first index that is not in between selections.
|
|
1961
|
+
var isPreviousInDrag = (ret > 0) ? objects.indexOf(content.objectAt(ret-1)) : -1 ;
|
|
1962
|
+
var isNextInDrag = (ret < content.get('length')-1) ? objects.indexOf(content.objectAt(ret)) : -1 ;
|
|
1963
|
+
if (isPreviousInDrag>=0 && isNextInDrag>=0) {
|
|
1964
|
+
if (this._lastInsertionIndex == null) {
|
|
1965
|
+
while((ret > 0) && (objects.indexOf(content.objectAt(ret)) >= 0)) ret-- ;
|
|
1966
|
+
} else ret = this._lastInsertionIndex ;
|
|
1590
1967
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1968
|
+
|
|
1969
|
+
// Now that we have verified that, check to see if a drop is allowed in the
|
|
1970
|
+
// insertion index with the delegate.
|
|
1971
|
+
// TODO
|
|
1593
1972
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
_cleanupMouseDown: function() {
|
|
1601
|
-
this._mouseDownAt = this._shouldDeselect = this._shouldReselect = this._refreshSelection = false;
|
|
1602
|
-
this._mouseDownEvent = this._mouseDownContent = this._mouseDownView = null ;
|
|
1603
|
-
},
|
|
1604
|
-
|
|
1605
|
-
// this can be used to initiate a drag. Only drags 100ms after mouseDown
|
|
1606
|
-
// to avoid responding to clicks.
|
|
1607
|
-
mouseDidMove: function(ev) {
|
|
1608
|
-
console.warn("mouseDidMove will be removed from CollectionView in the near future. Use mouseMoved instead");
|
|
1609
|
-
return this._mouseMoved(ev) ;
|
|
1610
|
-
},
|
|
1611
|
-
|
|
1612
|
-
mouseMoved: function(ev) {
|
|
1613
|
-
if (this.mouseDidMove != SC.CollectionView.prototype.mouseDidMove) {
|
|
1614
|
-
return this.mouseDidMove(ev) ;
|
|
1615
|
-
} else return this._mouseMoved(ev) ;
|
|
1616
|
-
},
|
|
1617
|
-
|
|
1618
|
-
_mouseMoved: function(ev) {
|
|
1619
|
-
var view = this.itemViewForEvent(ev) ;
|
|
1620
|
-
// handle hover events.
|
|
1621
|
-
if(this._lastHoveredItem && ((view === null) || (view != this._lastHoveredItem)) && this._lastHoveredItem.didMouseOut) {
|
|
1622
|
-
this._lastHoveredItem.didMouseOut(ev);
|
|
1973
|
+
if (this._lastInsertionIndex != ret) {
|
|
1974
|
+
var itemView = this.itemViewForContent(this.get('content').objectAt(ret));
|
|
1975
|
+
this.showInsertionPointBefore(itemView) ;
|
|
1976
|
+
}
|
|
1977
|
+
this._lastInsertionIndex = ret ;
|
|
1978
|
+
|
|
1623
1979
|
}
|
|
1624
|
-
|
|
1625
|
-
if (view && view.didMouseOver) view.didMouseOver(ev) ;
|
|
1980
|
+
return SC.DRAG_MOVE;
|
|
1626
1981
|
},
|
|
1627
1982
|
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
},
|
|
1632
|
-
|
|
1633
|
-
mouseOut: function(ev) {
|
|
1634
|
-
if (this.didMouseOut != SC.CollectionView.prototype.didMouseOut) {
|
|
1635
|
-
return this.didMouseOut(ev) ;
|
|
1636
|
-
} else return this._mouseOut(ev) ;
|
|
1637
|
-
},
|
|
1638
|
-
|
|
1639
|
-
_mouseOut: function(ev) {
|
|
1640
|
-
|
|
1641
|
-
var view = this._lastHoveredItem ;
|
|
1642
|
-
this._lastHoveredItem = null ;
|
|
1643
|
-
if (view && view.didMouseOut) view.didMouseOut(ev) ;
|
|
1983
|
+
dragExited: function() {
|
|
1984
|
+
this.hideInsertionPoint() ;
|
|
1985
|
+
this._lastInsertionIndex = null ;
|
|
1644
1986
|
},
|
|
1645
1987
|
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
return this._doubleClick(ev) ;
|
|
1988
|
+
dragEnded: function() {
|
|
1989
|
+
this.hideInsertionPoint() ;
|
|
1990
|
+
this._lastInsertionIndex = null ;
|
|
1650
1991
|
},
|
|
1651
1992
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
return this.didDoubleClick(ev) ;
|
|
1655
|
-
} else return this._doubleClick(ev) ;
|
|
1993
|
+
prepareForDragOperation: function(op, drag) {
|
|
1994
|
+
return SC.DRAG_ANY;
|
|
1656
1995
|
},
|
|
1657
1996
|
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
var collection = this.get('content');
|
|
1669
|
-
|
|
1670
|
-
// bounds of the collection...
|
|
1671
|
-
var collectionLowerBounds = 0;
|
|
1672
|
-
var collectionUpperBounds = (collection.get('length') - 1);
|
|
1673
|
-
|
|
1674
|
-
var selectionBeginIndex = collection.indexOf(selection.first());
|
|
1675
|
-
var selectionEndIndex = collection.indexOf(selection.last());
|
|
1676
|
-
|
|
1677
|
-
var previousMouseDownIndex = collection.indexOf(this._previousMouseDownContent);
|
|
1678
|
-
// _previousMouseDownContent couldn't be found... either it hasn't been set yet or the record has been deleted by the user
|
|
1679
|
-
// fall back to the first selected item.
|
|
1680
|
-
if (previousMouseDownIndex == -1) previousMouseDownIndex = selectionBeginIndex;
|
|
1997
|
+
performDragOperation: function(op, drag) {
|
|
1998
|
+
|
|
1999
|
+
SC.Benchmark.start('%@ performDragOperation'.fmt(SC.guidFor(this))) ;
|
|
2000
|
+
|
|
2001
|
+
var loc = drag.get('location') ;
|
|
2002
|
+
loc = this.convertFrameFromView(loc, null) ;
|
|
2003
|
+
|
|
2004
|
+
// if op is MOVE or COPY, add item to view.
|
|
2005
|
+
var objects = drag.dataForType('_mouseDownContent') ;
|
|
2006
|
+
if (objects && (op == SC.DRAG_MOVE)) {
|
|
1681
2007
|
|
|
2008
|
+
// find the index to for the new insertion
|
|
2009
|
+
var idx = this.insertionIndexForLocation(loc) ;
|
|
1682
2010
|
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
if (currentMouseDownIndex == -1) throw "Unable to extend selection to an item that's not in the collection!";
|
|
2011
|
+
var content = this.get('content') ;
|
|
2012
|
+
content.beginPropertyChanges(); // suspend notifications
|
|
1686
2013
|
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
if (currentMouseDownIndex == previousMouseDownIndex) {
|
|
1695
|
-
selectionBeginIndex = currentMouseDownIndex;
|
|
1696
|
-
selectionEndIndex = currentMouseDownIndex;
|
|
1697
|
-
} else if (currentMouseDownIndex > previousMouseDownIndex) {
|
|
1698
|
-
selectionBeginIndex = previousMouseDownIndex;
|
|
1699
|
-
selectionEndIndex = currentMouseDownIndex;
|
|
1700
|
-
} else if (currentMouseDownIndex < previousMouseDownIndex){
|
|
1701
|
-
selectionBeginIndex = currentMouseDownIndex;
|
|
1702
|
-
selectionEndIndex = previousMouseDownIndex;
|
|
2014
|
+
// find the old index and remove it.
|
|
2015
|
+
var objectsIdx = objects.get('length') ;
|
|
2016
|
+
while(--objectsIdx >= 0) {
|
|
2017
|
+
var obj = objects.objectAt(objectsIdx) ;
|
|
2018
|
+
var old = content.indexOf(obj) ;
|
|
2019
|
+
if (old >= 0) content.removeAt(old) ;
|
|
2020
|
+
if ((old >= 0) && (old < idx)) idx--; //adjust idx
|
|
1703
2021
|
}
|
|
2022
|
+
|
|
2023
|
+
// now insert objects at new location
|
|
2024
|
+
content.replace(idx, 0, objects) ;
|
|
2025
|
+
content.endPropertyChanges(); // restart notifications
|
|
1704
2026
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
return collection.slice(selectionBeginIndex, selectionEndIndex);
|
|
2027
|
+
|
|
2028
|
+
SC.Benchmark.end('%@ performDragOperation'.fmt(SC.guidFor(this))) ;
|
|
2029
|
+
console.log(SC.Benchmark.report()) ;
|
|
2030
|
+
|
|
2031
|
+
return SC.DRAG_MOVE;
|
|
1711
2032
|
},
|
|
1712
2033
|
|
|
2034
|
+
concludeDragOperation: function(op, drag) {
|
|
2035
|
+
this.hideInsertionPoint() ;
|
|
2036
|
+
this._lastInsertionIndex = null ;
|
|
2037
|
+
},
|
|
2038
|
+
|
|
1713
2039
|
|
|
1714
2040
|
|
|
1715
2041
|
// ......................................
|
|
@@ -1717,20 +2043,24 @@ SC.CollectionView = SC.View.extend(
|
|
|
1717
2043
|
//
|
|
1718
2044
|
|
|
1719
2045
|
init: function() {
|
|
2046
|
+
|
|
2047
|
+
// Initialize internal hashes and arrays. Normally the best approach to this
|
|
2048
|
+
// is to initialize a property only when it is used. However, these properties
|
|
2049
|
+
// are critical to layout and therefore will always be needed so it is faster
|
|
2050
|
+
// to do it once here.
|
|
2051
|
+
this._itemViewsByContent= {};
|
|
2052
|
+
this._groupViewsByValue= {};
|
|
2053
|
+
this._groupViewCounts= {};
|
|
2054
|
+
this._zombieGroupViews= {};
|
|
2055
|
+
this._itemViewsByGuid = {} ;
|
|
2056
|
+
|
|
2057
|
+
this._itemViewPool= [];
|
|
2058
|
+
this._groupViewPool= [];
|
|
2059
|
+
|
|
1720
2060
|
arguments.callee.base.apply(this, arguments) ;
|
|
1721
2061
|
this._dropTargetObserver();
|
|
1722
2062
|
},
|
|
1723
2063
|
|
|
1724
|
-
// When canReorderContent changes, add or remove drop target as necessary.
|
|
1725
|
-
_dropTargetObserver: function() {
|
|
1726
|
-
var canDrop = this.get('canReorderContent') || this.get('isDropTarget') ;
|
|
1727
|
-
if (canDrop) {
|
|
1728
|
-
SC.Drag.addDropTarget(this) ;
|
|
1729
|
-
} else {
|
|
1730
|
-
SC.Drag.removeDropTarget(this) ;
|
|
1731
|
-
}
|
|
1732
|
-
}.observes('canReorderContent', 'isDropTarget'),
|
|
1733
|
-
|
|
1734
2064
|
// Perform the action. Supports legacy behavior as well as newer style
|
|
1735
2065
|
// action dispatch.
|
|
1736
2066
|
_action: function(view, evt) {
|
|
@@ -1759,76 +2089,93 @@ SC.CollectionView = SC.View.extend(
|
|
|
1759
2089
|
return view.action(evt) ;
|
|
1760
2090
|
}
|
|
1761
2091
|
},
|
|
1762
|
-
|
|
1763
|
-
_viewsForContent: null,
|
|
1764
|
-
_content: [], // cached for changes.
|
|
1765
|
-
propertyObserver: function(observing,target,key,value)
|
|
1766
|
-
{
|
|
1767
|
-
if (target == this)
|
|
1768
|
-
{
|
|
1769
|
-
// update children when content changes.
|
|
1770
|
-
if (key == 'content')
|
|
1771
|
-
{
|
|
1772
|
-
// cache the observer binding
|
|
1773
|
-
if (!this._boundObserver)
|
|
1774
|
-
{
|
|
1775
|
-
this._boundObserver = this._contentPropertyObserver.bind(this);
|
|
1776
|
-
}
|
|
1777
2092
|
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
// remove and re-add the observer for "[]" before changing the content property
|
|
1787
|
-
// this triggers a render of the child item views whenever the array is modified.
|
|
1788
|
-
if (this._content && this._content.removeObserver) this._content.removeObserver('[]', this._boundObserver);
|
|
1789
|
-
this._content = value;
|
|
1790
|
-
if (this._content && this._content.addObserver) this._content.addObserver('[]', this._boundObserver);
|
|
1791
|
-
|
|
1792
|
-
// only re-render the collection if the content was actually changed to a new value.
|
|
1793
|
-
if (!isEqual)
|
|
1794
|
-
{
|
|
1795
|
-
this._contentPropertyObserver(target,key,value);
|
|
1796
|
-
}
|
|
1797
|
-
|
|
1798
|
-
// update selection when selection changes. set this as a timeout so
|
|
1799
|
-
// that a render can finish first.
|
|
1800
|
-
}
|
|
1801
|
-
else if (key == 'selection')
|
|
1802
|
-
{
|
|
1803
|
-
if (!this._updatingSel)
|
|
1804
|
-
{
|
|
1805
|
-
this._updatingSel = this.invokeLater('_updateSelectionState',1);
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
2093
|
+
/** Add/remove from drop targets as needed. */
|
|
2094
|
+
_dropTargetObserver: function() {
|
|
2095
|
+
var canDrop = this.get('canReorderContent') || this.get('isDropTarget') ;
|
|
2096
|
+
if (canDrop) {
|
|
2097
|
+
SC.Drag.addDropTarget(this) ;
|
|
2098
|
+
} else {
|
|
2099
|
+
SC.Drag.removeDropTarget(this) ;
|
|
1808
2100
|
}
|
|
1809
|
-
},
|
|
2101
|
+
}.observes('canReorderContent', 'isDropTarget'),
|
|
2102
|
+
|
|
2103
|
+
/** @private
|
|
2104
|
+
Whenever content changes, update children and also start observing
|
|
2105
|
+
new [] property.
|
|
2106
|
+
*/
|
|
2107
|
+
_contentObserver: function() {
|
|
2108
|
+
var content = this.get('content') ;
|
|
2109
|
+
if (SC.isEqual(content, this._content)) return ; // nothing to do
|
|
2110
|
+
|
|
2111
|
+
if (!this._boundContentPropertyObserver) {
|
|
2112
|
+
this._boundContentPropertyObserver = this._contentPropertyObserver.bind(this) ;
|
|
2113
|
+
}
|
|
2114
|
+
var func = this._boundContentPropertyObserver ;
|
|
2115
|
+
|
|
2116
|
+
// remove old observer, add new observer, and trigger content property change
|
|
2117
|
+
if (this._content) this._content.removeObserver('[]', func) ;
|
|
2118
|
+
if (content) content.addObserver('[]', func) ;
|
|
2119
|
+
this._content = content; //cache
|
|
2120
|
+
this._contentPropertyRevision = null ;
|
|
2121
|
+
this._contentPropertyObserver(this, '[]', content, content.propertyRevision) ;
|
|
2122
|
+
}.observes('content'),
|
|
2123
|
+
|
|
2124
|
+
/** @private
|
|
2125
|
+
Whenever the selection changes, update the itemViews.
|
|
2126
|
+
*/
|
|
2127
|
+
_selectionObserver: function() {
|
|
2128
|
+
var sel = this.get('selection') ;
|
|
2129
|
+
if (SC.isEqual(sel, this._selection)) return ; // nothing to do
|
|
1810
2130
|
|
|
2131
|
+
if (!this._boundSelectionPropertyObserver) {
|
|
2132
|
+
this._boundSelectionPropertyObserver = this._selectionPropertyObserver.bind(this) ;
|
|
2133
|
+
}
|
|
2134
|
+
var func = this._boundSelectionPropertyObserver ;
|
|
2135
|
+
|
|
2136
|
+
if (this._selection) this._selection.removeObserver('[]', func) ;
|
|
2137
|
+
if (sel) sel.addObserver('[]', func) ;
|
|
2138
|
+
this._selection = sel ;
|
|
2139
|
+
this._selectionPropertyRevision = null ;
|
|
2140
|
+
var propertyRevision = (sel) ? sel.propertyRevision : null;
|
|
2141
|
+
this._selectionPropertyObserver(this, '[]', sel, propertyRevision) ;
|
|
2142
|
+
}.observes('selection'),
|
|
2143
|
+
|
|
1811
2144
|
// called on content change *and* content.[] change...
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2145
|
+
// update children if this is a new propertyRevision
|
|
2146
|
+
//
|
|
2147
|
+
// UPDATE:
|
|
2148
|
+
// -- recheck all item views, add/remove children as needed
|
|
2149
|
+
// -- update layout on all item views.
|
|
2150
|
+
// -- optional: determine the first item view that does not match.
|
|
2151
|
+
//
|
|
2152
|
+
_contentPropertyObserver: function(target, key, value, rev) {
|
|
2153
|
+
if (!this._updatingContent && (!rev || (rev != this._contentPropertyRevision))) {
|
|
2154
|
+
this._contentPropertyRevision = rev ;
|
|
2155
|
+
this._updatingContent = true ;
|
|
2156
|
+
this._hasChildren = false ;
|
|
2157
|
+
this.updateChildren(true) ;
|
|
2158
|
+
this._updatingContent = false ;
|
|
1820
2159
|
}
|
|
1821
2160
|
},
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2161
|
+
|
|
2162
|
+
// called on selection change and selection.[] change...
|
|
2163
|
+
// update selection states if this is a new propertyRevision
|
|
2164
|
+
_selectionPropertyObserver: function(target, key, value, rev) {
|
|
2165
|
+
if (!this._updatingSel && (!rev || (rev != this._selectionPropertyRevision))) {
|
|
2166
|
+
this._selectionPropertyRevision = rev ;
|
|
2167
|
+
this._updatingSel = true ;
|
|
2168
|
+
this._selectionHash = null ; // flush cache
|
|
1825
2169
|
this.updateSelectionStates() ;
|
|
1826
|
-
|
|
1827
|
-
console.log('exception while updating selection states in %@: %@'.format(this,e)) ;
|
|
2170
|
+
this._updatingSel = false ;
|
|
1828
2171
|
}
|
|
1829
|
-
this._updatingSel = null ;
|
|
1830
2172
|
},
|
|
1831
2173
|
|
|
2174
|
+
// If isVisibleInWindow status changes, updateChildren if we are dirty.
|
|
2175
|
+
_isVisibleInWindowObserver: function() {
|
|
2176
|
+
if (this.get('isDirty')) this.updateChildren() ;
|
|
2177
|
+
}.observes('isVisibleInWindow'),
|
|
2178
|
+
|
|
1832
2179
|
// ======================================================================
|
|
1833
2180
|
// DEPRECATED APIS (Still available for compatibility)
|
|
1834
2181
|
|
|
@@ -1847,3 +2194,5 @@ SC.CollectionView = SC.View.extend(
|
|
|
1847
2194
|
|
|
1848
2195
|
|
|
1849
2196
|
}) ;
|
|
2197
|
+
|
|
2198
|
+
|