PDA

View Full Version : How can i force store commitRecords after sync in batch mode if just one record fails



maneljn
9 Nov 2012, 8:54 AM
Extjs 4.1.1.a

I'm doing a store.sync() batch save
For example i save 5 records.
4 of them in php were succesfully saved and one have an error.
php returns json with success: false, data: with array of 4 successfully saves records

I need to process in failure callback of sync that the 4 records refreshed data is commit in store, because of success = false it does'nt commit any records by default.

How ?

vietits
9 Nov 2012, 6:50 PM
For updating/creating requests if you return successful response then all newly created/modified records will be committed, if you return failed response then all records will not be committed. So with your case, I think you have to process the response and do the committing records by yourself. You can do this by some ways:
- Defining callbacks for store.sync():


store.sync({
success: function(batch, options){
...
},
failure: function(batch, options){
...
}
})

- Defining onCreateRecords, onUpdateRecords for the store:


var store = Ext.create('Ext.data.Store', {
...
onCreateRecords: function(records, operation, success){
...
},
onUpdateRecords: function(records, operation, success){
...
}
});

- Defining listeners for 'exception' event on store proxy.
- etc

maneljn
10 Nov 2012, 7:41 AM
That's my workaround

Basically i have to return diferent structure response if succes was true or false

If success = true, i return something like this , and sync() does all store commits automatically



{
"success": true,
"data": [...] // an array of updated or newly created records with ids
}


If success = false, i have to put the 'data' inside the 'message', because 'message' is the only thing that sync() will see with operation.getError()



{
"success": false,
"message": [
"myerrorsmessages":[{ ....... }],
"data": [...] // an array of updated or newly created records with ids
]
}


This is my pieces of source code working example.

The model definition


//@charset UTF-8

// Modelo de datos para las direcciones
Ext.define('esicontactos.model.direccion', {
extend: 'Ext.data.Model',

idProperty: 'dir_id',
fields: [
{ name: 'dir_id', type: 'integer' },
{ name: 'dir_guid', type: 'string' },
{ name: 'dir_contacto_id', type: 'integer' },
{ name: 'dir_fiscal', type: 'boolean' },
{ name: 'dir_alias', type: 'string' },
{ name: 'dir_via', type: 'string' },
{ name: 'dir_nom', type: 'string' },
{ name: 'dir_num', type: 'integer' },
{ name: 'dir_esc', type: 'string' },
{ name: 'dir_pis', type: 'string' },
{ name: 'dir_pue', type: 'string' },
{ name: 'dir_direc1', type: 'string' },
{ name: 'dir_direc2', type: 'string' },
{ name: 'dir_pos', type: 'string' },
{ name: 'dir_pob', type: 'string' },
{ name: 'dir_pro', type: 'string' },
{ name: 'dir_pais_iso3', type: 'string' },
{ name: 'dir_tel1', type: 'string' },
{ name: 'dir_tel2', type: 'string' },
{ name: 'dir_fax', type: 'string' },
{ name: 'dir_email', type: 'string' },
{ name: 'dir_web', type: 'string' },
{ name: 'dir_activo', type: 'boolean' },

{ name: 'dir_usralta_id', type: 'integer', persist: false },
{ name: 'dir_fechaalta', type: 'date', dateFormat: 'Y-m-d H:i:s', persist: false },
{ name: 'dir_usrmod_id', type: 'integer', persist: false },
{ name: 'dir_fechamod', type: 'date', dateFormat: 'Y-m-d H:i:s' }, // Este campo se usará para el aviso de sobreescritura en concurrencia (2 usuarios grabando en el mismo registro a la vez)
{ name: 'dir_usralta_nombre', type: 'string', persist: false },
{ name: 'dir_usrmod_nombre', type: 'string', persist: false }
]
});


The store definition


//@charset UTF-8

