Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / src / widgets / tree / TreeSelectionModel.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.tree.DefaultSelectionModel
9  * @extends Ext.util.Observable
10  * The default single selection for a TreePanel.
11  */
12 Ext.tree.DefaultSelectionModel = function(config){
13    this.selNode = null;
14    
15    this.addEvents(
16        /**
17         * @event selectionchange
18         * Fires when the selected node changes
19         * @param {DefaultSelectionModel} this
20         * @param {TreeNode} node the new selection
21         */
22        'selectionchange',
23
24        /**
25         * @event beforeselect
26         * Fires before the selected node changes, return false to cancel the change
27         * @param {DefaultSelectionModel} this
28         * @param {TreeNode} node the new selection
29         * @param {TreeNode} node the old selection
30         */
31        'beforeselect'
32    );
33
34     Ext.apply(this, config);
35     Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);
36 };
37
38 Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {
39     init : function(tree){
40         this.tree = tree;
41         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
42         tree.on('click', this.onNodeClick, this);
43     },
44     
45     onNodeClick : function(node, e){
46         this.select(node);
47     },
48     
49     /**
50      * Select a node.
51      * @param {TreeNode} node The node to select
52      * @return {TreeNode} The selected node
53      */
54     select : function(node, /* private*/ selectNextNode){
55         // If node is hidden, select the next node in whatever direction was being moved in.
56         if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) {
57             return selectNextNode.call(this, node);
58         }
59         var last = this.selNode;
60         if(node == last){
61             node.ui.onSelectedChange(true);
62         }else if(this.fireEvent('beforeselect', this, node, last) !== false){
63             if(last && last.ui){
64                 last.ui.onSelectedChange(false);
65             }
66             this.selNode = node;
67             node.ui.onSelectedChange(true);
68             this.fireEvent('selectionchange', this, node, last);
69         }
70         return node;
71     },
72     
73     /**
74      * Deselect a node.
75      * @param {TreeNode} node The node to unselect
76      * @param {Boolean} silent True to stop the selectionchange event from firing.
77      */
78     unselect : function(node, silent){
79         if(this.selNode == node){
80             this.clearSelections(silent);
81         }    
82     },
83     
84     /**
85      * Clear all selections
86      * @param {Boolean} silent True to stop the selectionchange event from firing.
87      */
88     clearSelections : function(silent){
89         var n = this.selNode;
90         if(n){
91             n.ui.onSelectedChange(false);
92             this.selNode = null;
93             if(silent !== true){
94                 this.fireEvent('selectionchange', this, null);
95             }
96         }
97         return n;
98     },
99     
100     /**
101      * Get the selected node
102      * @return {TreeNode} The selected node
103      */
104     getSelectedNode : function(){
105         return this.selNode;    
106     },
107     
108     /**
109      * Returns true if the node is selected
110      * @param {TreeNode} node The node to check
111      * @return {Boolean}
112      */
113     isSelected : function(node){
114         return this.selNode == node;  
115     },
116
117     /**
118      * Selects the node above the selected node in the tree, intelligently walking the nodes
119      * @return TreeNode The new selection
120      */
121     selectPrevious : function(/* private */ s){
122         if(!(s = s || this.selNode || this.lastSelNode)){
123             return null;
124         }
125         // Here we pass in the current function to select to indicate the direction we're moving
126         var ps = s.previousSibling;
127         if(ps){
128             if(!ps.isExpanded() || ps.childNodes.length < 1){
129                 return this.select(ps, this.selectPrevious);
130             } else{
131                 var lc = ps.lastChild;
132                 while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){
133                     lc = lc.lastChild;
134                 }
135                 return this.select(lc, this.selectPrevious);
136             }
137         } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
138             return this.select(s.parentNode, this.selectPrevious);
139         }
140         return null;
141     },
142
143     /**
144      * Selects the node above the selected node in the tree, intelligently walking the nodes
145      * @return TreeNode The new selection
146      */
147     selectNext : function(/* private */ s){
148         if(!(s = s || this.selNode || this.lastSelNode)){
149             return null;
150         }
151         // Here we pass in the current function to select to indicate the direction we're moving
152         if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){
153              return this.select(s.firstChild, this.selectNext);
154          }else if(s.nextSibling){
155              return this.select(s.nextSibling, this.selectNext);
156          }else if(s.parentNode){
157             var newS = null;
158             s.parentNode.bubble(function(){
159                 if(this.nextSibling){
160                     newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext);
161                     return false;
162                 }
163             });
164             return newS;
165          }
166         return null;
167     },
168
169     onKeyDown : function(e){
170         var s = this.selNode || this.lastSelNode;
171         // undesirable, but required
172         var sm = this;
173         if(!s){
174             return;
175         }
176         var k = e.getKey();
177         switch(k){
178              case e.DOWN:
179                  e.stopEvent();
180                  this.selectNext();
181              break;
182              case e.UP:
183                  e.stopEvent();
184                  this.selectPrevious();
185              break;
186              case e.RIGHT:
187                  e.preventDefault();
188                  if(s.hasChildNodes()){
189                      if(!s.isExpanded()){
190                          s.expand();
191                      }else if(s.firstChild){
192                          this.select(s.firstChild, e);
193                      }
194                  }
195              break;
196              case e.LEFT:
197                  e.preventDefault();
198                  if(s.hasChildNodes() && s.isExpanded()){
199                      s.collapse();
200                  }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
201                      this.select(s.parentNode, e);
202                  }
203              break;
204         };
205     }
206 });
207
208 /**
209  * @class Ext.tree.MultiSelectionModel
210  * @extends Ext.util.Observable
211  * Multi selection for a TreePanel.
212  */
213 Ext.tree.MultiSelectionModel = function(config){
214    this.selNodes = [];
215    this.selMap = {};
216    this.addEvents(
217        /**
218         * @event selectionchange
219         * Fires when the selected nodes change
220         * @param {MultiSelectionModel} this
221         * @param {Array} nodes Array of the selected nodes
222         */
223        'selectionchange'
224    );
225     Ext.apply(this, config);
226     Ext.tree.MultiSelectionModel.superclass.constructor.call(this);
227 };
228
229 Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {
230     init : function(tree){
231         this.tree = tree;
232         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
233         tree.on('click', this.onNodeClick, this);
234     },
235     
236     onNodeClick : function(node, e){
237         if(e.ctrlKey && this.isSelected(node)){
238             this.unselect(node);
239         }else{
240             this.select(node, e, e.ctrlKey);
241         }
242     },
243     
244     /**
245      * Select a node.
246      * @param {TreeNode} node The node to select
247      * @param {EventObject} e (optional) An event associated with the selection
248      * @param {Boolean} keepExisting True to retain existing selections
249      * @return {TreeNode} The selected node
250      */
251     select : function(node, e, keepExisting){
252         if(keepExisting !== true){
253             this.clearSelections(true);
254         }
255         if(this.isSelected(node)){
256             this.lastSelNode = node;
257             return node;
258         }
259         this.selNodes.push(node);
260         this.selMap[node.id] = node;
261         this.lastSelNode = node;
262         node.ui.onSelectedChange(true);
263         this.fireEvent('selectionchange', this, this.selNodes);
264         return node;
265     },
266     
267     /**
268      * Deselect a node.
269      * @param {TreeNode} node The node to unselect
270      */
271     unselect : function(node){
272         if(this.selMap[node.id]){
273             node.ui.onSelectedChange(false);
274             var sn = this.selNodes;
275             var index = sn.indexOf(node);
276             if(index != -1){
277                 this.selNodes.splice(index, 1);
278             }
279             delete this.selMap[node.id];
280             this.fireEvent('selectionchange', this, this.selNodes);
281         }
282     },
283     
284     /**
285      * Clear all selections
286      */
287     clearSelections : function(suppressEvent){
288         var sn = this.selNodes;
289         if(sn.length > 0){
290             for(var i = 0, len = sn.length; i < len; i++){
291                 sn[i].ui.onSelectedChange(false);
292             }
293             this.selNodes = [];
294             this.selMap = {};
295             if(suppressEvent !== true){
296                 this.fireEvent('selectionchange', this, this.selNodes);
297             }
298         }
299     },
300     
301     /**
302      * Returns true if the node is selected
303      * @param {TreeNode} node The node to check
304      * @return {Boolean}
305      */
306     isSelected : function(node){
307         return this.selMap[node.id] ? true : false;  
308     },
309     
310     /**
311      * Returns an array of the selected nodes
312      * @return {Array}
313      */
314     getSelectedNodes : function(){
315         return this.selNodes;    
316     },
317
318     onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,
319
320     selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,
321
322     selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
323 });