ExtJS 4: Models with Associations and Stores
Asked Answered
B

4

32

Introduction

I'm facing an application design problem with the Ext.data.Model class in ExtJS. I will try to develop my ideas to a very common online store scenario here, so you can follow me. I would really appreciate any comments on my thoughts and conclusions!

Models

Let's suppose you want to map the fact that "Every customer can order multiple products" to ExtJS. From the bare words one can identify these three models involved: Customer, Order, and Product. The Order in this case is what connects Customers and Products.

Associations

I found that ExtJS actually allows you to specify this (Customer)1-n(Order)1-n(Product) relation using the Ext.data.HasManyAssociation and Ext.data.BelongsToAssociation classes. But is this what one wants? Would you want to that a Product always belongs to an Order? What if you want to have a list of Products without any connection to Orders whatsoever?

Stores

This is where it get's more ExtJS specific. In ExtJS you have Ext.data.Stores to hold all your data. To me a natural way to organize my data is to have an Ext.data.Store for each of my models:

  1. CustomerStore
  2. OrderStore
  3. ProductStore

Consider having a three Ext.grid.Panels side-by-side; one for each store. When selecting a customer in the one grid, his orders automatically show up in the second grid. When selecting an order in the second grid the associated products appear in the third grid.

Does this sound natural to you? If not, please comment!

Bringing it all together

So now we have three things that we need to bring together:

  1. Models and their
  2. Associations (hasMany, belongsTo) and the
  3. Data (Stores)

Is it possible to define an association only from one side of a Model-Model relation? For instance, can I specify that an Order hasMany Products but leave out that a Product belongsTo an Order? Because a Product can actually belong to more than one Order. Therefore I specify that the Product model hasMany Orders below.

Here are the models in ExtJS:

Customer

Ext.define('Customer', {
    extend   : 'Ext.data.Model',
    requires : [
        'Order',
    ],
    fields   : [
           {name : 'id',          type : 'int'},
           {name : 'lastname',    type : 'string'}
           {name : 'firstname',   type : 'string'}
    ],
    hasMany: 'Order' /* Generates a orders() method on every Customer instance */
});

Order

Ext.define('Order', {
    extend   : 'Ext.data.Model',
    fields   : [
            {name : 'id',          type : 'int'},
            {name : 'customer_id', type : 'int'}, /* refers to the customer that this order belongs to*/
            {name : 'date',        type : 'date'}
    ],
    belongsTo: 'Customer', /* Generates a getCustomer method on every Order instance */
    hasMany: 'Product' /* Generates a products() method on every Order instance */
});

Product

Ext.define('Product', {
    extend   : 'Ext.data.Model',
    fields   : [
            {name : 'id',          type : 'int'},
            {name : 'name',        type : 'string'},
            {name : 'description', type : 'string'},
            {name : 'price',       type : 'float'}
    ],
    /*
        I don't specify the relation to the "Order" model here
        because it simply doesn't belong here.

        Will it still work?
    */
    hasMany: 'Order'
});

And here are the stores:

CustomerStore

Ext.define('CustomerStore', {
    extend      : 'Ext.data.Store',
    storeId     : 'CustomerStore',
    model       : 'Customer',
    proxy   : {
        type   : 'ajax',
        url    : 'data/customers.json',
        reader : {
            type           : 'json',
            root           : 'items',
            totalProperty  : 'total'
        }
    }
});

OrderStore

Ext.define('OrderStore', {
    extend      : 'Ext.data.Store',
    storeId     : 'OrderStore',
    model       : 'Order',
    proxy   : {
        type   : 'ajax',
        url    : 'data/orders.json',
        reader : {
            type           : 'json',
            root           : 'items',
            totalProperty  : 'total'
        }
    }
});

ProductStore

Ext.define('ProductStore', {
    extend      : 'Ext.data.Store',
    storeId     : 'ProductStore',
    model       : 'Product',
    proxy   : {
        type   : 'ajax',
        url    : 'data/products.json',
        reader : {
            type           : 'json',
            root           : 'items',
            totalProperty  : 'total'
        }
    }
});

Here is an example (not by me) with companies and their products http://superdit.com/2011/05/23/extjs-load-grid-from-another-grid/ . It uses two Models and two stores but there are no associations define.

Thank you in advance

-Konrad

Burchell answered 8/7, 2011 at 10:2 Comment(1)
You don't need to specify associations, I personally don't.Oblivious
B
15

Konrad. I recently faced with dealing with Models+Associations+Stores. This wasn't very pleasent experience. Here is what I've learned:

