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