4

So I have a parent and child store, illustrated here:

Parent Model

Ext.define('APP.model.Client', {
    extend: 'Ext.data.Model',
    requires: [
        'APP.model.Website', 'Ext.data.association.HasMany', 'Ext.data.association.BelongsTo'],
    fields: [{
        name: 'id',
        type: 'string'
    }, {
        name: 'name',
        type: 'string'
    }, {
        name: 'slug',
        type: 'string'
    }, {
        name: 'active',
        type: 'boolean'
    }, {
        name: 'current',
        type: 'boolean'
    }],
    hasMany: {
        model: 'APP.model.Website',
        name: 'websites'
    }
});

Child Model

Ext.define('APP.model.Website', {
    extend: 'Ext.data.Model',
    fields: [{
        name: 'id',
        type: 'string'
    }, {
        name: 'client_id',
        type: 'string'
    }, {
        name: 'sub_domain',
        type: 'string'
    }, {
        name: 'active',
        type: 'boolean'
    }],
    belongsTo: 'APP.model.Client'
});

Using an AJAX call via the server, I am loading the Clients store, and that is loading fine. But the Websites store isn't populated, and when I breakpoint on the Clients store on.load function, to see what it's populated with, the Client store is only populated with the client data, but in the raw property for that store, I can see all the websites data. So it's being returned correctly, but my extjs isn't correct. Here are the stores:

Client Store

Ext.define('APP.store.Clients', {
    extend: 'Ext.data.Store',
    autoLoad: false,
    model: 'APP.model.Client',
    proxy: {
        type: 'ajax',
        url: '/client/list',
        reader: {
            type: 'json',
            root: 'items'
        }
    },
    sorters: [{
        property: 'name',
        direction: 'ASC'
    }]
});

Websites Store

Ext.define('APP.store.Websites', {
    extend: 'Ext.data.Store',
    requires: ['Ext.ux.Msg'],
    autoLoad: false,
    model: 'APP.model.Website',
    proxy: {
        type: 'ajax',
        url: '/client/list',
        reader: {
            type: 'json',
            root: 'items'
        },
        writer: {
            type: 'json'
        }
    },
    sorters: [{
        property: 'sub_domain',
        direction: 'ASC'
    }]
});

My final result is...I would like to populate both stores so I can click on an element, and when it loads something from the parent store, I can access the child store(s) (there will be more when I figure out this problem) to populate a couple grid(s) in tabs.

What am I missing as far as my setup? I just downloaded extjs4 a couple days ago, so I am on 4.1.

Darin Kolev
  • 3,401
  • 13
  • 31
  • 46
