sproutcore 0.9.4 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|