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