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