/**
* @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);
}
}
}
});