Ext.define('Dbn.override.data.ProxyStore', { override: 'Ext.data.ProxyStore', onBatchComplete: function (batch) { var me = this, operations = batch.getOperations(), operationsCount = operations.length, i, j, k; for (i = 0; i < operationsCount; i++) { var operation = operations[i]; if (operation.wasSuccessful()/* && operation.getRequest().getAction() === 'update'*/) { var reader = operation.getProxy().getReader(), entityType = reader.getModel(), idProperty = entityType.idProperty, schema = entityType.schema, includes = schema.hasAssociations(entityType) && reader.getImplicitIncludes(), fieldExtractorInfo = reader.getFieldExtractorInfo(entityType.fieldExtractors), resultSetRecords = operation.getResultSet().getRecords(), resultSetRecordsCount = resultSetRecords.length, // Here we collect changes in records which are not processed by standard store logic. // Standard logic processes records which were in the request only. recordsToProcess = [], recordsProcessedByOperation = operation.getRecords(), recordsProcessedByOperationCount = recordsProcessedByOperation.length; // Get records which were not in request and require custom processing. for (j = 0; j < resultSetRecordsCount; j++) { var processed = false; for (k = 0; k < recordsProcessedByOperationCount; k++) { if (recordsProcessedByOperation[k].getId() == resultSetRecords[j][idProperty]) { processed = true; break; } } if (!processed) recordsToProcess.push(resultSetRecords[j]); } var recordsToProcessCount = recordsToProcess.length, existingRecordsToRefresh = [], unprocessedNewRecords = []; for (j = 0; j < recordsToProcessCount; j++) { var recordData = recordsToProcess[j], record; if (!recordData.isModel) { var extractedRecordData = Ext.clone(recordData); record = reader.extractRecord(extractedRecordData, {}, entityType, includes, fieldExtractorInfo); record.raw = extractedRecordData; } else { record = recordData; recordData = record.raw; } var existingRecord = me.getById(record.getId()); // IMPORTANT! Record should have 'isDeleted' field to let logic know how to process it. if (existingRecord) { //region Process existing records. if (record.get('isDeleted')) { //region Process deletion. me.suspendAutoSync(); existingRecord.drop(false); me.removedNodes.splice(me.removedNodes.indexOf(existingRecord), 1); me.resumeAutoSync(); //endregion } else { //region Update fields of existing record with new data from server. // recordData usage is potentially dangerous: it will break logic if properties of models are mapped to other server-side response fields. for (var entityProp in recordData) { if (!recordData.hasOwnProperty(entityProp)) continue; existingRecord.data[entityProp] = recordData[entityProp]; } for (var assocProp in record.associations) { //noinspection JSUnresolvedFunction if (!record.associations.hasOwnProperty(assocProp)) continue; delete existingRecord[record.associations[assocProp].instanceName]; existingRecord[record.associations[assocProp].instanceName] = record[record.associations[assocProp].instanceName]; } existingRecordsToRefresh.push(existingRecord); //endregion } //endregion } else { // Collect new unprocessed records. if (!record.get('isDeleted')) unprocessedNewRecords.push(record); } } // Fire refresh to notify listeners about made changes. me.fireEvent('refresh', me, existingRecordsToRefresh); // Processing of new records can differ for different types of stores: Store, TreeStore and so on. me.processNewRecordsFromSync(unprocessedNewRecords); } } return me.callParent(arguments); }, processNewRecordsFromSync: Ext.emptyFn, /** * @private * Filter function for updated records. */ filterUpdated: function (item) { // ADDED !item.dropped // only want dirty records, not phantoms that are valid return item.dirty === true && !item.dropped && item.phantom !== true && item.isValid(); } });