1. #1
    Sencha User
    Join Date
    Feb 2010
    Posts
    357
    Vote Rating
    4
    Answers
    15
    maneljn is on a distinguished road

      0  

    Default Answered: How can i force store commitRecords after sync in batch mode if just one record fails

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


    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 ?
    -------------------
    Manel Juàrez

  2. 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

    Code:
    {
        "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()

    Code:
    {
        "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
    Code:
    //@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
    Code:
    //@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
    Code:
            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()
    Code:
        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
    Code:
        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.

  3. #2
    Sencha - Community Support Team
    Join Date
    Jan 2012
    Posts
    1,376
    Vote Rating
    114
    Answers
    346
    vietits is a splendid one to behold vietits is a splendid one to behold vietits is a splendid one to behold vietits is a splendid one to behold vietits is a splendid one to behold vietits is a splendid one to behold

      0  

    Default


    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():
    Code:
    store.sync({
        success: function(batch, options){
            ...
        },
        failure: function(batch, options){
            ...
        }
    })
    - Defining onCreateRecords, onUpdateRecords for the store:
    Code:
    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

  4. #3
    Sencha User
    Join Date
    Feb 2010
    Posts
    357
    Vote Rating
    4
    Answers
    15
    maneljn is on a distinguished road

      0  

    Default


    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

    Code:
    {
        "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()

    Code:
    {
        "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
    Code:
    //@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
    Code:
    //@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
    Code:
            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()
    Code:
        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
    Code:
        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.
    -------------------
    Manel Juàrez

Thread Participants: 1