Logo text Governance.io - Governance.com
  • Benefits

  • Information

  • Customer area

  • More...

    • Benefits
      • Information
        • Careers
          • Contact
          • Customer area
            • License order form
              • Cart
                • General terms and conditions
                  • Privacy Policy
                    • User Agreement
                    LINKS
                    ABOUT
                    SOCIAL
                    • Grey Twitter Icon
                    • Grey Facebook Icon
                    • Grey LinkedIn Icon

                    2Gears S.A.

                    Creators of Governance.io - Governance.com

                    ​

                    Visiting address:

                    29, Boulevard GD Charlotte

                    "The Office"

                    L-1331 Luxembourg

                    ​

                    Registered address:

                    17, Boulevard Prince Henri

                    L-1724 Luxembourg

                    VAT: LU28166771
                    RCS: B0164267 

                    © 2016 2Gears SA

                    Logo Governance.io - Governance.com
                    THE PLATFORM

                    IMF: Luxembourg Financial Sector Assessment Program 2017

                    September 2, 2017

                    The Innovators — A Conversation with Bert Boerman, CEO of Governance.io

                    August 3, 2017

                    Governance.io welcomes Business Development Director Olus Kayacan

                    June 16, 2017

                    Trident Trust applies RegTech to lead in Alternative Investment Fund services

                    June 9, 2017

                    Governance.io listed on the European Hot Ten list of FinTech50

                    June 7, 2017

                    Governance.io team at ICT Spring in Luxembourg

                    May 23, 2017

                    Sneak preview of Governance.io VisualWorkBench™

                    April 8, 2017

                    Bert Boerman selected as RegTech book author

                    March 22, 2017

                    A TASTE OF FINTECH FROM 2 ANGLES... AND 2 SIDES OF THE WORLD

                    March 17, 2017

                    FINTECH AWARDS 2017 - who will take over the title "FinTech Startup of the Year" from Governance.io?

                    March 15, 2017

                    Please reload

                    Recent Posts
                    Sencha Touch
                    Tips and tricks

                    Using Model Associations in Sencha Touch 2 and Ext JS 4

                    July 8, 2012

                    The data package in Sencha Touch and ExtJS is awesome. Models let you easily and robustly configure your data structures and easily use them in all sorts of components. One of the model features that have a lot of potential but are used and understood relatively poorly are model associations. One of the most interesting uses of associations is that they allow you to use parent data when using the model in components. In practice this can be quite hard to accomplish however when all stores load their own data. In this blogpost I will show how to use parent (belongsTo) relations to automatically fetch and use parent data.

                     

                     

                    Model associations

                    First some basics about model associations. Associations define relationships between models. In Sencha Touch and ExtJS there are 3 types of associations:

                    • hasOne – The model instance has one and only one sibling (of that type)

                    • hasMany – The model instance has multiple children

                    • belongsTo – The model instance has one and only one parent (of that type)

                     
                    Sencha Touch

                    In Sencha Touch, models that implement the above illustration would be implemented as follows:

                     

                    1. Ext.define('MyApp.model.Owner', {

                    2. extend: 'Ext.data.Model',

                    3. config: {

                    4. idProperty: 'Id',

                    5. fields: [

                    6. { name: 'Id' },

                    7. { name: 'firstName' },

                    8. { name: 'lastName' }

                    9. ],

                    10. hasMany: [{ model: 'MyApp.model.Car' }]

                    11. }

                    12. });

                    13.  

                    14. Ext.define("MyApp.model.Car", {

                    15. extend: 'Ext.data.Model',

                    16. config: {

                    17. idProperty: 'Id',

                    18. fields: [

                    19. { name: 'Id' },

                    20. { name: 'brandName' },

                    21. { name: 'type' },

                    22. { name: 'ownerId' }

                    23. ],

                    24. belongsTo: [{ model: 'MyApp.model.Owner', associationKey: 'ownerId' }],

                    25. hasOne: [{ model: 'MyApp.model.Engine', associationKey: 'engineId' }],

                    26. hasMany: [{ model: 'MyApp.model.Tyre' }]

                    27. }

                    28. });

                    29.  

                    30. Ext.define("MyApp.model.Engine", {

                    31. extend: 'Ext.data.Model',

                    32. config: {

                    33. idProperty: 'Id',

                    34. fields: [

                    35. { name: 'Id' },

                    36. { name: 'cilinders' }

                    37. ],

                    38. hasOne: [{ model: 'MyApp.model.Car' }]

                    39. }

                    40. });

                    41.  

                    42. Ext.define("MyApp.model.Tyre", {

                    43. extend: 'Ext.data.Model',

                    44. config: {

                    45. idProperty: 'Id',

                    46. fields: [

                    47. { name: 'Id' },

                    48. { name: 'brandName' },

                    49. { name: 'position' },

                    50. { name: 'carId' }

                    51. ],

                    52. belongsTo: [{ model: 'MyApp.model.Car', associationKey: 'carId' }]

                    53. }

                    54. });

                     

                     

                    Now that we setup our associations, let’s put them to use.

                    For an imaginary car tyre dealer we are creating a mobile app. The client wants to include a list of all the tyres currently attached to it’s clients cars. The list should include the Tyre brand and position on the car, the car brand of the car it’s attached to and the client name.

                    We create 3 stores, a ‘Owners’ store containing the clients, a ‘Cars’ store that contains all cars and a ‘Tyres’ store that contains all tyres. The three stores operate on their respective models ‘Owner’, ‘Car’ and ‘Tyre’.

                    Now this is where it gets tricky. The ‘Tyres’ list operates on a store that contains ‘Tyre’ records. The ‘car brand’ and ‘customer name’ are part of other models however. Because we setup the ‘belongsTo’ relations on the Tyre and Car model we would think we can use those associations to gather the car brand name and client name. Unfortunately, that is not the case.

                     

                     

                    The reason for the list not being able to display associated data has to do with the way stores load their data from a backend server.

                    Simply put, the stores load data from the backend throught their configured proxy (which have not been drawn) and instantiate records (model instances) from that data. The load process is in most cases asynchronous. This means that the Tyre and Car records will be instantiated without knowing about their related (instantiated) counterparts. So when we fetch a Tyre record from the store, it has no reference to a parent Car record we would like to use in the list component. The only way to accomplish that is to load the entire data structure of owners, including car and tyre children in 1 go using a hyrarchical data structure. In practice this way of loading data is cumbersome and mostly unsupported by typical standardized backend API’s.

                     

                    The solution

                    Of course, in Sencha Touch and ExtJS there is always one or more ways to do it. After brainstorming a bit (Aaron Smith, thanks for your time) we figured out a solution. Our models need to be provided with a way to initiate the relations with other models when they are needed in components. Besides the definitions of the associations themselves on the model we need to instruct the model where the related objects can be found.

                    Most components get their data from models through the ‘getData’ method on the Model. That method already consumes an argument telling it to incorporate associated data in the results. The only problem is that it can’t find the related records. Therefore we chose to inject some custom code in the getData method that searches the associations. The easiest way for us was to let all our models inherit from a BaseModel class that includes those additions. By providing a couple of additions to the association we can completely transparently let the models take care of everything.

                     
                    BaseModel

                    First we create our BaseModel that includes a couple of methods:

                    • linkAssociations – Iterates over all parent (belongsTo) and straight (hasOne) associations. If the association includes our special foreignStore config we use the StoreManager to find the related record. The link to the related record is stored in a way in which Sencha Touch automatically picks it up.

                    • linkChildAssociations – Used to fetch the direct children (hasMany) of the record and again stored in a way the default model handlers pick it up. When using this recursive, things can quickly spin out of control, be careful with this one!

                    • getFlattenedData – Turns a hierarchical structure, fetched with getData, into a flat hash. The keys contain the relations in dot format. This way, the relations can be used for instance in a form panel.

                     

                    1. Ext.define("MyApp.model.BaseModel", {

                    2. extend: 'Ext.data.Model',

                    3.  

                    4. linkedAssociations: false,

                    5.  

                    6. /* uses information from the associations to fetch a parent from an associated store */

                    7. getParent: function(assocName) {

                    8. var assoc = this.associations.get(assocName);

                    9. if (!assoc) {

                    10. return null;

                    11. }

                    12. var store = Ext.StoreMgr.get(assoc.config.foreignStore);

                    13. if (!store) {

                    14. return null;

                    15. }

                    16.  

                    17. return store.findRecord(assoc.config.primaryKey, this.get(assoc.config.foreignKey)) || undefined;

                    18. },

                    19.  

                    20. getChildren: function(assocName) {

                    21. var assoc = this.associations.get(assocName),

                    22. id = this.get(assoc.config.primaryKey);

                    23.  

                    24. if (!assoc) {

                    25. return null;

                    26. }

                    27. var store = Ext.StoreMgr.get(assoc.config.foreignStore);

                    28. if (!store) {

                    29. return null;

                    30. }

                    31.  

                    32. store.suspendEvents(); /* make sure the store does not fire all sorts of events, triggering stuff we dont want */

                    33. store.clearFilter();

                    34. store.filterBy(function(record) {

                    35. return record.get(assoc.config.foreignKey) === id;

                    36. });

                    37.  

                    38. var range = store.getRange(); // return array of records

                    39. store.clearFilter();

                    40. store.resumeEvents();

                    41.  

                    42. return range;

                    43. },

                    44.  

                    45. /* warning, recursive down in combination with up can be dangerous when there are loops in associations */

                    46. getData: function(includeAssociated,down) {

                    47. if (includeAssociated && !this.linkedAssociations) {

                    48. this.linkedAssociations = true;

                    49. this.linkChildAssociations(includeAssociated);

                    50. this.linkAssociations(includeAssociated);

                    51. }

                    52.  

                    53. var data = this.callParent(arguments);

                    54. return data;

                    55. },

                    56.  

                    57. getFlattenedData: function(includeAssociated) {

                    58. var data = this.getData(includeAssociated, false); // don't ever recurse down when getting flattened data!

                    59.  

                    60. /* This function flattens the datastructure of am object such that it can be used in a form

                    61. * {foo:1,bar:{blah: {boo: 3}}} becomes {foo: 1, bar.blah.boo: 3}

                    62. * This is the only way to use associated data in a form

                    63. * thanks to http://stackoverflow.com/users/2214/matthew-crumley

                    64. */

                    65. var count=1;

                    66. var prop;

                    67. var flatten = function(obj, includePrototype, into, prefix) {

                    68. if (count++ > 20) {console.log('TOO DEEP RECURSION'); return;} // prevent infinite recursion

                    69. into = into || {};

                    70. prefix = prefix || "";

                    71.  

                    72. for (var k in obj) {

                    73. if (includePrototype || obj.hasOwnProperty(k)) {

                    74. var prop = obj[k];

                    75. if (prop instanceof Array) { continue; } // Don't recurse into hasMany relations

                    76. if (prop && typeof prop === "object" &&

                    77. !(prop instanceof Date || prop instanceof RegExp)) {

                    78. flatten(prop, includePrototype, into, prefix + k + ".");

                    79. }

                    80. else {

                    81. into[prefix + k] = prop;

                    82. }

                    83. }

                    84. }

                    85.  

                    86. return into;

                    87. };

                    88.  

                    89. return flatten(data, false);

                    90. },

                    91.  

                    92. /* this function ONLY recurses upwards (belongsTo), otherwise the data structure could become infinite */

                    93. linkAssociations: function(includeAssociated, count) {

                    94. var associations = this.associations.items,

                    95. associationCount = associations.length,

                    96. associationName,

                    97. association,

                    98. associatedRecord,

                    99. i,

                    100. type,

                    101. foreignStore;

                    102.  

                    103. count = count || 0;

                    104.  

                    105. if (count > 10) {

                    106. console.log('Too deep recursion in linkAssociations');

                    107. return;

                    108. }

                    109.  

                    110. for (i = 0; i < associationCount; i++) {

                    111. association = associations[i];

                    112. associationName = association.getName();

                    113. type = association.getType();

                    114. foreignStore = association.config.foreignStore;

                    115.  

                    116. if (!foreignStore) {

                    117. continue;

                    118. }

                    119.  

                    120. if (type.toLowerCase() == 'belongsto' || type.toLowerCase() == 'hasone') {

                    121. associatedRecord = this.getParent(associationName);

                    122. if (associatedRecord) {

                    123. this[association.getInstanceName()] = associatedRecord;

                    124. associatedRecord.linkAssociations(includeAssociated, (count+1));

                    125. }

                    126. }

                    127. }

                    128. },

                    129.  

                    130. linkChildAssociations: function(includeAssociated, count) {

                    131. var associations = this.associations.items,

                    132. associationCount = associations.length,

                    133. associationName,

                    134. association,

                    135. associatedRecord,

                    136. i,

                    137. type,

                    138. foreignStore;

                    139.  

                    140. count = count || 0;

                    141.  

                    142. if (count > 10) {

                    143. console.log('Too deep recursion in linkAssociations');

                    144. return;

                    145. }

                    146.  

                    147. for (i = 0; i < associationCount; i++) {

                    148. association = associations[i];

                    149. associationName = association.getName();

                    150. type = association.getType();

                    151. foreignStore = association.config.foreignStore;

                    152.  

                    153. if (!foreignStore) {

                    154. continue;

                    155. }

                    156.  

                    157. if (type.toLowerCase() == 'hasmany') {

                    158. var children = this.getChildren(associationName);

                    159. association.setStoreName('hasMany_'+associationName+'_'+Ext.id());

                    160. var store = Ext.create('Ext.data.Store',{

                    161. model: association.config.associatedModel

                    162. });

                    163. store.add(children);

                    164. this[association.getStoreName()] = store;

                    165. }

                    166. }

                    167. }

                    168. });

                    Models

                    We need a couple of minor changes to our model associations specified above:

                    • primaryKey – the field in the parent that identifies it.

                    • foreignKey – the key that identifies the parent in the child. In a belongsTo or hasOne relation, this is part of the model itself, in a hasMany relation this is a field of the child objects that refer to my Id.

                    • foreignStore – the store name that contains the related records

                    The adjusted model definitions become the following:

                    1. Ext.define("MyApp.model.Owner", {

                    2. extend: 'MyApp.model.BaseModel',

                    3. config: {

                    4. idProperty: 'Id',

                    5. fields: [

                    6. { name: 'Id' },

                    7. { name: 'firstName' },

                    8. { name: 'lastName' }

                    9. ],

                    10. hasMany: [{

                    11. model: 'MyApp.model.Car',

                    12. name: 'Car',

                    13. primaryKey: 'Id',

                    14. foreignKey: 'ownerId',

                    15. foreignStore: 'Cars'

                    16. }]

                    17. }

                    18. });

                    19.  

                    20. Ext.define("MyApp.model.Car", {

                    21. extend: 'MyApp.model.BaseModel',

                    22. config: {

                    23. idProperty: 'Id',

                    24. fields: [

                    25. { name: 'Id' },

                    26. { name: 'brandName' },

                    27. { name: 'type' },

                    28. { name: 'ownerId' },

                    29. { name: 'engineId' }

                    30. ],

                    31. belongsTo: [{

                    32. model: 'MyApp.model.Owner',

                    33. name: 'Owner',

                    34. primaryKey: 'Id',

                    35. foreignKey: 'ownerId',

                    36. foreignStore: 'Owners'

                    37. }],

                    38. hasMany: [{

                    39. model: 'MyApp.model.Tyre',

                    40. name: 'Tyre',

                    41. primaryKey: 'Id',

                    42. foreignKey: 'carId',

                    43. foreignStore: 'Tyres'

                    44. }],

                    45. hasOne: [{

                    46. model: 'MyApp.model.Engine',

                    47. name: 'Engine',

                    48. primaryKey: 'Id',

                    49. foreignKey: 'engineId',

                    50. foreignStore: 'Engines'

                    51. }]

                    52. }

                    53. });

                    54.  

                    55. Ext.define("MyApp.model.Engine", {

                    56. extend: 'MyApp.model.BaseModel',

                    57. config: {

                    58. idProperty: 'Id',

                    59. fields: [

                    60. { name: 'Id' },

                    61. { name: 'cilinders' },

                    62. { name: 'carId' }

                    63. ]

                    64. },

                    65. hasOne: [{

                    66. model: 'MyApp.model.Car',

                    67. name: 'Car',

                    68. primaryKey: 'Id',

                    69. foreignKey: 'carId',

                    70. foreignStore: 'Cars'

                    71. }]

                    72. });

                    73.  

                    74. Ext.define("MyApp.model.Tyre", {

                    75. extend: 'MyApp.model.BaseModel',

                    76. config: {

                    77. idProperty: 'Id',

                    78. fields: [

                    79. { name: 'Id' },

                    80. { name: 'brandName' },

                    81. { name: 'position' },

                    82. { name: 'carId' }

                    83. ],

                    84. belongsTo: [{

                    85. model: 'MyApp.model.Car',

                    86. name: 'Car',

                    87. primaryKey: 'Id',

                    88. foreignKey: 'carId',

                    89. foreignStore: 'Cars'

                    90. }]

                    91. }

                    92. });

                    List

                    Now that we structured our data let’s see how difficult it is to load related data in a List component. This assumes that the various stores have been configured using their respective model and set to autoLoad.

                    1. Ext.define("MyApp.view.TyreList", {

                    2. extend: "Ext.List",

                    3. config: {

                    4. store: 'Tyres',

                    5. emptyText: 'No tyres',

                    6. itemTpl: [

                    7. '<div class="myapp-list-item">',

                    8. '<p>Client name: {Car.Owner.lastName}, {Car.Owner.firstName}</p>',

                    9. '<p>Tyre brand: {brandName}</p>',

                    10. '<p>Tyre position: {position}</p>',

                    11. '<p>Car brand: {Car.brandName}</p>',

                    12. '</div>'

                    13. ]

                    14. }

                    15. });

                    That’s it! The List operates on the Tyres store but is able to use the defined associations to fetch and display parent data. Of course, the developer has to make sure that the parent relations are always there, otherwise the List’s XTemplate will croak. This has to do with the fact that at the moment of writing Sencha Touch triggers an error instead of a warning on these cases. Like everything there is also a way around this:

                    1. Ext.override(Ext.XTemplate,{

                    2. applyOut: function(values, out) {

                    3. var me = this,

                    4. compiler;

                    5.  

                    6. if (!me.fn) {

                    7. compiler = new Ext.XTemplateCompiler({

                    8. useFormat: me.disableFormats !== true

                    9. });

                    10.  

                    11. me.fn = compiler.compile(me.html);

                    12. }

                    13.  

                    14. try {

                    15. me.fn.call(me, out, values, {}, 1, 1);

                    16. } catch (e) {

                    17. //

                    18. Ext.Logger.log(e.message);

                    19. //

                    20. }

                    21. return out;

                    22. }

                    23. });

                    Conclusion

                    When understanding how associations work in Sencha Touch and ExtJS it’s pretty easy to make them work for you. The result is an enormous amount of freedom in the display and usage of associated data without having to resort to all kinds of nasty hacks. The above solution is one example of such a use with the associations neatly abstracted away in the data layer. Using the BaseModel’s getFlattenedData also allows one to use associated data in forms. This I will leave to the reader as a practical exercise. Good luck and let me know how it works for you.

                    In the next part of this series I will demonstrate how the next version of the base model can be used in a more real-life setup using SalesForce data.

                    Tags:

                    Sencha

                    Touch

                    ExtJS

                    Share on Facebook
                    Share on Twitter
                    Please reload

                    Follow Us

                    September 2017 (1)

                    August 2017 (1)

                    June 2017 (3)

                    May 2017 (1)

                    April 2017 (1)

                    March 2017 (4)

                    February 2017 (1)

                    January 2017 (3)

                    December 2016 (1)

                    October 2016 (2)

                    June 2016 (1)

                    May 2016 (1)

                    August 2014 (2)

                    July 2013 (2)

                    September 2012 (1)

                    July 2012 (1)

                    Please reload

                    Archive
                    • Facebook Basic Square
                    • Twitter Basic Square
                    • Google+ Basic Square