// Store para las direcciones
Ext.define('esicontactos.store.direcciones', {
extend: 'Ext.data.Store',

requires: [
'esicontactos.model.direccion'
],

constructor: function(cfg) {
var me = this;
cfg = cfg || {};
me.callParent([Ext.apply({
autoLoad: false,
autoSync: false,
buffered: false,
model: 'esicontactos.model.direccion',
remoteSort: true,
remoteFilter: true,
pageSize: 50,
proxy: {
type: 'direct',
batchActions: true, // Al ser gestionado en una sublista permitimos hacer sync() en batch mode
batchOrder: 'destroy,create,update', // Cambiamos orden por defecto para que primero haga los destroy.
paramAsHash: true,
extraParams: {
buscar: null
},
api: {
read: Ext.esicontactosDirect.esicontactos_direcciones.getDirecciones,
create: Ext.esicontactosDirect.esicontactos_direcciones.createDireccion,
update: Ext.esicontactosDirect.esicontactos_direcciones.updateDireccion,
destroy: Ext.esicontactosDirect.esicontactos_direcciones.destroyDireccion
},
reader: {
type: 'json',
root: 'data',
idProperty: 'dir_id',
totalProperty: 'total',
successProperty: 'success',
messageProperty : 'message'
}
}
}, cfg)]);
}


});


The store creation


me.store = Ext.create('esicontactos.store.direcciones', {
storeId: Ext.id(),
pageSize: -1,
// Impedimos que la reordenacion haga un query en php porque perderiamos los cambios pendientes de guardar.
// Al no paginar los datos se ordenara localmente sin problemas.
remoteSort: false
});


The controller sync()


