3
votes

Let's say I have a model called Vehicle

Ext.define('AM.model.Vehicle', {
    extend: 'Ext.data.Model',
    fields: [
        {name: 'brandId',  type: 'int'}, 
        {name: 'brandName',  type: 'string'},
        {name: 'modelId',  type: 'int'}, 
        {name: 'modelName',  type: 'string'},
        {name: 'yearOfRelease',  type: 'int'}
    ]

});

As you can see, the vehicle model has fields "brandId", "brandName", etc. For example, I want to edit an instance of this model. I create an edit form, which has combobox linked to 'brandId' field. Then I save the form values to a model instance with this

values = form.getValues();
record.set(values);

It all works fine, but there is a problem with all the fields that represent some outer models: only id's are updated, while all other fields, that depend on id remain the same.

In a regular OOP language(Java for example), I would create a class CarBrand and put an instance of this class inside the Vehicle class. In ExtJs 4 they have hasMany relationship, but don't have hasOne.

What is the best approach in ExtJS 4 for such nested models?

2

2 Answers

10
votes

4.0.* doesn't support the hasOne relationship. 4.1 is meant to support it.

for 4.0.7 I have the following override in place.

Ext.define('HOD.overrides.Form', {}, function() {

/*
 * Implementing a nested setValues for forms with
 * arrays in them.
 */
Ext.override(Ext.form.Basic, {
    setValues: function(values, arrayField) {
        var me = this;

        function setVal(fieldId, val) {
            if (arrayField) {
                fieldId = arrayField + '.' + fieldId;
            }
            var field = me.findField(fieldId);
            if (field) {
                field.setValue(val);
                if (me.trackResetOnLoad) {
                    field.resetOriginalValue();
                }
            } else if(Ext.isObject(val)) {
                me.setValues(val, fieldId);
            }
        }

        if (Ext.isArray(values)) {
            // array of objects
            Ext.each(values, function(val) {
                setVal(val.id, val.value);
            });
        } else {
            // object hash
            Ext.iterate(values, setVal);
        }
        return this;
    },
    /**
     * Persists the values in this form into the passed {@link Ext.data.Model} object in a beginEdit/endEdit block.
     * @param {Ext.data.Model} record The record to edit
     * @return {Ext.form.Basic} this
     */
    updateRecord: function(record) {
        var values = this.getFieldValues(),
        name,
        obj = {};

        function populateObj(record, values) {
            var obj = {},
            name;

            record.fields.each(function(field) {
                name = field.name;
                if (field.model) {
                    var nestedValues = {};
                    var hasValues = false;
                    for(var v in values) {
                        if (v.indexOf('.') > 0) {
                            var parent = v.substr(0, v.indexOf('.'));
                            if (parent == field.name) {
                                var key = v.substr(v.indexOf('.') + 1);
                                nestedValues[key] = values[v];
                                hasValues = true;
                            }
                        }
                    }
                    if (hasValues) {
                        obj[name] = populateObj(Ext.create(field.model), nestedValues);
                    }
                } else if (name in values) {
                    obj[name] = values[name];
                }
            });
            return obj;
        }

        obj = populateObj(record, values);

        record.beginEdit();
        record.set(obj);
        record.endEdit();

        return this;
    }
});

Whats this lets me do is in my forms I create them with names like so.

// Contact details
{
    xtype: 'fieldcontainer',
    defaultType: 'textfield',
    layout: 'anchor',
    fieldDefaults: {
        anchor: '80%',
        allowBlank: true
    },
    items: [

    {
        xtype: 'textfield',
        name: 'homePhone',
        fieldLabel: 'Home phone number'
    },
    {
        xtype: 'textfield',
        name: 'mobilePhone',
        fieldLabel: 'Mobile phone number'
    }]
},
{
    xtype: 'fieldcontainer',
    defaultType: 'textfield',
    layout: 'anchor',
    fieldDefaults: {
        anchor: '80%',
        allowBlank: true
    },
    items: [
    {
        name: 'address.id',
        xtype: 'hidden'
    },
    {
        name: 'address.building',
        fieldLabel: 'Building'
    },
    {
        name: 'address.street',
        fieldLabel: 'Street'
    },
    {
        name: 'address.city',
        fieldLabel: 'City'
    },
    {
        name: 'address.county',
        fieldLabel: 'County'
    },
    {
        name: 'address.postcode',
        fieldLabel: 'Postcode'
    },
    {
        name: 'address.country',
        fieldLabel: 'Country'
    }
    ]
},
]

Notice the . in the name field which lets the overridden setValues and updateRecord form know it needs to map these values to the new model, which is defined in the model like so.

Ext.define('HOD.model.Employee', {
    extend: 'Ext.data.Model',
    fields: [
        // Person Class
        // Auto
        'id',
        'name',
        'homePhone',
        'mobilePhone',
        {model: 'HOD.model.Address', name: 'address'}, //Address
        {model: 'HOD.model.Contact', name: 'iceInformation'} //Person
    ]
});

Ext.define('HOD.model.Address', {
    extend: 'Ext.data.Model',
    fields: [
        'building',
        'street',
        'city',
        'county',
        'postcode',
        'country'
    ],
    belongsTo: 'Employee'
});
2
votes

There is no nested models, however it's not clear from your example why are you doing this. Why do you need to save both id and names in the Vehicle record if you already have that relationship in some other database (I assume you have something like Brand Dictionary where you keep all pairs id/name?).

Update: I would do the following:

  • Keep only brand_id in the vehicle record.
  • When you have a form, present combobox with values from the dictionary store and update brand_id
  • When you have a grid - define custom 'renderer' function for the column and get brand name from the dictionary store by brand_id

Here is sample code for such column:

{ text: 'Brand Name', dataIndex: 'brand_id', 
    renderer: function(value) {
       var rec = Ext.getStore('BrandDict').getById(value);
       return rec ? rec.get('name') : '';
}