You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ExtJS可编辑网格无实际数据变更时清除dirty flag的方案

我来帮你搞定这个ExtJS网格的dirty flag问题,这种类型混用、空值导致的无意义标记,在实际业务里真的很常见——用户哪管什么字符串还是数字,只要值没改就不该标脏对吧?咱们一步步来解决,完全满足你提的三个需求:

问题根源先理清楚

为啥Tab切换会触发dirty flag?核心是两点:

  1. 你的原始数据类型混乱:record_id有数字也有字符串,data有空值、数字、字符串数字
  2. ExtJS默认是严格类型比较(用===),比如数字62和字符串"62"会被当成不同值,哪怕用户根本没改内容,编辑器失去焦点就会标脏

解决方案分四步走

1. 手动定义数据模型,统一类型转换

别用默认的JsonStore自动猜字段类型了,自己写个Model,用convert函数把所有输入统一转成目标类型(比如数字),同时处理空值:

Ext.define('MyDataModel', {
    extend: 'Ext.data.Model',
    fields: [
        {
            name: 'record_id',
            type: 'int',
            convert: function(value) {
                // 空值、undefined、空字符串全转成null
                if (value === '' || value === undefined || value === null) {
                    return null;
                }
                // 字符串转数字,转失败也返回null
                return parseInt(value, 10) || null;
            },
            allowNull: true
        },
        {
            name: 'data',
            type: 'int',
            convert: function(value) {
                if (value === '' || value === undefined || value === null) {
                    return null;
                }
                return parseInt(value, 10) || null;
            },
            allowNull: true
        }
    ]
});

2. 重写字段的isEqual方法,只比值不比类型

默认的比较是严格相等,咱们改成只要值一样就算相等,比如"62"和62视为同一个值:

// 给每个字段加isEqual函数,比如record_id字段:
{
    name: 'record_id',
    // ... 其他配置
    isEqual: function(value1, value2) {
        // 空值之间算相等
        if ((value1 === null || value1 === undefined) && (value2 === null || value2 === undefined)) {
            return true;
        }
        // 都转成数字再比
        var num1 = parseInt(value1, 10) || null;
        var num2 = parseInt(value2, 10) || null;
        return num1 === num2;
    }
}

data字段也加一模一样的isEqual即可。

3. 给CellEditing加监听,编辑后自动清脏标记

编辑完成后,判断当前值和原始值是不是真的变了,如果没变就把dirty标记清掉:

plugins: {
    cellediting: {
        clicksToEdit: 1,
        listeners: {
            edit: function(editor, context) {
                var record = context.record;
                var field = context.field;
                var originalValue = record.getOriginal(field);
                var currentValue = record.get(field);
                
                // 用咱们自定义的isEqual判断值是否真的变了
                var fieldDef = record.getField(field);
                if (fieldDef.isEqual(currentValue, originalValue)) {
                    // 清除当前字段的脏标记
                    record.set(field, originalValue, { dirty: false });
                    // 如果整个记录没有其他脏字段,把记录的脏标记也清了
                    if (!record.hasDirtyFields()) {
                        record.setDirty(false);
                    }
                }
            }
        }
    }
}

4. 把Store改成用自定义Model

原来的JsonStore换成指定Model的Store:

var myStore = Ext.create('Ext.data.Store', {
    storeId : 'myStore_data',
    model: 'MyDataModel', // 用咱们自己定义的Model
    autoLoad : true,
    autoDestroy : true,
    proxy : {
        type : 'ajax',
        url : 'data.json',
        reader : {
            type : 'json',
            rootProperty : 'rows'
        }
    },
});

完整修改后的data-ext.js代码

直接替换原来的文件就行,还加了个Save按钮方便测试提交:

Ext.require([
    'Ext.grid.*',
    'Ext.data.*',
    'Ext.util.*',
    'Ext.state.*'
]);

