Roo/grid/RowSelectionModel.js
[roojs1] / Roo / grid / RowSelectionModel.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11 /**
12  * @extends Roo.grid.AbstractSelectionModel
13  * @class Roo.grid.RowSelectionModel
14  * The default SelectionModel used by {@link Roo.grid.Grid}.
15  * It supports multiple selections and keyboard selection/navigation. 
16  * @constructor
17  * @param {Object} config
18  */
19 Roo.grid.RowSelectionModel = function(config){
20     Roo.apply(this, config);
21     this.selections = new Roo.util.MixedCollection(false, function(o){
22         return o.id;
23     });
24
25     this.last = false;
26     this.lastActive = false;
27
28     this.addEvents({
29         /**
30              * @event selectionchange
31              * Fires when the selection changes
32              * @param {SelectionModel} this
33              */
34             "selectionchange" : true,
35         /**
36              * @event afterselectionchange
37              * Fires after the selection changes (eg. by key press or clicking)
38              * @param {SelectionModel} this
39              */
40             "afterselectionchange" : true,
41         /**
42              * @event beforerowselect
43              * Fires when a row is selected being selected, return false to cancel.
44              * @param {SelectionModel} this
45              * @param {Number} rowIndex The selected index
46              * @param {Boolean} keepExisting False if other selections will be cleared
47              */
48             "beforerowselect" : true,
49         /**
50              * @event rowselect
51              * Fires when a row is selected.
52              * @param {SelectionModel} this
53              * @param {Number} rowIndex The selected index
54              * @param {Roo.data.Record} r The record
55              */
56             "rowselect" : true,
57         /**
58              * @event rowdeselect
59              * Fires when a row is deselected.
60              * @param {SelectionModel} this
61              * @param {Number} rowIndex The selected index
62              */
63         "rowdeselect" : true
64     });
65     Roo.grid.RowSelectionModel.superclass.constructor.call(this);
66     this.locked = false;
67 };
68
69 Roo.extend(Roo.grid.RowSelectionModel, Roo.grid.AbstractSelectionModel,  {
70     /**
71      * @cfg {Boolean} singleSelect
72      * True to allow selection of only one row at a time (defaults to false)
73      */
74     singleSelect : false,
75
76     // private
77     initEvents : function(){
78
79         if(!this.grid.enableDragDrop && !this.grid.enableDrag){
80             this.grid.on("mousedown", this.handleMouseDown, this);
81         }else{ // allow click to work like normal
82             this.grid.on("rowclick", this.handleDragableRowClick, this);
83         }
84
85         this.rowNav = new Roo.KeyNav(this.grid.getGridEl(), {
86             "up" : function(e){
87                 if(!e.shiftKey){
88                     this.selectPrevious(e.shiftKey);
89                 }else if(this.last !== false && this.lastActive !== false){
90                     var last = this.last;
91                     this.selectRange(this.last,  this.lastActive-1);
92                     this.grid.getView().focusRow(this.lastActive);
93                     if(last !== false){
94                         this.last = last;
95                     }
96                 }else{
97                     this.selectFirstRow();
98                 }
99                 this.fireEvent("afterselectionchange", this);
100             },
101             "down" : function(e){
102                 if(!e.shiftKey){
103                     this.selectNext(e.shiftKey);
104                 }else if(this.last !== false && this.lastActive !== false){
105                     var last = this.last;
106                     this.selectRange(this.last,  this.lastActive+1);
107                     this.grid.getView().focusRow(this.lastActive);
108                     if(last !== false){
109                         this.last = last;
110                     }
111                 }else{
112                     this.selectFirstRow();
113                 }
114                 this.fireEvent("afterselectionchange", this);
115             },
116             scope: this
117         });
118
119         var view = this.grid.view;
120         view.on("refresh", this.onRefresh, this);
121         view.on("rowupdated", this.onRowUpdated, this);
122         view.on("rowremoved", this.onRemove, this);
123     },
124
125     // private
126     onRefresh : function(){
127         Roo.log('calling on refresh!!!!!!!!!!!!!!!!!!!!!!!!');
128         var ds = this.grid.dataSource, i, v = this.grid.view;
129         var s = this.selections;
130         s.each(function(r){
131             if((i = ds.indexOfId(r.id)) != -1){
132                 v.onRowSelect(i);
133                 s.add(ds.getAt(i)); // updating the selection relate data
134             }else{
135                 s.remove(r);
136             }
137         });
138     },
139
140     // private
141     onRemove : function(v, index, r){
142         this.selections.remove(r);
143     },
144
145     // private
146     onRowUpdated : function(v, index, r){
147         if(this.isSelected(r)){
148             v.onRowSelect(index);
149         }
150     },
151
152     /**
153      * Select records.
154      * @param {Array} records The records to select
155      * @param {Boolean} keepExisting (optional) True to keep existing selections
156      */
157     selectRecords : function(records, keepExisting){
158         if(!keepExisting){
159             this.clearSelections();
160         }
161         var ds = this.grid.dataSource;
162         for(var i = 0, len = records.length; i < len; i++){
163             this.selectRow(ds.indexOf(records[i]), true);
164         }
165     },
166
167     /**
168      * Gets the number of selected rows.
169      * @return {Number}
170      */
171     getCount : function(){
172         return this.selections.length;
173     },
174
175     /**
176      * Selects the first row in the grid.
177      */
178     selectFirstRow : function(){
179         this.selectRow(0);
180     },
181
182     /**
183      * Select the last row.
184      * @param {Boolean} keepExisting (optional) True to keep existing selections
185      */
186     selectLastRow : function(keepExisting){
187         this.selectRow(this.grid.dataSource.getCount() - 1, keepExisting);
188     },
189
190     /**
191      * Selects the row immediately following the last selected row.
192      * @param {Boolean} keepExisting (optional) True to keep existing selections
193      */
194     selectNext : function(keepExisting){
195         if(this.last !== false && (this.last+1) < this.grid.dataSource.getCount()){
196             this.selectRow(this.last+1, keepExisting);
197             this.grid.getView().focusRow(this.last);
198         }
199     },
200
201     /**
202      * Selects the row that precedes the last selected row.
203      * @param {Boolean} keepExisting (optional) True to keep existing selections
204      */
205     selectPrevious : function(keepExisting){
206         if(this.last){
207             this.selectRow(this.last-1, keepExisting);
208             this.grid.getView().focusRow(this.last);
209         }
210     },
211
212     /**
213      * Returns the selected records
214      * @return {Array} Array of selected records
215      */
216     getSelections : function(){
217         return [].concat(this.selections.items);
218     },
219
220     /**
221      * Returns the first selected record.
222      * @return {Record}
223      */
224     getSelected : function(){
225         return this.selections.itemAt(0);
226     },
227
228
229     /**
230      * Clears all selections.
231      */
232     clearSelections : function(fast){
233         if(this.locked) return;
234         if(fast !== true){
235             var ds = this.grid.dataSource;
236             var s = this.selections;
237             s.each(function(r){
238                 this.deselectRow(ds.indexOfId(r.id));
239             }, this);
240             s.clear();
241         }else{
242             this.selections.clear();
243         }
244         this.last = false;
245     },
246
247
248     /**
249      * Selects all rows.
250      */
251     selectAll : function(){
252         if(this.locked) return;
253         this.selections.clear();
254         for(var i = 0, len = this.grid.dataSource.getCount(); i < len; i++){
255             this.selectRow(i, true);
256         }
257     },
258
259     /**
260      * Returns True if there is a selection.
261      * @return {Boolean}
262      */
263     hasSelection : function(){
264         return this.selections.length > 0;
265     },
266
267     /**
268      * Returns True if the specified row is selected.
269      * @param {Number/Record} record The record or index of the record to check
270      * @return {Boolean}
271      */
272     isSelected : function(index){
273         var r = typeof index == "number" ? this.grid.dataSource.getAt(index) : index;
274         return (r && this.selections.key(r.id) ? true : false);
275     },
276
277     /**
278      * Returns True if the specified record id is selected.
279      * @param {String} id The id of record to check
280      * @return {Boolean}
281      */
282     isIdSelected : function(id){
283         return (this.selections.key(id) ? true : false);
284     },
285
286     // private
287     handleMouseDown : function(e, t){
288         var view = this.grid.getView(), rowIndex;
289         if(this.isLocked() || (rowIndex = view.findRowIndex(t)) === false){
290             return;
291         };
292         if(e.shiftKey && this.last !== false){
293             var last = this.last;
294             this.selectRange(last, rowIndex, e.ctrlKey);
295             this.last = last; // reset the last
296             view.focusRow(rowIndex);
297         }else{
298             var isSelected = this.isSelected(rowIndex);
299             if(e.button !== 0 && isSelected){
300                 view.focusRow(rowIndex);
301             }else if(e.ctrlKey && isSelected){
302                 this.deselectRow(rowIndex);
303             }else if(!isSelected){
304                 this.selectRow(rowIndex, e.button === 0 && (e.ctrlKey || e.shiftKey));
305                 view.focusRow(rowIndex);
306             }
307         }
308         this.fireEvent("afterselectionchange", this);
309     },
310     // private
311     handleDragableRowClick :  function(grid, rowIndex, e) 
312     {
313         if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
314             this.selectRow(rowIndex, false);
315             grid.view.focusRow(rowIndex);
316              this.fireEvent("afterselectionchange", this);
317         }
318     },
319     
320     /**
321      * Selects multiple rows.
322      * @param {Array} rows Array of the indexes of the row to select
323      * @param {Boolean} keepExisting (optional) True to keep existing selections
324      */
325     selectRows : function(rows, keepExisting){
326         if(!keepExisting){
327             this.clearSelections();
328         }
329         for(var i = 0, len = rows.length; i < len; i++){
330             this.selectRow(rows[i], true);
331         }
332     },
333
334     /**
335      * Selects a range of rows. All rows in between startRow and endRow are also selected.
336      * @param {Number} startRow The index of the first row in the range
337      * @param {Number} endRow The index of the last row in the range
338      * @param {Boolean} keepExisting (optional) True to retain existing selections
339      */
340     selectRange : function(startRow, endRow, keepExisting){
341         if(this.locked) return;
342         if(!keepExisting){
343             this.clearSelections();
344         }
345         if(startRow <= endRow){
346             for(var i = startRow; i <= endRow; i++){
347                 this.selectRow(i, true);
348             }
349         }else{
350             for(var i = startRow; i >= endRow; i--){
351                 this.selectRow(i, true);
352             }
353         }
354     },
355
356     /**
357      * Deselects a range of rows. All rows in between startRow and endRow are also deselected.
358      * @param {Number} startRow The index of the first row in the range
359      * @param {Number} endRow The index of the last row in the range
360      */
361     deselectRange : function(startRow, endRow, preventViewNotify){
362         if(this.locked) return;
363         for(var i = startRow; i <= endRow; i++){
364             this.deselectRow(i, preventViewNotify);
365         }
366     },
367
368     /**
369      * Selects a row.
370      * @param {Number} row The index of the row to select
371      * @param {Boolean} keepExisting (optional) True to keep existing selections
372      */
373     selectRow : function(index, keepExisting, preventViewNotify){
374         if(this.locked || (index < 0 || index >= this.grid.dataSource.getCount())) return;
375         if(this.fireEvent("beforerowselect", this, index, keepExisting) !== false){
376             if(!keepExisting || this.singleSelect){
377                 this.clearSelections();
378             }
379             var r = this.grid.dataSource.getAt(index);
380             Roo.log('running select row!!!!!!!!!!!!!!!!!!!!!!!!');
381             Roo.log(r);
382             
383             this.selections.add(r);
384             this.last = this.lastActive = index;
385             if(!preventViewNotify){
386                 this.grid.getView().onRowSelect(index);
387             }
388             this.fireEvent("rowselect", this, index, r);
389             this.fireEvent("selectionchange", this);
390         }
391     },
392
393     /**
394      * Deselects a row.
395      * @param {Number} row The index of the row to deselect
396      */
397     deselectRow : function(index, preventViewNotify){
398         if(this.locked) return;
399         if(this.last == index){
400             this.last = false;
401         }
402         if(this.lastActive == index){
403             this.lastActive = false;
404         }
405         var r = this.grid.dataSource.getAt(index);
406         this.selections.remove(r);
407         if(!preventViewNotify){
408             this.grid.getView().onRowDeselect(index);
409         }
410         this.fireEvent("rowdeselect", this, index);
411         this.fireEvent("selectionchange", this);
412     },
413
414     // private
415     restoreLast : function(){
416         if(this._last){
417             this.last = this._last;
418         }
419     },
420
421     // private
422     acceptsNav : function(row, col, cm){
423         return !cm.isHidden(col) && cm.isCellEditable(col, row);
424     },
425
426     // private
427     onEditorKey : function(field, e){
428         var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;
429         if(k == e.TAB){
430             e.stopEvent();
431             ed.completeEdit();
432             if(e.shiftKey){
433                 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
434             }else{
435                 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
436             }
437         }else if(k == e.ENTER && !e.ctrlKey){
438             e.stopEvent();
439             ed.completeEdit();
440             if(e.shiftKey){
441                 newCell = g.walkCells(ed.row-1, ed.col, -1, this.acceptsNav, this);
442             }else{
443                 newCell = g.walkCells(ed.row+1, ed.col, 1, this.acceptsNav, this);
444             }
445         }else if(k == e.ESC){
446             ed.cancelEdit();
447         }
448         if(newCell){
449             g.startEditing(newCell[0], newCell[1]);
450         }
451     }
452 });