syncDirecciones: function(registroContacto, panelEdicion) {
var me= this;
var cto_id = registroContacto.get('cto_id');
var gridDirecciones = panelEdicion.down('esicontactos_view_direcciones_mantGrid');
var storeDirecciones = gridDirecciones.getStore();
var formulario = panelEdicion.formularioContactos;
var registro = formulario.getRecord();
var mainStatusBar = panelEdicion.up('esicontactos_view_main').down('esicontactos_view_mainStatusBar[name="mainStatusBar"]');

// Comprobar si hay registros que actualizar
if ( !(storeDirecciones.getModifiedRecords().length>0 || storeDirecciones.getRemovedRecords().length>0) ) {
// Informar que hemos completado correctamente la tarea de guardar las direcciones para el multiguardar
me.completarMultiGuardar(registroContacto, panelEdicion, 'direcciones', true);
return true;
}

// Si el contacto era nuevo y aun no se habia guardado por primera vez hay que poner el contacto_id en las direcciones
if ( panelEdicion.modo=="nuevo" ) {
var regsModificadosoNuevos = storeDirecciones.getModifiedRecords();
for (var i=0; i<regsModificadosoNuevos.length; i++) {
regsModificadosoNuevos[i].set('dir_contacto_id',cto_id);
}
var regsBorrados = storeDirecciones.getRemovedRecords(); // No haria falta ponerlo en los borrados.
for (var i=0; i<regsBorrados.length; i++) {
regsBorrados[i].set('dir_contacto_id',cto_id);
}
}

// Mostrar "guardando" en barra de estado
if (mainStatusBar) {
mainStatusBar.setStatus({
text: gt.dgettext('esicontactos','Guardando registro ... ') + registroContacto.get('cto_nombre_fiscal') + ' ' + gt.dgettext('esicontactos','direcciones ... '),
iconCls: 'x-status-busy',
clear: false
});
}
// Mostrar mascara
panelEdicion.getEl().mask(gt.dgettext('esicontactos','Guardando direcciones ...'));


// Ejecutar el sync del store (lanza en modo batch todos los destroy, create y update a través del proxy de forma asincrona)
storeDirecciones.sync({

// operaciones crud todas correctas
success: function(batch, options) {
// Limpiar barra de estado
if (mainStatusBar) {
mainStatusBar.clearStatus({ useDefaults:true, anim: false });
}
panelEdicion.getEl().unmask();
// Informar que hemos completado correctamente la tarea de guardar las direcciones para el multiguardar
me.completarMultiGuardar(registroContacto, panelEdicion, 'direcciones', true);
},

// Alguna operacion crud con fallos
failure: function(batch, options) {

// Limpiar barra de estado
if (mainStatusBar) {
mainStatusBar.clearStatus({ useDefaults:true, anim: false });
}
panelEdicion.getEl().unmask();

// Cadena con el mensaje de error a mostrar
var mensajeError = null;

// Recorrer resultados de las operaciones crud generadas automaticamente por sync()
for (var i=0; i<batch.operations.length; i++) {
var operacion = batch.operations[i];
// Error en la operacion (solo puede ser create, update o destroy)
if (operacion.hasException()) {
// Obtener registros que han generado la operacion.
var registrosOperacion = operacion.getRecords();
// Si solo se mandó un registro el único error posible no viene en un array sino en un string directo
if (registrosOperacion.length==1 || (typeof operacion.getError())=="string") {
var registroOperacion = registrosOperacion[0];
if (registroOperacion) {
// Si la accion era borrar y ha fallado volver a poner el registro borrado en el store
if (operacion.action=='destroy') {
storeDirecciones.add(registroOperacion);
}
// Montar cadena mensaje de error
mensajeError = (mensajeError==null ? '' : mensajeError + '<br />') + gt.dgettext('esicontactos','Dirección:') + ' ' + registroOperacion.get('dir_alias') + '<br />' + operacion.getError();
}

} else {

// Si se mandaron multiples registros aunque solo haya un error viene en un array con 2 partes
// php $respuesta['message']['errores'] array de mensajes de error
// php $respuesta['message']['data'] registros refrescados despues de procesar o con el valor original si han fallado
// obtener array de mensajes de error si los hay (en sync batch mode sera un array con estas keys 'indexRegOpe', 'errorNum' , 'errorMsg', 'message'
var mensajesErrorOperacion = operacion.getError().errores;

// Montar cadena error y recuperar borrados que han fallado
if (mensajesErrorOperacion==null || mensajesErrorOperacion.length<=0) {
mensajeError = (mensajeError==null ? '' : mensajeError + '<br />') + gt.dgettext('esicontactos','Error al guardar las direcciones.') +' ('+operacion.action+')';
} else {
// Bucle para montar el mensaje de error
for (var j=0; j<mensajesErrorOperacion.length; j++) {
if (typeof mensajesErrorOperacion[j]['indexRegOpe'] != "undefined") {
if ( mensajesErrorOperacion[j]['indexRegOpe']>=0 && mensajesErrorOperacion[j]['indexRegOpe']<registrosOperacion.length) {
// Coger el registro original que corresponde al error recibido
var registroOperacion = registrosOperacion[ mensajesErrorOperacion[j]['indexRegOpe'] ];
if (registroOperacion) {
// Si la accion era borrar y ha fallado volver a poner el registro borrado en el store
if (operacion.action=='destroy') {
storeDirecciones.add(registroOperacion);
}
// Montar cadena mensaje de error
mensajeError = (mensajeError==null ? '' : mensajeError + '<br />') + gt.dgettext('esicontactos','Dirección:') + ' ' + registroOperacion.get('dir_alias') + '<br />' + mensajesErrorOperacion[j]['message'];
}
}
}
}
}


// Aunque haya errores actualizamos los otros registros en create y update
// que si hayan ido bien y han devuelto el data refrescado
// php $respuesta['message']['data'] registros refrescados despues de procesar o con el valor original si han fallado
var dataRespuestaOperacion = operacion.getError().data;
if (dataRespuestaOperacion) {
var registrosRespuestaOperacion = storeDirecciones.getProxy().getReader().read(dataRespuestaOperacion).records;
if ((operacion.action=='create' || operacion.action=='update') && registrosRespuestaOperacion) {
// Recorremos todos los registros originales enviados en la operacion para ver si se pueden actualizar
// con su homonimo recibido del php
for (var r=0; r<registrosOperacion.length; r++) {
// Mirar si el index r no esta entre los registros que tiene error
var regConError = false;
for (var j=0; j<mensajesErrorOperacion.length; j++) {
if ( mensajesErrorOperacion[j]['indexRegOpe']==r) {
regConError = true;
break;
}
}
if (!regConError) {
var registroOperacion = registrosOperacion[r];
var registroRespuestaOperacion = registrosRespuestaOperacion[r];
if (registroOperacion && registroRespuestaOperacion) {
// Si el registro original no es nuevo (phantom), asegurarse que los ids coinciden entre el registro original enviado y el recibido.
if(registroOperacion.getId() === registroRespuestaOperacion.getId() || registroOperacion.phantom ) {
registroOperacion.copyFrom(registroRespuestaOperacion);
registroOperacion.commit();
}
}
}
}
}
} // Fin commits

} // Fin varios registros
} // Fin tiene algun error operacion
} // Fin bucle operaciones

// Se muestra un solo mensaje de error con los textos de todas las operaciones
if (mensajeError) {
Ext.Msg.show({
title: gt.dgettext('esicontactos','Direcciones'),
msg: mensajeError,
buttons: Ext.Msg.OK,
icon: Ext.Msg.WARNING
});
}

// Puesto que si falla algun borrar se reagrega el registro al store, limpiamos el array de borrados para el siguiente sync.
storeDirecciones.removed = [];

// Informar que hemos completado con errores la tarea de guardar las direcciones para el multiguardar
me.completarMultiGuardar(registroContacto, panelEdicion, 'direcciones', false);

}
});

}


