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