Let's say we have Store1, Store2, Model1, Model2, Grid1, Grid2. Grid1 uses Store1, Store1 uses Model1 and similarly Grid2 uses Store2, Store2 uses Model2.

So far all is working, data is loading and so on.

But now we want to add hasMany association to Model1. Ok, we are adding to Model1's config:

hasMany: {model: 'Model2', name: 'model2'}

After this you think that you can populate Store2 with data on Grid1's itemclick by doing something like following:

Grid1.on('itemclick', function(view, item_which_uses_model1){
  //item_which_uses_model1.model2() supposed to return Model2's store
  item_which_uses_model1.model2().load();
}

You are expecting that Grid2 is populated with data on Grid1's itemclick. But nothing happens. Actualy requests are being posted, responses are gotten. But Grid2 is not populated with data.
And after some time you are realising that item_which_uses_model1.model2() IS NOT Store2.

when Model2 "sees" that it has association with Model1 it creats Model1's own store which is not equal to Store2.

It's not cool, because Grid2 is using Store2.

Actualy you can hack it by, for example, adding to Grid2's config:

store: new Model1().model2(),

but what if you are using ExtJS mvc pattern. Grid2 doen't have to know anything about Model1.

I don't recommend using associations in real world projects. At least now. Instead use approach which is shown in your example: Model+Store+Filtering.

Beacon answered 31/7, 2011 at 8:51 Comment(4)
Hi Molecule, thank you for your answer. I've created a github repo with my sourcecode. It is a complete working example of the problem I've described above, although the filtering works only on second load of the store. It's a bit itchy but sort of gives the idea. Here the link to the GitHub Repo github.com/kwk/ExtJS-examplesBurchell
Why don't you just use Grid2.reconfigure(item_which_uses_model1.model2()) ?Vitek
@Neil, because my store2 is not a simple store, it is extended one. If I do (item_which_uses_model1.model2()) it substitutes my tuned store with new plain store.Beacon
exactly the issue I had.Ettaettari
V
9

It all works fine in ExtJS 4, it is just hard to get right the first time. Well, the first few times.

Yes, you can avoid specifying the belongsTo association of the child, and still use it in a Has Many relationship.

In my project, we have dozens of models, many with deep relationships, and it works great.

Regarding Many-Many relationships, you should either have

Customer -< CustomerOrder >- Order

Which is three models,

Or you can "fake" it by doing a bunch of work on the server to pretend that a customer HasMany Orders. Nested json can help here.

Vitek answered 24/5, 2012 at 20:1 Comment(6)
Vote up for answer. Thanks to your blog, loading nested models is not a problem anymore. Is it possible to save a model which has hasMany association? as I see, nested child data is not submitted, when I save parent model.Dative
It is possible. You need to override JsonWriter.getRecordData() and in that call Ext.apply(record.data, record.getAssociatedData()); then return record.dataVitek
thanks @Neil. Though override is also an option, I ended up with added required fields directly to my model before submit (I am optimistic about Ext JS development :) and hope they will implement nested model saving in near future)Dative
@Dative ... What is the link to the blog? I am interested in seeing the implementation using JsonWriter.getRecordData().Custom
@MacGyver here you go extjs-tutorials.blogspot.ca/2012/05/…Vitek
@MacGyver I don't think I mention getRecordData() in my blog. I think I saw it originally on the Sencha forums (a couple of years ago)Vitek
A
3

i fully agree with Molecular Man. By reading the ExtJS documenation it appeared more as evidence that this is a good route when you are making use of a tree set structure distributed to different stores. Frankly, this all could be true only if such association had in any way a reference of stores to make calls. That's not the case from what I have tested. Model associations don't have such relationship. Filtering is the best way to keep up with sequentially updating other grids whose models depend on others.

Ailing answered 24/5, 2012 at 14:58 Comment(0)
V
0

Please note that your assumption:

(Customer)1-n(Order)1-n(Product)

is wrong. Your actual model should be:

(Customer)1-n(Order)n-n(Product)

That's why you found also difficulties in modeling it with hasMany-belongsTo

Vineyard answered 23/1, 2013 at 8:44 Comment(1)
For 1 customer it's n-orders with n-products in each. Relations n-n would appear in total overview, where it would be ever Customer n-n Order n-n Product. In any case, every developer was waiting for cross-link within table records, not for creating nested object of type MyRecordMimicry

© 2022 - 2024 — McMap. All rights reserved.