Results 1 to 6 of 6

Thread: [4.1 RC3] CopyFrom model not copy hasmany

    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
      0  

    Default [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 doesnt have its childs 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
      0  

    Default

    confirmed the bug?

  3. #3
    Ext JS Premium Member
    Join Date
    Nov 2011
    Posts
    35
    Vote Rating
    1
      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
      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
      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
      0  

    Default 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 Drmbolaget; 16 Jul 2013 at 3:25 AM. Reason: Improved code

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •