Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / selection / RowModel.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.selection.RowModel
17  * @extends Ext.selection.Model
18  *
19  * Implement row based navigation via keyboard.
20  *
21  * Must synchronize across grid sections
22  */
23 Ext.define('Ext.selection.RowModel', {
24     extend: 'Ext.selection.Model',
25     alias: 'selection.rowmodel',
26     requires: ['Ext.util.KeyNav'],
27
28     /**
29      * @private
30      * Number of pixels to scroll to the left/right when pressing
31      * left/right keys.
32      */
33     deltaScroll: 5,
34
35     /**
36      * @cfg {Boolean} enableKeyNav
37      *
38      * Turns on/off keyboard navigation within the grid. Defaults to true.
39      */
40     enableKeyNav: true,
41
42     constructor: function(){
43         this.addEvents(
44             /**
45              * @event beforedeselect
46              * Fired before a record is deselected. If any listener returns false, the
47              * deselection is cancelled.
48              * @param {Ext.selection.RowSelectionModel} this
49              * @param {Ext.data.Model} record The deselected record
50              * @param {Number} index The row index deselected
51              */
52             'beforedeselect',
53
54             /**
55              * @event beforeselect
56              * Fired before a record is selected. If any listener returns false, the
57              * selection is cancelled.
58              * @param {Ext.selection.RowSelectionModel} this
59              * @param {Ext.data.Model} record The selected record
60              * @param {Number} index The row index selected
61              */
62             'beforeselect',
63
64             /**
65              * @event deselect
66              * Fired after a record is deselected
67              * @param {Ext.selection.RowSelectionModel} this
68              * @param {Ext.data.Model} record The deselected record
69              * @param {Number} index The row index deselected
70              */
71             'deselect',
72
73             /**
74              * @event select
75              * Fired after a record is selected
76              * @param {Ext.selection.RowSelectionModel} this
77              * @param {Ext.data.Model} record The selected record
78              * @param {Number} index The row index selected
79              */
80             'select'
81         );
82         this.callParent(arguments);
83     },
84
85     bindComponent: function(view) {
86         var me = this;
87
88         me.views = me.views || [];
89         me.views.push(view);
90         me.bind(view.getStore(), true);
91
92         view.on({
93             itemmousedown: me.onRowMouseDown,
94             scope: me
95         });
96
97         if (me.enableKeyNav) {
98             me.initKeyNav(view);
99         }
100     },
101
102     initKeyNav: function(view) {
103         var me = this;
104
105         if (!view.rendered) {
106             view.on('render', Ext.Function.bind(me.initKeyNav, me, [view], 0), me, {single: true});
107             return;
108         }
109
110         view.el.set({
111             tabIndex: -1
112         });
113
114         // view.el has tabIndex -1 to allow for
115         // keyboard events to be passed to it.
116         me.keyNav = new Ext.util.KeyNav(view.el, {
117             up: me.onKeyUp,
118             down: me.onKeyDown,
119             right: me.onKeyRight,
120             left: me.onKeyLeft,
121             pageDown: me.onKeyPageDown,
122             pageUp: me.onKeyPageUp,
123             home: me.onKeyHome,
124             end: me.onKeyEnd,
125             scope: me
126         });
127         view.el.on(Ext.EventManager.getKeyEvent(), me.onKeyPress, me);
128     },
129
130     // Returns the number of rows currently visible on the screen or
131     // false if there were no rows. This assumes that all rows are
132     // of the same height and the first view is accurate.
133     getRowsVisible: function() {
134         var rowsVisible = false,
135             view = this.views[0],
136             row = view.getNode(0),
137             rowHeight, gridViewHeight;
138
139         if (row) {
140             rowHeight = Ext.fly(row).getHeight();
141             gridViewHeight = view.el.getHeight();
142             rowsVisible = Math.floor(gridViewHeight / rowHeight);
143         }
144
145         return rowsVisible;
146     },
147
148     // go to last visible record in grid.
149     onKeyEnd: function(e, t) {
150         var me = this,
151             last = me.store.getAt(me.store.getCount() - 1);
152
153         if (last) {
154             if (e.shiftKey) {
155                 me.selectRange(last, me.lastFocused || 0);
156                 me.setLastFocused(last);
157             } else if (e.ctrlKey) {
158                 me.setLastFocused(last);
159             } else {
160                 me.doSelect(last);
161             }
162         }
163     },
164
165     // go to first visible record in grid.
166     onKeyHome: function(e, t) {
167         var me = this,
168             first = me.store.getAt(0);
169
170         if (first) {
171             if (e.shiftKey) {
172                 me.selectRange(first, me.lastFocused || 0);
173                 me.setLastFocused(first);
174             } else if (e.ctrlKey) {
175                 me.setLastFocused(first);
176             } else {
177                 me.doSelect(first, false);
178             }
179         }
180     },
181
182     // Go one page up from the lastFocused record in the grid.
183     onKeyPageUp: function(e, t) {
184         var me = this,
185             rowsVisible = me.getRowsVisible(),
186             selIdx,
187             prevIdx,
188             prevRecord,
189             currRec;
190
191         if (rowsVisible) {
192             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
193             prevIdx = selIdx - rowsVisible;
194             if (prevIdx < 0) {
195                 prevIdx = 0;
196             }
197             prevRecord = me.store.getAt(prevIdx);
198             if (e.shiftKey) {
199                 currRec = me.store.getAt(selIdx);
200                 me.selectRange(prevRecord, currRec, e.ctrlKey, 'up');
201                 me.setLastFocused(prevRecord);
202             } else if (e.ctrlKey) {
203                 e.preventDefault();
204                 me.setLastFocused(prevRecord);
205             } else {
206                 me.doSelect(prevRecord);
207             }
208
209         }
210     },
211
212     // Go one page down from the lastFocused record in the grid.
213     onKeyPageDown: function(e, t) {
214         var me = this,
215             rowsVisible = me.getRowsVisible(),
216             selIdx,
217             nextIdx,
218             nextRecord,
219             currRec;
220
221         if (rowsVisible) {
222             selIdx = me.lastFocused ? me.store.indexOf(me.lastFocused) : 0;
223             nextIdx = selIdx + rowsVisible;
224             if (nextIdx >= me.store.getCount()) {
225                 nextIdx = me.store.getCount() - 1;
226             }
227             nextRecord = me.store.getAt(nextIdx);
228             if (e.shiftKey) {
229                 currRec = me.store.getAt(selIdx);
230                 me.selectRange(nextRecord, currRec, e.ctrlKey, 'down');
231                 me.setLastFocused(nextRecord);
232             } else if (e.ctrlKey) {
233                 // some browsers, this means go thru browser tabs
234                 // attempt to stop.
235                 e.preventDefault();
236                 me.setLastFocused(nextRecord);
237             } else {
238                 me.doSelect(nextRecord);
239             }
240         }
241     },
242
243     // Select/Deselect based on pressing Spacebar.
244     // Assumes a SIMPLE selectionmode style
245     onKeyPress: function(e, t) {
246         if (e.getKey() === e.SPACE) {
247             e.stopEvent();
248             var me = this,
249                 record = me.lastFocused;
250
251             if (record) {
252                 if (me.isSelected(record)) {
253                     me.doDeselect(record, false);
254                 } else {
255                     me.doSelect(record, true);
256                 }
257             }
258         }
259     },
260
261     // Navigate one record up. This could be a selection or
262     // could be simply focusing a record for discontiguous
263     // selection. Provides bounds checking.
264     onKeyUp: function(e, t) {
265         var me = this,
266             view = me.views[0],
267             idx  = me.store.indexOf(me.lastFocused),
268             record;
269
270         if (idx > 0) {
271             // needs to be the filtered count as thats what
272             // will be visible.
273             record = me.store.getAt(idx - 1);
274             if (e.shiftKey && me.lastFocused) {
275                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
276                     me.doDeselect(me.lastFocused, true);
277                     me.setLastFocused(record);
278                 } else if (!me.isSelected(me.lastFocused)) {
279                     me.doSelect(me.lastFocused, true);
280                     me.doSelect(record, true);
281                 } else {
282                     me.doSelect(record, true);
283                 }
284             } else if (e.ctrlKey) {
285                 me.setLastFocused(record);
286             } else {
287                 me.doSelect(record);
288                 //view.focusRow(idx - 1);
289             }
290         }
291         // There was no lastFocused record, and the user has pressed up
292         // Ignore??
293         //else if (this.selected.getCount() == 0) {
294         //
295         //    this.doSelect(record);
296         //    //view.focusRow(idx - 1);
297         //}
298     },
299
300     // Navigate one record down. This could be a selection or
301     // could be simply focusing a record for discontiguous
302     // selection. Provides bounds checking.
303     onKeyDown: function(e, t) {
304         var me = this,
305             view = me.views[0],
306             idx  = me.store.indexOf(me.lastFocused),
307             record;
308
309         // needs to be the filtered count as thats what
310         // will be visible.
311         if (idx + 1 < me.store.getCount()) {
312             record = me.store.getAt(idx + 1);
313             if (me.selected.getCount() === 0) {
314                 me.doSelect(record);
315                 //view.focusRow(idx + 1);
316             } else if (e.shiftKey && me.lastFocused) {
317                 if (me.isSelected(me.lastFocused) && me.isSelected(record)) {
318                     me.doDeselect(me.lastFocused, true);
319                     me.setLastFocused(record);
320                 } else if (!me.isSelected(me.lastFocused)) {
321                     me.doSelect(me.lastFocused, true);
322                     me.doSelect(record, true);
323                 } else {
324                     me.doSelect(record, true);
325                 }
326             } else if (e.ctrlKey) {
327                 me.setLastFocused(record);
328             } else {
329                 me.doSelect(record);
330                 //view.focusRow(idx + 1);
331             }
332         }
333     },
334
335     scrollByDeltaX: function(delta) {
336         var view    = this.views[0],
337             section = view.up(),
338             hScroll = section.horizontalScroller;
339
340         if (hScroll) {
341             hScroll.scrollByDeltaX(delta);
342         }
343     },
344
345     onKeyLeft: function(e, t) {
346         this.scrollByDeltaX(-this.deltaScroll);
347     },
348
349     onKeyRight: function(e, t) {
350         this.scrollByDeltaX(this.deltaScroll);
351     },
352
353     // Select the record with the event included so that
354     // we can take into account ctrlKey, shiftKey, etc
355     onRowMouseDown: function(view, record, item, index, e) {
356         view.el.focus();
357         this.selectWithEvent(record, e);
358     },
359
360     // Allow the GridView to update the UI by
361     // adding/removing a CSS class from the row.
362     onSelectChange: function(record, isSelected, suppressEvent, commitFn) {
363         var me      = this,
364             views   = me.views,
365             viewsLn = views.length,
366             store   = me.store,
367             rowIdx  = store.indexOf(record),
368             eventName = isSelected ? 'select' : 'deselect',
369             i = 0;
370
371         if ((suppressEvent || me.fireEvent('before' + eventName, me, record, rowIdx)) !== false &&
372                 commitFn() !== false) {
373
374             for (; i < viewsLn; i++) {
375                 if (isSelected) {
376                     views[i].onRowSelect(rowIdx, suppressEvent);
377                 } else {
378                     views[i].onRowDeselect(rowIdx, suppressEvent);
379                 }
380             }
381
382             if (!suppressEvent) {
383                 me.fireEvent(eventName, me, record, rowIdx);
384             }
385         }
386     },
387
388     // Provide indication of what row was last focused via
389     // the gridview.
390     onLastFocusChanged: function(oldFocused, newFocused, supressFocus) {
391         var views   = this.views,
392             viewsLn = views.length,
393             store   = this.store,
394             rowIdx,
395             i = 0;
396
397         if (oldFocused) {
398             rowIdx = store.indexOf(oldFocused);
399             if (rowIdx != -1) {
400                 for (; i < viewsLn; i++) {
401                     views[i].onRowFocus(rowIdx, false);
402                 }
403             }
404         }
405
406         if (newFocused) {
407             rowIdx = store.indexOf(newFocused);
408             if (rowIdx != -1) {
409                 for (i = 0; i < viewsLn; i++) {
410                     views[i].onRowFocus(rowIdx, true, supressFocus);
411                 }
412             }
413         }
414     },
415
416     onEditorTab: function(editingPlugin, e) {
417         var me = this,
418             view = me.views[0],
419             record = editingPlugin.getActiveRecord(),
420             header = editingPlugin.getActiveColumn(),
421             position = view.getPosition(record, header),
422             direction = e.shiftKey ? 'left' : 'right',
423             newPosition  = view.walkCells(position, direction, e, this.preventWrap);
424
425         if (newPosition) {
426             editingPlugin.startEditByPosition(newPosition);
427         }
428     },
429
430     selectByPosition: function(position) {
431         var record = this.store.getAt(position.row);
432         this.select(record);
433     }
434 });