extjs7 store重新加载导致异常Uncaught TypeError: Cannot read properties of null (reading ‘focus‘)解决
版本
7.4.0 classic
现象
grid/treegrid使用actioncolumn或其他能获得焦点的单元格元素交互后,刷新store,如果操作的目标行不在新数据中(例如actioncolumn中按钮执行删除操作,异步提交后刷新store),将报错如下
Uncaught TypeError: Cannot read properties of null (reading ‘focus’)
at constructor.setActionableMode (Table.js?_dc=1640829487431:4262)
at constructor.setActionableMode (Table.js?_dc=1640829487431:2744)
at constructor.onFocusLeave (Table.js?_dc=1640829487431:2438)
at constructor.onGlobalFocus (ComponentManager.js?_dc=1640829487428:315)
at constructor.fire (Event.js?_dc=1640829487428:523)
at constructor.doFireEvent (Observable.js?_dc=1640829487428:884)
at constructor.prototype.doFireEvent (EventDomain.js?_dc=1640829487428:302)
at constructor.fireEventArgs (Observable.js?_dc=1640829487428:717)
at constructor.fireEvent (Observable.js?_dc=1640829487428:664)
at constructor.processFocusIn (Focus.js?_dc=1640829487430:121)
解决
删除操作提交成功后,使用store.remove(recordRemoved)将已删除数据从store中移出,如有需要(远端分页查询场景)在执行store.load()
源码分析
- load后会根据此前焦点的行记录重新定位焦点
但是记录已经不存在,源码没有重新校验导致定位焦点异常
ext-classic/src/view/Table.js
/**
*
* @param {Boolean} enabled
* @param {Ext.grid.CellContext} position The cell to activate.
* @param {HTMLElement/Ext.dom.Element} [position.target] The element within the referenced
* cell to focus.
* @return {Boolean} Returns `false` if the mode did not change.
* @private
*/
setActionableMode: function(enabled, position) {
var me = this,
navModel = me.getNavigationModel(),
actionables = me.grid.actionables,
len = actionables.length,
isActionable = false,
activeEl, record, column, lockingPartner, cell, i;
// No mode change.
// ownerGrid's call will NOT fire mode change event upon false return.
if (me.actionableMode === enabled) {
// If we're not actionable already, or (we are actionable already at that position)
// return false.
// Test using mandatory passed position because we may not have an actionPosition
// if we are the lockingPartner of an actionable view that contained
// the action position.
//
// If we being told to go into actionable mode but at another position,
// we must continue. This is just actionable navigation.
if (!enabled || position.isEqual(me.actionPosition)) {
return false;
}
}
// If this View or its lockingPartner contains the current focus position,
// then make the tab bumpers tabbable and move them to surround the focused row.
if (enabled) {
if (position && (position.view === me ||
(position.view === (lockingPartner = me.lockingPartner) &&
lockingPartner.actionableMode))) {
isActionable = me.activateCell(position);
}
// Did not enter actionable mode.
// ownerGrid's call will NOT fire mode change event upon false return.
return isActionable;
}
else {
// Capture before exiting from actionable mode moves focus
activeEl = Ext.fly(Ext.Element.getActiveElement());
// Blur the focused descendant, but do not trigger focusLeave.
// This is so that when the focus is restored to the cell which contained
// the active content, it will not be a FocusEnter from the universe.
if (me.el.contains(activeEl) && !Ext.fly(activeEl).is(me.getCellSelector())) {
// Row to return focus to.
// 此处会获取到此前操作焦点的行记录
record =
(me.actionPosition && me.actionPosition.record) || me.getRecord(activeEl);
column = me.getHeaderByCell(activeEl.findParent(me.getCellSelector()));
cell = position && position.getCell(true);
// Do not allow focus to fly out of the view when the actionables
// are deactivated (and blurred/hidden). Restore focus to the cell in which
// actionable mode is active.
// Note that the original position may no longer be valid, e.g. when the record
// was removed.
if (!position || !cell) {
// 重新根据record获取position和cell后没有校验,此时position.rowIdx=-1,cell=flase
position =
new Ext.grid.CellContext(me).setPosition(record || 0, column || 0);
cell = position.getCell(true);
}
// Ext.grid.NavigationModel#onFocusMove will NOT react and navigate
// because the actionableMode flag is still set at this point.
Ext.fly(cell).focus();
// Let's update the activeEl after focus here
activeEl = Ext.fly(Ext.Element.getActiveElement());
// If that focus triggered handlers (eg CellEditor after edit handlers) which
// programmatically moved focus somewhere, and the target cell has been
// unfocused, defer to that, null out position, so that we do not navigate
// to that cell below.
// See EXTJS-20395
if (!(me.el.contains(activeEl) && activeEl.is(me.getCellSelector()))) {
position = null;
}
}
// We are exiting actionable mode.
// Tell all registered Actionables about this fact if they need to know.
for (i = 0; i < len; i++) {
if (actionables[i].deactivate) {
actionables[i].deactivate();
}
}
// If we had begun action (we may be a dormant lockingPartner),
// make any tabbables untabbable
if (me.actionRow) {
me.actionRow.saveTabbableState({
skipSelf: true,
includeSaved: false
});
}
if (me.destroyed) {
return false;
}
// These flags MUST be set before focus restoration to the owning cell.
// so that when Ext.grid.NavigationModel#setPosition attempts to exit
// actionable mode, we don't recurse.
me.actionableMode = me.ownerGrid.actionableMode = false;
me.actionPosition = navModel.actionPosition = me.actionRow = null;
// Push focus out to where it was requested to go.
if (position) {
navModel.setPosition(position);
}
}
},
- 执行remove操作后,会自动释放焦点
此后在执行load操作则不会报错。
ext-classic/src/view/AbstractView.js
onRemove: function(store, records, index) {
var me = this,
rows = me.all,
currIdx, i, record, nodes, node, restoreFocus;
if (me.rendered && !me.refreshNeeded && rows.getCount()) {
if (me.dataSource.getCount() === 0) {
me.refresh();
}
else {
// If this view contains focus, this will return
// a function which will restore that state.
restoreFocus = me.saveFocusState();
// Just remove the elements which corresponds to the removed records
// The tpl's full HTML will still be in place.
nodes = [];
for (i = records.length - 1; i >= 0; --i) {
record = records[i];
currIdx = index + i;
if (nodes) {
node = rows.item(currIdx);
nodes[i] = node ? node.dom : undefined;
}
if (rows.item(currIdx)) {
me.doRemove(record, currIdx);
}
}
me.fireItemMutationEvent('itemremove', records, index, nodes, me);
// If focus was in this view, this will restore it
// 此处释放焦点
restoreFocus();
me.updateIndexes(index);
}
// Ensure layout system knows about new content size
me.refreshSizePending = true;
}
},