/** * @class Ext.grid.RowSelectionModel * @extends Ext.AbstractStoreSelectionModel * * Implement row based navigation via keyboard. * * Must synchronize across grid sections */ Ext.define('Ext.grid.RowSelectionModel', { extend: 'Ext.selection.Model', alias: 'selection.rowselectionmodel', requires: ['Ext.util.KeyNav'], /** * @private * Number of pixels to scroll to the left/right when pressing * left/right keys. */ deltaScroll: 5,
/** * @cfg {Boolean} enableKeyNav * Turns on/off keyboard navigation within the grid. Defaults to true. */ enableKeyNav: true, bindComponent: function(view) { this.views = this.views || []; this.views.push(view); this.bind(view.getStore(), true); view.on({ refresh: this.refresh, rowupdated: this.onRowUpdated, click: this.onRowMouseDown, scope: this }); if (this.enableKeyNav) { this.initKeyNav(view); } }, initKeyNav: function(view) { if (!view.rendered) { view.on('render', Ext.Function.bind(this.initKeyNav, this, [view], 0), this, {single: true}); return; } view.el.set({ tabIndex: -1 }); // view.el has tabIndex -1 to allow for // keyboard events to be passed to it. this.keyNav = new Ext.util.KeyNav(view.el, { "up": this.onKeyUp, "down": this.onKeyDown, "right": this.onKeyRight, "left": this.onKeyLeft, "pageDown": this.onKeyPageDown, "pageUp": this.onKeyPageUp, "home": this.onKeyHome, "end": this.onKeyEnd, scope: this }); view.el.on(Ext.EventManager.getKeyEvent(), this.onKeyPress, this); }, // Returns the number of rows currently visible on the screen or // false if there were no rows. This assumes that all rows are // of the same height and the first view is accurate. getRowsVisible: function() { var rowsVisible = false, view = this.views[0], row = view.getNode(0), rowHeight, gridViewHeight; if (row) { rowHeight = Ext.fly(row).getHeight(); gridViewHeight = view.el.getHeight(); rowsVisible = Math.floor(gridViewHeight/rowHeight); } return rowsVisible; }, // go to last visible record in grid. onKeyEnd: function(e, t) { var last = this.store.getAt(this.store.getCount() - 1); if (last) { if (e.shiftKey) { this.selectRange(last, this.lastFocused || 0); this.setLastFocused(last); } else if (e.ctrlKey) { this.setLastFocused(last); } else { this.doSelect(last); } } }, // go to first visible record in grid. onKeyHome: function(e, t) { var first = this.store.getAt(0); if (first) { if (e.shiftKey) { this.selectRange(first, this.lastFocused || 0); this.setLastFocused(first); } else if (e.ctrlKey) { this.setLastFocused(first); } else { this.doSelect(first, false); } } }, // Go one page up from the lastFocused record in the grid. onKeyPageUp: function(e, t) { var rowsVisible = this.getRowsVisible(); if (rowsVisible) { var selIdx = this.lastFocused ? this.store.indexOf(this.lastFocused) : 0; var prevIdx = selIdx - rowsVisible; if (prevIdx < 0) { prevIdx = 0; } var prevRecord = this.store.getAt(prevIdx); if (e.shiftKey) { var currRec = this.store.getAt(selIdx); this.selectRange(prevRecord, currRec, e.ctrlKey, 'up'); this.setLastFocused(prevRecord); } else if (e.ctrlKey) { e.preventDefault(); this.setLastFocused(prevRecord); } else { this.doSelect(prevRecord); } } }, // Go one page down from the lastFocused record in the grid. onKeyPageDown: function(e, t) { var rowsVisible = this.getRowsVisible(); if (rowsVisible) { var selIdx = this.lastFocused ? this.store.indexOf(this.lastFocused) : 0; var nextIdx = selIdx + rowsVisible; if (nextIdx >= this.store.getCount()) { nextIdx = this.store.getCount() - 1; } var nextRecord = this.store.getAt(nextIdx); if (e.shiftKey) { var currRec = this.store.getAt(selIdx); this.selectRange(nextRecord, currRec, e.ctrlKey, 'down'); this.setLastFocused(nextRecord); } else if (e.ctrlKey) { // some browsers, this means go thru browser tabs // attempt to stop. e.preventDefault(); this.setLastFocused(nextRecord); } else { this.doSelect(nextRecord); } } }, // Select/Deselect based on pressing Spacebar. // Assumes a SIMPLE selectionmode style onKeyPress: function(e, t) { if (e.getKey() === e.SPACE) { e.stopEvent(); var record = this.lastFocused; if (record) { if (this.isSelected(record)) { this.doDeselect(record, false); } else { this.doSelect(record, true); } } } }, // Navigate one record up. This could be a selection or // could be simply focusing a record for discontiguous // selection. Provides bounds checking. onKeyUp: function(e, t) { var view = this.views[0], idx = this.store.indexOf(this.lastFocused); if (idx > 0) { // needs to be the filtered count as thats what // will be visible. var record = this.store.getAt(idx - 1); if (e.shiftKey && this.lastFocused) { if (this.isSelected(this.lastFocused) && this.isSelected(record)) { this.doDeselect(this.lastFocused, true); this.setLastFocused(record); } else if (!this.isSelected(this.lastFocused)) { this.doSelect(this.lastFocused, true); this.doSelect(record, true); } else { this.doSelect(record, true); } } else if (e.ctrlKey) { this.setLastFocused(record); } else { this.doSelect(record); view.focusRow(idx - 1); } } else if (this.selected.getCount() == 0) { this.doSelect(record); view.focusRow(idx - 1); } }, // Navigate one record down. This could be a selection or // could be simply focusing a record for discontiguous // selection. Provides bounds checking. onKeyDown: function(e, t) { var view = this.views[0], idx = this.store.indexOf(this.lastFocused); // needs to be the filtered count as thats what // will be visible. if (idx + 1 < this.store.getCount()) { var record = this.store.getAt(idx + 1); if (this.selected.getCount() == 0) { this.doSelect(record); view.focusRow(idx + 1); } else if (e.shiftKey && this.lastFocused) { if (this.isSelected(this.lastFocused) && this.isSelected(record)) { this.doDeselect(this.lastFocused, true); this.setLastFocused(record); } else if (!this.isSelected(this.lastFocused)) { this.doSelect(this.lastFocused, true); this.doSelect(record, true); } else { this.doSelect(record, true); } } else if (e.ctrlKey) { this.setLastFocused(record); } else { this.doSelect(record); view.focusRow(idx + 1); } } }, scrollByDeltaX: function(delta) { var view = this.views[0], grid = view.up('gridpanel'), hScroll = grid.down('gridscroller[dock=bottom]'); hScroll.scrollByDeltaX(delta); }, onKeyLeft: function(e, t) { this.scrollByDeltaX(-this.deltaScroll); }, onKeyRight: function(e, t) { this.scrollByDeltaX(this.deltaScroll); }, // Select the record with the event included so that // we can take into account ctrlKey, shiftKey, etc onRowMouseDown: function(view, rowIdx, node, e) { view.el.focus(); var record = view.getStore().getAt(rowIdx); this.selectWithEvent(record, e); }, // row has been repainted, lets maintain selection onRowUpdated: function(view, rowIdx, record) { if (this.isSelected(record)) { view.onRowSelect(rowIdx); } }, // Allow the GridView to update the UI by // adding/removing a CSS class from the row. onSelectChange: function(record, isSelected) { var views = this.views, viewsLn = views.length, store = this.store, rowIdx = store.indexOf(record), i = 0; for (; i < viewsLn; i++) { if (isSelected) { views[i].onRowSelect(rowIdx); } else { views[i].onRowDeselect(rowIdx); } } }, // Provide indication of what row was last focused via // the gridview. onLastFocusChanged: function(oldFocused, newFocused) { var views = this.views, viewsLn = views.length, store = this.store, rowIdx, i = 0; rowIdx = store.indexOf(oldFocused); if (rowIdx != -1) { for (; i < viewsLn; i++) { views[i].onRowFocus(rowIdx, false); } } rowIdx = store.indexOf(newFocused); if (rowIdx != -1) { for (i = 0; i < viewsLn; i++) { views[i].onRowFocus(rowIdx, true); } } } });