Skip to content

Instantly share code, notes, and snippets.

@lukehorvat
Last active June 20, 2024 00:09
Show Gist options
  • Save lukehorvat/5607821 to your computer and use it in GitHub Desktop.
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.
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