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) return;
224         if(fast !== true){
225             var ds = this.grid.dataSource;
226             var s = this.selections;
227             s.each(function(r){
228                 this.deselectRow(ds.indexOfId(r.id));
229             }, this);
230             s.clear();
231         }else{
232             this.selections.clear();
233         }
234         this.last = false;
235     },
236
237
238     /**
239      * Selects all rows.
240      */
241     selectAll : function(){
242         if(this.locked) return;
243         this.selections.clear();
244         for(var i = 0, len = this.grid.dataSource.getCount(); i < len; i++){
245             this.selectRow(i, true);
246         }
247     },
248
249     /**
250      * Returns True if there is a selection.
251      * @return {Boolean}
252      */
253     hasSelection : function(){
254         return this.selections.length > 0;
255     },
256
257     /**
258      * Returns True if the specified row is selected.
259      * @param {Number/Record} record The record or index of the record to check
260      * @return {Boolean}
261      */
262     isSelected : function(index){
263         var r = typeof index == "number" ? this.grid.dataSource.getAt(index) : index;
264         return (r && this.selections.key(r.id) ? true : false);
265     },
266
267     /**
268      * Returns True if the specified record id is selected.
269      * @param {String} id The id of record to check
270      * @return {Boolean}
271      */
272     isIdSelected : function(id){
273         return (this.selections.key(id) ? true : false);
274     },
275
276     // private
277     handleMouseDown : function(e, t){
278         var view = this.grid.getView(), rowIndex;
279         if(this.isLocked() || (rowIndex = view.findRowIndex(t)) === false){
280             return;
281         };
282         if(e.shiftKey && this.last !== false){
283             var last = this.last;
284             this.selectRange(last, rowIndex, e.ctrlKey);
285             this.last = last; // reset the last
286             view.focusRow(rowIndex);
287         }else{
288             var isSelected = this.isSelected(rowIndex);
289             if(e.button !== 0 && isSelected){
290                 view.focusRow(rowIndex);
291             }else if(e.ctrlKey && isSelected){
292                 this.deselectRow(rowIndex);
293             }else if(!isSelected){
294                 this.selectRow(rowIndex, e.button === 0 && (e.ctrlKey || e.shiftKey));
295                 view.focusRow(rowIndex);
296             }
297         }
298         this.fireEvent("afterselectionchange", this);
299     },
300     // private
301     handleDragableRowClick :  function(grid, rowIndex, e) 
302     {
303         if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
304             this.selectRow(rowIndex, false);
305             grid.view.focusRow(rowIndex);
306              this.fireEvent("afterselectionchange", this);
307         }
308     },
309     
310     /**
311      * Selects multiple rows.
312      * @param {Array} rows Array of the indexes of the row to select
313      * @param {Boolean} keepExisting (optional) True to keep existing selections
314      */
315     selectRows : function(rows, keepExisting){
316         if(!keepExisting){
317             this.clearSelections();
318         }
319         for(var i = 0, len = rows.length; i < len; i++){
320             this.selectRow(rows[i], true);
321         }
322     },
323
324     /**
325      * Selects a range of rows. All rows in between startRow and endRow are also selected.
326      * @param {Number} startRow The index of the first row in the range
327      * @param {Number} endRow The index of the last row in the range
328      * @param {Boolean} keepExisting (optional) True to retain existing selections
329      */
330     selectRange : function(startRow, endRow, keepExisting){
331         if(this.locked) return;
332         if(!keepExisting){
333             this.clearSelections();
334         }
335         if(startRow <= endRow){
336             for(var i = startRow; i <= endRow; i++){
337                 this.selectRow(i, true);
338             }
339         }else{
340             for(var i = startRow; i >= endRow; i--){
341                 this.selectRow(i, true);
342             }
343         }
344     },
345
346     /**
347      * Deselects a range of rows. All rows in between startRow and endRow are also deselected.
348      * @param {Number} startRow The index of the first row in the range
349      * @param {Number} endRow The index of the last row in the range
350      */
351     deselectRange : function(startRow, endRow, preventViewNotify){
352         if(this.locked) return;
353         for(var i = startRow; i <= endRow; i++){
354             this.deselectRow(i, preventViewNotify);
355         }
356     },
357
358     /**
359      * Selects a row.
360      * @param {Number} row The index of the row to select
361      * @param {Boolean} keepExisting (optional) True to keep existing selections
362      */
363     selectRow : function(index, keepExisting, preventViewNotify){
364         if(this.locked || (index < 0 || index >= this.grid.dataSource.getCount())) return;
365         if(this.fireEvent("beforerowselect", this, index, keepExisting) !== false){
366             if(!keepExisting || this.singleSelect){
367                 this.clearSelections();
368             }
369             var r = this.grid.dataSource.getAt(index);
370             this.selections.add(r);
371             this.last = this.lastActive = index;
372             if(!preventViewNotify){
373                 this.grid.getView().onRowSelect(index);
374             }
375             this.fireEvent("rowselect", this, index, r);
376             this.fireEvent("selectionchange", this);
377         }
378     },
379
380     /**
381      * Deselects a row.
382      * @param {Number} row The index of the row to deselect
383      */
384     deselectRow : function(index, preventViewNotify){
385         if(this.locked) return;
386         if(this.last == index){
387             this.last = false;
388         }
389         if(this.lastActive == index){
390             this.lastActive = false;
391         }
392         var r = this.grid.dataSource.getAt(index);
393         this.selections.remove(r);
394         if(!preventViewNotify){
395             this.grid.getView().onRowDeselect(index);
396         }
397         this.fireEvent("rowdeselect", this, index);
398         this.fireEvent("selectionchange", this);
399     },
400
401     // private
402     restoreLast : function(){
403         if(this._last){
404             this.last = this._last;
405         }
406     },
407
408     // private
409     acceptsNav : function(row, col, cm){
410         return !cm.isHidden(col) && cm.isCellEditable(col, row);
411     },
412
413     // private
414     onEditorKey : function(field, e){
415         var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;
416         if(k == e.TAB){
417             e.stopEvent();
418             ed.completeEdit();
419             if(e.shiftKey){
420                 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
421             }else{
422                 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
423             }
424         }else if(k == e.ENTER && !e.ctrlKey){
425             e.stopEvent();
426             ed.completeEdit();
427             if(e.shiftKey){
428                 newCell = g.walkCells(ed.row-1, ed.col, -1, this.acceptsNav, this);
429             }else{
430                 newCell = g.walkCells(ed.row+1, ed.col, 1, this.acceptsNav, this);
431             }
432         }else if(k == e.ESC){
433             ed.cancelEdit();
434         }
435         if(newCell){
436             g.startEditing(newCell[0], newCell[1]);
437         }
438     }
439 });