commit extjs-2.2.1
[extjs.git] / source / widgets / grid / CellSelectionModel.js
1 /*\r
2  * Ext JS Library 2.2.1\r
3  * Copyright(c) 2006-2009, Ext JS, LLC.\r
4  * licensing@extjs.com\r
5  * \r
6  * http://extjs.com/license\r
7  */\r
8 \r
9 /**\r
10  * @class Ext.grid.CellSelectionModel\r
11  * @extends Ext.grid.AbstractSelectionModel\r
12  * This class provides the basic implementation for single cell selection in a grid. The object stored\r
13  * as the selection and returned by {@link getSelectedCell} contains the following properties:\r
14  * <div class="mdetail-params"><ul>\r
15  * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record Record}\r
16  * which provides the data for the row containing the selection</p></li>\r
17  * <li><b>cell</b> : Ext.data.record<p class="sub-desc">An object containing the\r
18  * following properties:\r
19  * <div class="mdetail-params"><ul>\r
20  * <li><b>rowIndex</b> : Number<p class="sub-desc">The index of the selected row</p></li>\r
21  * <li><b>cellIndex</b> : Number<p class="sub-desc">The index of the selected cell<br>\r
22  * <b>Note that due to possible column reordering, the cellIndex should not be used as an index into\r
23  * the Record's data. Instead, the <i>name</i> of the selected field should be determined\r
24  * in order to retrieve the data value from the record by name:</b><pre><code>\r
25     var fieldName = grid.getColumnModel().getDataIndex(cellIndex);\r
26     var data = record.get(fieldName);\r
27 </code></pre></p></li>\r
28  * </ul></div></p></li>\r
29  * </ul></div>\r
30  * @constructor\r
31  * @param {Object} config The object containing the configuration of this model.\r
32  */\r
33 Ext.grid.CellSelectionModel = function(config){\r
34     Ext.apply(this, config);\r
35 \r
36     this.selection = null;\r
37 \r
38     this.addEvents(\r
39         /**\r
40              * @event beforecellselect\r
41              * Fires before a cell is selected.\r
42              * @param {SelectionModel} this\r
43              * @param {Number} rowIndex The selected row index\r
44              * @param {Number} colIndex The selected cell index\r
45              */\r
46             "beforecellselect",\r
47         /**\r
48              * @event cellselect\r
49              * Fires when a cell is selected.\r
50              * @param {SelectionModel} this\r
51              * @param {Number} rowIndex The selected row index\r
52              * @param {Number} colIndex The selected cell index\r
53              */\r
54             "cellselect",\r
55         /**\r
56              * @event selectionchange\r
57              * Fires when the active selection changes.\r
58              * @param {SelectionModel} this\r
59              * @param {Object} selection null for no selection or an object (o) with two properties\r
60                 <ul>\r
61                 <li>o.record: the record object for the row the selection is in</li>\r
62                 <li>o.cell: An array of [rowIndex, columnIndex]</li>\r
63                 </ul>\r
64              */\r
65             "selectionchange"\r
66     );\r
67 \r
68     Ext.grid.CellSelectionModel.superclass.constructor.call(this);\r
69 };\r
70 \r
71 Ext.extend(Ext.grid.CellSelectionModel, Ext.grid.AbstractSelectionModel,  {\r
72 \r
73     /** @ignore */\r
74     initEvents : function(){\r
75         this.grid.on("cellmousedown", this.handleMouseDown, this);\r
76         this.grid.getGridEl().on(Ext.isIE || Ext.isSafari3 ? "keydown" : "keypress", this.handleKeyDown, this);\r
77         var view = this.grid.view;\r
78         view.on("refresh", this.onViewChange, this);\r
79         view.on("rowupdated", this.onRowUpdated, this);\r
80         view.on("beforerowremoved", this.clearSelections, this);\r
81         view.on("beforerowsinserted", this.clearSelections, this);\r
82         if(this.grid.isEditor){\r
83             this.grid.on("beforeedit", this.beforeEdit,  this);\r
84         }\r
85     },\r
86 \r
87         //private\r
88     beforeEdit : function(e){\r
89         this.select(e.row, e.column, false, true, e.record);\r
90     },\r
91 \r
92         //private\r
93     onRowUpdated : function(v, index, r){\r
94         if(this.selection && this.selection.record == r){\r
95             v.onCellSelect(index, this.selection.cell[1]);\r
96         }\r
97     },\r
98 \r
99         //private\r
100     onViewChange : function(){\r
101         this.clearSelections(true);\r
102     },\r
103 \r
104         /**\r
105          * Returns the currently selected cell's row and column indexes as an array (e.g., [0, 0]).\r
106          * @return {Array} An array containing the row and column indexes of the selected cell, or null if none selected.\r
107          */\r
108     getSelectedCell : function(){\r
109         return this.selection ? this.selection.cell : null;\r
110     },\r
111 \r
112     /**\r
113      * Clears all selections.\r
114      * @param {Boolean} true to prevent the gridview from being notified about the change.\r
115      */\r
116     clearSelections : function(preventNotify){\r
117         var s = this.selection;\r
118         if(s){\r
119             if(preventNotify !== true){\r
120                 this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);\r
121             }\r
122             this.selection = null;\r
123             this.fireEvent("selectionchange", this, null);\r
124         }\r
125     },\r
126 \r
127     /**\r
128      * Returns true if there is a selection.\r
129      * @return {Boolean}\r
130      */\r
131     hasSelection : function(){\r
132         return this.selection ? true : false;\r
133     },\r
134 \r
135     /** @ignore */\r
136     handleMouseDown : function(g, row, cell, e){\r
137         if(e.button !== 0 || this.isLocked()){\r
138             return;\r
139         };\r
140         this.select(row, cell);\r
141     },\r
142 \r
143     /**\r
144      * Selects a cell.\r
145      * @param {Number} rowIndex\r
146      * @param {Number} collIndex\r
147      */\r
148     select : function(rowIndex, colIndex, preventViewNotify, preventFocus, /*internal*/ r){\r
149         if(this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false){\r
150             this.clearSelections();\r
151             r = r || this.grid.store.getAt(rowIndex);\r
152             this.selection = {\r
153                 record : r,\r
154                 cell : [rowIndex, colIndex]\r
155             };\r
156             if(!preventViewNotify){\r
157                 var v = this.grid.getView();\r
158                 v.onCellSelect(rowIndex, colIndex);\r
159                 if(preventFocus !== true){\r
160                     v.focusCell(rowIndex, colIndex);\r
161                 }\r
162             }\r
163             this.fireEvent("cellselect", this, rowIndex, colIndex);\r
164             this.fireEvent("selectionchange", this, this.selection);\r
165         }\r
166     },\r
167 \r
168         //private\r
169     isSelectable : function(rowIndex, colIndex, cm){\r
170         return !cm.isHidden(colIndex);\r
171     },\r
172 \r
173     /** @ignore */\r
174     handleKeyDown : function(e){\r
175         if(!e.isNavKeyPress()){\r
176             return;\r
177         }\r
178         var g = this.grid, s = this.selection;\r
179         if(!s){\r
180             e.stopEvent();\r
181             var cell = g.walkCells(0, 0, 1, this.isSelectable,  this);\r
182             if(cell){\r
183                 this.select(cell[0], cell[1]);\r
184             }\r
185             return;\r
186         }\r
187         var sm = this;\r
188         var walk = function(row, col, step){\r
189             return g.walkCells(row, col, step, sm.isSelectable,  sm);\r
190         };\r
191         var k = e.getKey(), r = s.cell[0], c = s.cell[1];\r
192         var newCell;\r
193 \r
194         switch(k){\r
195              case e.TAB:\r
196                  if(e.shiftKey){\r
197                      newCell = walk(r, c-1, -1);\r
198                  }else{\r
199                      newCell = walk(r, c+1, 1);\r
200                  }\r
201              break;\r
202              case e.DOWN:\r
203                  newCell = walk(r+1, c, 1);\r
204              break;\r
205              case e.UP:\r
206                  newCell = walk(r-1, c, -1);\r
207              break;\r
208              case e.RIGHT:\r
209                  newCell = walk(r, c+1, 1);\r
210              break;\r
211              case e.LEFT:\r
212                  newCell = walk(r, c-1, -1);\r
213              break;\r
214              case e.ENTER:\r
215                  if(g.isEditor && !g.editing){\r
216                     g.startEditing(r, c);\r
217                     e.stopEvent();\r
218                     return;\r
219                 }\r
220              break;\r
221         };\r
222         if(newCell){\r
223             this.select(newCell[0], newCell[1]);\r
224             e.stopEvent();\r
225         }\r
226     },\r
227 \r
228     acceptsNav : function(row, col, cm){\r
229         return !cm.isHidden(col) && cm.isCellEditable(col, row);\r
230     },\r
231 \r
232     onEditorKey : function(field, e){\r
233         var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;\r
234         if(k == e.TAB){\r
235             if(e.shiftKey){\r
236                 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);\r
237             }else{\r
238                 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);\r
239             }\r
240             e.stopEvent();\r
241         }else if(k == e.ENTER){\r
242             ed.completeEdit();\r
243             e.stopEvent();\r
244         }else if(k == e.ESC){\r
245                 e.stopEvent();\r
246             ed.cancelEdit();\r
247         }\r
248         if(newCell){\r
249             g.startEditing(newCell[0], newCell[1]);\r
250         }\r
251     }\r
252 });