1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('private/tree_item_observer');
  9 
 10 /*
 11   TODO Document more
 12 */
 13 
 14 /**
 15   @class
 16 
 17   A TreeController manages a tree of model objects that you might want to
 18   display in the UI using a collection view.  For the most part, you should
 19   work with a TreeController much like you would an ObjectController, except
 20   that the TreeController will also provide an arrangedObjects property that
 21   can be used as the content of a CollectionView.
 22 
 23   @extends SC.ObjectController
 24   @extends SC.SelectionSupport
 25   @since SproutCore 1.0
 26 */
 27 SC.TreeController = SC.ObjectController.extend(SC.SelectionSupport,
 28 /** @scope SC.TreeController.prototype */ {
 29 
 30   // ..........................................................
 31   // PROPERTIES
 32   //
 33 
 34   /**
 35     Set to YES if you want the top-level items in the tree to be displayed as
 36     group items in the collection view.
 37 
 38     @type Boolean
 39     @default NO
 40   */
 41   treeItemIsGrouped: NO,
 42 
 43   /**
 44     If your content support expanding and collapsing of content, then set this
 45     property to the name of the key on your model that should be used to
 46     determine the expansion state of the item.  The default is
 47     "treeItemIsExpanded"
 48 
 49     @type String
 50     @default "treeItemIsExpanded"
 51   */
 52   treeItemIsExpandedKey: "treeItemIsExpanded",
 53 
 54   /**
 55     Set to the name of the property on your content object that holds the
 56     children array for each tree node.  The default is "treeItemChildren".
 57 
 58     @type String
 59     @default "treeItemChildren"
 60   */
 61   treeItemChildrenKey: "treeItemChildren",
 62 
 63   /**
 64     Returns an SC.Array object that actually will represent the tree as a
 65     flat array suitable for use by a CollectionView.  Other than binding this
 66     property as the content of a CollectionView, you generally should not
 67     use this property directly.  Instead, work on the tree content using the
 68     TreeController like you would any other ObjectController.
 69 
 70     @type SC.Array
 71   */
 72   arrangedObjects: null,
 73 
 74   // ..........................................................
 75   // PRIVATE
 76   //
 77 
 78   /** @private - setup observer on init if needed. */
 79   init: function() {
 80     sc_super();
 81 
 82     // Initialize arrangedObjects.
 83     this._contentDidChange();
 84   },
 85 
 86   /** @private */
 87   _contentDidChange: function () {
 88     var arrangedObjects = this.get('arrangedObjects'),
 89       content = this.get('content');
 90 
 91     if (content) {
 92       if (arrangedObjects) {
 93         arrangedObjects.set('item', content);
 94       } else {
 95         arrangedObjects = SC.TreeItemObserver.create({ item: content, delegate: this });
 96 
 97         // Bind selection properties across to the observer.
 98         arrangedObjects.bind('allowsSelection', this, 'allowsSelection');
 99         arrangedObjects.bind('allowsMultipleSelection', this, 'allowsMultipleSelection');
100         arrangedObjects.bind('allowsEmptySelection', this, 'allowsEmptySelection');
101 
102         // Observe the enumerable property in order to update the selection when it changes.
103         arrangedObjects.addObserver('[]', this, this._sctc_arrangedObjectsContentDidChange);
104 
105         this.set('arrangedObjects', arrangedObjects);
106       }
107     } else {
108       // Since there is no content. Destroy the previous tree item observer and indicate that arrangedObjects has changed.
109       if (arrangedObjects) {
110         arrangedObjects.destroy();
111         this.set('arrangedObjects', null);
112 
113         // Update the selection if it exists.
114         this._sctc_arrangedObjectsContentDidChange();
115       }
116     }
117   }.observes('content'),
118 
119   /** @private */
120   _sctc_arrangedObjectsContentDidChange: function () {
121     this.updateSelectionAfterContentChange();
122   }.observes(),
123 
124   canSelectGroups: NO,
125 
126   /**
127     @private
128 
129     Returns the first item in arrangedObjects that is not a group.  This uses
130     a brute force approach right now; we assume you probably don't have a lot
131     of groups up front.
132   */
133   firstSelectableObject: function () {
134     var objects = this.get('arrangedObjects'),
135         indexes, len, idx     = 0;
136 
137     if (!objects) return null; // fast track
138 
139     // other fast track. if you want something fancier use collectionViewDelegate
140     if (this.get('canSelectGroups')) return objects.get('firstObject');
141 
142     indexes = objects.contentGroupIndexes(null, objects);
143     len = objects.get('length');
144     while (indexes.contains(idx) && (idx < len)) idx++;
145     return idx >= len ? null : objects.objectAt(idx);
146   }.property()
147 
148 });
149 
150