The php server side of create/update batch process


public static function guardarBatchDireccion( $modo, $_parametros)
{
// Cuando se usa el sync() del store en batchActions=true se reciben todos los CREATEs y UPDATEs en un solo POST
// y los parametros vienen en un array de objetos.
// Sistema sync() batch mode (bucle)
$respuestaData = null;
$respuestaErrores = null;
foreach($_parametros as $indexRegOpe => $_parametrosRegistro) {
$respuestaRegistro = self::guardarDireccion( $modo, $_parametrosRegistro );
if (!$respuestaRegistro['success']) {
$respuestaErrores[] = array(
'indexRegOpe' => $indexRegOpe, // Indicamos en que registro origen se produjo el error de proceso
'errorNum' => (isset($respuestaRegistro['errorNum']) ? $respuestaRegistro['errorNum'] : null),
'errorMsg' => (isset($respuestaRegistro['errorMsg']) ? $respuestaRegistro['errorMsg'] : null),
'message' => (isset($respuestaRegistro['message']) ? $respuestaRegistro['message'] : null)
);
}
// Nos autoobligamos por la estructuracion de los eventos succes i failure del sync en extjs receptor
// a devolver siempre el mismo número de registros recibidos para procesar en el mismo orden.
// Si tenemos datos refrescados ponemos los refrescados, y sino por error o cualquier otro motivo
// ponemos el registro original.
if (isset($respuestaRegistro['data'][0])) {
$respuestaData[] = $respuestaRegistro['data'][0];
} else {
$respuestaData[] = $_parametrosRegistro;
}
}
// El formato de respuesta serà distinto si és succes true o false
// Para true, anadimos el array ['data'] con los registros a devolver.
// En la parte cliente extjs el sync hace los commits en el store automàticamente.
// Si es false, tenemos que colar el ['data'] dentro del message que es lo único que procesa el sync () con el evento failure
// y así en la parte cliente se pueden actualizar los registros que si que han sido correctos, mostrando error en los otros.
if (count($respuestaErrores)>0) {
$respuesta['success'] = false;
$respuesta['errores'] = count($respuestaErrores);
$respuesta['message']['errores'] = $respuestaErrores;
$respuesta['message']['data'] = $respuestaData;
} else {
$respuesta['success'] = true;
$respuesta['data'] = $respuestaData;
}
return $respuesta;
}


Like this i can send some sync() actions: multiple creates, updates, deletes and refresh/commit in store all the records that went good, and show all the error messages from all operations.