Ext.define('MyDataModel', {
    extend: 'Ext.data.Model',
    fields: [
        {
            name: 'record_id',
            type: 'int',
            convert: function(value) {
                if (value === '' || value === undefined || value === null) {
                    return null;
                }
                return parseInt(value, 10) || null;
            },
            allowNull: true,
            isEqual: function(value1, value2) {
                if ((value1 === null || value1 === undefined) && (value2 === null || value2 === undefined)) {
                    return true;
                }
                var num1 = parseInt(value1, 10) || null;
                var num2 = parseInt(value2, 10) || null;
                return num1 === num2;
            }
        },
        {
            name: 'data',
            type: 'int',
            convert: function(value) {
                if (value === '' || value === undefined || value === null) {
                    return null;
                }
                return parseInt(value, 10) || null;
            },
            allowNull: true,
            isEqual: function(value1, value2) {
                if ((value1 === null || value1 === undefined) && (value2 === null || value2 === undefined)) {
                    return true;
                }
                var num1 = parseInt(value1, 10) || null;
                var num2 = parseInt(value2, 10) || null;
                return num1 === num2;
            }
        }
    ]
});

Ext.onReady(function() {
    var myStore = Ext.create('Ext.data.Store', {
        storeId : 'myStore_data',
        model: 'MyDataModel',
        autoLoad : true,
        autoDestroy : true,
        proxy : {
            type : 'ajax',
            url : 'data.json',
            reader : {
                type : 'json',
                rootProperty : 'rows'
            }
        },
    });

    var grid = Ext.create('Ext.grid.Panel', {
        store : Ext.data.StoreManager.lookup('myStore_data'),
        columnLines : true,
        border : true,
        title : 'Grid Data Testing',
        columns : [
            {
                text : "Record ID",
                dataIndex : "record_id",
                width : 200,
                format : '0',
                editor : {
                    xtype : 'numberfield',
                    allowBlank : true,
                    allowNull : true,
                }
            },
            {
                text : "Data",
                dataIndex : "data",
                width : 200,
                format : '0',
                editor : {
                    xtype : 'numberfield',
                    allowBlank : true,
                    allowNull : true,
                }
            }
        ],
        selModel : {
            selType : 'cellmodel'
        },
        height : 230,
        width : 402,
        title : 'Grid Data Testing',
        renderTo : 'grid-example',
        viewConfig : {
            stripeRows : true
        },
        plugins : {
            cellediting : {
                clicksToEdit : 1,
                listeners: {
                    edit: function(editor, context) {
                        var record = context.record;
                        var field = context.field;
                        var originalValue = record.getOriginal(field);
                        var currentValue = record.get(field);
                        
                        var fieldDef = record.getField(field);
                        if (fieldDef.isEqual(currentValue, originalValue)) {
                            record.set(field, originalValue, { dirty: false });
                            if (!record.hasDirtyFields()) {
                                record.setDirty(false);
                            }
                        }
                    }
                }
            }
        },
        // 测试用的Save按钮
        tbar: [{
            text: 'Save',
            handler: function() {
                var modifiedRecords = grid.getStore().getModifiedRecords();
                if (modifiedRecords.length > 0) {
                    console.log('要提交的变更:', modifiedRecords.map(r => r.getChanges()));
                    // 提交后重置所有脏标记
                    grid.getStore().commitChanges();
                } else {
                    console.log('没有需要保存的变更');
                }
            }
        }]
    });
});

验证你的三个需求

  1. 无实际变更清脏标记:Tab切换单元格时,不管原始类型是啥,只要值没变,dirty flag会自动清除,getChanges()里也不会有这些记录
  2. 实际修改显示脏标记:比如把62改成625,值真变了,会正常显示dirty标记
  3. 改回原值无脏标记:把625改回62后,系统判断值和原始值相等,立刻清掉dirty标记,直到点击Save按钮提交后,原始值会被更新

这样就完美解决你的问题啦!

内容的提问来源于stack exchange,提问作者caot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:36:13