sproutcore 0.9.4 → 0.9.5
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 +70 -2
- data/Manifest.txt +3 -15
- data/config/hoe.rb +1 -1
- data/frameworks/sproutcore/animation/animation.js +411 -0
- data/frameworks/sproutcore/controllers/array.js +68 -21
- data/frameworks/sproutcore/controllers/object.js +21 -2
- data/frameworks/sproutcore/drag/drag.js +13 -4
- data/frameworks/sproutcore/drag/drop_target.js +26 -19
- data/frameworks/sproutcore/english.lproj/core.css +4 -0
- data/frameworks/sproutcore/english.lproj/strings.js +5 -0
- data/frameworks/sproutcore/english.lproj/theme.css +5 -0
- data/frameworks/sproutcore/foundation/application.js +1 -2
- data/frameworks/sproutcore/foundation/set.js +31 -12
- data/frameworks/sproutcore/foundation/sorted_set.js +590 -0
- data/frameworks/sproutcore/foundation/string.js +43 -9
- data/frameworks/sproutcore/globals/window.js +34 -9
- data/frameworks/sproutcore/lib/button_views.rb +1 -0
- data/frameworks/sproutcore/lib/collection_view.rb +1 -0
- data/frameworks/sproutcore/lib/core_views.rb +3 -0
- data/frameworks/sproutcore/lib/index.rhtml +1 -1
- data/frameworks/sproutcore/mixins/collection_view_delegate.js +201 -0
- data/frameworks/sproutcore/mixins/observable.js +2 -7
- data/frameworks/sproutcore/models/record.js +1 -1
- data/frameworks/sproutcore/models/store.js +81 -28
- data/frameworks/sproutcore/tests/views/view/clippingFrame.rhtml +9 -6
- data/frameworks/sproutcore/views/collection/collection.js +649 -211
- data/frameworks/sproutcore/views/collection/grid.js +62 -26
- data/frameworks/sproutcore/views/collection/list.js +57 -21
- data/frameworks/sproutcore/views/collection/source_list.js +61 -13
- data/frameworks/sproutcore/views/image.js +7 -0
- data/frameworks/sproutcore/views/inline_text_field.js +4 -5
- data/frameworks/sproutcore/views/slider.js +2 -0
- data/frameworks/sproutcore/views/view.js +2 -2
- data/lib/sproutcore/build_tools/html_builder.rb +4 -6
- data/lib/sproutcore/build_tools/resource_builder.rb +32 -20
- data/lib/sproutcore/bundle.rb +130 -32
- data/lib/sproutcore/bundle_manifest.rb +24 -21
- data/lib/sproutcore/helpers/static_helper.rb +22 -9
- data/lib/sproutcore/merb/bundle_controller.rb +4 -3
- data/lib/sproutcore/version.rb +1 -1
- metadata +14 -17
- data/clients/view_builder/builders/builder.js +0 -339
- data/clients/view_builder/builders/button.js +0 -81
- data/clients/view_builder/controllers/document.js +0 -21
- data/clients/view_builder/core.js +0 -19
- data/clients/view_builder/english.lproj/body.css +0 -77
- data/clients/view_builder/english.lproj/body.rhtml +0 -39
- data/clients/view_builder/english.lproj/controls.css +0 -0
- data/clients/view_builder/english.lproj/strings.js +0 -14
- data/clients/view_builder/main.js +0 -38
- data/clients/view_builder/mixins/design_mode.js +0 -92
- data/clients/view_builder/tests/controllers/document.rhtml +0 -20
- data/clients/view_builder/tests/views/builder.rhtml +0 -20
- data/clients/view_builder/tests/views/palette.rhtml +0 -21
- data/clients/view_builder/views/builder.js +0 -26
- data/clients/view_builder/views/palette.js +0 -30
@@ -175,8 +175,10 @@ Test.context("CASE 2: A scrollable view - child view cannot fit within visible a
|
|
175
175
|
Test.context("CASE 3: A scrollable view with extra height - nested child view can fit within visible area", {
|
176
176
|
|
177
177
|
"clippingFrame should == frame when view is entirely visible": function() {
|
178
|
-
|
179
|
-
var
|
178
|
+
|
179
|
+
var f = nested.get('frame') ;
|
180
|
+
var cf = nested.get('clippingFrame') ;
|
181
|
+
console.log('%@: f=%@ -- cf=%@'.fmt(nested, $I(f), $I(cf)));
|
180
182
|
SC.rectsEqual(f,cf).shouldEqual(true) ;
|
181
183
|
},
|
182
184
|
|
@@ -197,8 +199,6 @@ Test.context("CASE 3: A scrollable view with extra height - nested child view ca
|
|
197
199
|
// collect new frame and compare
|
198
200
|
var ncf = this.nested.get('clippingFrame') ;
|
199
201
|
|
200
|
-
console.log('ncf: %@'.fmt($H(ncf).inspect()));
|
201
|
-
console.log('cf: %@'.fmt($H(cf).inspect()));
|
202
202
|
window.case3 = this ;
|
203
203
|
|
204
204
|
SC.rectsEqual(ncf,cf).shouldEqual(true) ;
|
@@ -219,7 +219,7 @@ Test.context("CASE 3: A scrollable view with extra height - nested child view ca
|
|
219
219
|
"clippingFrame should == frame when moved around within view (and notify)": function() {
|
220
220
|
var of= this.nested.get('frame') ;
|
221
221
|
this.nested.set('frame', { x: 20, y: 20 }) ;
|
222
|
-
|
222
|
+
|
223
223
|
var f = this.nested.get('frame') ;
|
224
224
|
var cf = this.nested.get('clippingFrame') ;
|
225
225
|
SC.rectsEqual(f,cf).shouldEqual(true) ;
|
@@ -228,7 +228,10 @@ Test.context("CASE 3: A scrollable view with extra height - nested child view ca
|
|
228
228
|
this.nested.set('frame', of) ;
|
229
229
|
},
|
230
230
|
|
231
|
-
setup: function() {
|
231
|
+
setup: function() {
|
232
|
+
this.v = SC.page.get('case3');
|
233
|
+
this.nested = this.v.child.nestedChild;
|
234
|
+
}
|
232
235
|
|
233
236
|
});
|
234
237
|
|
@@ -5,10 +5,17 @@
|
|
5
5
|
|
6
6
|
require('views/view') ;
|
7
7
|
require('views/label') ;
|
8
|
+
require('mixins/collection_view_delegate') ;
|
8
9
|
|
9
10
|
SC.BENCHMARK_UPDATE_CHILDREN = NO ;
|
10
11
|
SC.VALIDATE_COLLECTION_CONSISTANCY = NO ;
|
11
12
|
|
13
|
+
/**
|
14
|
+
Special drag operation passed to delegate if the collection view proposes
|
15
|
+
to perform a reorder event.
|
16
|
+
*/
|
17
|
+
SC.DRAG_REORDER = 0xfff0001 ;
|
18
|
+
|
12
19
|
/** Indicates that selection points should be selected using horizontal
|
13
20
|
orientation.
|
14
21
|
*/
|
@@ -43,8 +50,10 @@ SC.REMOVE_COLLECTION_ROOT_ELEMENT_DURING_RENDER = NO ;
|
|
43
50
|
property to allow selection.)
|
44
51
|
|
45
52
|
@extends SC.View
|
53
|
+
@extends SC.CollectionViewDelegate
|
54
|
+
|
46
55
|
*/
|
47
|
-
SC.CollectionView = SC.View.extend(
|
56
|
+
SC.CollectionView = SC.View.extend(SC.CollectionViewDelegate,
|
48
57
|
/** @scope SC.CollectionView.prototype */
|
49
58
|
{
|
50
59
|
|
@@ -98,6 +107,17 @@ SC.CollectionView = SC.View.extend(
|
|
98
107
|
/** @private */
|
99
108
|
selectionBindingDefault: SC.Binding.Multiple,
|
100
109
|
|
110
|
+
/**
|
111
|
+
Delegate used to implement fine-grained control over collection view
|
112
|
+
behaviors.
|
113
|
+
|
114
|
+
You can assign a delegate object to this property that will be consulted
|
115
|
+
for various decisions regarding drag and drop, selection behavior, and
|
116
|
+
even rendering. The object you place here must implement some or all of
|
117
|
+
the SC.CollectionViewDelegate mixin.
|
118
|
+
*/
|
119
|
+
delegate: null,
|
120
|
+
|
101
121
|
/**
|
102
122
|
Allow user to select content using the mouse and keyboard
|
103
123
|
|
@@ -131,9 +151,9 @@ SC.CollectionView = SC.View.extend(
|
|
131
151
|
Allow user to edit content views.
|
132
152
|
|
133
153
|
The collection view will set the isEditable property on its item views to
|
134
|
-
reflect the same value of this property. Whenever isEditable is false,
|
135
|
-
user will not be able to reorder, add, or delete items regardless of
|
136
|
-
canReorderContent and canDeleteContent and isDropTarget properties.
|
154
|
+
reflect the same value of this property. Whenever isEditable is false,
|
155
|
+
the user will not be able to reorder, add, or delete items regardless of
|
156
|
+
the canReorderContent and canDeleteContent and isDropTarget properties.
|
137
157
|
*/
|
138
158
|
isEditable: true,
|
139
159
|
|
@@ -151,14 +171,14 @@ SC.CollectionView = SC.View.extend(
|
|
151
171
|
|
152
172
|
/** @private */
|
153
173
|
canReorderContentBindingDefault: SC.Binding.Bool,
|
154
|
-
|
174
|
+
|
155
175
|
/**
|
156
176
|
Allow the user to delete items using the delete key
|
157
177
|
|
158
178
|
If true the user will be allowed to delete selected items using the delete
|
159
179
|
key. Otherwise deletes will not be permitted.
|
160
180
|
*/
|
161
|
-
canDeleteContent:
|
181
|
+
canDeleteContent: NO,
|
162
182
|
|
163
183
|
/** @private */
|
164
184
|
canDeleteContentBindingDefault: SC.Binding.Bool,
|
@@ -166,10 +186,11 @@ SC.CollectionView = SC.View.extend(
|
|
166
186
|
/**
|
167
187
|
Accept drops for data other than reordering.
|
168
188
|
|
169
|
-
Setting this property to return true when the view is instantiated will
|
170
|
-
it to be registered as a drop target, activating the other drop
|
189
|
+
Setting this property to return true when the view is instantiated will
|
190
|
+
cause it to be registered as a drop target, activating the other drop
|
191
|
+
machinery.
|
171
192
|
*/
|
172
|
-
isDropTarget:
|
193
|
+
isDropTarget: NO,
|
173
194
|
|
174
195
|
/**
|
175
196
|
Use toggle selection instead of normal click behavior.
|
@@ -180,7 +201,7 @@ SC.CollectionView = SC.View.extend(
|
|
180
201
|
|
181
202
|
@type Boolean
|
182
203
|
*/
|
183
|
-
useToggleSelection:
|
204
|
+
useToggleSelection: NO,
|
184
205
|
|
185
206
|
/**
|
186
207
|
Trigger the action method on a single click.
|
@@ -197,6 +218,20 @@ SC.CollectionView = SC.View.extend(
|
|
197
218
|
@type {Boolean}
|
198
219
|
*/
|
199
220
|
actOnSelect: false,
|
221
|
+
|
222
|
+
|
223
|
+
/**
|
224
|
+
Select an item immediately on mouse down
|
225
|
+
|
226
|
+
Normally as soon as you begin a click the item will be selected.
|
227
|
+
|
228
|
+
In some UI scenarios, you might want to prevent selection until
|
229
|
+
the mouse is released, so you can perform, for instance, a drag operation
|
230
|
+
without actually selecting the target item.
|
231
|
+
|
232
|
+
@type {Boolean}
|
233
|
+
*/
|
234
|
+
selectOnMouseDown: true,
|
200
235
|
|
201
236
|
/**
|
202
237
|
Property key to use to group objects.
|
@@ -615,8 +650,6 @@ SC.CollectionView = SC.View.extend(
|
|
615
650
|
SC.Benchmark.start(bkey);
|
616
651
|
}
|
617
652
|
|
618
|
-
//console.log('updateChildren') ;
|
619
|
-
|
620
653
|
this.beginPropertyChanges() ; // avoid sending notifications
|
621
654
|
|
622
655
|
// STEP 1: Update frame size if needed. Required to compute the
|
@@ -712,8 +745,19 @@ SC.CollectionView = SC.View.extend(
|
|
712
745
|
didChange = true ;
|
713
746
|
}
|
714
747
|
}
|
715
|
-
|
748
|
+
|
749
|
+
// Recache frames just in case this changed the scroll height.
|
750
|
+
this.recacheFrames() ;
|
751
|
+
|
752
|
+
// Set this to true once children have been rendered. Whenever the
|
753
|
+
// content changes, we don't want resize or clipping frame changes to
|
754
|
+
// cause a refresh until the content has been rendered for the first time.
|
755
|
+
this._hasChildren = range.length>0 ;
|
756
|
+
this.set('isDirty',false);
|
757
|
+
|
716
758
|
// Clean out some cached items and notify their changes.
|
759
|
+
// NOTE: This must be called after _hasChildren has been set or
|
760
|
+
// updateSelectionStates() may not run.
|
717
761
|
if (didChange) {
|
718
762
|
this._flushZombieGroupViews() ;
|
719
763
|
this.updateSelectionStates() ;
|
@@ -725,16 +769,6 @@ SC.CollectionView = SC.View.extend(
|
|
725
769
|
this.notifyPropertyChange('groupViews') ;
|
726
770
|
}
|
727
771
|
|
728
|
-
// Recache frames just in case this changed the scroll height.
|
729
|
-
this.recacheFrames() ;
|
730
|
-
|
731
|
-
|
732
|
-
// Set this to true once children have been rendered. Whenever the
|
733
|
-
// content changes, we don't want resize or clipping frame changes to
|
734
|
-
// cause a refresh until the content has been rendered for the first time.
|
735
|
-
this._hasChildren = range.length>0 ;
|
736
|
-
|
737
|
-
this.set('isDirty',false);
|
738
772
|
this.endPropertyChanges() ;
|
739
773
|
if (SC.BENCHMARK_UPDATE_CHILDREN) SC.Benchmark.end(bkey);
|
740
774
|
},
|
@@ -783,7 +817,7 @@ SC.CollectionView = SC.View.extend(
|
|
783
817
|
properties changed. You should not need to call or override it often.
|
784
818
|
*/
|
785
819
|
updateSelectionStates: function() {
|
786
|
-
if (!this.
|
820
|
+
if (!this._hasChildren) return ;
|
787
821
|
var selection = this.get('selection') || [];
|
788
822
|
|
789
823
|
// First, for efficiency, turn the selection into a hash by GUID. This
|
@@ -1002,7 +1036,7 @@ SC.CollectionView = SC.View.extend(
|
|
1002
1036
|
@returns {SC.View} the new itemView.
|
1003
1037
|
*/
|
1004
1038
|
_insertItemViewFor: function(content, groupBy, contentIndex) {
|
1005
|
-
|
1039
|
+
|
1006
1040
|
// first look for a matching record.
|
1007
1041
|
var key = SC.guidFor(content) ;
|
1008
1042
|
var ret = this._itemViewsByContent[key];
|
@@ -1247,6 +1281,9 @@ SC.CollectionView = SC.View.extend(
|
|
1247
1281
|
// SELECTION
|
1248
1282
|
//
|
1249
1283
|
|
1284
|
+
/** @private
|
1285
|
+
Finds the smallest index of a content object in the selected array.
|
1286
|
+
*/
|
1250
1287
|
_indexOfSelectionTop: function() {
|
1251
1288
|
var content = this.get('content');
|
1252
1289
|
var sel = this.get('selection');
|
@@ -1262,7 +1299,10 @@ SC.CollectionView = SC.View.extend(
|
|
1262
1299
|
|
1263
1300
|
return (indexOfSelected >= contentLength) ? -1 : indexOfSelected ;
|
1264
1301
|
},
|
1265
|
-
|
1302
|
+
|
1303
|
+
/**
|
1304
|
+
Finds the largest index of a content object in the selection array.
|
1305
|
+
*/
|
1266
1306
|
_indexOfSelectionBottom: function() {
|
1267
1307
|
var content = this.get('content');
|
1268
1308
|
var sel = this.get('selection');
|
@@ -1422,6 +1462,7 @@ SC.CollectionView = SC.View.extend(
|
|
1422
1462
|
}
|
1423
1463
|
if (itemView) this.scrollToItemView(itemView);
|
1424
1464
|
},
|
1465
|
+
|
1425
1466
|
/**
|
1426
1467
|
Scroll the rootElement (if needed) to ensure that the item is visible.
|
1427
1468
|
@param {SC.View} view The item view to scroll to
|
@@ -1450,7 +1491,8 @@ SC.CollectionView = SC.View.extend(
|
|
1450
1491
|
var base = (extendSelection) ? this.get('selection') : [] ;
|
1451
1492
|
var sel = [items].concat(base).flatten().uniq() ;
|
1452
1493
|
|
1453
|
-
// if you are not extending the selection, then clear the selection
|
1494
|
+
// if you are not extending the selection, then clear the selection
|
1495
|
+
// anchor.
|
1454
1496
|
this._selectionAnchor = null ;
|
1455
1497
|
this.set('selection',sel) ;
|
1456
1498
|
},
|
@@ -1466,6 +1508,62 @@ SC.CollectionView = SC.View.extend(
|
|
1466
1508
|
this.set('selection',sel) ;
|
1467
1509
|
},
|
1468
1510
|
|
1511
|
+
/**
|
1512
|
+
Deletes the selected content if canDeleteContent is YES.
|
1513
|
+
|
1514
|
+
This will invoke delegate methods to provide fine-grained control.
|
1515
|
+
|
1516
|
+
@returns {Boolean} YES if deletion is possible, even if none actually occurred.
|
1517
|
+
*/
|
1518
|
+
deleteSelection: function() {
|
1519
|
+
|
1520
|
+
// perform some basic checks...
|
1521
|
+
if (!this.get('canDeleteContent')) return NO;
|
1522
|
+
var sel = Array.from(this.get('selection'));
|
1523
|
+
if (!sel || sel.get('length') === 0) return NO ;
|
1524
|
+
|
1525
|
+
// let the delegate decide what to actually delete. If this returns an
|
1526
|
+
// empty array or null, just do nothing.
|
1527
|
+
sel = this.invokeDelegateMethod(this.delegate, 'collectionViewShouldDeleteContent', this, sel) ;
|
1528
|
+
sel = Array.from(sel) ; // ensure this is an array
|
1529
|
+
if (!sel || sel.get('length') === 0) return YES ;
|
1530
|
+
|
1531
|
+
// now have the delegate (or us) perform the deletion. The collection
|
1532
|
+
// view implements a default version of this method.
|
1533
|
+
this.invokeDelegateMethod(this.delegate, 'collectionViewDeleteContent', this, sel) ;
|
1534
|
+
return YES ;
|
1535
|
+
},
|
1536
|
+
|
1537
|
+
/**
|
1538
|
+
Default implementation of the delegate method.
|
1539
|
+
|
1540
|
+
This method will delete the passed items from the content array using
|
1541
|
+
standard array methods. This is often suitable if you are using an
|
1542
|
+
array controller or a real array for your content.
|
1543
|
+
|
1544
|
+
@param view {SC.CollectionView} this
|
1545
|
+
@param sel {Array} the items to delete
|
1546
|
+
@returns {Boolean} YES if the deletion was a success.
|
1547
|
+
*/
|
1548
|
+
collectionViewDeleteContent: function(view, sel) {
|
1549
|
+
|
1550
|
+
// get the content. Bail if this cannot be used as an array.
|
1551
|
+
var content = this.get('content') ;
|
1552
|
+
if (!content || !content.removeObject) return NO ;
|
1553
|
+
|
1554
|
+
// suspend property notifications and remove the objects...
|
1555
|
+
if (content.beginPropertyChanges) content.beginPropertyChanges();
|
1556
|
+
var idx = sel.get('length') ;
|
1557
|
+
while(--idx >= 0) {
|
1558
|
+
var item = sel.objectAt(idx) ;
|
1559
|
+
content.removeObject(item) ;
|
1560
|
+
}
|
1561
|
+
// begin notifying again...
|
1562
|
+
if (content.endPropertyChanges) content.endPropertyChanges() ;
|
1563
|
+
|
1564
|
+
return YES ; // done!
|
1565
|
+
},
|
1566
|
+
|
1469
1567
|
// ......................................
|
1470
1568
|
// EVENT HANDLING
|
1471
1569
|
//
|
@@ -1477,7 +1575,30 @@ SC.CollectionView = SC.View.extend(
|
|
1477
1575
|
keyUp: function() { return true; },
|
1478
1576
|
|
1479
1577
|
/** @private
|
1480
|
-
|
1578
|
+
Handle select all keyboard event.
|
1579
|
+
*/
|
1580
|
+
selectAll: function(evt) {
|
1581
|
+
var content = (this.get('content') || []).slice() ;
|
1582
|
+
this.selectItems(content, NO) ;
|
1583
|
+
return YES ;
|
1584
|
+
},
|
1585
|
+
|
1586
|
+
/** @private
|
1587
|
+
Handle delete keyboard event.
|
1588
|
+
*/
|
1589
|
+
deleteBackward: function(evt) {
|
1590
|
+
return this.deleteSelection() ;
|
1591
|
+
},
|
1592
|
+
|
1593
|
+
/** @private
|
1594
|
+
Handle delete keyboard event.
|
1595
|
+
*/
|
1596
|
+
deleteForward: function(evt) {
|
1597
|
+
return this.deleteSelection() ;
|
1598
|
+
},
|
1599
|
+
|
1600
|
+
/** @private
|
1601
|
+
Selects the same item on the next row or moves down one if
|
1481
1602
|
itemsPerRow = 1
|
1482
1603
|
*/
|
1483
1604
|
moveDown: function(sender, evt) {
|
@@ -1486,7 +1607,7 @@ SC.CollectionView = SC.View.extend(
|
|
1486
1607
|
},
|
1487
1608
|
|
1488
1609
|
/** @private
|
1489
|
-
Selects the same item on the next row
|
1610
|
+
Selects the same item on the next row or moves up one if
|
1490
1611
|
itemsPerRow = 1
|
1491
1612
|
*/
|
1492
1613
|
moveUp: function(sender, evt) {
|
@@ -1536,9 +1657,24 @@ SC.CollectionView = SC.View.extend(
|
|
1536
1657
|
return true ;
|
1537
1658
|
},
|
1538
1659
|
|
1660
|
+
/**
|
1661
|
+
Handles mouse down events on the collection view or on any of its
|
1662
|
+
children.
|
1663
|
+
|
1664
|
+
The default implementation of this method can handle a wide variety
|
1665
|
+
of user behaviors depending on how you have configured the various
|
1666
|
+
options for the collection view.
|
1667
|
+
|
1668
|
+
@param ev {Event} the mouse down event
|
1669
|
+
@returns {Boolean} Usually YES.
|
1670
|
+
*/
|
1539
1671
|
mouseDown: function(ev) {
|
1540
1672
|
|
1541
|
-
//
|
1673
|
+
// When the user presses the mouse down, we don't do much just yet.
|
1674
|
+
// Instead, we just need to save a bunch of state about the mouse down
|
1675
|
+
// so we can choose the right thing to do later.
|
1676
|
+
|
1677
|
+
// save the original mouse down event for use in dragging.
|
1542
1678
|
this._mouseDownEvent = ev ;
|
1543
1679
|
|
1544
1680
|
// Toggle selection only triggers on mouse up. Do nothing.
|
@@ -1550,6 +1686,9 @@ SC.CollectionView = SC.View.extend(
|
|
1550
1686
|
this._mouseDownAt = this._shouldDeselect =
|
1551
1687
|
this._shouldReselect = this._refreshSelection = false;
|
1552
1688
|
|
1689
|
+
// find the actual view the mouse was pressed down on. This will call
|
1690
|
+
// hitTest() on item views so they can implement non-square detection
|
1691
|
+
// modes. -- once we have an item view, get its content object as well.
|
1553
1692
|
var mouseDownView = this._mouseDownView = this.itemViewForEvent(ev);
|
1554
1693
|
var mouseDownContent =
|
1555
1694
|
this._mouseDownContent = (mouseDownView) ? mouseDownView.get('content') : null;
|
@@ -1557,12 +1696,13 @@ SC.CollectionView = SC.View.extend(
|
|
1557
1696
|
// become first responder if possible.
|
1558
1697
|
this.becomeFirstResponder() ;
|
1559
1698
|
|
1560
|
-
// recieved a mouseDown on the collection element, but not on one of the
|
1699
|
+
// recieved a mouseDown on the collection element, but not on one of the
|
1700
|
+
// childItems... unless we do not allow empty selections, set it to empty.
|
1561
1701
|
if (!mouseDownView) {
|
1562
1702
|
if (this.get('allowDeselectAll')) this.selectItems([], false);
|
1563
1703
|
return true ;
|
1564
1704
|
}
|
1565
|
-
|
1705
|
+
|
1566
1706
|
// collection some basic setup info
|
1567
1707
|
var selection = this.get('selection') || [];
|
1568
1708
|
var isSelected = selection.include(mouseDownContent);
|
@@ -1589,10 +1729,16 @@ SC.CollectionView = SC.View.extend(
|
|
1589
1729
|
} else if (!modifierKeyPressed && isSelected) {
|
1590
1730
|
this._shouldReselect = mouseDownContent;
|
1591
1731
|
|
1592
|
-
// Otherwise, simply select the clicked on item,
|
1732
|
+
// Otherwise, if selecting on mouse down, simply select the clicked on item,
|
1733
|
+
// adding it to the current
|
1593
1734
|
// selection if a modifier key was pressed.
|
1594
1735
|
} else {
|
1595
|
-
|
1736
|
+
if(this.get("selectOnMouseDown")){
|
1737
|
+
this.selectItems(mouseDownContent, modifierKeyPressed);
|
1738
|
+
}
|
1739
|
+
|
1740
|
+
|
1741
|
+
|
1596
1742
|
}
|
1597
1743
|
|
1598
1744
|
// saved for extend by shift ops.
|
@@ -1617,6 +1763,8 @@ SC.CollectionView = SC.View.extend(
|
|
1617
1763
|
} else this.selectItems([content],true) ;
|
1618
1764
|
|
1619
1765
|
} else {
|
1766
|
+
var content = (view) ? view.get('content') : null ;
|
1767
|
+
if(this._previousMouseDownContent == content) { this.selectItems(content); }
|
1620
1768
|
if (this._shouldDeselect) this.deselectItems(this._shouldDeselect);
|
1621
1769
|
|
1622
1770
|
// begin editing of an item view IF all of the following is true:
|
@@ -1676,20 +1824,7 @@ SC.CollectionView = SC.View.extend(
|
|
1676
1824
|
if (view && view.didMouseOut) view.didMouseOut(ev) ;
|
1677
1825
|
},
|
1678
1826
|
|
1679
|
-
// invoked when the user double clicks on an item.
|
1680
|
-
didDoubleClick: function(ev) {
|
1681
|
-
console.warn("didDoubleClick will be removed from CollectionView in the near future. Use mouseOut instead");
|
1682
|
-
return this._doubleClick(ev) ;
|
1683
|
-
},
|
1684
|
-
|
1685
1827
|
doubleClick: function(ev) {
|
1686
|
-
if (this.didDoubleClick != SC.CollectionView.prototype.didDoubleClick) {
|
1687
|
-
return this.didDoubleClick(ev) ;
|
1688
|
-
} else return this._doubleClick(ev) ;
|
1689
|
-
},
|
1690
|
-
|
1691
|
-
_doubleClick: function(ev) {
|
1692
|
-
console.info('_doubleClick!') ;
|
1693
1828
|
var view = this.itemViewForEvent(ev) ;
|
1694
1829
|
if (view) {
|
1695
1830
|
this._action(view, ev) ;
|
@@ -1787,6 +1922,409 @@ SC.CollectionView = SC.View.extend(
|
|
1787
1922
|
// DRAG AND DROP SUPPORT
|
1788
1923
|
//
|
1789
1924
|
|
1925
|
+
_reorderDataType: function() {
|
1926
|
+
if (!this._reorderDataTypeKey) {
|
1927
|
+
this._reorderDataTypeKey = "SC.CollectionView.Reorder.%@".fmt(SC.guidFor(this)) ;
|
1928
|
+
}
|
1929
|
+
return this._reorderDataTypeKey ;
|
1930
|
+
},
|
1931
|
+
|
1932
|
+
/**
|
1933
|
+
This property is set to the array of content objects that are the subject
|
1934
|
+
of a drag whenever a drag is initiated on the collection view. You can
|
1935
|
+
consult this property when implementing your collection view delegate
|
1936
|
+
methods, but otherwise you should not use this property in your code.
|
1937
|
+
|
1938
|
+
Note that drag content will always appear in the same order the content
|
1939
|
+
appears in the source content array.
|
1940
|
+
|
1941
|
+
@field
|
1942
|
+
@type {Array}
|
1943
|
+
*/
|
1944
|
+
dragContent: null,
|
1945
|
+
|
1946
|
+
/**
|
1947
|
+
This property is set to the proposed insertion index during a call to
|
1948
|
+
collectionViewValidateDrop(). Your delegate implementations can change
|
1949
|
+
the value of this property to enforce a drop some in some other location.
|
1950
|
+
|
1951
|
+
@type {Number}
|
1952
|
+
@field
|
1953
|
+
*/
|
1954
|
+
proposedInsertionIndex: null,
|
1955
|
+
|
1956
|
+
/**
|
1957
|
+
This property is set to the proposed drop operation during a call to
|
1958
|
+
collectionViewValidateDrop(). Your delegate implementations can change
|
1959
|
+
the value of this property to enforce a different type of drop operation.
|
1960
|
+
|
1961
|
+
@type {Number}
|
1962
|
+
@field
|
1963
|
+
*/
|
1964
|
+
proposedDropOperation: null,
|
1965
|
+
|
1966
|
+
/** @private
|
1967
|
+
mouseDragged event handler. Initiates a drag if the following conditions
|
1968
|
+
are met:
|
1969
|
+
|
1970
|
+
- collectionViewShouldBeginDrag() returns YES *OR*
|
1971
|
+
- the above method is not implemented and canReorderContent is true.
|
1972
|
+
- the dragDataTypes property returns a non-empty array
|
1973
|
+
- a mouse down event was saved by the mouseDown method.
|
1974
|
+
*/
|
1975
|
+
mouseDragged: function(ev) {
|
1976
|
+
// if the mouse down event was cleared, there is nothing to do; return.
|
1977
|
+
if (this._mouseDownEvent === null) return YES ;
|
1978
|
+
|
1979
|
+
// Don't do anything unless the user has been dragging for 123msec
|
1980
|
+
if ((Date.now() - this._mouseDownAt) < 123) return YES ;
|
1981
|
+
|
1982
|
+
// OK, they must be serious, decide if a drag will be allowed.
|
1983
|
+
if (this.invokeDelegateMethod(this.delegate, 'collectionViewShouldBeginDrag', this)) {
|
1984
|
+
|
1985
|
+
// First, get the selection to drag. Drag an array of selected
|
1986
|
+
// items appearing in this collection, in the order of the
|
1987
|
+
// collection.
|
1988
|
+
//
|
1989
|
+
// Set this to the dragContent property.
|
1990
|
+
var content = this.get('content') || [] ;
|
1991
|
+
var dragContent;
|
1992
|
+
if (this.get("selectOnMouseDown") == false) {
|
1993
|
+
dragContent = [this._previousMouseDownContent];
|
1994
|
+
} else {
|
1995
|
+
dragContent = this.get('selection').sort(function(a,b) {
|
1996
|
+
a = content.indexOf(a) ;
|
1997
|
+
b = content.indexOf(b) ;
|
1998
|
+
return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
|
1999
|
+
});
|
2000
|
+
}
|
2001
|
+
|
2002
|
+
this.set('dragContent', dragContent) ;
|
2003
|
+
|
2004
|
+
// Get the set of data types supported by the delegate. If this returns
|
2005
|
+
// a null or empty array and reordering content is not also supported
|
2006
|
+
// then do not start the drag.
|
2007
|
+
if (this.get('dragDataTypes').get('length') > 0) {
|
2008
|
+
// Build the drag view to use for the ghost drag. This
|
2009
|
+
// should essentially contain any visible drag items.
|
2010
|
+
var view = this.ghostViewFor(dragContent) ;
|
2011
|
+
|
2012
|
+
// Initiate the drag
|
2013
|
+
SC.Drag.start({
|
2014
|
+
event: this._mouseDownEvent,
|
2015
|
+
source: this,
|
2016
|
+
dragView: view,
|
2017
|
+
ghost: NO,
|
2018
|
+
slideBack: YES,
|
2019
|
+
dataSource: this
|
2020
|
+
}) ;
|
2021
|
+
|
2022
|
+
// Also use this opportunity to clean up since mouseUp won't
|
2023
|
+
// get called.
|
2024
|
+
this._cleanupMouseDown() ;
|
2025
|
+
this._lastInsertionIndex = null ;
|
2026
|
+
|
2027
|
+
// Drag was not allowed by the delegate, so bail.
|
2028
|
+
} else {
|
2029
|
+
this.set('dragContent', null) ;
|
2030
|
+
}
|
2031
|
+
|
2032
|
+
return YES ;
|
2033
|
+
}
|
2034
|
+
},
|
2035
|
+
|
2036
|
+
/**
|
2037
|
+
Implements the drag data source protocol for the collection view. This
|
2038
|
+
property will consult the collection view delegate if one is provided. It
|
2039
|
+
will also do the right thing if you have set canReorderContent to YES.
|
2040
|
+
|
2041
|
+
@field
|
2042
|
+
@type {Array}
|
2043
|
+
*/
|
2044
|
+
dragDataTypes: function() {
|
2045
|
+
|
2046
|
+
// consult delegate.
|
2047
|
+
var ret = this.invokeDelegateMethod(this.delegate, 'collectionViewDragDataTypes', this) ;
|
2048
|
+
var canReorderContent = this.get('canReorderContent') ;
|
2049
|
+
|
2050
|
+
// bail if ret returned null or empty array and cannot reorder.
|
2051
|
+
if ((!ret || ret.get('length')===0) && !canReorderContent) return [];
|
2052
|
+
|
2053
|
+
// add reorder type if needed.
|
2054
|
+
if (canReorderContent) {
|
2055
|
+
ret = (ret) ? ret.slice() : [] ;
|
2056
|
+
|
2057
|
+
var key = this._reorderDataType() ;
|
2058
|
+
if (ret.indexOf(key) < 0) ret.push(key) ;
|
2059
|
+
}
|
2060
|
+
return ret ;
|
2061
|
+
|
2062
|
+
//data: { "_mouseDownContent": dragContent }
|
2063
|
+
|
2064
|
+
}.property(),
|
2065
|
+
|
2066
|
+
/**
|
2067
|
+
Implements the drag data source protocol method. The implementation of
|
2068
|
+
this method will consult the collection view delegate if one has been
|
2069
|
+
provided. It also respects the canReoderContent method.
|
2070
|
+
*/
|
2071
|
+
dragDataForType: function(dataType, drag) {
|
2072
|
+
|
2073
|
+
// if this is a reorder, then return drag content.
|
2074
|
+
if (this.get('canReorderContent')) {
|
2075
|
+
if (dataType === this._reorderDataType()) return this.get('dragContent') ;
|
2076
|
+
}
|
2077
|
+
|
2078
|
+
// otherwise, just pass along to the delegate.
|
2079
|
+
return this.invokeDelegateMethod(this.delegate, 'collectionViewDragDataForType', this, dataType, drag) ;
|
2080
|
+
},
|
2081
|
+
|
2082
|
+
/**
|
2083
|
+
Implements the SC.DropTarget interface. The default implementation will
|
2084
|
+
consult the collection view delegate, if you implement those methods.
|
2085
|
+
*/
|
2086
|
+
dragEntered: function(drag, evt) {
|
2087
|
+
|
2088
|
+
// the proposed drag operation is either DRAG_MOVE only if we can reorder
|
2089
|
+
// content and the drag contains reorder content.
|
2090
|
+
var op = SC.DRAG_NONE ;
|
2091
|
+
if (this.get('canReorderContent')) {
|
2092
|
+
var types = drag.get('dataTypes') ;
|
2093
|
+
if (types.indexOf(this._reorderDataType()) >= 0) op = SC.DRAG_MOVE ;
|
2094
|
+
}
|
2095
|
+
|
2096
|
+
// Now pass this onto the delegate.
|
2097
|
+
op = this.invokeDelegateMethod(this.delegate, 'collectionViewValidateDrop', this, drag, SC.DROP_ANY, -1, op) ;
|
2098
|
+
|
2099
|
+
// return
|
2100
|
+
return op ;
|
2101
|
+
},
|
2102
|
+
|
2103
|
+
// Determines the allowed drop operation insertion point, operation type,
|
2104
|
+
// and the drag operation to be performed. Used by dragUpdated() and
|
2105
|
+
// performDragOperation().
|
2106
|
+
_computeDropOperationState: function(drag, evt) {
|
2107
|
+
|
2108
|
+
// get the insertion index for this location. This can be computed
|
2109
|
+
// by a subclass using whatever method. This method is not expected to
|
2110
|
+
// do any data valdidation, just to map the location to an insertion
|
2111
|
+
// index.
|
2112
|
+
var loc = drag.get('location') ;
|
2113
|
+
loc = this.convertFrameFromView(loc, null) ;
|
2114
|
+
|
2115
|
+
var dropOp = SC.DROP_BEFORE ;
|
2116
|
+
var dragOp = SC.DRAG_NONE ;
|
2117
|
+
|
2118
|
+
// STEP 1: Try with a DROP_ON option -- send straight to delegate if
|
2119
|
+
// supported by view.
|
2120
|
+
|
2121
|
+
// get the computed insertion index and possibly drop operation.
|
2122
|
+
// prefer to drop ON.
|
2123
|
+
var idx = this.insertionIndexForLocation(loc, SC.DROP_ON) ;
|
2124
|
+
if ($type(idx) === T_ARRAY) {
|
2125
|
+
dropOp = idx[1] ;
|
2126
|
+
idx = idx[0] ;
|
2127
|
+
}
|
2128
|
+
|
2129
|
+
// if the return drop operation is DROP_ON, then just check it with the
|
2130
|
+
// delegate method. If the delegate method does not support dropping on,
|
2131
|
+
// then it will return DRAG_NONE, in which case we will try again with
|
2132
|
+
// drop before.
|
2133
|
+
if (dropOp === SC.DROP_ON) {
|
2134
|
+
|
2135
|
+
// Now save the insertion index and the dropOp. This may be changed by
|
2136
|
+
// the collection delegate.
|
2137
|
+
this.set('proposedInsertionIndex', idx) ;
|
2138
|
+
this.set('proposedDropOperation', dropOp) ;
|
2139
|
+
dragOp = this.invokeDelegateMethod(this.delegate, 'collectionViewValidateDrop', this, drag, dropOp, idx, dragOp) ;
|
2140
|
+
idx = this.get('proposedInsertionIndex') ;
|
2141
|
+
dropOp = this.get('proposedDropOperation') ;
|
2142
|
+
this._dropInsertionIndex = this._dropOperation = null ;
|
2143
|
+
|
2144
|
+
// The delegate is OK with a drop on also, so just return.
|
2145
|
+
if (dragOp !== SC.DRAG_NONE) {
|
2146
|
+
return [idx, dropOp, dragOp] ;
|
2147
|
+
|
2148
|
+
// The delegate is NOT OK with a drop on, try to get the insertion
|
2149
|
+
// index again, but this time prefer SC.DROP_BEFORE, then let the
|
2150
|
+
// rest of the method run...
|
2151
|
+
} else {
|
2152
|
+
dropOp = SC.DROP_BEFORE ;
|
2153
|
+
idx = this.insertionIndexForLocation(loc, SC.DROP_BEFORE) ;
|
2154
|
+
if ($type(idx) === T_ARRAY) {
|
2155
|
+
dropOp = idx[1] ;
|
2156
|
+
idx = idx[0] ;
|
2157
|
+
}
|
2158
|
+
}
|
2159
|
+
}
|
2160
|
+
|
2161
|
+
// if this is a reorder drag, set the proposed op to SC.DRAG_REORDER and
|
2162
|
+
// validate the insertion point. This only works if the insertion point
|
2163
|
+
// is DROP_BEFORE. DROP_ON is not handled by reordering content.
|
2164
|
+
if ((idx >= 0) && this.get('canReorderContent') && (dropOp === SC.DROP_BEFORE)) {
|
2165
|
+
|
2166
|
+
var objects = drag.dataForType(this._reorderDataType()) || [];
|
2167
|
+
var content = this.get('content') || [] ;
|
2168
|
+
|
2169
|
+
// if the insertion index is in between two items in the drag itself,
|
2170
|
+
// then this is not allowed. Either use the last insertion index or
|
2171
|
+
// find the first index that is not in between selections. Stop when
|
2172
|
+
// we get to the beginning.
|
2173
|
+
var previousContent = (idx > 0) ? content.objectAt(idx-1) : null ;
|
2174
|
+
var nextContent = (idx < content.get('length')) ? content.objectAt(idx) : null;
|
2175
|
+
|
2176
|
+
var isPreviousInDrag = (previousContent) ? objects.indexOf(previousContent)>=0 : NO;
|
2177
|
+
var isNextInDrag = (nextContent) ? objects.indexOf(nextContent)>=0 : NO;
|
2178
|
+
|
2179
|
+
if (isPreviousInDrag && isNextInDrag) {
|
2180
|
+
if (this._lastInsertionIndex == null) {
|
2181
|
+
while((idx >= 0) && (objects.indexOf(content.objectAt(idx)) >= 0)) {
|
2182
|
+
idx-- ;
|
2183
|
+
}
|
2184
|
+
} else idx = this._lastInsertionIndex ;
|
2185
|
+
}
|
2186
|
+
|
2187
|
+
// If we found a valid insertion point to reorder at, then set the op
|
2188
|
+
// to custom DRAG_REORDER.
|
2189
|
+
if (idx >= 0) dragOp = SC.DRAG_REORDER ;
|
2190
|
+
}
|
2191
|
+
|
2192
|
+
// Now save the insertion index and the dropOp. This may be changed by
|
2193
|
+
// the collection delegate.
|
2194
|
+
this.set('proposedInsertionIndex', idx) ;
|
2195
|
+
this.set('proposedDropOperation', dropOp) ;
|
2196
|
+
dragOp = this.invokeDelegateMethod(this.delegate, 'collectionViewValidateDrop', this, drag, dropOp, idx, dragOp) ;
|
2197
|
+
idx = this.get('proposedInsertionIndex') ;
|
2198
|
+
dropOp = this.get('proposedDropOperation') ;
|
2199
|
+
this._dropInsertionIndex = this._dropOperation = null ;
|
2200
|
+
|
2201
|
+
// return generated state
|
2202
|
+
return [idx, dropOp, dragOp] ;
|
2203
|
+
},
|
2204
|
+
|
2205
|
+
/**
|
2206
|
+
Implements the SC.DropTarget interface. The default implementation will
|
2207
|
+
determine the drop location and then consult the collection view delegate
|
2208
|
+
if you implement those methods. Otherwise it will handle reordering
|
2209
|
+
content on its own.
|
2210
|
+
*/
|
2211
|
+
dragUpdated: function(drag, evt) {
|
2212
|
+
|
2213
|
+
var state = this._computeDropOperationState(drag, evt) ;
|
2214
|
+
var idx = state[0], dropOp = state[1], dragOp = state[2] ;
|
2215
|
+
|
2216
|
+
// if the insertion index or dropOp have changed, update the insertion
|
2217
|
+
// point
|
2218
|
+
if (dragOp !== SC.DRAG_NONE) {
|
2219
|
+
if ((this._lastInsertionIndex !== idx) || (this._lastDropOperation !== dropOp)) {
|
2220
|
+
var itemView = this.itemViewForContent(this.get('content').objectAt(idx));
|
2221
|
+
this.showInsertionPoint(itemView, dropOp) ;
|
2222
|
+
}
|
2223
|
+
|
2224
|
+
this._lastInsertionIndex = idx ;
|
2225
|
+
this._lastDropOperation = dropOp ;
|
2226
|
+
|
2227
|
+
} else {
|
2228
|
+
this.hideInsertionPoint() ;
|
2229
|
+
this._lastInsertionIndex = this._lastDropOperation = null ;
|
2230
|
+
}
|
2231
|
+
|
2232
|
+
// Normalize drag operation to the standard kinds accepted by the drag
|
2233
|
+
// system.
|
2234
|
+
return (dragOp === SC.DRAG_REORDER) ? SC.DRAG_MOVE : dragOp;
|
2235
|
+
},
|
2236
|
+
|
2237
|
+
/**
|
2238
|
+
Implements the SC.DropTarget protocol. Hides any visible insertion
|
2239
|
+
point and clears some cached values.
|
2240
|
+
*/
|
2241
|
+
dragExited: function() {
|
2242
|
+
this.hideInsertionPoint() ;
|
2243
|
+
this._lastInsertionIndex = this._lastDropOperation = null ;
|
2244
|
+
},
|
2245
|
+
|
2246
|
+
/**
|
2247
|
+
Implements the SC.DropTarget protocol. Hides any visible insertion
|
2248
|
+
point and clears some cached values.
|
2249
|
+
*/
|
2250
|
+
dragEnded: function() {
|
2251
|
+
this.hideInsertionPoint() ;
|
2252
|
+
this._lastInsertionIndex = this._lastDropOperation = null ;
|
2253
|
+
},
|
2254
|
+
|
2255
|
+
/**
|
2256
|
+
Implements the SC.DropTarget protocol.
|
2257
|
+
*/
|
2258
|
+
prepareForDragOperation: function(op, drag) { return YES; },
|
2259
|
+
|
2260
|
+
/**
|
2261
|
+
Implements the SC.DropTarget protocol. Consults the collection view
|
2262
|
+
delegate to actually perform the operation unless the operation is
|
2263
|
+
reordering content.
|
2264
|
+
*/
|
2265
|
+
performDragOperation: function(op, drag) {
|
2266
|
+
|
2267
|
+
// Get the correct insertion point, drop operation, etc.
|
2268
|
+
var state = this._computeDropOperationState(drag, null, op) ;
|
2269
|
+
var idx = state[0], dropOp = state[1], dragOp = state[2] ;
|
2270
|
+
|
2271
|
+
// The dragOp is the kinds of ops allowed. The drag operation must
|
2272
|
+
// be included in that set.
|
2273
|
+
if (dragOp === SC.DRAG_REORDER) {
|
2274
|
+
op = (op & SC.DRAG_MOVE) ? SC.DRAG_REORDER : SC.DRAG_NONE ;
|
2275
|
+
} else {
|
2276
|
+
op = op & dragOp ;
|
2277
|
+
}
|
2278
|
+
|
2279
|
+
// If no allowed drag operation could be found, just return.
|
2280
|
+
if (op === SC.DRAG_NONE) return op;
|
2281
|
+
|
2282
|
+
// Some operation is allowed through, give the delegate a chance to
|
2283
|
+
// handle it.
|
2284
|
+
var performed = this.invokeDelegateMethod(this.delegate, 'collectionViewAcceptDrop', this, drag, dropOp, idx, op) ;
|
2285
|
+
|
2286
|
+
// If the delegate did not handle the drag (i.e. returned SC.DRAG_NONE),
|
2287
|
+
// and the op type is REORDER, then do the reorder here.
|
2288
|
+
if ((performed === SC.DRAG_NONE) && (op === SC.DRAG_REORDER)) {
|
2289
|
+
var objects = drag.dataForType(this._reorderDataType()) ;
|
2290
|
+
if (!objects) return SC.DRAG_NONE ;
|
2291
|
+
|
2292
|
+
var content = this.get('content') ;
|
2293
|
+
content.beginPropertyChanges(); // suspend notifications
|
2294
|
+
|
2295
|
+
// find the old index and remove it.
|
2296
|
+
var objectsIdx = objects.get('length') ;
|
2297
|
+
while(--objectsIdx >= 0) {
|
2298
|
+
var obj = objects.objectAt(objectsIdx) ;
|
2299
|
+
var old = content.indexOf(obj) ;
|
2300
|
+
if (old >= 0) content.removeAt(old) ;
|
2301
|
+
if ((old >= 0) && (old < idx)) idx--; //adjust idx
|
2302
|
+
}
|
2303
|
+
|
2304
|
+
// now insert objects at new location
|
2305
|
+
content.replace(idx, 0, objects) ;
|
2306
|
+
content.endPropertyChanges(); // restart notifications
|
2307
|
+
|
2308
|
+
// make the op into its actual value
|
2309
|
+
op = SC.DRAG_MOVE ;
|
2310
|
+
}
|
2311
|
+
|
2312
|
+
return op;
|
2313
|
+
},
|
2314
|
+
|
2315
|
+
/**
|
2316
|
+
Default delegate method implementation, returns YES if canReorderContent
|
2317
|
+
is also true.
|
2318
|
+
*/
|
2319
|
+
collectionViewShouldBeginDrag: function(view) {
|
2320
|
+
return this.get('canReorderContent') ;
|
2321
|
+
},
|
2322
|
+
|
2323
|
+
concludeDragOperation: function(op, drag) {
|
2324
|
+
this.hideInsertionPoint() ;
|
2325
|
+
this._lastInsertionIndex = null ;
|
2326
|
+
},
|
2327
|
+
|
1790
2328
|
/**
|
1791
2329
|
The insertion orientation. This is used to determine which
|
1792
2330
|
dimension we should pay attention to when determining insertion point for
|
@@ -1803,10 +2341,32 @@ SC.CollectionView = SC.View.extend(
|
|
1803
2341
|
Get the preferred insertion point for the given location, including
|
1804
2342
|
an insertion preference of before or after the named index.
|
1805
2343
|
|
1806
|
-
|
1807
|
-
|
2344
|
+
You can implement this method in a subclass if you like to perform a
|
2345
|
+
more efficient check. The default implementation will loop through the
|
2346
|
+
item views looking for the first view to "switch sides" in the orientation
|
2347
|
+
you specify.
|
2348
|
+
|
2349
|
+
This method should return an array with two values. The first value is
|
2350
|
+
the insertion point index and the second value is the drop operation,
|
2351
|
+
which should be one of SC.DROP_BEFORE or SC.DROP_ON.
|
2352
|
+
|
2353
|
+
The preferred drop operation passed in should be used as a hint as to
|
2354
|
+
the type of operation the drag and drop could would prefer to receive.
|
2355
|
+
If the dropOperaiton is SC.DROP_ON, then you should return a DROP_ON
|
2356
|
+
mode if possible. Otherwise, you should never return DROP_ON.
|
2357
|
+
|
2358
|
+
For compatibility, you can also return just the insertion index. If you
|
2359
|
+
do this, then the collction view will assume the drop operation is
|
2360
|
+
SC.DROP_BEFORE.
|
2361
|
+
|
2362
|
+
If an insertion is NOT allowed, you should return -1 as the insertion
|
2363
|
+
point. In this case, the drop operation will be ignored.
|
2364
|
+
|
2365
|
+
@param loc {Point} the mouse location.
|
2366
|
+
@param dropOperation {DropOp} the preferred drop operation.
|
2367
|
+
@returns {Array} [proposed drop index, drop operation]
|
1808
2368
|
*/
|
1809
|
-
insertionIndexForLocation: function(loc) {
|
2369
|
+
insertionIndexForLocation: function(loc, dropOperation) {
|
1810
2370
|
var content = this.get('content') ;
|
1811
2371
|
var f, itemView, curSide, lastSide = null ;
|
1812
2372
|
var orient = this.get('insertionOrientation') ;
|
@@ -1872,8 +2432,27 @@ SC.CollectionView = SC.View.extend(
|
|
1872
2432
|
|
1873
2433
|
The default implementation of this method does nothing.
|
1874
2434
|
|
1875
|
-
@param {SC.View}
|
2435
|
+
@param itemView {SC.View} view the insertion point should appear directly before. If null, show insertion point at end.
|
2436
|
+
@param dropOperation {Number} the drop operation. will be SC.DROP_BEFORE or SC.DROP_ON
|
2437
|
+
|
2438
|
+
@returns {void}
|
2439
|
+
*/
|
2440
|
+
showInsertionPoint: function(itemView, dropOperation) {
|
2441
|
+
return (dropOperation === SC.DROP_BEFORE) ? this.showInsertionPointBefore(itemView) : this.hideInsertionPoint() ;
|
2442
|
+
},
|
2443
|
+
|
2444
|
+
/**
|
2445
|
+
@deprecated
|
2446
|
+
|
2447
|
+
Show the insertion point during a drag before the named item view.
|
2448
|
+
|
2449
|
+
This method has been deprecated in favor of the more generic
|
2450
|
+
showInsertionPoint() which can be used to show drops occurring both on
|
2451
|
+
and before an itemView. If you do not implement showInsertionPoint()
|
2452
|
+
yourself, the default implementation will call this method whenever the
|
2453
|
+
drop operation is SC.DROP_BEFORE.
|
1876
2454
|
|
2455
|
+
@param itemView {SC.View} the item view to show before.
|
1877
2456
|
@returns {void}
|
1878
2457
|
*/
|
1879
2458
|
showInsertionPointBefore: function(itemView) {},
|
@@ -1881,13 +2460,14 @@ SC.CollectionView = SC.View.extend(
|
|
1881
2460
|
/**
|
1882
2461
|
Override to hide the insertion point when a drag ends.
|
1883
2462
|
|
1884
|
-
Called during a drag to hide the insertion point. This will be called
|
1885
|
-
user exits the view, cancels the drag or completes the drag. It
|
1886
|
-
called when the insertion point changes during a drag.
|
2463
|
+
Called during a drag to hide the insertion point. This will be called
|
2464
|
+
when the user exits the view, cancels the drag or completes the drag. It
|
2465
|
+
will not be called when the insertion point changes during a drag.
|
1887
2466
|
|
1888
|
-
You should expect to receive one or more calls to
|
1889
|
-
during a drag followed by at least one call to
|
1890
|
-
method should not raise an error if it is
|
2467
|
+
You should expect to receive one or more calls to
|
2468
|
+
showInsertionPointBefore() during a drag followed by at least one call to
|
2469
|
+
this method at the end. Your method should not raise an error if it is
|
2470
|
+
called more than once.
|
1891
2471
|
|
1892
2472
|
@returns {void}
|
1893
2473
|
*/
|
@@ -1896,10 +2476,11 @@ SC.CollectionView = SC.View.extend(
|
|
1896
2476
|
/**
|
1897
2477
|
Override this method to provide your own ghost image for a drag.
|
1898
2478
|
|
1899
|
-
Note that the only purpose of this view is to render a visible drag
|
1900
|
-
not critical that you make this element bindable, etc.
|
2479
|
+
Note that the only purpose of this view is to render a visible drag
|
2480
|
+
element. It is not critical that you make this element bindable, etc.
|
1901
2481
|
|
1902
|
-
@param dragContent {Array} Array of content objects that will be used in
|
2482
|
+
@param dragContent {Array} Array of content objects that will be used in
|
2483
|
+
the drag.
|
1903
2484
|
*/
|
1904
2485
|
ghostViewFor: function(dragContent) {
|
1905
2486
|
var view = SC.View.create() ;
|
@@ -1926,16 +2507,16 @@ SC.CollectionView = SC.View.extend(
|
|
1926
2507
|
if (SC.minY(f) < minY) minY = SC.minY(f) ;
|
1927
2508
|
|
1928
2509
|
// Clone the contents of this node. We should probably apply the
|
1929
|
-
// computed style to the cloned nodes in order to make sure they match
|
1930
|
-
// CSS styles do not match. Make sure the items are
|
1931
|
-
// positioned.
|
2510
|
+
// computed style to the cloned nodes in order to make sure they match
|
2511
|
+
// even if the CSS styles do not match. Make sure the items are
|
2512
|
+
// properly positioned.
|
1932
2513
|
dom = dom.cloneNode(true) ;
|
1933
2514
|
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) }) ;
|
1934
2515
|
view.rootElement.appendChild(dom) ;
|
1935
2516
|
}
|
1936
2517
|
|
1937
|
-
// Now we have a view, create another view that will wrap the other view
|
1938
|
-
// inside.
|
2518
|
+
// Now we have a view, create another view that will wrap the other view
|
2519
|
+
// and position it inside.
|
1939
2520
|
var wrapper = SC.View.create() ;
|
1940
2521
|
wrapper.setStyle({ position: 'absolute', overflow: 'hidden' }) ;
|
1941
2522
|
wrapper.set('frame', {
|
@@ -1947,151 +2528,6 @@ SC.CollectionView = SC.View.extend(
|
|
1947
2528
|
return wrapper ;
|
1948
2529
|
},
|
1949
2530
|
|
1950
|
-
mouseDragged: function(ev) {
|
1951
|
-
// Don't do anything unless the user has been dragging for 123msec
|
1952
|
-
if ((Date.now() - this._mouseDownAt) < 123) return true ;
|
1953
|
-
|
1954
|
-
// OK, they must be serious, start a drag if possible.
|
1955
|
-
if (this.get('canReorderContent') && this._mouseDownEvent != null) {
|
1956
|
-
|
1957
|
-
// First, get the selection to drag. Drag an array of selected
|
1958
|
-
// items appearing in this collection, in the order of the
|
1959
|
-
// collection.
|
1960
|
-
var content = this.get('content') || [] ;
|
1961
|
-
var dragContent = this.get('selection').sort(function(a,b) {
|
1962
|
-
a = content.indexOf(a) ; b = content.indexOf(b) ;
|
1963
|
-
return (a<b) ? -1 : ((a>b) ? 1 : 0) ;
|
1964
|
-
});
|
1965
|
-
|
1966
|
-
// Build the drag view to use for the ghost drag. This
|
1967
|
-
// should essentially contain any visible drag items.
|
1968
|
-
var view = this.ghostViewFor(dragContent) ;
|
1969
|
-
|
1970
|
-
// Initiate the drag
|
1971
|
-
SC.Drag.start({
|
1972
|
-
event: this._mouseDownEvent,
|
1973
|
-
source: this,
|
1974
|
-
dragView: view,
|
1975
|
-
ghost: NO,
|
1976
|
-
slideBack: YES,
|
1977
|
-
data: { "_mouseDownContent": dragContent }
|
1978
|
-
}) ;
|
1979
|
-
|
1980
|
-
// Also use this opportunity to clean up since mouseUp won't
|
1981
|
-
// get called.
|
1982
|
-
this._cleanupMouseDown() ;
|
1983
|
-
this._lastInsertionIndex = null ;
|
1984
|
-
}
|
1985
|
-
},
|
1986
|
-
|
1987
|
-
// Drop Source.
|
1988
|
-
dragEntered: function(drag, evt) {
|
1989
|
-
if ((drag.get('source') == this) && this.get('canReorderContent')) {
|
1990
|
-
return SC.DRAG_MOVE ;
|
1991
|
-
} else {
|
1992
|
-
return SC.DRAG_NONE ;
|
1993
|
-
}
|
1994
|
-
},
|
1995
|
-
|
1996
|
-
// If reordering is allowed, then show insertion point
|
1997
|
-
dragUpdated: function(drag, evt) {
|
1998
|
-
if (this.get('canReorderContent')) {
|
1999
|
-
var loc = drag.get('location') ;
|
2000
|
-
loc = this.convertFrameFromView(loc, null) ;
|
2001
|
-
|
2002
|
-
// get the insertion index for this location. This can be computed
|
2003
|
-
// by a subclass using whatever method. This method is not expected to
|
2004
|
-
// do any data valdidation, just to map the location to an insertion index.
|
2005
|
-
var ret = this.insertionIndexForLocation(loc) ;
|
2006
|
-
|
2007
|
-
// now that we have an index, find the nearest index that we can
|
2008
|
-
// actually insert at, or do not allow.
|
2009
|
-
var objects = (drag.source == this) ? (drag.dataForType('_mouseDownContent') || []) : [];
|
2010
|
-
var content = this.get('content') || [] ;
|
2011
|
-
|
2012
|
-
// if the insertion index is in between two items in the drag itself,
|
2013
|
-
// then this is not allowed. Either use the last insertion index or
|
2014
|
-
// find the first index that is not in between selections.
|
2015
|
-
var isPreviousInDrag = (ret > 0) ? objects.indexOf(content.objectAt(ret-1)) : -1 ;
|
2016
|
-
var isNextInDrag = (ret < content.get('length')-1) ? objects.indexOf(content.objectAt(ret)) : -1 ;
|
2017
|
-
if (isPreviousInDrag>=0 && isNextInDrag>=0) {
|
2018
|
-
if (this._lastInsertionIndex == null) {
|
2019
|
-
while((ret > 0) && (objects.indexOf(content.objectAt(ret)) >= 0)) ret-- ;
|
2020
|
-
} else ret = this._lastInsertionIndex ;
|
2021
|
-
}
|
2022
|
-
|
2023
|
-
// Now that we have verified that, check to see if a drop is allowed in the
|
2024
|
-
// insertion index with the delegate.
|
2025
|
-
// TODO
|
2026
|
-
|
2027
|
-
if (this._lastInsertionIndex != ret) {
|
2028
|
-
var itemView = this.itemViewForContent(this.get('content').objectAt(ret));
|
2029
|
-
this.showInsertionPointBefore(itemView) ;
|
2030
|
-
}
|
2031
|
-
this._lastInsertionIndex = ret ;
|
2032
|
-
|
2033
|
-
}
|
2034
|
-
return SC.DRAG_MOVE;
|
2035
|
-
},
|
2036
|
-
|
2037
|
-
dragExited: function() {
|
2038
|
-
this.hideInsertionPoint() ;
|
2039
|
-
this._lastInsertionIndex = null ;
|
2040
|
-
},
|
2041
|
-
|
2042
|
-
dragEnded: function() {
|
2043
|
-
this.hideInsertionPoint() ;
|
2044
|
-
this._lastInsertionIndex = null ;
|
2045
|
-
},
|
2046
|
-
|
2047
|
-
prepareForDragOperation: function(op, drag) {
|
2048
|
-
return SC.DRAG_ANY;
|
2049
|
-
},
|
2050
|
-
|
2051
|
-
performDragOperation: function(op, drag) {
|
2052
|
-
|
2053
|
-
SC.Benchmark.start('%@ performDragOperation'.fmt(SC.guidFor(this))) ;
|
2054
|
-
|
2055
|
-
var loc = drag.get('location') ;
|
2056
|
-
loc = this.convertFrameFromView(loc, null) ;
|
2057
|
-
|
2058
|
-
// if op is MOVE or COPY, add item to view.
|
2059
|
-
var objects = drag.dataForType('_mouseDownContent') ;
|
2060
|
-
if (objects && (op == SC.DRAG_MOVE)) {
|
2061
|
-
|
2062
|
-
// find the index to for the new insertion
|
2063
|
-
var idx = this.insertionIndexForLocation(loc) ;
|
2064
|
-
|
2065
|
-
var content = this.get('content') ;
|
2066
|
-
content.beginPropertyChanges(); // suspend notifications
|
2067
|
-
|
2068
|
-
// find the old index and remove it.
|
2069
|
-
var objectsIdx = objects.get('length') ;
|
2070
|
-
while(--objectsIdx >= 0) {
|
2071
|
-
var obj = objects.objectAt(objectsIdx) ;
|
2072
|
-
var old = content.indexOf(obj) ;
|
2073
|
-
if (old >= 0) content.removeAt(old) ;
|
2074
|
-
if ((old >= 0) && (old < idx)) idx--; //adjust idx
|
2075
|
-
}
|
2076
|
-
|
2077
|
-
// now insert objects at new location
|
2078
|
-
content.replace(idx, 0, objects) ;
|
2079
|
-
content.endPropertyChanges(); // restart notifications
|
2080
|
-
}
|
2081
|
-
|
2082
|
-
SC.Benchmark.end('%@ performDragOperation'.fmt(SC.guidFor(this))) ;
|
2083
|
-
console.log(SC.Benchmark.report()) ;
|
2084
|
-
|
2085
|
-
return SC.DRAG_MOVE;
|
2086
|
-
},
|
2087
|
-
|
2088
|
-
concludeDragOperation: function(op, drag) {
|
2089
|
-
this.hideInsertionPoint() ;
|
2090
|
-
this._lastInsertionIndex = null ;
|
2091
|
-
},
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
2531
|
// ......................................
|
2096
2532
|
// INTERNAL
|
2097
2533
|
//
|
@@ -2172,7 +2608,9 @@ SC.CollectionView = SC.View.extend(
|
|
2172
2608
|
if (content) content.addObserver('[]', func) ;
|
2173
2609
|
this._content = content; //cache
|
2174
2610
|
this._contentPropertyRevision = null ;
|
2175
|
-
|
2611
|
+
|
2612
|
+
var rev = (content) ? content.propertyRevision : -1 ;
|
2613
|
+
this._contentPropertyObserver(this, '[]', content, rev) ;
|
2176
2614
|
}.observes('content'),
|
2177
2615
|
|
2178
2616
|
/** @private
|