sproutcore 0.9.4 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/History.txt +70 -2
  2. data/Manifest.txt +3 -15
  3. data/config/hoe.rb +1 -1
  4. data/frameworks/sproutcore/animation/animation.js +411 -0
  5. data/frameworks/sproutcore/controllers/array.js +68 -21
  6. data/frameworks/sproutcore/controllers/object.js +21 -2
  7. data/frameworks/sproutcore/drag/drag.js +13 -4
  8. data/frameworks/sproutcore/drag/drop_target.js +26 -19
  9. data/frameworks/sproutcore/english.lproj/core.css +4 -0
  10. data/frameworks/sproutcore/english.lproj/strings.js +5 -0
  11. data/frameworks/sproutcore/english.lproj/theme.css +5 -0
  12. data/frameworks/sproutcore/foundation/application.js +1 -2
  13. data/frameworks/sproutcore/foundation/set.js +31 -12
  14. data/frameworks/sproutcore/foundation/sorted_set.js +590 -0
  15. data/frameworks/sproutcore/foundation/string.js +43 -9
  16. data/frameworks/sproutcore/globals/window.js +34 -9
  17. data/frameworks/sproutcore/lib/button_views.rb +1 -0
  18. data/frameworks/sproutcore/lib/collection_view.rb +1 -0
  19. data/frameworks/sproutcore/lib/core_views.rb +3 -0
  20. data/frameworks/sproutcore/lib/index.rhtml +1 -1
  21. data/frameworks/sproutcore/mixins/collection_view_delegate.js +201 -0
  22. data/frameworks/sproutcore/mixins/observable.js +2 -7
  23. data/frameworks/sproutcore/models/record.js +1 -1
  24. data/frameworks/sproutcore/models/store.js +81 -28
  25. data/frameworks/sproutcore/tests/views/view/clippingFrame.rhtml +9 -6
  26. data/frameworks/sproutcore/views/collection/collection.js +649 -211
  27. data/frameworks/sproutcore/views/collection/grid.js +62 -26
  28. data/frameworks/sproutcore/views/collection/list.js +57 -21
  29. data/frameworks/sproutcore/views/collection/source_list.js +61 -13
  30. data/frameworks/sproutcore/views/image.js +7 -0
  31. data/frameworks/sproutcore/views/inline_text_field.js +4 -5
  32. data/frameworks/sproutcore/views/slider.js +2 -0
  33. data/frameworks/sproutcore/views/view.js +2 -2
  34. data/lib/sproutcore/build_tools/html_builder.rb +4 -6
  35. data/lib/sproutcore/build_tools/resource_builder.rb +32 -20
  36. data/lib/sproutcore/bundle.rb +130 -32
  37. data/lib/sproutcore/bundle_manifest.rb +24 -21
  38. data/lib/sproutcore/helpers/static_helper.rb +22 -9
  39. data/lib/sproutcore/merb/bundle_controller.rb +4 -3
  40. data/lib/sproutcore/version.rb +1 -1
  41. metadata +14 -17
  42. data/clients/view_builder/builders/builder.js +0 -339
  43. data/clients/view_builder/builders/button.js +0 -81
  44. data/clients/view_builder/controllers/document.js +0 -21
  45. data/clients/view_builder/core.js +0 -19
  46. data/clients/view_builder/english.lproj/body.css +0 -77
  47. data/clients/view_builder/english.lproj/body.rhtml +0 -39
  48. data/clients/view_builder/english.lproj/controls.css +0 -0
  49. data/clients/view_builder/english.lproj/strings.js +0 -14
  50. data/clients/view_builder/main.js +0 -38
  51. data/clients/view_builder/mixins/design_mode.js +0 -92
  52. data/clients/view_builder/tests/controllers/document.rhtml +0 -20
  53. data/clients/view_builder/tests/views/builder.rhtml +0 -20
  54. data/clients/view_builder/tests/views/palette.rhtml +0 -21
  55. data/clients/view_builder/views/builder.js +0 -26
  56. 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
- var f = this.nested.get('frame') ;
179
- var cf = this.nested.get('clippingFrame') ;
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() { this.v = SC.page.get('case3'); this.nested = this.v.child.nestedChild; }
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, the
135
- user will not be able to reorder, add, or delete items regardless of the
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: false,
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 cause
170
- it to be registered as a drop target, activating the other drop machinery.
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: false,
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: false,
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._itemViews) return ;
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 anchor.
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
- Selects the same item on the next row. Or moves down one if
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. Or moves up one if
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
- // save for drag opt
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 childItems... bail
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, adding it to the current
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
- this.selectItems(mouseDownContent, modifierKeyPressed);
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
- The default implementation will loop through the item views looking for
1807
- the first view to "switch sides" in the orientation you specify.
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} itemView view the insertion point should appear directly before. If null, show insertion point at end.
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 when the
1885
- user exits the view, cancels the drag or completes the drag. It will not be
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 showInsertionPointBefore()
1889
- during a drag followed by at least one call to this method at the end. Your
1890
- method should not raise an error if it is called more than once.
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 element. It is
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 the drag.
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 even if the
1930
- // CSS styles do not match. Make sure the items are properly
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 and position it
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
- this._contentPropertyObserver(this, '[]', content, content.propertyRevision) ;
2611
+
2612
+ var rev = (content) ? content.propertyRevision : -1 ;
2613
+ this._contentPropertyObserver(this, '[]', content, rev) ;
2176
2614
  }.observes('content'),
2177
2615
 
2178
2616
  /** @private