Last active
June 20, 2024 00:09
-
-
Save lukehorvat/5607821 to your computer and use it in GitHub Desktop.
An Ext JS 4 grid plugin that provides Microsoft Excel-esque cell navigation and editing via keyboard.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var newGridCellValue = '', | |
deleteGridCellValue = false; | |
Ext.define('ExcelCellEditing', { | |
extend: 'Ext.grid.plugin.CellEditing', | |
alias: 'plugin.excelcellediting', | |
initEditTriggers: function () { | |
var me = this; | |
var keys = []; | |
for (var i = 48; i < 58; i++) { keys.push(i); } // 0-9 | |
for (var i = 65; i < 91; i++) { keys.push(i); } // A-Z | |
for (var i = 97; i < 123; i++) { keys.push(i); } // a-z | |
// listen for particular key presses made over cells | |
me.view.addListener('render', function () { | |
var keyPressMap = new Ext.util.KeyMap({ | |
target: me.view.el, | |
eventName: 'keypress', | |
binding: [{ | |
key: keys, | |
handler: me.onAlphaNumericKey, | |
scope: me | |
}] | |
}); | |
var keyDownMap = new Ext.util.KeyMap({ | |
target: me.view.el, | |
eventName: 'keydown', | |
binding: [{ | |
key: Ext.EventObject.DELETE, | |
handler: me.onDeleteKey, | |
scope: me | |
}, { | |
key: Ext.EventObject.BACKSPACE, | |
handler: me.onBackspaceKey, | |
scope: me | |
}] | |
}); | |
}, me, { single: true }); | |
me.callParent(arguments); | |
}, | |
onAlphaNumericKey: function (keyCode, e) { // only executes before a cell has entered edit mode | |
var me = this, | |
grid = me.grid, | |
pos = grid.getSelectionModel().getCurrentPosition(), | |
record = grid.store.getAt(pos.row), | |
columnHeader = grid.headerCt.getHeaderAtIndex(pos.column); | |
if (me.getEditor(record, columnHeader)) { | |
// insert the key pressed into the editor field and start editing | |
newGridCellValue = String.fromCharCode(keyCode); | |
me.startEdit(record, columnHeader); | |
} | |
}, | |
onBackspaceKey: function (keyCode, e) { // only executes before a cell has entered edit mode | |
var me = this; | |
e.stopEvent(); // cancel the default browser behaviour of navigating one page back | |
me.onDeleteKey(keyCode, e); | |
}, | |
onDeleteKey: function (keyCode, e) { // only executes before a cell has entered edit mode | |
var me = this, | |
grid = me.grid, | |
pos = grid.getSelectionModel().getCurrentPosition(), | |
record = grid.store.getAt(pos.row), | |
columnHeader = grid.headerCt.getHeaderAtIndex(pos.column); | |
if (me.getEditor(record, columnHeader)) { | |
// perform an "instantaneous" edit that essentially just sets the value of the cell to nothing | |
deleteGridCellValue = true; | |
me.startEdit(record, columnHeader); | |
} | |
}, | |
onEnterKey: function (e) { // only executes before a cell has entered edit mode | |
var me = this, | |
grid = me.grid, | |
sm = grid.getSelectionModel(); | |
me.callParent(arguments); | |
// cellmodel navigates left/right when tab key is pressed by default, but not up/down when enter is pressed | |
// the code below enables this functionality for cells that are not editable | |
if (!grid.headerCt.getHeaderAtIndex(sm.getCurrentPosition().column).getEditor()) { | |
sm.move(e.shiftKey ? 'up' : 'down', e); | |
} | |
}, | |
onSpecialKey: function (ed, field, e) { // only executes when a cell is in edit mode | |
var me = this, | |
sm = me.grid.getSelectionModel(); | |
// cellmodel navigates left/right when tab key is pressed by default, but not up/down when enter is pressed | |
// the code below enables this functionality for cells that are editable | |
if (e.getKey() === e.ENTER) { | |
me.addListener('edit', function () { | |
sm.move(e.shiftKey ? 'up' : 'down', e); // navigate up/down once edit completes | |
}, me, { single: true }); | |
} | |
me.callParent(arguments); | |
} | |
}); | |
/** | |
* The following override allows for keys pressed over a grid cell that isn't yet in edit mode to be persisted into | |
* the cell once edit mode activates. It does this by getting the value of a temporary global variable that is set | |
* by the cell editing plugin above. | |
*/ | |
Ext.override(Ext.Editor, { | |
startEdit: function (el, value) { | |
var me = this, | |
field = me.field; | |
me.completeEdit(); | |
me.boundEl = Ext.get(el); | |
value = Ext.isDefined(value) ? value : me.boundEl.dom.innerHTML; | |
if (!me.rendered) { | |
me.render(me.parentEl || document.body); | |
} | |
if (me.fireEvent('beforestartedit', me, me.boundEl, value) !== false) { | |
me.startValue = value; | |
me.show(); | |
field.reset(); | |
if (deleteGridCellValue) { | |
field.setValue(''); | |
me.editing = true; | |
me.completeEdit(); | |
deleteGridCellValue = false; // reset global variable | |
} | |
else { | |
if (newGridCellValue == '') { | |
// default behaviour of Ext.Editor (see source if needed) | |
field.setValue(value); | |
} | |
else { | |
// custom behaviour to handle an alphanumeric key press from non-edit mode | |
field.setRawValue(newGridCellValue); | |
newGridCellValue = ''; // reset global variable | |
if (field instanceof Ext.form.field.ComboBox) { | |
// force the combo box's filtered dropdown list to be displayed (some browsers need this) | |
field.doQueryTask.delay(field.queryDelay); | |
} | |
} | |
me.realign(true); | |
field.focus(false, 10); | |
if (field.autoSize) { | |
field.autoSize(); | |
} | |
me.editing = true; | |
} | |
} | |
} | |
}); | |
/** | |
* The following override fixes a quirk in Internet Explorer where the text cursor/caret is positioned at the beginning | |
* of a cell's field the first time its editor is activated. The quirk makes instant grid cell editing on keypress difficult, | |
* because the caret gets positioned BEFORE text already in the cell, causing the user to prepend text rather than append. | |
*/ | |
Ext.override(Ext.dom.Element, { | |
focus: function (defer, dom) { | |
var me = this, | |
dom = dom || me.dom; | |
try { | |
if (Number(defer)) { | |
Ext.defer(me.focus, defer, me, [null, dom]); | |
} else { | |
dom.focus(); | |
// start override | |
dom.value = dom.value; | |
dom.focus(); | |
if (dom.sof) { | |
dom.select(); | |
} | |
// end override | |
} | |
} catch (e) { } | |
return me; | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment