facebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskype
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
700 lines
28 KiB
700 lines
28 KiB
// TODO: Implement http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#grid standards |
|
/** |
|
* @class Ext.grid.NavigationModel |
|
* @private |
|
* This class listens for key events fired from a {@link Ext.grid.Panel GridPanel}, and moves the currently focused item |
|
* by adding the class {@link #focusCls}. |
|
*/ |
|
Ext.define('Ext.grid.NavigationModel', { |
|
extend: 'Ext.view.NavigationModel', |
|
|
|
alias: 'view.navigation.grid', |
|
|
|
/** |
|
* @event navigate Fired when a key has been used to navigate around the view. |
|
* @param {Object} event |
|
* @param {Ext.event.Event} event.keyEvent The key event which caused the navigation. |
|
* @param {Number} event.previousRecordIndex The previously focused record index. |
|
* @param {Ext.data.Model} event.previousRecord The previously focused record. |
|
* @param {HTMLElement} event.previousItem The previously focused grid cell. |
|
* @param {Ext.grid.Column} event.previousColumn The previously focused grid column. |
|
* @param {Number} event.recordIndex The newly focused record index. |
|
* @param {Ext.data.Model} event.record the newly focused record. |
|
* @param {HTMLElement} event.item the newly focused grid cell. |
|
* @param {Ext.grid.Column} event.column The newly focused grid column. |
|
*/ |
|
|
|
focusCls: Ext.baseCSSPrefix + 'grid-item-focused', |
|
|
|
getViewListeners: function() { |
|
var me = this; |
|
|
|
return { |
|
containermousedown: me.onContainerMouseDown, |
|
cellmousedown: me.onCellMouseDown, |
|
|
|
// We focus on click if the mousedown handler did not focus because it was a translated "touchstart" event. |
|
cellclick: me.onCellClick, |
|
itemmousedown: me.onItemMouseDown, |
|
|
|
// We focus on click if the mousedown handler did not focus because it was a translated "touchstart" event. |
|
itemclick: me.onItemClick, |
|
itemcontextmenu: me.onItemClick, |
|
scope: me |
|
}; |
|
}, |
|
|
|
initKeyNav: function(view) { |
|
var me = this; |
|
|
|
me.position = new Ext.grid.CellContext(view); |
|
|
|
// Drive the KeyNav off the View's itemkeydown event so that beforeitemkeydown listeners may veto. |
|
// By default KeyNav uses defaultEventAction: 'stopEvent', and this is required for movement keys |
|
// which by default affect scrolling. |
|
me.keyNav = new Ext.util.KeyNav({ |
|
target: view, |
|
ignoreInputFields: true, |
|
eventName: 'itemkeydown', |
|
defaultEventAction: 'stopEvent', |
|
|
|
// Every key event is tagged with the source view, so the NavigationModel is independent. |
|
processEvent: function(view, record, row, recordIndex, event) { |
|
return event; |
|
}, |
|
up: me.onKeyUp, |
|
down: me.onKeyDown, |
|
right: me.onKeyRight, |
|
left: me.onKeyLeft, |
|
pageDown: me.onKeyPageDown, |
|
pageUp: me.onKeyPageUp, |
|
home: me.onKeyHome, |
|
end: me.onKeyEnd, |
|
tab: me.onKeyTab, |
|
space: me.onKeySpace, |
|
enter: me.onKeyEnter, |
|
A: { |
|
ctrl: true, |
|
// Need a separate function because we don't want the key |
|
// events passed on to selectAll (causes event suppression). |
|
handler: me.onSelectAllKeyPress |
|
}, |
|
scope: me |
|
}); |
|
}, |
|
|
|
onKeyTab: function(keyEvent) { |
|
var view = keyEvent.position.view, |
|
selModel = view.getSelectionModel(), |
|
editingPlugin = view.editingPlugin; |
|
|
|
// If we were in editing mode, but just focused on a non-editable cell, behave as if we tabbed off an editable field |
|
if (editingPlugin && selModel.wasEditing) { |
|
keyEvent.preventDefault(); |
|
selModel.onEditorTab(editingPlugin, keyEvent); |
|
} else { |
|
return this.callParent([keyEvent]); |
|
} |
|
}, |
|
|
|
onCellMouseDown: function(view, cell, cellIndex, record, row, recordIndex, mousedownEvent) { |
|
var parentEvent = mousedownEvent.parentEvent, |
|
cmp = Ext.Component.fromElement(mousedownEvent.target, cell); |
|
|
|
if (cmp && cmp.isFocusable && cmp.isFocusable()) { |
|
return; |
|
} |
|
|
|
// If the ExtJS mousedown event is a translated touchstart, leave it until the click to focus |
|
if (!parentEvent || parentEvent.type !== 'touchstart') { |
|
this.setPosition(mousedownEvent.position, null, mousedownEvent); |
|
} |
|
}, |
|
|
|
onCellClick: function(view, cell, cellIndex, record, row, recordIndex, clickEvent) { |
|
var cmp = Ext.Component.fromElement(clickEvent.target, cell); |
|
|
|
// We must not steal focus and place it on the cell if the user clicked on a focusable component |
|
this.preventCellFocus = cmp && cmp.focusable && cmp.isFocusable(); |
|
|
|
// If the mousedown that initiated the click has navigated us to the correct spot, just fire the event |
|
if (this.position.isEqual(clickEvent.position)) { |
|
this.fireNavigateEvent(clickEvent); |
|
} else { |
|
this.setPosition(clickEvent.position, null, clickEvent); |
|
} |
|
|
|
this.preventCellFocus = false; |
|
}, |
|
|
|
onItemMouseDown: function(view, record, item, index, mousedownEvent) { |
|
var me = this, |
|
x, |
|
columns, |
|
len, |
|
i, column, b, |
|
parentEvent = mousedownEvent.parentEvent; |
|
|
|
// If the ExtJS mousedown event is a translated touchstart, leave it until the click to focus |
|
if (!parentEvent || parentEvent.type !== 'touchstart') { |
|
|
|
// A mousedown outside a cell. Must be in a Feature |
|
if (!mousedownEvent.position.cellElement) { |
|
x = mousedownEvent.getX(); |
|
columns = view.getVisibleColumnManager().getColumns(); |
|
len = columns.length; |
|
for (i = 0; i < len; i++) { |
|
column = columns[i]; |
|
b = columns[i].getBox(); |
|
if (x >= b.left && x < b.right) { |
|
me.setPosition(record, columns[i], mousedownEvent); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
}, |
|
|
|
onItemClick: function(view, record, item, index, clickEvent) { |
|
// A mousedown outside a cell. Must be in a Feature |
|
if (!clickEvent.position.cellElement) { |
|
this.fireNavigateEvent(clickEvent); |
|
} |
|
}, |
|
|
|
beforeViewRefresh: function(view) { |
|
// Override at TableView level because NavigationModel is shared between two sides of a lockable |
|
// So we have to check that the focus position applies to us before caching |
|
var position = this.getPosition(); |
|
|
|
if (position && position.view === view) { |
|
this.focusRestorePosition = position.clone(); |
|
} else { |
|
this.focusRestorePosition = null; |
|
} |
|
}, |
|
|
|
// On record remove, it might have bumped the selection upwards. |
|
// Pass the "preventSelection" flag. |
|
onStoreRemove: function() { |
|
if (this.position) { |
|
this.setPosition(this.getPosition(), null, null, null, true); |
|
} |
|
}, |
|
|
|
deferSetPosition: function(delay, recordIndex, columnIndex, keyEvent, suppressEvent, preventNavigation) { |
|
var setPositionTask = this.view.getFocusTask(); |
|
|
|
// This is essentially a focus operation. Use the singleton focus task used by Focusable Components |
|
// to schedule a setPosition call. This way it can be superseded programmatically by regular Component focus calls. |
|
setPositionTask.delay(delay, this.setPosition, this, [recordIndex, columnIndex, keyEvent, suppressEvent, preventNavigation]); |
|
return setPositionTask; |
|
}, |
|
|
|
setPosition: function(recordIndex, columnIndex, keyEvent, suppressEvent, preventNavigation) { |
|
var me = this, |
|
view, |
|
selModel, |
|
dataSource, |
|
newRecordIndex, |
|
newColumnIndex, |
|
newRecord, |
|
newColumn, |
|
clearing = recordIndex == null && columnIndex == null, |
|
isClear = me.record == null && me.recordIndex == null && me.item == null; |
|
|
|
// Work out the view we are operating on. |
|
// If they passed a CellContext, use the view from that. |
|
// Otherwise, use the view injected into the event by Ext.view.View#processEvent. |
|
// Otherwise, use the last focused view. |
|
// Failing that, use the view we were bound to. |
|
if (recordIndex && recordIndex.isCellContext) { |
|
view = recordIndex.view; |
|
} |
|
else if (keyEvent && keyEvent.view) { |
|
view = keyEvent.view; |
|
} |
|
else if (me.lastFocused) { |
|
view = me.lastFocused.view; |
|
} |
|
else { |
|
view = me.view; |
|
} |
|
selModel = view.getSelectionModel(); |
|
dataSource = view.dataSource; |
|
|
|
// In case any async focus was requested before this call. |
|
view.getFocusTask().cancel(); |
|
|
|
// Return if the view was destroyed between the deferSetPosition call and now, or if the call is a no-op |
|
// or if there are no items which could be focused. |
|
if (view.isDestroyed || !view.refreshCounter || clearing && isClear || !view.all.getCount()) { |
|
return; |
|
} |
|
|
|
// If a CellContext is passed, use it. |
|
// Passing null happens on blur to remove focus class. |
|
if (recordIndex && recordIndex.isCellContext) { |
|
newRecord = recordIndex.record; |
|
newRecordIndex = recordIndex.rowIdx; |
|
newColumnIndex = recordIndex.colIdx; |
|
newColumn = recordIndex.column; |
|
|
|
// If the record being focused is not available (eg, after a sort), then go to 0,0 |
|
if (dataSource.indexOf(newRecord) === -1) { |
|
newRecordIndex = dataSource.indexOfId(newRecord.id); |
|
if (newRecordIndex === -1) { |
|
// Change recordIndex so that the "No movement" test is bypassed if the record is not found |
|
me.recordIndex = -1; |
|
newRecord = dataSource.getAt(0); |
|
newRecordIndex = 0; |
|
newColumnIndex = 0; |
|
newColumn = view.getVisibleColumnManager().getColumns()[0]; |
|
} else { |
|
newRecord = dataSource.getById(newRecord.id); |
|
} |
|
} |
|
} else { |
|
// Both axes are null, we defocus |
|
if (clearing) { |
|
newRecord = newRecordIndex = null; |
|
} else { |
|
// AbstractView's default behaviour on focus is to call setPosition(0); |
|
// A call like this should default to the last column focused, or column 0; |
|
if (columnIndex == null) { |
|
columnIndex = me.lastFocused ? me.lastFocused.column : 0; |
|
} |
|
|
|
if (typeof recordIndex === 'number') { |
|
newRecordIndex = Math.max(Math.min(recordIndex, dataSource.getCount() - 1), 0); |
|
newRecord = dataSource.getAt(recordIndex); |
|
} |
|
// row is a Record |
|
else if (recordIndex.isEntity) { |
|
newRecord = recordIndex; |
|
newRecordIndex = dataSource.indexOf(newRecord); |
|
} |
|
// row is a grid row |
|
else if (recordIndex.tagName) { |
|
newRecord = view.getRecord(recordIndex); |
|
newRecordIndex = dataSource.indexOf(newRecord); |
|
if (newRecordIndex === -1) { |
|
newRecord = null; |
|
} |
|
} |
|
else { |
|
if (isClear) { |
|
return; |
|
} |
|
clearing = true; |
|
newRecord = newRecordIndex = null; |
|
} |
|
} |
|
|
|
// Record position was successful |
|
if (newRecord) { |
|
// If the record being focused is not available (eg, after a sort), then go to 0,0 |
|
if (newRecordIndex === -1) { |
|
// Change recordIndex so that the "No movement" test is bypassed if the record is not found |
|
me.recordIndex = -1; |
|
newRecord = dataSource.getAt(0); |
|
newRecordIndex = 0; |
|
columnIndex = null; |
|
} |
|
// No columnIndex passed, and no previous column position - default to column 0 |
|
if (columnIndex == null) { |
|
if (!(newColumn = me.column)) { |
|
newColumnIndex = 0; |
|
newColumn = view.getVisibleColumnManager().getColumns()[0]; |
|
} |
|
} |
|
else if (typeof columnIndex === 'number') { |
|
newColumn = view.getVisibleColumnManager().getColumns()[columnIndex]; |
|
newColumnIndex = columnIndex; |
|
} else { |
|
newColumn = columnIndex; |
|
newColumnIndex = view.getVisibleColumnManager().indexOf(columnIndex); |
|
} |
|
} else { |
|
clearing = true; |
|
newColumn = newColumnIndex = null; |
|
} |
|
} |
|
|
|
// No movement; just ensure the correct item is focused and return early. |
|
// Do not push current position into previous position, do not fire events. |
|
if (newRecordIndex === me.recordIndex && newColumnIndex === me.columnIndex) { |
|
return me.focusPosition(me.position); |
|
} |
|
|
|
if (me.cell) { |
|
me.cell.removeCls(me.focusCls); |
|
} |
|
|
|
// Track the last position. |
|
// Used by SelectionModels as the navigation "from" position. |
|
me.previousRecordIndex = me.recordIndex; |
|
me.previousRecord = me.record; |
|
me.previousItem = me.item; |
|
me.previousCell = me.cell; |
|
me.previousColumn = me.column; |
|
me.previousColumnIndex = me.columnIndex; |
|
me.previousPosition = me.position.clone(); |
|
// Track the last selectionStart position to correctly track ranges (i.e., SHIFT + selection). |
|
me.selectionStart = selModel.selectionStart; |
|
|
|
// Set our CellContext to the new position |
|
me.position.setAll( |
|
view, |
|
me.recordIndex = newRecordIndex, |
|
me.columnIndex = newColumnIndex, |
|
me.record = newRecord, |
|
me.column = newColumn |
|
); |
|
|
|
if (clearing) { |
|
me.item = me.cell = null; |
|
} |
|
else { |
|
me.focusPosition(me.position, preventNavigation); |
|
} |
|
|
|
// Legacy API is that the SelectionModel fires focuschange events and the TableView fires rowfocus and cellfocus events. |
|
if (!suppressEvent) { |
|
selModel.fireEvent('focuschange', selModel, me.previousRecord, me.record); |
|
view.fireEvent('rowfocus', me.record, me.item, me.recordIndex); |
|
view.fireEvent('cellfocus', me.record, me.cell, me.position); |
|
} |
|
|
|
// If we have moved, fire an event |
|
if (keyEvent && !preventNavigation && me.cell !== me.previousCell) { |
|
me.fireNavigateEvent(keyEvent); |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
* Focuses the currently active position. |
|
* This is used on view refresh and on replace. |
|
* @return {undefined} |
|
*/ |
|
focusPosition: function(position) { |
|
var me = this, |
|
view, |
|
row; |
|
|
|
me.item = me.cell = null; |
|
if (position && position.record && position.column) { |
|
view = position.view; |
|
|
|
// If the position is passed from a grid event, the rowElement will be stamped into it. |
|
// Otherwise, select it from the indicated item. |
|
if (position.rowElement) { |
|
row = me.item = position.rowElement; |
|
} else { |
|
// Get the dataview item for the position's record |
|
row = view.getRowByRecord(position.record); |
|
// If there is no item at that index, it's probably because there's buffered rendering. |
|
// This is handled below. |
|
} |
|
if (row) { |
|
|
|
// If the position is passed from a grid event, the cellElement will be stamped into it. |
|
// Otherwise, select it from the row. |
|
me.cell = position.cellElement || Ext.fly(row).down(position.column.getCellSelector(), true); |
|
|
|
// Maintain the cell as a Flyweight to avoid transient elements ending up in the cache as full Ext.Elements. |
|
if (me.cell) { |
|
me.cell = new Ext.dom.Fly(me.cell); |
|
|
|
// Maintain lastFocused in the view so that on non-specific focus of the View, we can focus the view's correct descendant. |
|
view.lastFocused = me.lastFocused = me.position.clone(); |
|
me.focusItem(me.cell); |
|
view.focusEl = me.cell; |
|
} |
|
// Cell no longer in view. Clear current position. |
|
else { |
|
me.position.setAll(); |
|
me.record = me.column = me.recordIndex = me.columnIndex = null; |
|
} |
|
} |
|
// View node no longer in view. Clear current position. |
|
// Attempt to scroll to the record if it is in the store, but out of rendered range. |
|
else { |
|
row = view.dataSource.indexOf(position.record); |
|
me.position.setAll(); |
|
me.record = me.column = me.recordIndex = me.columnIndex = null; |
|
|
|
// The reason why the row could not be selected from the DOM could be because it's |
|
// out of rendered range, so scroll to the row, and then try focusing it. |
|
if (row !== -1 && view.bufferedRenderer) { |
|
me.lastKeyEvent = null; |
|
view.bufferedRenderer.scrollTo(row, false, me.afterBufferedScrollTo, me); |
|
} |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* @template |
|
* @protected |
|
* Called to focus an item in the client {@link Ext.view.View DataView}. |
|
* The default implementation adds the {@link #focusCls} to the passed item focuses it. |
|
* Subclasses may choose to keep focus in another target. |
|
* |
|
* For example {@link Ext.view.BoundListKeyNav} maintains focus in the input field. |
|
* @param {Ext.dom.Element} item |
|
* @return {undefined} |
|
*/ |
|
focusItem: function(item) { |
|
item.addCls(this.focusCls); |
|
|
|
// If they clicked on a focusable widget in a cell, we must not steal focus |
|
if (!this.preventCellFocus) { |
|
item.focus(); |
|
} |
|
}, |
|
|
|
getCell: function() { |
|
return this.cell; |
|
}, |
|
|
|
getPosition: function() { |
|
var me = this, |
|
position = me.position, |
|
curIndex, |
|
view, |
|
dataSource; |
|
|
|
if (position.record && position.column) { |
|
view = position.view; |
|
dataSource = view.dataSource; |
|
|
|
curIndex = dataSource.indexOf(position.record); |
|
|
|
// If not with the same ID, at the same index if that is in range |
|
if (curIndex === -1) { |
|
curIndex = position.rowIdx; |
|
// If no record now at that index (even if its less than the totalCount, it may be a BufferedStore) |
|
// then there is no focus position, and we must return null |
|
if (!dataSource.getAt(curIndex)) { |
|
curIndex = -1; |
|
} |
|
} |
|
|
|
// If the positioned record or column has gone away, we have no position |
|
if (curIndex === -1 || view.getVisibleColumnManager().indexOf(position.column) === -1) { |
|
position.setAll(); |
|
me.record = me.column = me.recordIndex = me.columnIndex = null; |
|
} else { |
|
return position; |
|
} |
|
} |
|
return null; |
|
}, |
|
|
|
getLastFocused: function() { |
|
var me = this, |
|
view, |
|
lastFocused = me.lastFocused; |
|
|
|
if (lastFocused && lastFocused.record && lastFocused.column) { |
|
view = lastFocused.view; |
|
|
|
// If the last focused record or column has gone away, we have no lastFocused |
|
if (view.dataSource.indexOf(lastFocused.record) !== -1 && view.getVisibleColumnManager().indexOf(lastFocused.column) !== -1) { |
|
return lastFocused; |
|
} |
|
} |
|
}, |
|
|
|
onKeyUp: function(keyEvent) { |
|
var newRecord = keyEvent.view.walkRecs(keyEvent.record, -1); |
|
|
|
if (newRecord) { |
|
this.setPosition(newRecord, this.columnIndex, keyEvent); |
|
} |
|
}, |
|
|
|
onKeyDown: function(keyEvent) { |
|
// If we are in the middle of an animated node expand, jump to next sibling. |
|
// The first child record is in a temp animation DIV and will be removed, so will blur. |
|
var newRecord = keyEvent.record.isExpandingOrCollapsing ? null : keyEvent.view.walkRecs(keyEvent.record, 1); |
|
|
|
if (newRecord) { |
|
this.setPosition(newRecord, this.columnIndex, keyEvent); |
|
} |
|
}, |
|
|
|
onKeyRight: function(keyEvent) { |
|
var newPosition = this.move('right', keyEvent); |
|
|
|
if (newPosition) { |
|
this.setPosition(newPosition, null, keyEvent); |
|
} |
|
}, |
|
|
|
onKeyLeft: function(keyEvent) { |
|
var newPosition = this.move('left', keyEvent); |
|
|
|
if (newPosition) { |
|
this.setPosition(newPosition, null, keyEvent); |
|
} |
|
}, |
|
|
|
move: function(dir, keyEvent) { |
|
var me = this, |
|
position = me.getPosition(); |
|
|
|
if (position && position.record) { |
|
// Calculate the new row and column position. |
|
// walkCells makes assumptions about event ctrlKey modifier, so do not pass it. |
|
return position.view.walkCells(position, dir, null, me.preventWrap); |
|
} |
|
// <debug> |
|
// Enforce code correctness in unbuilt source. |
|
return null; |
|
// </debug> |
|
}, |
|
|
|
// Go one page down from the lastFocused record in the grid. |
|
onKeyPageDown: function(keyEvent) { |
|
var me = this, |
|
view = keyEvent.view, |
|
rowsVisible = me.getRowsVisible(), |
|
newIdx, |
|
newRecord; |
|
|
|
if (rowsVisible) { |
|
// If rendering is buffered, we cannot just increment the row - the row may not be there |
|
// We have to ask the BufferedRenderer to navigate to the target. |
|
// And that may involve asynchronous I/O, so must post-process in a callback. |
|
if (view.bufferedRenderer) { |
|
newIdx = Math.min(keyEvent.recordIndex + rowsVisible, view.dataSource.getCount() - 1); |
|
me.lastKeyEvent = keyEvent; |
|
view.bufferedRenderer.scrollTo(newIdx, false, me.afterBufferedScrollTo, me); |
|
} else { |
|
newRecord = view.walkRecs(keyEvent.record, rowsVisible); |
|
me.setPosition(newRecord, null, keyEvent); |
|
} |
|
} |
|
}, |
|
|
|
// Go one page up from the lastFocused record in the grid. |
|
onKeyPageUp: function(keyEvent) { |
|
var me = this, |
|
view = keyEvent.view, |
|
rowsVisible = me.getRowsVisible(), |
|
newIdx, |
|
newRecord; |
|
|
|
if (rowsVisible) { |
|
// If rendering is buffered, we cannot just increment the row - the row may not be there |
|
// We have to ask the BufferedRenderer to navigate to the target. |
|
// And that may involve asynchronous I/O, so must post-process in a callback. |
|
if (view.bufferedRenderer) { |
|
newIdx = Math.max(keyEvent.recordIndex - rowsVisible, 0); |
|
me.lastKeyEvent = keyEvent; |
|
view.bufferedRenderer.scrollTo(newIdx, false, me.afterBufferedScrollTo, me); |
|
} else { |
|
newRecord = view.walkRecs(keyEvent.record, -rowsVisible); |
|
me.setPosition(newRecord, null, keyEvent); |
|
} |
|
} |
|
}, |
|
|
|
// Home moves the focus to the first cell of the current row. |
|
onKeyHome: function(keyEvent) { |
|
var me = this, |
|
view = keyEvent.view; |
|
|
|
// ALT+Home - go to first visible record in grid. |
|
if (keyEvent.altKey) { |
|
if (view.bufferedRenderer) { |
|
// If rendering is buffered, we cannot just increment the row - the row may not be there |
|
// We have to ask the BufferedRenderer to navigate to the target. |
|
// And that may involve asynchronous I/O, so must post-process in a callback. |
|
me.lastKeyEvent = keyEvent; |
|
view.bufferedRenderer.scrollTo(0, false, me.afterBufferedScrollTo, me); |
|
} else { |
|
// Walk forwards to the first record |
|
me.setPosition(view.walkRecs(keyEvent.record, -view.dataSource.indexOf(keyEvent.record)), null, keyEvent); |
|
} |
|
} |
|
// Home moves the focus to the First cell in the current row. |
|
else { |
|
me.setPosition(keyEvent.record, 0, keyEvent); |
|
} |
|
}, |
|
|
|
afterBufferedScrollTo: function(newIdx, newRecord) { |
|
this.setPosition(newRecord, null, this.lastKeyEvent, null, !this.lastKeyEvent); |
|
}, |
|
|
|
// End moves the focus to the last cell in the current row. |
|
onKeyEnd: function(keyEvent) { |
|
var me = this, |
|
view = keyEvent.view; |
|
|
|
// ALT/End - go to last visible record in grid. |
|
if (keyEvent.altKey) { |
|
if (view.bufferedRenderer) { |
|
// If rendering is buffered, we cannot just increment the row - the row may not be there |
|
// We have to ask the BufferedRenderer to navigate to the target. |
|
// And that may involve asynchronous I/O, so must post-process in a callback. |
|
me.lastKeyEvent = keyEvent; |
|
view.bufferedRenderer.scrollTo(view.store.getCount() - 1, false, me.afterBufferedScrollTo, me); |
|
} else { |
|
// Walk forwards to the end record |
|
me.setPosition(view.walkRecs(keyEvent.record, view.dataSource.getCount() - 1 - view.dataSource.indexOf(keyEvent.record)), null, keyEvent); |
|
} |
|
} |
|
// End moves the focus to the last cell in the current row. |
|
else { |
|
me.setPosition(keyEvent.record, keyEvent.view.getVisibleColumnManager().getColumns().length - 1, keyEvent); |
|
} |
|
}, |
|
|
|
// 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.view, |
|
firstRow = view.all.first(), |
|
rowHeight, gridViewHeight; |
|
|
|
if (firstRow) { |
|
rowHeight = firstRow.getHeight(); |
|
gridViewHeight = view.el.getHeight(); |
|
rowsVisible = Math.floor(gridViewHeight / rowHeight); |
|
} |
|
|
|
return rowsVisible; |
|
}, |
|
|
|
fireNavigateEvent: function(keyEvent) { |
|
var me = this; |
|
|
|
me.fireEvent('navigate', { |
|
view: me.position.view, |
|
navigationModel: me, |
|
keyEvent: keyEvent || new Ext.event.Event({}), |
|
previousPosition: me.previousPosition, |
|
previousRecordIndex: me.previousRecordIndex, |
|
previousRecord: me.previousRecord, |
|
previousItem: me.previousItem, |
|
previousCell: me.previousCell, |
|
previousColumnIndex: me.previousColumnIndex, |
|
previousColumn: me.previousColumn, |
|
position: me.position, |
|
recordIndex: me.recordIndex, |
|
record: me.record, |
|
selectionStart: me.selectionStart, |
|
item: me.item, |
|
cell: me.cell, |
|
columnIndex: me.columnIndex, |
|
column: me.column |
|
}); |
|
} |
|
});
|
|
|