ExtJS可编辑网格无实际数据变更时清除dirty flag的方案
我来帮你搞定这个ExtJS网格的dirty flag问题,这种类型混用、空值导致的无意义标记,在实际业务里真的很常见——用户哪管什么字符串还是数字,只要值没改就不该标脏对吧?咱们一步步来解决,完全满足你提的三个需求:
问题根源先理清楚
为啥Tab切换会触发dirty flag?核心是两点:
- 你的原始数据类型混乱:
record_id有数字也有字符串,data有空值、数字、字符串数字 - 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('没有需要保存的变更'); } } }] }); });
验证你的三个需求
- 无实际变更清脏标记:Tab切换单元格时,不管原始类型是啥,只要值没变,dirty flag会自动清除,
getChanges()里也不会有这些记录 - 实际修改显示脏标记:比如把62改成625,值真变了,会正常显示dirty标记
- 改回原值无脏标记:把625改回62后,系统判断值和原始值相等,立刻清掉dirty标记,直到点击Save按钮提交后,原始值会被更新
这样就完美解决你的问题啦!
内容的提问来源于stack exchange,提问作者caot
相关产品推荐
相关产品推荐

