You found a bug! We've classified it as EXTJS-6710 . We encourage you to continue the discussion and to find an acceptable workaround while we work on a permanent fix.
  1. #1
    Sencha User
    Join Date
    Dec 2010
    Posts
    14
    Vote Rating
    0
    educosta1000 is on a distinguished road

      0  

    Default [4.1 RC3] CopyFrom model not copy hasmany

    [4.1 RC3] CopyFrom model not copy hasmany


    REQUIRED INFORMATION


    Ext version tested:
    • Ext 4.1 GA


    Browser versions tested against:
    • IE9
    • FF11

    Description:
    • When I save a model with a hasmany property, the model is updated on the server correctly. But when the model is interpreted by the EXTJS the child relations are not updated correctly.

      After some debugging, I found that the Ext.data.Operation method uses the commitRecords method to copy the server record to the client record.
      The server record is OK, but the client record doesn´t have its child’s updated after the copyFrom method.


    Steps to reproduce the problem:
    • Run test case


    The result that was expected:
    • Orginal and Copy is equal


    The result that occurs instead:
    • Original and Copy is different


    Test Case:

    Code:
        <!DOCTYPE html>
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=iso-8859-1" />
        <link rel="stylesheet" type="text/css" href="http://dbs-gmf-service/extjs/ext-4.1/resources/css/ext-all-gray.css" />
    </head>
    <body>
    </body>
    <script type="text/javascript" src="http://dbs-gmf-service/extjs/ext-4.1/ext-all-debug.js"></script>
    <script type="text/javascript">
    Ext.onReady(function(){
    
    Ext.define('GMF.model.DTODeTelefone', {
        extend: 'Ext.data.Model'
        ,fields: [
            { name: 'Id'}
            ,{ name: 'Telefone'} 
        ]
     ,proxy: {
        type: 'rest',
        url: '/Apresentacao/Telefone'
        , api: {
            read: '/Apresentacao/Telefone/',
            create: '/Apresentacao/Telefone/Novo',
            update: '/Apresentacao/Telefone/Editar',
            destroy: '/Apresentacao/Telefone/Excluir'
        }
        , actionMethods: {
            create: 'POST',
            read: 'GET',
            update: 'POST',
            destroy: 'POST'
        }
        , reader: { root: 'Entidade' }
        }
            ,idProperty : 'Id'
    });
    
    Ext.define('GMF.model.DTODePessoaTelefone', {
        extend: 'Ext.data.Model'
        ,fields: [
            { name: 'ID'}
            ,{ name: 'Nome'}
            ,{ name: 'NomeDaMae'}
            ,{ name: 'CPF'} 
        ]
     ,proxy: {
        type: 'rest',
        url: '/Apresentacao/PessoaTelefone'
        , api: {
            read: '/Apresentacao/PessoaTelefone/',
            update: '/Apresentacao/PessoaTelefone/Editar'
        }
        , actionMethods: {
            create: 'POST',
            read: 'GET',
            update: 'POST',
            destroy: 'POST'
        }
        , reader: { root: 'Entidade' }
        }
        , hasMany: [
            { model: 'GMF.model.DTODeTelefone', name: 'Telefones' }
        ]
        , idProperty: 'ID'
    });
    
    var pessoaTelefone = Ext.create('GMF.model.DTODePessoaTelefone');
    var telefone = Ext.create('GMF.model.DTODeTelefone');
    pessoaTelefone.Telefones().add(telefone);
    
    var pessoaTelefoneCopy = Ext.create('GMF.model.DTODePessoaTelefone');
    pessoaTelefoneCopy.copyFrom(pessoaTelefone);
    
    document.body.innerHTML = 'Copy: ' + pessoaTelefoneCopy.Telefones().count();
    document.body.innerHTML += '<br/>';
    document.body.innerHTML +='Original: ' + pessoaTelefone.Telefones().count();
    
    });
    </script>


    HELPFUL INFORMATION


    Debugging already done:
    • commitResults in Ext.data.Operation


    Operating System:
    • Win7

  2. #2
    Sencha User
    Join Date
    Dec 2010
    Posts
    14
    Vote Rating
    0
    educosta1000 is on a distinguished road

      0  

    Default


    confirmed the bug?

  3. #3
    Ext JS Premium Member
    Join Date
    Nov 2011
    Posts
    35
    Vote Rating
    1
    JamaSoftware-Sencha is on a distinguished road

      0  

    Default


    UPDATE: I am using Ext 4.1.0

    I'm seeing this same issue with my belongsTo associations.

    For me, I am just initially saving the record. The server returns the object with the associations pre-populated, but the client record doesn't pick them up.

    The original poster has this issue nailed. The Ext.data.Model.copyFrom method doesn't do anything with associations.

    Can we confirm this as a bug or feature request? I'll see if I can write a hack that fixes my issue, and if worthy, I'll post it here.

  4. #4
    Ext JS Premium Member
    Join Date
    Nov 2011
    Posts
    35
    Vote Rating
    1
    JamaSoftware-Sencha is on a distinguished road

      0  

    Default


    This seems to work for my situation. I haven't tested with all associations or all operations:

    Code:
        Ext.data.Model.override({
            
            /**
             * @Override
             * Copies association data after calling super.copyFrom
             */
            copyFrom: function(sourceRecord) {
                this.callParent(arguments);
                var associations = this.associations.items,
                    i, association, mine, theirs, instanceName;
                
                for (i=0; i<associations.length; i++) {
                    association = associations[i];
                    instanceName = association.instanceName;
                    theirs = sourceRecord[instanceName];
                    if (theirs) {
                        mine = this[instanceName];
                        if (mine) {
                            mine.copyFrom(theirs);
                        }
                        else {
                            this[instanceName] = theirs;
                        }
                    }
                }
            }
            
        });

  5. #5
    Sencha User
    Join Date
    Aug 2012
    Posts
    7
    Vote Rating
    0
    fdalesio is on a distinguished road

      0  

    Default


    Here's a patch for HasMany associations, starting from the JamaSoftware-Sencha one.

    Code:
    Ext.data.Model.override({
    
        /**
         * @Override
         * Copies association data after calling super.copyFrom
         */
        copyFrom: function(sourceRecord) {
            this.callParent(arguments);
            var associations = this.associations.items,
                len = associations.length, i,
                association, storeName, targetStore, sourceStore;
    
    
            for (i=0; i<associations.length; i++) {
                association = associations[i];
                
                if(association.type == 'hasMany') {
                    storeName = association.storeName;
                    sourceStore = sourceRecord[storeName];
                    
                    if(sourceStore) {
                        delete this[storeName];
                        association.createStore().call(this);
                        this[storeName].loadRecords(sourceStore.getRange());
                    }
                }
            }
        }
    });
    It first delete the associated store, then it creates a new one and finally it populates the store with server data.

    The creation of a new store seems necessary when creating new records because the store has a filter that need to be updated with server data.
    Initially, the hasManyStore has a filter {property: pkey, value: null}
    After the record has been saved, it will have a pkey, so the filter must be updated.
    So this line
    association.createStore().call(this);
    avoid the duplication of
    Ext.data.association.HasMany#createStore

    Hope this helps

  6. #6
    Sencha Premium Member
    Join Date
    Jul 2012
    Posts
    53
    Vote Rating
    2
    Drömbolaget is on a distinguished road

      0  

    Default Another version of copyFrom that loads associated data

    Another version of copyFrom that loads associated data


    I too stumbled upon this problem. This is the version we're using now. it's a bit better in that it doesn't unnecessarily delete the store or existing records if not needed. Might help someone who expects save() on a model to update associated records from server response too.

    Code:
    // Override proxy reader so that it loads nested data from server response on model save()
    Ext.define('MyApp.overrides.extjs.DataModelCopyFrom', {
    	constructor: function () {
    		Ext.override(Ext.data.Model, {
    			copyFrom: function (sourceRecord) {
    				var result = this.set(sourceRecord.data);
    				this.callParent(arguments);
    
    
    				var n = 0;
    				var me = this;
    
    
    				if (sourceRecord && sourceRecord.associations.length) {
    					for (var assocCount = sourceRecord.associations.length; n < assocCount; n++) {
    						var association = sourceRecord.associations.getAt(n);
    						if (association.type === 'belongsTo') {
    							break;
    						}
    
    
    						var storeName = association.storeName;
    						if (typeof (me[storeName]) === 'undefined') {
    							me[storeName] = Ext.clone(sourceRecord[storeName]);
    						}
    							// We don't want to replace the store if it already exists as it could have events bound already
    						else {
    							// The result could be without the store entirely since the server deemed nothing changed.
    							// If the server want's to tell us that all items in the association have been removed it must send an empty array in json response.
    							if (sourceRecord[storeName]) {
    								var
    									toAdd = [],
    									toRemove = [];
    
    
    								//We might need to copy more properties...
    								me[storeName].modelDefaults = Ext.clone(sourceRecord[storeName].modelDefaults);
    
    
    								// Add new records, update existing records (again existing records may have event handlers bound so we can't just replace them)
    								sourceRecord[storeName].each(function (record) {
    									var meRecord = me[storeName].getById(record.get('id'));
    									if (meRecord) {
    										meRecord.copyFrom(record);
    									}
    									else {
    										toAdd.push(record);
    									}
    								}, this);
    								me[storeName].add(toAdd);
    
    
    								// Remove removed records
    								me[storeName].each(function (record) {
    									if (sourceRecord[storeName].getById(record.get('id')) === null) {
    										toRemove.push(record);
    									}
    								}, this);
    								me[storeName].remove(toRemove);
    							}
    						}
    					}
    				}
    
    
    				return result;
    			}
    		});
    	}
    });
    Last edited by Drömbolaget; 16 Jul 2013 at 3:25 AM. Reason: Improved code