Nathan
  • 2,941
  • 6
  • 49
  • 80
  • 2
    When you use association extjs is not using your Websites store. Instead it creates its own plain store. For more info check out my answer [here](http://stackoverflow.com/a/6888446/842075). – Molecular Man May 25 '12 at 06:52
  • With your example, it seems I would need to do `selectedClient.websites().load()`, but I face the same problem. The associated data isn't being loaded into the store/model properly (note my comment on the answer below), so I get the error `Object has no method 'websites'`. While the data is being returned from the server, it's not being loaded into the model/store correctly, so there is a config problem in one of them. – Nathan May 25 '12 at 14:11
  • Then when I change `hasMany: {model: 'Website', name: 'websites'}` to `hasMany: {model: 'APP.model.Website', name: 'websites'}` I get a new error `Cannot call method 'indexOf' of undefined`. So it looks like it's recognizing the websites() method, but I have a new error now. I will step through the debug file to try and see what's going on. – Nathan May 25 '12 at 14:24
  • It looks as though it when I run this `selectedClient.websites().load()` it finds the correct store with `websites()`, but there is no URL configured in the proxy. As you can see in my definition above, I do in fact have a URL configured on the proxy... – Nathan May 25 '12 at 15:14

2 Answers2

12

Put your proxies in your models, unless you have a good reason not to [1]

Make sure you require the related model(s), either in the same file, or earlier in the application

Use foreignKey if you want to load the related data at will (i.e. with a later network request).

Use associationKey if the related data is loaded in the same (nested) response

Or just use both

Always name your relationships (otherwise the name will be weird if using namespaces).

Always use the fully qualified model name for the model property in your relationships

Working code:

model/Contact.js:

Ext.define('Assoc.model.Contact', {
    extend:'Ext.data.Model',

    requires:[
    'Assoc.model.PhoneNumber' 
    ],

    fields:[
    'name' /* automatically has an 'id' field */
    ],

    hasMany:[
    {
        model:'Assoc.model.PhoneNumber', /*use the fully-qualified name here*/
        name:'phoneNumbers', 
        foreignKey:'contact_id',
        associationKey:'phoneNumbers'
    }
    ],

    proxy:{
    type:'ajax',
    url:'assoc/data/contacts.json',
    reader:{
        type:'json',
        root:'data'
    }
    }
});

model/PhoneNumber.js:

Ext.define('Assoc.model.PhoneNumber', {
    extend:'Ext.data.Model',

    fields:[
    'number',
    'contact_id'
    ],

    proxy:{
    type:'ajax',
    url:'assoc/data/phone-numbers.json',
    reader:{
        type:'json',
        root:'data'
    }
    }
});

data/contacts.json:

{
    "data":[
    {
    "id":1,
    "name":"neil",
    "phoneNumbers":[
    {
        "id":999,
        "contact_id":1,
        "number":"9005551234"
    }
    ]
    }
    ]

}

data/phone-numbers.json

{
    "data":[
    {
    "id":7,
    "contact_id":1,
    "number":"6045551212"
    },
    {
    "id":88,
    "contact_id":1,
    "number":"8009996541"
    },
    ]

}

app.js:

Ext.Loader.setConfig({
    enabled:true
});

Ext.application({

    requires:[
    'Assoc.model.Contact'
    ],

    name:'Assoc',
    appFolder:'Assoc',

    launch:function(){

    /* load child models that are in the response (uses associationKey): */
    Assoc.model.Contact.load(1, {
        success: function(record){
        console.log(record.phoneNumbers());
        }
    });

    /* load child models at will (uses foreignKey). this overwrites child model that are in the first load response */
    Assoc.model.Contact.load(1, {
        success: function(record){
        record.phoneNumbers().load({
            callback:function(){
            console.log(arguments);
            }
        });
        }
    });

    }
});

[1] A store will use its model's proxy. You can always override the store's proxy if need be. You won't be able to use Model.load() if the model has no proxy.

Neil McGuigan
  • 46,580
  • 12
  • 123
  • 152
  • Thanks for the notes & code sample. This really helped! I noticed you don't use `belongsTo` in your PhoneNumber model. Not necessary if the parent model is configured the way you have it? – Nathan May 25 '12 at 18:59
  • You only need to use belongsTo if you are trying to reference the parent from the child. You don't need it to reference the child from the parent. – Neil McGuigan May 25 '12 at 19:04
  • see http://extjs-tutorials.blogspot.ca/2012/05/extjs-belongsto-association-rules.html – Neil McGuigan May 25 '12 at 19:22
  • Thanks your solution here helped me find my prob. BEWARE deriving your store class from ArrayStore i.e. `extend: 'Ext.data.ArrayStore'`. You will wind up with an `Ext.data.reader.Array` which will silently override your configured Json reader and leave you scratching your head for hours as your nested data loads but you parent data is uninitialised. – Ilan Jul 19 '13 at 20:04
0

Your assumption is wrong. You expect that the WebSite store loads itself but that is not how it works. What you can do is the following (this is untested but is how I do it in all my projects):

In your clients grid add a listener for the itemclick event to call the following method (showWebSites). It will receive the selected client record containing the selected APP.model.Client instance. Then, and given that each client has a set of WebSites, the method will load the APP.store.Websites store and the client´s websites will be displayed in your view.

showWebSites: function (sender, selectedClient) {
    Ext.StoreManager.lookup('APP.store.Websites')
       .loadData(selectedClient.data.WebSites);
}

This is the way. I hope you find it useful.

lontivero
  • 5,235
  • 5
  • 25
  • 42
  • That makes sense, but nothing is contained within `selectedClient.data.websites`. HOWEVER, the data is there in `selectedClient.raw.websites`, which tells me the data is being returned from the server, but not loaded properly. So something in my models/stores is incorrect. – Nathan May 25 '12 at 14:05
  • I will add, that loading it using `selectedClient.raw.websites` works, but does not seem like the correct solution – Nathan May 25 '12 at 17:24