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