1. #1
    Sencha Premium Member
    Join Date
    Jul 2011
    Posts
    16
    Vote Rating
    1
    Stokes is on a distinguished road

      0  

    Default Answered: Cannot extend a Model extension

    Answered: Cannot extend a Model extension


    In several ways, the way Ext.data.Model instances are constructed causes problems. One of them is extending an extension. I can create a subclass of Ext.data.Model, but when I create a subclass of that subclass (which I would expect to be pretty common in OO code using an MVC pattern) bad stuff starts to happen.

    The code below demonstrates the problem I'm seeing. We just want to create a Person class, and then have some specialized subclass (NicePerson in this example, I probably could have thought of something better). The Ext.define() of the subclass causes the original Person class to be corrupted.

    Code:
    // Create a model Person, which has a 'child' model Address using a HasMany association
    Ext.define('Address', {
        extend: 'Ext.data.Model',
        fields: [
            {name: 'street1', type: 'string'},
            {name: 'street1', type: 'string'},
            {name: 'city', type: 'string'},
            {name: 'state', type: 'string'},
            {name: 'zip', type: 'string'}
        ]
    });
    Ext.define('Person', {
        extend: 'Ext.data.Model',
        fields: [
            {name: 'id', type: 'int'},
            {name: 'firstName', type: 'string'},
            {name: 'lastName', type: 'string'}
        ],
        hasMany: [
            {model: 'Address', associationKey: 'addresses', name: 'getAddressStore'}
        ]
    });
    // create a Store of Person objects, and it works just fine.
    var somePeople = Ext.create('Ext.data.Store', {
        model: 'Person',
        proxy: { type: 'memory', reader: { type: 'json'}},
        data: [
            {id: 1, firstName: 'Brian', lastName: 'M', addresses: [{street1: '1234 My Street', city: 'Austin', state: 'TX', zip: '77777'}]},
            {id: 2, firstName: 'Miro', lastName: 'P', addresses: [{street1: '1234 Breakaway', city: 'Cedar Park', state: 'TX', zip: '77777'}]},
            {id: 3, firstName: 'Stokes', lastName: 'J', addresses: [{street1: '1234 Vance', city: 'Jonestown', state: 'TX', zip: '77777'}]}
        ]
    });
    // I can even do that again, and create another store with very similar people.
    var someMorePeople = Ext.create('Ext.data.Store', {
        model: 'Person',
        proxy: { type: 'memory', reader: { type: 'json'}},
        data: [
            {id: 1, firstName: 'Brian', lastName: 'M', addresses: [{street1: '1234 My Street', city: 'Austin', state: 'TX', zip: '77777'}]},
            {id: 2, firstName: 'Miro', lastName: 'P', addresses: [{street1: '1234 Breakaway', city: 'Cedar Park', state: 'TX', zip: '77777'}]},
            {id: 3, firstName: 'Stokes', lastName: 'J', addresses: [{street1: '1234 Vance', city: 'Jonestown', state: 'TX', zip: '77777'}]}
        ]
    });
    // Now I create a subclass of Person.  It doesn't matter (in this case) whether the subclass defines associations or not, or
    // even whether it defines fields, just extending the Person class causes bad stuff to happen.  The Person class appears
    // to be corrupt after this.
    Ext.define('NicePerson', {
        extend: 'Person',
        fields: [
            {name: 'id', type: 'int'},
            {name: 'firstName', type: 'string'},
            {name: 'lastName', type: 'string'},
            {name: 'phoneNumber', type: 'string'}
        ]
    });
    // This demonstrates the corruption.  Trying to create the same store that we did twice above now throws, in ExtJS 4.1.0rc3:
    //    TypeError: Cannot read property 'observable' of undefined
    //       at new <anonymous> (ext-all-debug-w-comments.js:57742:18)
    //       at [object Object].readAssociated (ext-all-debug-w-comments.js:57950:34)
    //       at [object Object].<anonymous> (ext-all-debug-w-comments.js:57917:20)
    //       at [object Object].<anonymous> (ext-all-debug-w-comments.js:6084:32)
    //       at [object Object].extractData (ext-all-debug-w-comments.js:58517:21)
    //       at [object Object].<anonymous> (ext-all-debug-w-comments.js:57870:30)
    //       at [object Object].<anonymous> (ext-all-debug-w-comments.js:6084:32)
    //       at [object Object].readRecords (ext-all-debug-w-comments.js:58449:21)
    //       at [object Object].read (ext-all-debug-w-comments.js:57795:82)
    //       at [object Object].read (ext-all-debug-w-comments.js:56075:29)
    
    var theSamePeople = Ext.create('Ext.data.Store', {
        model: 'Person',
        proxy: { type: 'memory', reader: { type: 'json'}},
        data: [
            {id: 1, firstName: 'Brian', lastName: 'M', addresses: [{street1: '1234 My Street', city: 'Austin', state: 'TX', zip: '77777'}]},
            {id: 2, firstName: 'Miro', lastName: 'P', addresses: [{street1: '1234 Breakaway', city: 'Cedar Park', state: 'TX', zip: '77777'}]},
            {id: 3, firstName: 'Stokes', lastName: 'J', addresses: [{street1: '1234 Vance', city: 'Jonestown', state: 'TX', zip: '77777'}]}
        ]
    });
    Any ideas how to work around this? (Or am I misunderstanding something?)

    Stokes.

  2. My previous fix is for Ext 4.0.7. Below is my fix for Ext 4.1.0-rc3.
    Code:
    Ext.define('FixedModel', {
        extend: 'Ext.data.Model',
    
    
        onClassExtended: function(cls, data, hooks) {
            // var onBeforeClassCreated = hooks.onBeforeCreated;
            var onBeforeClassCreated = function(Class, data, hooks){
                Class.addMembers(data);
                hooks.onCreated.call(Class, Class);
            }
    
    
            hooks.onBeforeCreated = function(cls, data) {
                var me = this,
                    name = Ext.getClassName(cls),
                    prototype = cls.prototype,
                    superCls = cls.prototype.superclass,
    
    
                    validations = data.validations || [],
                    fields = data.fields || [],
                    associations = data.associations || [],
                    belongsTo = data.belongsTo,
                    hasMany = data.hasMany,
                    hasOne = data.hasOne,
                    addAssociations = function(items, type) {
                        var i = 0,
                            len,
                            item;
    
    
                        if (items) {
                            items = Ext.Array.from(items);
    
    
                            for (len = items.length; i < len; ++i) {
                                item = items[i];
    
    
                                if (!Ext.isObject(item)) {
                                    item = {model: item};
                                }
    
    
                                item.type = type;
                                associations.push(item);
                            }
                        }
                    },
                    idgen = data.idgen,
    
    
                    fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
    
                    associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
    
                    superValidations = superCls.validations,
                    superFields = superCls.fields,
                    superAssociations = superCls.associations,
    
    
                    association, i, ln,
                    dependencies = [],
                    idProperty = data.idProperty || cls.prototype.idProperty,
                    fieldConvertSortFn = Ext.Function.bind(
                        fieldsMixedCollection.sortBy, 
                        fieldsMixedCollection,
                        [prototype.sortConvertFields], false),
    
    
                    // Use the proxy from the class definition object if present, otherwise fall back to the inherited one, or the default    
                    clsProxy = data.proxy || cls.prototype.proxy || cls.prototype.defaultProxyType;
    
    
                // Save modelName on class and its prototype
                cls.modelName = name;
                prototype.modelName = name;
    
    
                // Merge the validations of the superclass and the new subclass
                if (superValidations) {
                    validations = superValidations.concat(validations);
                }
    
    
                data.validations = validations;
    
    
                // Merge the fields of the superclass and the new subclass
                if (superFields) {
                    fields = superFields.items.concat(fields);
                }
    
    
                fieldsMixedCollection.on({
                    add: fieldConvertSortFn,
                    replace: fieldConvertSortFn
                });  
    
    
                for (i = 0, ln = fields.length; i < ln; ++i) {
                    fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
                }
                if (!fieldsMixedCollection.get(idProperty)) {
                    fieldsMixedCollection.add(new Ext.data.Field(idProperty));
                }
                data.fields = fieldsMixedCollection;
    
    
                if (idgen) {
                    data.idgen = Ext.data.IdGenerator.get(idgen);
                }
    
    
                //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
                //we support that here
                addAssociations(data.belongsTo, 'belongsTo');
                delete data.belongsTo;
                addAssociations(data.hasMany, 'hasMany');
                delete data.hasMany;
                addAssociations(data.hasOne, 'hasOne');
                delete data.hasOne;
    
    
                if (superAssociations) {
                    associations = superAssociations.items.concat(associations);
                }
    
    
                for (i = 0, ln = associations.length; i < ln; ++i) {
                    dependencies.push('association.' + associations[i].type.toLowerCase());
                }
    
    
                // If we have not been supplied with a Proxy *instance*, then add the proxy type to our dependency list
                if (clsProxy && !clsProxy.isProxy) {
                    dependencies.push('proxy.' + (typeof clsProxy === 'string' ? clsProxy : clsProxy.type));
                }
    
    
                Ext.require(dependencies, function() {
                    Ext.ModelManager.registerType(name, cls);
    
    
                    for (i = 0, ln = associations.length; i < ln; ++i) {                    // association = associations[i];
    
                        association = Ext.apply({},associations[i]);
    
    
    
                        Ext.apply(association, {
                            ownerModel: name,
                            associatedModel: association.model
                        });
    
    
                        if (Ext.ModelManager.getModel(association.model) === undefined) {
                            Ext.ModelManager.registerDeferredAssociation(association);
                        } else {
                            associationsMixedCollection.add(Ext.data.association.Association.create(association));
                        }
                    }
    
    
                    data.associations = associationsMixedCollection;
    
    
                    // onBeforeCreated may get called *asynchronously* if any of those required classes caused
                    // an asynchronous script load. This would mean that the class definition object
                    // has not been applied to the prototype when the Model definition has returned.
                    // The Reader constructor does not attempt to buildExtractors if the fields MixedCollection
                    // has not yet been set. The cls.setProxy call triggers a build of extractor methods.
                    onBeforeClassCreated.call(me, cls, data, hooks);
                    
                    cls.setProxy(clsProxy);
    
    
                    // Fire the onModelDefined template method on ModelManager
                    Ext.ModelManager.onModelDefined(cls);
                });
            };
        }
    });
    BTW: I have posted this bug at http://www.sencha.com/forum/showthre...h-associations

  3. #2
    Sencha - Community Support Team
    Join Date
    Jan 2012
    Posts
    1,376
    Vote Rating
    115
    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


    I think it's a bug because when you define NicePerson class (a child class of Person) it causes the associations property of Person class (parent class) to be changed. This is a problem of using shared objects.

    This is from Ext.data.Model (Ext 4.0.7 and Ext 4.1.):
    Code:
                ...
                Ext.require(dependencies, function() {
                    Ext.ModelManager.registerType(name, cls);
    
                    for (i = 0, ln = associations.length; i < ln; ++i) {
                        association = associations[i]; <- assocation is a reference to object in parent class 
    
                         Ext.apply(association, { //<- cause parent class changed
                            ownerModel: name,
                            associatedModel: association.model
                        });
    
    Below is my suggestion to fix this problem:
    1. Define FixedModel (or anyname you like):
    Code:
    Ext.define('FixedModel',{
        extend: 'Ext.data.Model',
    
        onClassExtended: function(cls, data) {
             // var onBeforeClassCreated = data.onBeforeClassCreated;
            var onBeforeClassCreated = function(cls, data) {
                onClassCreated = data.onClassCreated;
                delete data.onBeforeClassCreated;
                delete data.onClassCreated;
                cls.implement(data);
                onClassCreated.call(cls, cls);
            };
    
    
            data.onBeforeClassCreated = function(cls, data) {
                var me = this,
                    name = Ext.getClassName(cls),
                    prototype = cls.prototype,
                    superCls = cls.prototype.superclass,
    
                    validations = data.validations || [],
                    fields = data.fields || [],
                    associations = data.associations || [],
                    belongsTo = data.belongsTo,
                    hasMany = data.hasMany,
                    idgen = data.idgen,
    
                    fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
                        return field.name;
                    }),
    
                    associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
                        return association.name;
                    }),
    
                    superValidations = superCls.validations,
                    superFields = superCls.fields,
                    superAssociations = superCls.associations,
    
                    association, i, ln,
                    dependencies = [];
    
                // Save modelName on class and its prototype
                cls.modelName = name;
                prototype.modelName = name;
    
                // Merge the validations of the superclass and the new subclass
                if (superValidations) {
                    validations = superValidations.concat(validations);
                }
    
                data.validations = validations;
    
                // Merge the fields of the superclass and the new subclass
                if (superFields) {
                    fields = superFields.items.concat(fields);
                }
    
                for (i = 0, ln = fields.length; i < ln; ++i) {
                    fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
                }
    
                data.fields = fieldsMixedCollection;
    
                if (idgen) {
                    data.idgen = Ext.data.IdGenerator.get(idgen);
                }
    
                //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
                //we support that here
                if (belongsTo) {
                    belongsTo = Ext.Array.from(belongsTo);
    
                     for (i = 0, ln = belongsTo.length; i < ln; ++i) {
                        association = belongsTo[i];
    
                        if (!Ext.isObject(association)) {
                            association = {model: association};
                        }
    
                        association.type = 'belongsTo';
                        associations.push(association);
                    }
    
                    delete data.belongsTo;
                }
    
                if (hasMany) {
                    hasMany = Ext.Array.from(hasMany);
                    for (i = 0, ln = hasMany.length; i < ln; ++i) {
                        association = hasMany[i];
    
                         if (!Ext.isObject(association)) {
                            association = {model: association};
                        }
    
                        association.type = 'hasMany';
                        associations.push(association);
                    }
    
                    delete data.hasMany;
                }
    
                if (superAssociations) {
                     associations = superAssociations.items.concat(associations);
                }
    
                 for (i = 0, ln = associations.length; i < ln; ++i) {
                    dependencies.push('association.' + associations[i].type.toLowerCase());
                }
    
                if (data.proxy) {
                    if (typeof data.proxy === 'string') {
                        dependencies.push('proxy.' + data.proxy);
                    }
                    else if (typeof data.proxy.type === 'string') {
                        dependencies.push('proxy.' + data.proxy.type);
                    }
                }
    
                Ext.require(dependencies, function() {
                    Ext.ModelManager.registerType(name, cls);
    
                    for (i = 0, ln = associations.length; i < ln; ++i) {
                        // association = associations[i];  
                        association = Ext.apply({}, associations[i]);  // <- Create assocaition as new object. Logically, we can use Ext.clone() but in fact it has no effect.
    
                         Ext.apply(association, {
                            ownerModel: name,
                            associatedModel: association.model
                        });
    
                        if (Ext.ModelManager.getModel(association.model) === undefined) {
                            Ext.ModelManager.registerDeferredAssociation(association);
                        } else {
                            associationsMixedCollection.add(Ext.data.Association.create(association));
                        }
                    }
                    data.associations = associationsMixedCollection;
    
                    onBeforeClassCreated.call(me, cls, data);
    
                    cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
    
                     // Fire the onModelDefined template method on ModelManager
                    Ext.ModelManager.onModelDefined(cls);
                });
            };
        }
    });
    2. Define your models by extending them from FixedModel instead of Ext.data.Model
    Code:
    Ext.define('Address', {
        extend: 'FixedModel', // here you can use Ext.data.Model
        fields: [
            {name: 'street1', type: 'string'},
            {name: 'street1', type: 'string'},
            {name: 'city', type: 'string'},
            {name: 'state', type: 'string'},
            {name: 'zip', type: 'string'}
        ]
    });
    Ext.define('Person', {
        extend: 'FixedModel', 
        fields: [
            {name: 'id', type: 'int'},
            {name: 'firstName', type: 'string'},
            {name: 'lastName', type: 'string'}
        ],
        hasMany: [
            {model: 'Address', associationKey: 'addresses', name: 'getAddressStore'}
        ]
    });
    Ext.define('NicePerson', {
        extend: 'Person',
        fields: [
            {name: 'id', type: 'int'},
            {name: 'firstName', type: 'string'},
            {name: 'lastName', type: 'string'},
            {name: 'phoneNumber', type: 'string'}
        ]
    });
    
    
    var theSamePeople = Ext.create('Ext.data.Store', {
        model: 'Person',
        proxy: { type: 'memory', reader: { type: 'json'}},
        data: [
            {id: 1, firstName: 'Brian', lastName: 'M', addresses: [{street1: '1234 My Street', city: 'Austin', state: 'TX', zip: '77777'}]},
            {id: 2, firstName: 'Miro', lastName: 'P', addresses: [{street1: '1234 Breakaway', city: 'Cedar Park', state: 'TX', zip: '77777'}]},
            {id: 3, firstName: 'Stokes', lastName: 'J', addresses: [{street1: '1234 Vance', city: 'Jonestown', state: 'TX', zip: '77777'}]}
        ]
    });

  4. #3
    Sencha Premium Member
    Join Date
    Jul 2011
    Posts
    16
    Vote Rating
    1
    Stokes is on a distinguished road

      0  

    Default


    Vietits,

    Thanks! That got me closer (I think), but I still get an error, this time when it's creating the Reader of the first Store. I've seen this kind of thing before too, but don't understand what causes it.

    Code:
    TypeError: Cannot read property 'observable' of undefined
        at new <anonymous> (ext-all-debug-w-comments.js:57742:18)
        at [object Object].readAssociated (ext-all-debug-w-comments.js:57950:34)
        at [object Object].<anonymous> (ext-all-debug-w-comments.js:57917:20)
        at [object Object].<anonymous> (ext-all-debug-w-comments.js:6084:32)
        at [object Object].extractData (ext-all-debug-w-comments.js:58517:21)
        at [object Object].<anonymous> (ext-all-debug-w-comments.js:57870:30)
        at [object Object].<anonymous> (ext-all-debug-w-comments.js:6084:32)
        at [object Object].readRecords (ext-all-debug-w-comments.js:58449:21)
        at [object Object].read (ext-all-debug-w-comments.js:57795:82)
        at [object Object].read (ext-all-debug-w-comments.js:56075:29)
    Stokes.

  5. #4
    Sencha Premium Member
    Join Date
    Jul 2011
    Posts
    16
    Vote Rating
    1
    Stokes is on a distinguished road

      0  

    Default


    Actually, I spoke too soon. When I looked more carefully, your version of Ext.data.Model is different than the one I have in 4.1rc3. So I applied your change to my Ext.data.Model extension as follows:
    Code:
    Ext.define('FixedModel',{
        extend: 'Ext.data.Model',
        requires: [
            'Ext.util.Observable'
        ],
        onClassExtended: function(cls, data, hooks) {
            var onBeforeClassCreated = hooks.onBeforeCreated;
    
            hooks.onBeforeCreated = function(cls, data) {
                var me = this,
                    name = Ext.getClassName(cls),
                    prototype = cls.prototype,
                    superCls = cls.prototype.superclass,
    
                    validations = data.validations || [],
                    fields = data.fields || [],
                    associations = data.associations || [],
                    belongsTo = data.belongsTo,
                    hasMany = data.hasMany,
                    hasOne = data.hasOne,
                    addAssociations = function(items, type) {
                        var i = 0,
                            len,
                            item;
    
                        if (items) {
                            items = Ext.Array.from(items);
    
                            for (len = items.length; i < len; ++i) {
                                item = items[i];
    
                                if (!Ext.isObject(item)) {
                                    item = {model: item};
                                }
    
                                item.type = type;
                                associations.push(item);
                            }
                        }
                    },
                    idgen = data.idgen,
    
                    fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
                    associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
                    superValidations = superCls.validations,
                    superFields = superCls.fields,
                    superAssociations = superCls.associations,
    
                    association, i, ln,
                    dependencies = [],
                    idProperty = data.idProperty || cls.prototype.idProperty,
                    fieldConvertSortFn = Ext.Function.bind(
                        fieldsMixedCollection.sortBy,
                        fieldsMixedCollection,
                        [prototype.sortConvertFields], false),
    
                // Use the proxy from the class definition object if present, otherwise fall back to the inherited one, or the default
                    clsProxy = data.proxy || cls.prototype.proxy || cls.prototype.defaultProxyType;
    
                // Save modelName on class and its prototype
                cls.modelName = name;
                prototype.modelName = name;
    
                // Merge the validations of the superclass and the new subclass
                if (superValidations) {
                    validations = superValidations.concat(validations);
                }
    
                data.validations = validations;
    
                // Merge the fields of the superclass and the new subclass
                if (superFields) {
                    fields = superFields.items.concat(fields);
                }
    
                fieldsMixedCollection.on({
                    add: fieldConvertSortFn,
                    replace: fieldConvertSortFn
                });
    
                for (i = 0, ln = fields.length; i < ln; ++i) {
                    fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
                }
                if (!fieldsMixedCollection.get(idProperty)) {
                    fieldsMixedCollection.add(new Ext.data.Field(idProperty));
                }
                data.fields = fieldsMixedCollection;
    
                if (idgen) {
                    data.idgen = Ext.data.IdGenerator.get(idgen);
                }
    
                //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
                //we support that here
                addAssociations(data.belongsTo, 'belongsTo');
                delete data.belongsTo;
                addAssociations(data.hasMany, 'hasMany');
                delete data.hasMany;
                addAssociations(data.hasOne, 'hasOne');
                delete data.hasOne;
    
                if (superAssociations) {
                    associations = superAssociations.items.concat(associations);
                }
    
                for (i = 0, ln = associations.length; i < ln; ++i) {
                    dependencies.push('association.' + associations[i].type.toLowerCase());
                }
    
                // If we have not been supplied with a Proxy *instance*, then add the proxy type to our dependency list
                if (clsProxy && !clsProxy.isProxy) {
                    dependencies.push('proxy.' + (typeof clsProxy === 'string' ? clsProxy : clsProxy.type));
                }
    
                Ext.require(dependencies, function() {
                    Ext.ModelManager.registerType(name, cls);
    
                    for (i = 0, ln = associations.length; i < ln; ++i) {
                        // BEGIN: override
                        //association = associations[i];
                        association = Ext.apply({}, associations[i]);  // <- Create assocaition as new object. Logically, we can use Ext.clone() but in fact it has no effect.
                        // END: override
    
                        Ext.apply(association, {
                            ownerModel: name,
                            associatedModel: association.model
                        });
    
                        if (Ext.ModelManager.getModel(association.model) === undefined) {
                            Ext.ModelManager.registerDeferredAssociation(association);
                        } else {
                            associationsMixedCollection.add(Ext.data.association.Association.create(association));
                        }
                    }
    
                    data.associations = associationsMixedCollection;
    
                    // onBeforeCreated may get called *asynchronously* if any of those required classes caused
                    // an asynchronous script load. This would mean that the class definition object
                    // has not been applied to the prototype when the Model definition has returned.
                    // The Reader constructor does not attempt to buildExtractors if the fields MixedCollection
                    // has not yet been set. The cls.setProxy call triggers a build of extractor methods.
                    onBeforeClassCreated.call(me, cls, data, hooks);
    
                    cls.setProxy(clsProxy);
    
                    // Fire the onModelDefined template method on ModelManager
                    Ext.ModelManager.onModelDefined(cls);
                });
            };
        }
    });
    But I still get an error.
    Code:
    TypeError: Cannot call method 'toLowerCase' of undefined
        at Function.<anonymous> (ext-all-debug-w-comments.js:69715:73)
        at FixedModel.js:145:38
        at [object Object].require (ext-all-debug-w-comments.js:9391:26)
        at Object.require (ext-all-debug-w-comments.js:2926:39)
        at Function.<anonymous> (FixedModel.js:117:17)
        at Function.doProcess (ext-all-debug-w-comments.js:6501:39)
        at Function.doProcess (ext-all-debug-w-comments.js:6506:20)
        at ext-all-debug-w-comments.js:9727:24
        at [object Object].require (ext-all-debug-w-comments.js:9391:26)
        at Function.<anonymous> (ext-all-debug-w-comments.js:9694:16)
    It's failing in the Ext.define() of my first subclass of FixedModel, but on this line in Ext.data.Model.onClassExtended().
    Code:
                for (i = 0, ln = associations.length; i < ln; ++i) {
                    dependencies.push('association.' + associations[i].type.toLowerCase());
                }
    Was the intent for both onClassExtended() functions to get called?

    Thanks,
    Stokes.

  6. #5
    Sencha - Community Support Team
    Join Date
    Jan 2012
    Posts
    1,376
    Vote Rating
    115
    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


    My previous fix is for Ext 4.0.7. Below is my fix for Ext 4.1.0-rc3.
    Code:
    Ext.define('FixedModel', {
        extend: 'Ext.data.Model',
    
    
        onClassExtended: function(cls, data, hooks) {
            // var onBeforeClassCreated = hooks.onBeforeCreated;
            var onBeforeClassCreated = function(Class, data, hooks){
                Class.addMembers(data);
                hooks.onCreated.call(Class, Class);
            }
    
    
            hooks.onBeforeCreated = function(cls, data) {
                var me = this,
                    name = Ext.getClassName(cls),
                    prototype = cls.prototype,
                    superCls = cls.prototype.superclass,
    
    
                    validations = data.validations || [],
                    fields = data.fields || [],
                    associations = data.associations || [],
                    belongsTo = data.belongsTo,
                    hasMany = data.hasMany,
                    hasOne = data.hasOne,
                    addAssociations = function(items, type) {
                        var i = 0,
                            len,
                            item;
    
    
                        if (items) {
                            items = Ext.Array.from(items);
    
    
                            for (len = items.length; i < len; ++i) {
                                item = items[i];
    
    
                                if (!Ext.isObject(item)) {
                                    item = {model: item};
                                }
    
    
                                item.type = type;
                                associations.push(item);
                            }
                        }
                    },
                    idgen = data.idgen,
    
    
                    fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
    
                    associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
    
                    superValidations = superCls.validations,
                    superFields = superCls.fields,
                    superAssociations = superCls.associations,
    
    
                    association, i, ln,
                    dependencies = [],
                    idProperty = data.idProperty || cls.prototype.idProperty,
                    fieldConvertSortFn = Ext.Function.bind(
                        fieldsMixedCollection.sortBy, 
                        fieldsMixedCollection,
                        [prototype.sortConvertFields], false),
    
    
                    // Use the proxy from the class definition object if present, otherwise fall back to the inherited one, or the default    
                    clsProxy = data.proxy || cls.prototype.proxy || cls.prototype.defaultProxyType;
    
    
                // Save modelName on class and its prototype
                cls.modelName = name;
                prototype.modelName = name;
    
    
                // Merge the validations of the superclass and the new subclass
                if (superValidations) {
                    validations = superValidations.concat(validations);
                }
    
    
                data.validations = validations;
    
    
                // Merge the fields of the superclass and the new subclass
                if (superFields) {
                    fields = superFields.items.concat(fields);
                }
    
    
                fieldsMixedCollection.on({
                    add: fieldConvertSortFn,
                    replace: fieldConvertSortFn
                });  
    
    
                for (i = 0, ln = fields.length; i < ln; ++i) {
                    fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
                }
                if (!fieldsMixedCollection.get(idProperty)) {
                    fieldsMixedCollection.add(new Ext.data.Field(idProperty));
                }
                data.fields = fieldsMixedCollection;
    
    
                if (idgen) {
                    data.idgen = Ext.data.IdGenerator.get(idgen);
                }
    
    
                //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
                //we support that here
                addAssociations(data.belongsTo, 'belongsTo');
                delete data.belongsTo;
                addAssociations(data.hasMany, 'hasMany');
                delete data.hasMany;
                addAssociations(data.hasOne, 'hasOne');
                delete data.hasOne;
    
    
                if (superAssociations) {
                    associations = superAssociations.items.concat(associations);
                }
    
    
                for (i = 0, ln = associations.length; i < ln; ++i) {
                    dependencies.push('association.' + associations[i].type.toLowerCase());
                }
    
    
                // If we have not been supplied with a Proxy *instance*, then add the proxy type to our dependency list
                if (clsProxy && !clsProxy.isProxy) {
                    dependencies.push('proxy.' + (typeof clsProxy === 'string' ? clsProxy : clsProxy.type));
                }
    
    
                Ext.require(dependencies, function() {
                    Ext.ModelManager.registerType(name, cls);
    
    
                    for (i = 0, ln = associations.length; i < ln; ++i) {                    // association = associations[i];
    
                        association = Ext.apply({},associations[i]);
    
    
    
                        Ext.apply(association, {
                            ownerModel: name,
                            associatedModel: association.model
                        });
    
    
                        if (Ext.ModelManager.getModel(association.model) === undefined) {
                            Ext.ModelManager.registerDeferredAssociation(association);
                        } else {
                            associationsMixedCollection.add(Ext.data.association.Association.create(association));
                        }
                    }
    
    
                    data.associations = associationsMixedCollection;
    
    
                    // onBeforeCreated may get called *asynchronously* if any of those required classes caused
                    // an asynchronous script load. This would mean that the class definition object
                    // has not been applied to the prototype when the Model definition has returned.
                    // The Reader constructor does not attempt to buildExtractors if the fields MixedCollection
                    // has not yet been set. The cls.setProxy call triggers a build of extractor methods.
                    onBeforeClassCreated.call(me, cls, data, hooks);
                    
                    cls.setProxy(clsProxy);
    
    
                    // Fire the onModelDefined template method on ModelManager
                    Ext.ModelManager.onModelDefined(cls);
                });
            };
        }
    });
    BTW: I have posted this bug at http://www.sencha.com/forum/showthre...h-associations

  7. #6
    Sencha Premium Member
    Join Date
    Jul 2011
    Posts
    16
    Vote Rating
    1
    Stokes is on a distinguished road

      0  

    Default


    Works perfectly for me. Thanks!

  8. #7
    Sencha Premium Member
    Join Date
    Jan 2011
    Posts
    17
    Vote Rating
    0
    oilid is on a distinguished road

      0  

    Default


    Hey vietits,

    is there maybe a solution/workaround for 4.1.0 final? I couldn't adapt your solution for Ext 4.1.0-rc3 to work with the 4.1.0 final release :-(

    Thanks and regards,
    Oilid

  9. #8
    Sencha - Community Support Team
    Join Date
    Jan 2012
    Posts
    1,376
    Vote Rating
    115
    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


    Try this. Hope that it will solve your problem.
    Code:
    Ext.define('FixedModel', {
        extend: 'Ext.data.Model',
    
    
        onClassExtended: function(cls, data, hooks) {
            var onBeforeClassCreated = function(Class, data, hooks){
                Class.addMembers(data);
                hooks.onCreated.call(Class, Class);
            };
            hooks.onBeforeCreated = function(cls, data) {
                var me = this,
                    name = Ext.getClassName(cls),
                    prototype = cls.prototype,
                    superCls = cls.prototype.superclass,
    
    
                    validations = data.validations || [],
                    fields = data.fields || [],
                    associations = data.associations || [],
                    addAssociations = function(items, type) {
                        var i = 0,
                            len,
                            item;
    
    
                        if (items) {
                            items = Ext.Array.from(items);
    
    
                            for (len = items.length; i < len; ++i) {
                                item = items[i];
    
    
                                if (!Ext.isObject(item)) {
                                    item = {model: item};
                                }
    
    
                                item.type = type;
                                associations.push(item);
                            }
                        }
                    },
                    idgen = data.idgen,
    
    
                    fieldsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
    
                    associationsMixedCollection = new Ext.util.MixedCollection(false, prototype.itemNameFn),
    
    
                    superValidations = superCls.validations,
                    superFields = superCls.fields,
                    superAssociations = superCls.associations,
    
    
                    association, i, ln,
                    dependencies = [],
                    idProperty = data.idProperty || cls.prototype.idProperty,
    
    
                    // Process each Field upon add into the collection
                    onFieldAddReplace = function(arg0, arg1, arg2) {
                        var newField,
                            pos;
    
    
                        if (fieldsMixedCollection.events.add.firing) {
                            // Add event signature is (position, value, key);
                            pos = arg0;
                            newField  = arg1;
                        } else {
                            // Replace event signature is (key, oldValue, newValue);
                            newField = arg2;
                            pos = arg1.originalIndex;
                        }
    
    
                        // Set the originalIndex for ArrayReader to get the default mapping from in case
                        // compareConvertFields changes the order due to some fields having custom convert functions.
                        newField.originalIndex = pos;
    
    
                        // The field(s) which encapsulates the idProperty must never have a default value set
                        // if no value arrives from the server side. So override any possible prototype-provided
                        // defaultValue with undefined which will inhibit generation of defaulting code in Reader.buildRecordDataExtractor
                        if (newField.mapping === idProperty || (newField.mapping == null && newField.name === idProperty)) {
                            newField.defaultValue = undefined;
                        }
                    },
    
    
                    // Use the proxy from the class definition object if present, otherwise fall back to the inherited one, or the default    
                    clsProxy = data.proxy || cls.prototype.proxy || cls.prototype.defaultProxyType,
    
    
                    // Sort upon add function to be used in case of dynamically added Fields
                    fieldConvertSortFn = function() {
                        fieldsMixedCollection.sortBy(prototype.compareConvertFields);
                    };
    
    
                // Save modelName on class and its prototype
                cls.modelName = name;
                prototype.modelName = name;
    
    
                // Merge the validations of the superclass and the new subclass
                if (superValidations) {
                    validations = superValidations.concat(validations);
                }
    
    
                data.validations = validations;
    
    
                // Merge the fields of the superclass and the new subclass
                if (superFields) {
                    fields = superFields.items.concat(fields);
                }
    
    
                fieldsMixedCollection.on({
                    add:     onFieldAddReplace,
                    replace: onFieldAddReplace
                });  
    
    
                for (i = 0, ln = fields.length; i < ln; ++i) {
                    fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
                }
                if (!fieldsMixedCollection.get(idProperty)) {
                    fieldsMixedCollection.add(new Ext.data.Field(idProperty));
                }
    
    
                // Ensure the Fields are on correct order: Fields with custom convert function last
                fieldConvertSortFn();
                fieldsMixedCollection.on({
                    add:     fieldConvertSortFn,
                    replace: fieldConvertSortFn
                });
    
    
                data.fields = fieldsMixedCollection;
    
    
                if (idgen) {
                    data.idgen = Ext.data.IdGenerator.get(idgen);
                }
    
    
                //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
                //we support that here
                addAssociations(data.belongsTo, 'belongsTo');
                delete data.belongsTo;
                addAssociations(data.hasMany, 'hasMany');
                delete data.hasMany;
                addAssociations(data.hasOne, 'hasOne');
                delete data.hasOne;
    
    
                if (superAssociations) {
                    associations = superAssociations.items.concat(associations);
                }
    
    
                for (i = 0, ln = associations.length; i < ln; ++i) {
                    dependencies.push('association.' + associations[i].type.toLowerCase());
                }
    
    
                // If we have not been supplied with a Proxy *instance*, then add the proxy type to our dependency list
                if (clsProxy && !clsProxy.isProxy) {
                    dependencies.push('proxy.' + (typeof clsProxy === 'string' ? clsProxy : clsProxy.type));
                }
    
    
                Ext.require(dependencies, function() {
                    Ext.ModelManager.registerType(name, cls);
    
    
                    for (i = 0, ln = associations.length; i < ln; ++i) {
                        association = Ext.apply({}, associations[i]);
    
    
                        Ext.apply(association, {
                            ownerModel: name,
                            associatedModel: association.model
                        });
    
    
                        if (Ext.ModelManager.getModel(association.model) === undefined) {
                            Ext.ModelManager.registerDeferredAssociation(association);
                        } else {
                            associationsMixedCollection.add(Ext.data.association.Association.create(association));
                        }
                    }
    
    
                    data.associations = associationsMixedCollection;
    
    
                    // onBeforeCreated may get called *asynchronously* if any of those required classes caused
                    // an asynchronous script load. This would mean that the class definition object
                    // has not been applied to the prototype when the Model definition has returned.
                    // The Reader constructor does not attempt to buildExtractors if the fields MixedCollection
                    // has not yet been set. The cls.setProxy call triggers a build of extractor methods.
                    onBeforeClassCreated.call(me, cls, data, hooks);
    
    
                    cls.setProxy(clsProxy);
    
    
                    // Fire the onModelDefined template method on ModelManager
                    Ext.ModelManager.onModelDefined(cls);
                });
            };
        }
    });

  10. #9
    Sencha Premium Member
    Join Date
    Jan 2011
    Posts
    17
    Vote Rating
    0
    oilid is on a distinguished road

      0  

    Default


    Hey vietits,

    thanks. This works for the associations. But I have a similiar bug/problem with the convert-function from Ext.data.Field:

    Code:
    Uncaught TypeError: Object #<Object> has no method 'convert'
    Ext.define.constructor ext-all-debug.js:37449
    constructor ext-all-debug.js:3892
    hooks.onBeforeCreated Overrides.js:136
    Ext.apply.doProcess ext-all-debug.js:4003
    Ext.apply.doProcess ext-all-debug.js:4008
    Manager.registerPostprocessor.uses ext-all-debug.js:6037
    Ext.apply.require ext-all-debug.js:5771
    Manager.registerPostprocessor.uses ext-all-debug.js:6004
    Ext.apply.doProcess ext-all-debug.js:4007
    Ext.apply.doProcess ext-all-debug.js:4008
    Ext.apply.process ext-all-debug.js:3995
    Ext.Class.ExtClass ext-all-debug.js:3911
    Ext.ClassManager.create ext-all-debug.js:4676
    Ext.apply.define ext-all-debug.js:5095
    (anonymous function) NicePerson.js:1
    The Overrides.js defines your override with the FixedModel.

    I implemented something like this:
    Code:
    Ext.define('Person', {
        extend: 'FixedModel',
        fields: [
            'id',
            'firstName',
            'lastName'
        ]
    });
    
    Ext.define('NicePerson', {
        extend: 'Person',
        fields: [
            {name: 'fullName',
                convert: function(value, record) {
                    return record.get('firstName') + " " + record.get('lastName');
                }
            }
        ]
    });
    Do you maybe know how to fix this bug/problem in ExtJS 4.1.0?

    Thank you again!
    Last edited by oilid; 27 Apr 2012 at 4:47 AM. Reason: remove name and type property in fields because this could causes the problem/bug.

  11. #10
    Sencha Premium Member
    Join Date
    Jan 2011
    Posts
    17
    Vote Rating
    0
    oilid is on a distinguished road

      0  

    Default


    Ah, I already found this bug report and the corresponding override:
    http://www.sencha.com/forum/showthre...099&viewfull=1

    This helps ;-)