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
72         if(!this.grid.enableDragDrop && !this.grid.enableDrag){
73             this.grid.on("mousedown", this.handleMouseDown, this);
74         }else{ // allow click to work like normal
75             this.grid.on("rowclick", this.handleDragableRowClick, this);
76         }
77
78         this.rowNav = new Roo.KeyNav(this.grid.getGridEl(), {
79             "up" : function(e){
80                 if(!e.shiftKey){
81                     this.selectPrevious(e.shiftKey);
82                 }else if(this.last !== false && this.lastActive !== false){
83                     var last = this.last;
84                     this.selectRange(this.last,  this.lastActive-1);
85                     this.grid.getView().focusRow(this.lastActive);
86                     if(last !== false){
87                         this.last = last;
88                     }
89                 }else{
90                     this.selectFirstRow();
91                 }
92                 this.fireEvent("afterselectionchange", this);
93             },
94             "down" : function(e){
95                 if(!e.shiftKey){
96                     this.selectNext(e.shiftKey);
97                 }else if(this.last !== false && this.lastActive !== false){
98                     var last = this.last;
99                     this.selectRange(this.last,  this.lastActive+1);
100                     this.grid.getView().focusRow(this.lastActive);
101                     if(last !== false){
102                         this.last = last;
103                     }
104                 }else{
105                     this.selectFirstRow();
106                 }
107                 this.fireEvent("afterselectionchange", this);
108             },
109             scope: this
110         });
111
112         var view = this.grid.view;
113         view.on("refresh", this.onRefresh, this);
114         view.on("rowupdated", this.onRowUpdated, this);
115         view.on("rowremoved", this.onRemove, this);
116     },
117
118     // private
119     onRefresh : function(){
120         var ds = this.grid.dataSource, i, v = this.grid.view;
121         var s = this.selections;
122         s.each(function(r){
123             if((i = ds.indexOfId(r.id)) != -1){
124                 v.onRowSelect(i);
125             }else{
126                 s.remove(r);
127             }
128         });
129     },
130
131     // private
132     onRemove : function(v, index, r){
133         this.selections.remove(r);
134     },
135
136     // private
137     onRowUpdated : function(v, index, r){
138         if(this.isSelected(r)){
139             v.onRowSelect(index);
140         }
141     },
142
143     /**
144      * Select records.
145      * @param {Array} records The records to select
146      * @param {Boolean} keepExisting (optional) True to keep existing selections
147      */
148     selectRecords : function(records, keepExisting){
149         if(!keepExisting){
150             this.clearSelections();
151         }
152         var ds = this.grid.dataSource;
153         for(var i = 0, len = records.length; i < len; i++){
154             this.selectRow(ds.indexOf(records[i]), true);
155         }
156     },
157
158     /**
159      * Gets the number of selected rows.
160      * @return {Number}
161      */
162     getCount : function(){
163         return this.selections.length;
164     },
165
166     /**
167      * Selects the first row in the grid.
168      */
169     selectFirstRow : function(){
170         this.selectRow(0);
171     },
172
173     /**
174      * Select the last row.
175      * @param {Boolean} keepExisting (optional) True to keep existing selections
176      */
177     selectLastRow : function(keepExisting){
178         this.selectRow(this.grid.dataSource.getCount() - 1, keepExisting);
179     },
180
181     /**
182      * Selects the row immediately following the last selected row.
183      * @param {Boolean} keepExisting (optional) True to keep existing selections
184      */
185     selectNext : function(keepExisting){
186         if(this.last !== false && (this.last+1) < this.grid.dataSource.getCount()){
187             this.selectRow(this.last+1, keepExisting);
188             this.grid.getView().focusRow(this.last);
189         }
190     },
191
192     /**
193      * Selects the row that precedes the last selected row.
194      * @param {Boolean} keepExisting (optional) True to keep existing selections
195      */
196     selectPrevious : function(keepExisting){
197         if(this.last){
198             this.selectRow(this.last-1, keepExisting);
199             this.grid.getView().focusRow(this.last);
200         }
201     },
202
203     /**
204      * Returns the selected records
205      * @return {Array} Array of selected records
206      */
207     getSelections : function(){
208         return [].concat(this.selections.items);
209     },
210
211     /**
212      * Returns the first selected record.
213      * @return {Record}
214      */
215     getSelected : function(){
216         return this.selections.itemAt(0);
217     },
218
219
220     /**
221      * Clears all selections.
222      */
223     clearSelections : function(fast){
224         if(this.locked) {
225             return;
226         }
227         if(fast !== true){
228             var ds = this.grid.dataSource;
229             var s = this.selections;
230             s.each(function(r){
231                 this.deselectRow(ds.indexOfId(r.id));
232             }, this);
233             s.clear();
234         }else{
235             this.selections.clear();
236         }
237         this.last = false;
238     },
239
240
241     /**
242      * Selects all rows.
243      */
244     selectAll : function(){
245         if(this.locked) {
246             return;
247         }
248         this.selections.clear();
249         for(var i = 0, len = this.grid.dataSource.getCount(); i < len; i++){
250             this.selectRow(i, true);
251         }
252     },
253
254     /**
255      * Returns True if there is a selection.
256      * @return {Boolean}
257      */
258     hasSelection : function(){
259         return this.selections.length > 0;
260     },
261
262     /**
263      * Returns True if the specified row is selected.
264      * @param {Number/Record} record The record or index of the record to check
265      * @return {Boolean}
266      */
267     isSelected : function(index){
268         var r = typeof index == "number" ? this.grid.dataSource.getAt(index) : index;
269         return (r && this.selections.key(r.id) ? true : false);
270     },
271
272     /**
273      * Returns True if the specified record id is selected.
274      * @param {String} id The id of record to check
275      * @return {Boolean}
276      */
277     isIdSelected : function(id){
278         return (this.selections.key(id) ? true : false);
279     },
280
281     // private
282     handleMouseDown : function(e, t){
283         var view = this.grid.getView(), rowIndex;
284         if(this.isLocked() || (rowIndex = view.findRowIndex(t)) === false){
285             return;
286         };
287         if(e.shiftKey && this.last !== false){
288             var last = this.last;
289             this.selectRange(last, rowIndex, e.ctrlKey);
290             this.last = last; // reset the last
291             view.focusRow(rowIndex);
292         }else{
293             var isSelected = this.isSelected(rowIndex);
294             if(e.button !== 0 && isSelected){
295                 view.focusRow(rowIndex);
296             }else if(e.ctrlKey && isSelected){
297                 this.deselectRow(rowIndex);
298             }else if(!isSelected){
299                 this.selectRow(rowIndex, e.button === 0 && (e.ctrlKey || e.shiftKey));
300                 view.focusRow(rowIndex);
301             }
302         }
303         this.fireEvent("afterselectionchange", this);
304     },
305     // private
306     handleDragableRowClick :  function(grid, rowIndex, e) 
307     {
308         if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
309             this.selectRow(rowIndex, false);
310             grid.view.focusRow(rowIndex);
311              this.fireEvent("afterselectionchange", this);
312         }
313     },
314     
315     /**
316      * Selects multiple rows.
317      * @param {Array} rows Array of the indexes of the row to select
318      * @param {Boolean} keepExisting (optional) True to keep existing selections
319      */
320     selectRows : function(rows, keepExisting){
321         if(!keepExisting){
322             this.clearSelections();
323         }
324         for(var i = 0, len = rows.length; i < len; i++){
325             this.selectRow(rows[i], true);
326         }
327     },
328
329     /**
330      * Selects a range of rows. All rows in between startRow and endRow are also selected.
331      * @param {Number} startRow The index of the first row in the range
332      * @param {Number} endRow The index of the last row in the range
333      * @param {Boolean} keepExisting (optional) True to retain existing selections
334      */
335     selectRange : function(startRow, endRow, keepExisting){
336         if(this.locked) {
337             return;
338         }
339         if(!keepExisting){
340             this.clearSelections();
341         }
342         if(startRow <= endRow){
343             for(var i = startRow; i <= endRow; i++){
344                 this.selectRow(i, true);
345             }
346         }else{
347             for(var i = startRow; i >= endRow; i--){
348                 this.selectRow(i, true);
349             }
350         }
351     },
352
353     /**
354      * Deselects a range of rows. All rows in between startRow and endRow are also deselected.
355      * @param {Number} startRow The index of the first row in the range
356      * @param {Number} endRow The index of the last row in the range
357      */
358     deselectRange : function(startRow, endRow, preventViewNotify){
359         if(this.locked) {
360             return;
361         }
362         for(var i = startRow; i <= endRow; i++){
363             this.deselectRow(i, preventViewNotify);
364         }
365     },
366
367     /**
368      * Selects a row.
369      * @param {Number} row The index of the row to select
370      * @param {Boolean} keepExisting (optional) True to keep existing selections
371      */
372     selectRow : function(index, keepExisting, preventViewNotify){
373         if(this.locked || (index < 0 || index >= this.grid.dataSource.getCount())) {
374             return;
375         }
376         if(this.fireEvent("beforerowselect", this, index, keepExisting) !== false){
377             if(!keepExisting || this.singleSelect){
378                 this.clearSelections();
379             }
380             var r = this.grid.dataSource.getAt(index);
381             this.selections.add(r);
382             this.last = this.lastActive = index;
383             if(!preventViewNotify){
384                 this.grid.getView().onRowSelect(index);
385             }
386             this.fireEvent("rowselect", this, index, r);
387             this.fireEvent("selectionchange", this);
388         }
389     },
390
391     /**
392      * Deselects a row.
393      * @param {Number} row The index of the row to deselect
394      */
395     deselectRow : function(index, preventViewNotify){
396         if(this.locked) {
397             return;
398         }
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 });