Pages

Friday, May 25, 2012

Rules for HasMany Associations in ExtJS

  1. Always put your Proxies in your Models, not your Stores, unless you have a very good reason not to *
  2. Always require your child models if using them in hasMany relationships. ** 
  3. Always use foreignKey if you want to load the children at will
  4. Always use associationKey if you return the children in the same response as the parent
  5. You can use both foreignKey and associationKey if you like
  6. Always name your hasMany relationships
  7. Always use fully qualified model names in your hasMany relationship
  8. Consider giving the reader root a meaningful name (other than "data")
  9. The child model does not need a belongsTo relationship for the hasMany to work

Example:
Ext.define('Assoc.model.Contact', {

    extend:'Ext.data.Model',

    requires:[
        'Assoc.model.PhoneNumber'          /* rule 2 */
    ],

    fields:[
        'name'                             /* id field is inherited from Ext.data.Model */
    ],

    hasMany:[
    {
        foreignKey: 'contact_id',          /* rule 3, 5 */
        associationKey: 'phoneNumbers',    /* rule 4, 5 */
        name: 'phoneNumbers',              /* rule 6 */
        model: 'Assoc.model.PhoneNumber'   /* rule 7 */
}
    ],

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

// example usage:

var c = new Assoc.model.Contact({id:99});

/*
 * assuming that PhoneNumber.proxy is AJAX and has a url set to 'phonenumbers', the below function would make an http request like this:
 * /phonenumbers?page=1&start=0&limit=25&filter=[{"property":"contact_id","value":"99"}]
 * ie, it would make a request for all phone numbers, but ask the server to filter them by the contact_id of the Contact, which is 99 in this case
 */
c.phoneNumbers().load(); 

* The store will inherit its model's proxy, and you can always override it
** To make it easy, and avoid potential circular references, you can require them in app.js .

32 comments:

  1. works ok, without proxy in model.
    its stupid to bind model to proxy, why would you use MVC then at all?

    ReplyDelete
    Replies
    1. 1. Be polite and kind or I will ban you.

      2. It's easier to load a single record if the proxy is on the model

      3. The store inherits the model's proxy, and you can always override it.

      4. If you have two stores that use the same model, you are duplicating code by putting the same proxy in both stores.

      5. Whether a proxy is on a model or on a list of models (a store) has absolutely nothing to do with MVC.

      Delete
  2. ban anonymous?

    2. its easier, its wrong pattern.
    Why - not composable.
    You mixing concerns and putting too much knowledge into data representation.

    Representation and operation/action - different things.

    3.4. You mixing causality here.
    Its not model responsibility - it responsibility of dependency resolver to inject right Proxy. Proxy should be injectable as a dependency.

    5. Really?
    Let say like this, i enjoy your blog for ExtJS gritty nitty details.
    But its pitty that extjs mvc community does not have any strong OOP evangelists, and that's why this framework has so messy details leaking all over the place.
    And that's the reason i need those nitty gritty details. Sigh.

    In this regard i recommend to see the reasoning behind mvvm.

    ReplyDelete
    Replies
    1. I am receiving json data where each "entity" is the same and it contains a type to differentiate them, because some are children of others. For that reason each entity also holds a collection of its children as a string formed with comma separated ids to the children.
      I need to build a panel with some rectangles at each of the 3 levels, representing the entities at each level.
      How would any of you attack this challenge?

      Delete
  3. p.s. your blog is still awesome, and no offense meant to you personally.
    ExtJs inconsistency just pissing me off.

    As an example - assigning rootNode to 'Ext.tree.Panel' works differently for node from treeStore vs Store.
    Moreover, events not getting relayed in treeStore case back into store.
    treePanel.getView().setRootNode(treeStore.getRootNode());

    versus

    treePanel.setRootNode(store.getRootNode());

    ReplyDelete
  4. I hear what you are saying. They are sort of using the ActiveRecord pattern with their models and proxies. I think it would be better with a DataMapper style pattern (with a different class to load and save models), but that's what we have.

    It's not a perfect library, that's for sure. But it's better than the rest, AFAIC.

    ReplyDelete
  5. Seems to me too.
    I looked through several frameworks, but ExtJS has the most mature controls.
    http://coding.smashingmagazine.com/2012/07/27/journey-through-the-javascript-mvc-jungle/

    Though the price of inconsistencies is high as well.

    I get around in my opinion bad MVC design decisions (especially in model/store part being tied to view) though using Joose, Rx.

    So far good, but lots of fixture code.

    ReplyDelete
  6. Hi, I saw your JSFiddle example regarding nested XML here:
    http://jsfiddle.net/el_chief/668Fg/5/

    I'm working with a nested data example as well, but the data is a little different. Keeping the data consistent with the JSFiddle example, how would you set up the associations if the address tags were not wrapped by tags? That's the data I have to work with in my current scenario and I'm having trouble finding an example of how to set that sort of association up.

    ReplyDelete
    Replies
    1. EDIT: that should have read .... if the address tags were not wrapped by addressList tags.

      Delete
    2. I believe you can use an array operator to access elements as well. Also, check out the convert function of the data field class.

      Delete
  7. Extjs means nothing, say which version of JS are your posts for.

    You should use Proxies in Stores, as they seems to use in Sencha Architect since this is the way they want to do it.

    Ext.define('MyApp.store.Whatever', {
    extend: 'Ext.data.Store',
    alias: 'store.whatever',
    requires: [
    'MyApp.model.Whatever'
    ],

    constructor: function(cfg) {
    var me = this;
    cfg = cfg || {};
    me.callParent([Ext.apply({
    storeId: 'Whatever',
    model: 'MyApp.model.Whatever',
    proxy: {
    type: 'ajax',
    url: 'http://localhost/sandbox/users.json',
    reader: {
    type: 'json',
    root: 'root'
    }
    }
    }, cfg)]);
    }
    });

    Regards

    ReplyDelete
    Replies
    1. How do you create and save a single model if the proxy is in the store?

      Why put the proxy in the store, when it automatically inherit's its model's proxy anyways?

      Delete
    2. Sencha courses teach you to use proxies in models. That way it can be used for one or more records (ask neil said).

      One reason to put proxy in store is when more than one store uses same model, but different proxy (aka local storage vs jsonp) or proxy settings (aka url).

      Delete
  8. proxy in model... very bad design

    ReplyDelete
    Replies
    1. It's called ActiveRecord, promoted by Martin Fowler, used by Ruby on Rails, Spring Roo, and is perfectly acceptable. If you want to put a post on your blog showing how DataMapper is demonstrably better, please post a link :)

      Delete
    2. Rather than hollow criticism "very bad design"; it will be more useful to provide constructive criticism i.e provide a "better example".

      Delete
  9. Hi, I am little confused..here, and I am new in extjs.

    you have only name in contact and written contact_id as foreign key in hasMany..and as u have mentioned that id is inherited from Ext.app.Model. so,
    is it like that it takes name contact and id and combined that as contact_id in foreign key ?

    and what is root in it ?

    Thanks :)

    ReplyDelete
    Replies
    1. There are two models, contact and phone_number, though I didn't get into the definition of phone_number. The models look like this:

      CONTACT
      id
      name


      PHONE
      id
      contact_id
      number

      Delete
  10. This comment has been removed by the author.

    ReplyDelete
  11. There are a couple of reasons why I think defining proxies inside your model is NOT bad design. Aside from the reasons mentioned by Neil repeatedly, remember that we are leaving proxy *configuration* inside the model, not actual logic. What the ExtJs source is doing it is nothing of our concern (though reading the source is a great way to understand ExtJs better).

    Furthermore, remember this is javascript, not Java or C#. Over-engineering things like this will just make things harder and bloat your code.

    Now if having your proxy inside the model actually gives you troubles, that's a complete story. But I doubt it will.

    ReplyDelete
  12. Take a look at this guide...
    http://docs.sencha.com/extjs/4.2.0/#/guide/data

    RobertoM

    ReplyDelete
  13. Is it possible to specify configuration options for autogenerated store?
    I mean, for example, how to set config options like autoSync, autoDestroy for store, returned by c.phoneNumbers()

    ReplyDelete
    Replies
    1. Yes, you can add a storeConfig:{...} property to the hasMany relationship to setup the store.

      Delete
  14. I have the same problem, but your solution doesn't work for me...

    See my post..

    http://www.sencha.com/forum/showthread.php?268099-Children-elements-aren-t-show-using-nested-json-Model-and-hasMany-association.

    Can you help me?

    ReplyDelete
  15. I understand you people about putting the proxy configuration in the store and not in the model but I also got the point of Neil regarding this one.

    Actually you can't compare it how Sencha Architect want to do it, there are lots of ways on how to apply it in Sencha you just choose what you prefer.

    ReplyDelete
  16. I have a query on this one..

    /phonenumbers?page=1&start=0&limit=25&filter=[{"property":"contact_id","value":"99"}]

    The problem is that we don't have filter query on our API, but I can get the child use the ID like

    /endpoint/contacts/[contactid]/phonenumbers

    What do you think of this implementation?

    ReplyDelete
  17. I commonly reference this post when answering questions on StackOverflow, just figured I'd pop in here and say "Thanks" as well as "Good job!" :) I just wrote up a (rather long) blog post on ExtJS associations and linked to you as an awesome reference: http://codingishard.com/2013/08/29/extjs-associations-the-good-the-bad-and-the-ugly/

    ReplyDelete
  18. Hi could you please tell me how we can filter hasMany data in memory proxy store.? here is my response
    routes: [
    {
    cdcName: "-----",
    routeID: -----,
    routeName: "------",
    stores: [
    {
    storeAddress: "-----",
    storeCity: "-------",
    storeEmailAddress: "--------",
    storeID: ---
    },
    {
    },
    and so on ....


    Ext.define('Pro.model.Route', {

    extend: 'Ext.data.Model',

    fields: [
    {
    name: 'routeID',
    dataType: 'int',
    optional: false
    },
    {
    name: 'cdcName',
    dataType: 'string',
    optional: false
    },
    {
    name: 'routeName',
    dataType: 'string',
    optional: false
    },'stores'
    ],
    associations: {
    type: 'hasMany',
    model: 'Pro.model.Store',
    name:'stores',
    associationKey: 'stores'
    }
    });

    i am able to filter record on the basis of cdcName , routeName but i am not able to filter record on the basis of storeID
    please tell me . Thanks

    ReplyDelete
    Replies
    1. Hi,
      I have a similar issue as Humayun Javed ^^. How do I filter a grid containing columns from both models, in a similar hasMany association scenario like he describes

      Delete
    2. Hi,
      I have a similar issue as Humayun Javed ^^. How do I filter a grid containing columns from both models, in a similar hasMany association scenario like he describes

      Delete
  19. Codeureka.com offers customized onsite Online Training Mobile Application Development for iOS application development for iPhone and iPad Using Objective-C.

    ReplyDelete