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;
    }
},

posted on 2022-04-11 22:38  路过君  阅读(655)  评论(0编辑  收藏  举报

导航