Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / HasManyAssociation.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-data-HasManyAssociation'>/**
19 </span> * @author Ed Spencer
20  * @class Ext.data.HasManyAssociation
21  * @extends Ext.data.Association
22  * 
23  * &lt;p&gt;Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:&lt;/p&gt;
24  * 
25 &lt;pre&gt;&lt;code&gt;
26 Ext.define('Product', {
27     extend: 'Ext.data.Model',
28     fields: [
29         {name: 'id',      type: 'int'},
30         {name: 'user_id', type: 'int'},
31         {name: 'name',    type: 'string'}
32     ]
33 });
34
35 Ext.define('User', {
36     extend: 'Ext.data.Model',
37     fields: [
38         {name: 'id',   type: 'int'},
39         {name: 'name', type: 'string'}
40     ],
41     // we can use the hasMany shortcut on the model to create a hasMany association
42     hasMany: {model: 'Product', name: 'products'}
43 });
44 &lt;/pre&gt;&lt;/code&gt;
45
46  * &lt;p&gt;Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives
47  * us a new function on every User instance, in this case the function is called 'products' because that is the name
48  * we specified in the association configuration above.&lt;/p&gt;
49  * 
50  * &lt;p&gt;This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load
51  * only Products for the given model instance:&lt;/p&gt;
52  * 
53 &lt;pre&gt;&lt;code&gt;
54 //first, we load up a User with id of 1
55 var user = Ext.create('User', {id: 1, name: 'Ed'});
56
57 //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
58 //the created store is automatically scoped to the set of Products for the User with id of 1
59 var products = user.products();
60
61 //we still have all of the usual Store functions, for example it's easy to add a Product for this User
62 products.add({
63     name: 'Another Product'
64 });
65
66 //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
67 products.sync();
68 &lt;/code&gt;&lt;/pre&gt;
69  * 
70  * &lt;p&gt;The new Store is only instantiated the first time you call products() to conserve memory and processing time,
71  * though calling products() a second time returns the same store instance.&lt;/p&gt;
72  * 
73  * &lt;p&gt;&lt;u&gt;Custom filtering&lt;/u&gt;&lt;/p&gt;
74  * 
75  * &lt;p&gt;The Store is automatically furnished with a filter - by default this filter tells the store to only return
76  * records where the associated model's foreign key matches the owner model's primary key. For example, if a User
77  * with ID = 100 hasMany Products, the filter loads only Products with user_id == 100.&lt;/p&gt;
78  * 
79  * &lt;p&gt;Sometimes we want to filter by another field - for example in the case of a Twitter search application we may
80  * have models for Search and Tweet:&lt;/p&gt;
81  * 
82 &lt;pre&gt;&lt;code&gt;
83 Ext.define('Search', {
84     extend: 'Ext.data.Model',
85     fields: [
86         'id', 'query'
87     ],
88
89     hasMany: {
90         model: 'Tweet',
91         name : 'tweets',
92         filterProperty: 'query'
93     }
94 });
95
96 Ext.define('Tweet', {
97     extend: 'Ext.data.Model',
98     fields: [
99         'id', 'text', 'from_user'
100     ]
101 });
102
103 //returns a Store filtered by the filterProperty
104 var store = new Search({query: 'Sencha Touch'}).tweets();
105 &lt;/code&gt;&lt;/pre&gt;
106  * 
107  * &lt;p&gt;The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
108  * equivalent to this:&lt;/p&gt;
109  * 
110 &lt;pre&gt;&lt;code&gt;
111 var store = Ext.create('Ext.data.Store', {
112     model: 'Tweet',
113     filters: [
114         {
115             property: 'query',
116             value   : 'Sencha Touch'
117         }
118     ]
119 });
120 &lt;/code&gt;&lt;/pre&gt;
121  */
122 Ext.define('Ext.data.HasManyAssociation', {
123     extend: 'Ext.data.Association',
124     requires: ['Ext.util.Inflector'],
125
126     alias: 'association.hasmany',
127
128 <span id='Ext-data-HasManyAssociation-cfg-foreignKey'>    /**
129 </span>     * @cfg {String} foreignKey The name of the foreign key on the associated model that links it to the owner
130      * model. Defaults to the lowercased name of the owner model plus &quot;_id&quot;, e.g. an association with a where a
131      * model called Group hasMany Users would create 'group_id' as the foreign key. When the remote store is loaded,
132      * the store is automatically filtered so that only records with a matching foreign key are included in the 
133      * resulting child store. This can be overridden by specifying the {@link #filterProperty}.
134      * &lt;pre&gt;&lt;code&gt;
135 Ext.define('Group', {
136     extend: 'Ext.data.Model',
137     fields: ['id', 'name'],
138     hasMany: 'User'
139 });
140
141 Ext.define('User', {
142     extend: 'Ext.data.Model',
143     fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
144     belongsTo: 'Group'
145 });
146      * &lt;/code&gt;&lt;/pre&gt;
147      */
148     
149 <span id='Ext-data-HasManyAssociation-cfg-name'>    /**
150 </span>     * @cfg {String} name The name of the function to create on the owner model to retrieve the child store.
151      * If not specified, the pluralized name of the child model is used.
152      * &lt;pre&gt;&lt;code&gt;
153 // This will create a users() method on any Group model instance
154 Ext.define('Group', {
155     extend: 'Ext.data.Model',
156     fields: ['id', 'name'],
157     hasMany: 'User'
158 });
159 var group = new Group();
160 console.log(group.users());
161
162 // The method to retrieve the users will now be getUserList
163 Ext.define('Group', {
164     extend: 'Ext.data.Model',
165     fields: ['id', 'name'],
166     hasMany: {model: 'User', name: 'getUserList'}
167 });
168 var group = new Group();
169 console.log(group.getUserList());
170      * &lt;/code&gt;&lt;/pre&gt;
171      */
172     
173 <span id='Ext-data-HasManyAssociation-cfg-storeConfig'>    /**
174 </span>     * @cfg {Object} storeConfig Optional configuration object that will be passed to the generated Store. Defaults to 
175      * undefined.
176      */
177     
178 <span id='Ext-data-HasManyAssociation-cfg-filterProperty'>    /**
179 </span>     * @cfg {String} filterProperty Optionally overrides the default filter that is set up on the associated Store. If
180      * this is not set, a filter is automatically created which filters the association based on the configured 
181      * {@link #foreignKey}. See intro docs for more details. Defaults to undefined
182      */
183     
184 <span id='Ext-data-HasManyAssociation-cfg-autoLoad'>    /**
185 </span>     * @cfg {Boolean} autoLoad True to automatically load the related store from a remote source when instantiated.
186      * Defaults to &lt;tt&gt;false&lt;/tt&gt;.
187      */
188     
189 <span id='Ext-data-HasManyAssociation-cfg-type'>    /**
190 </span>     * @cfg {String} type The type configuration can be used when creating associations using a configuration object.
191      * Use 'hasMany' to create a HasManyAssocation
192      * &lt;pre&gt;&lt;code&gt;
193 associations: [{
194     type: 'hasMany',
195     model: 'User'
196 }]
197      * &lt;/code&gt;&lt;/pre&gt;
198      */
199     
200     constructor: function(config) {
201         var me = this,
202             ownerProto,
203             name;
204             
205         me.callParent(arguments);
206         
207         me.name = me.name || Ext.util.Inflector.pluralize(me.associatedName.toLowerCase());
208         
209         ownerProto = me.ownerModel.prototype;
210         name = me.name;
211         
212         Ext.applyIf(me, {
213             storeName : name + &quot;Store&quot;,
214             foreignKey: me.ownerName.toLowerCase() + &quot;_id&quot;
215         });
216         
217         ownerProto[name] = me.createStore();
218     },
219     
220 <span id='Ext-data-HasManyAssociation-method-createStore'>    /**
221 </span>     * @private
222      * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
223      * by the owner model's primary key - e.g. in a hasMany association where Group hasMany Users, this function
224      * returns a Store configured to return the filtered set of a single Group's Users.
225      * @return {Function} The store-generating function
226      */
227     createStore: function() {
228         var that            = this,
229             associatedModel = that.associatedModel,
230             storeName       = that.storeName,
231             foreignKey      = that.foreignKey,
232             primaryKey      = that.primaryKey,
233             filterProperty  = that.filterProperty,
234             autoLoad        = that.autoLoad,
235             storeConfig     = that.storeConfig || {};
236         
237         return function() {
238             var me = this,
239                 config, filter,
240                 modelDefaults = {};
241                 
242             if (me[storeName] === undefined) {
243                 if (filterProperty) {
244                     filter = {
245                         property  : filterProperty,
246                         value     : me.get(filterProperty),
247                         exactMatch: true
248                     };
249                 } else {
250                     filter = {
251                         property  : foreignKey,
252                         value     : me.get(primaryKey),
253                         exactMatch: true
254                     };
255                 }
256                 
257                 modelDefaults[foreignKey] = me.get(primaryKey);
258                 
259                 config = Ext.apply({}, storeConfig, {
260                     model        : associatedModel,
261                     filters      : [filter],
262                     remoteFilter : false,
263                     modelDefaults: modelDefaults
264                 });
265                 
266                 me[storeName] = Ext.create('Ext.data.Store', config);
267                 if (autoLoad) {
268                     me[storeName].load();
269                 }
270             }
271             
272             return me[storeName];
273         };
274     },
275     
276 <span id='Ext-data-HasManyAssociation-method-read'>    /**
277 </span>     * Read associated data
278      * @private
279      * @param {Ext.data.Model} record The record we're writing to
280      * @param {Ext.data.reader.Reader} reader The reader for the associated model
281      * @param {Object} associationData The raw associated data
282      */
283     read: function(record, reader, associationData){
284         var store = record[this.name](),
285             inverse;
286     
287         store.add(reader.read(associationData).records);
288     
289         //now that we've added the related records to the hasMany association, set the inverse belongsTo
290         //association on each of them if it exists
291         inverse = this.associatedModel.prototype.associations.findBy(function(assoc){
292             return assoc.type === 'belongsTo' &amp;&amp; assoc.associatedName === record.$className;
293         });
294     
295         //if the inverse association was found, set it now on each record we've just created
296         if (inverse) {
297             store.data.each(function(associatedRecord){
298                 associatedRecord[inverse.instanceName] = record;
299             });
300         }
301     }
302 });</pre>
303 </body>
304 </html>