]>
git.proxmox.com Git - sencha-touch.git/blob - src/src/data/association/HasMany.js
2 * Represents a one-to-many relationship between two models. Usually created indirectly via a model definition:
4 * Ext.define('Product', {
5 * extend: 'Ext.data.Model',
8 * {name: 'id', type: 'int'},
9 * {name: 'user_id', type: 'int'},
10 * {name: 'name', type: 'string'}
15 * Ext.define('User', {
16 * extend: 'Ext.data.Model',
19 * {name: 'id', type: 'int'},
20 * {name: 'name', type: 'string'}
22 * // we can use the hasMany shortcut on the model to create a hasMany association
23 * hasMany: {model: 'Product', name: 'products'}
27 * Above we created Product and User models, and linked them by saying that a User hasMany Products. This gives us a new
28 * function on every User instance, in this case the function is called 'products' because that is the name we specified
29 * in the association configuration above.
31 * This new function returns a specialized {@link Ext.data.Store Store} which is automatically filtered to load only
32 * Products for the given model instance:
34 * //first, we load up a User with id of 1
35 * var user = Ext.create('User', {id: 1, name: 'Ed'});
37 * //the user.products function was created automatically by the association and returns a {@link Ext.data.Store Store}
38 * //the created store is automatically scoped to the set of Products for the User with id of 1
39 * var products = user.products();
41 * //we still have all of the usual Store functions, for example it's easy to add a Product for this User
43 * name: 'Another Product'
46 * //saves the changes to the store - this automatically sets the new Product's user_id to 1 before saving
49 * The new Store is only instantiated the first time you call products() to conserve memory and processing time, though
50 * calling products() a second time returns the same store instance.
54 * The Store is automatically furnished with a filter - by default this filter tells the store to only return records
55 * where the associated model's foreign key matches the owner model's primary key. For example, if a User with ID = 100
56 * hasMany Products, the filter loads only Products with user_id == 100.
58 * Sometimes we want to filter by another field - for example in the case of a Twitter search application we may have
59 * models for Search and Tweet:
61 * Ext.define('Search', {
62 * extend: 'Ext.data.Model',
71 * filterProperty: 'query'
76 * Ext.define('Tweet', {
77 * extend: 'Ext.data.Model',
80 * 'id', 'text', 'from_user'
85 * //returns a Store filtered by the filterProperty
86 * var store = new Search({query: 'Sencha Touch'}).tweets();
88 * The tweets association above is filtered by the query property by setting the {@link #filterProperty}, and is
91 * var store = Ext.create('Ext.data.Store', {
96 * value : 'Sencha Touch'
102 * [Sencha Touch Models and Associations](../../../core_concepts/data/models.html)
104 Ext
.define('Ext.data.association.HasMany', {
105 extend
: 'Ext.data.association.Association',
106 alternateClassName
: 'Ext.data.HasManyAssociation',
107 requires
: ['Ext.util.Inflector'],
109 alias
: 'association.hasmany',
113 * @cfg {String} foreignKey
114 * The name of the foreign key on the associated model that links it to the owner model. Defaults to the
115 * lowercased name of the owner model plus "_id", e.g. an association with a model called Group hasMany Users
116 * would create 'group_id' as the foreign key. When the remote store is loaded, the store is automatically
117 * filtered so that only records with a matching foreign key are included in the resulting child store. This can
118 * be overridden by specifying the {@link #filterProperty}.
120 * Ext.define('Group', {
121 * extend: 'Ext.data.Model',
122 * fields: ['id', 'name'],
126 * Ext.define('User', {
127 * extend: 'Ext.data.Model',
128 * fields: ['id', 'name', 'group_id'], // refers to the id of the group that this user belongs to
132 foreignKey
: undefined,
136 * The name of the function to create on the owner model to retrieve the child store. If not specified, the
137 * pluralized name of the child model is used.
139 * // This will create a users() method on any Group model instance
140 * Ext.define('Group', {
141 * extend: 'Ext.data.Model',
142 * fields: ['id', 'name'],
145 * var group = new Group();
146 * console.log(group.users());
148 * // The method to retrieve the users will now be getUserList
149 * Ext.define('Group', {
150 * extend: 'Ext.data.Model',
151 * fields: ['id', 'name'],
152 * hasMany: {model: 'User', name: 'getUserList'}
154 * var group = new Group();
155 * console.log(group.getUserList());
159 * @cfg {Object} store
160 * Optional configuration object that will be passed to the generated Store. Defaults to an empty Object.
165 * @cfg {String} storeName
166 * Optional The name of the store by which you can reference it on this class as a property.
168 storeName
: undefined,
171 * @cfg {String} filterProperty
172 * Optionally overrides the default filter that is set up on the associated Store. If this is not set, a filter
173 * is automatically created which filters the association based on the configured {@link #foreignKey}. See intro
174 * docs for more details.
176 filterProperty
: null,
179 * @cfg {Boolean} autoLoad
180 * `true` to automatically load the related store from a remote source when instantiated.
185 * @cfg {Boolean} autoSync
186 * true to automatically synchronize the related store with the remote source
191 constructor: function(config
) {
192 config
= config
|| {};
194 if (config
.storeConfig
) {
196 Ext
.Logger
.warn('storeConfig is deprecated on an association. Instead use the store configuration.');
198 config
.store
= config
.storeConfig
;
199 delete config
.storeConfig
;
202 this.callParent([config
]);
205 applyName: function(name
) {
207 name
= Ext
.util
.Inflector
.pluralize(this.getAssociatedName().toLowerCase());
212 applyStoreName: function(name
) {
214 name
= this.getName() + 'Store';
219 applyForeignKey: function(foreignKey
) {
221 var inverse
= this.getInverseAssociation();
223 foreignKey
= inverse
.getForeignKey();
225 foreignKey
= this.getOwnerName().toLowerCase() + '_id';
231 applyAssociationKey: function(associationKey
) {
232 if (!associationKey
) {
233 var associatedName
= this.getAssociatedName();
234 associationKey
= Ext
.util
.Inflector
.pluralize(associatedName
[0].toLowerCase() + associatedName
.slice(1));
236 return associationKey
;
239 updateForeignKey: function(foreignKey
, oldForeignKey
) {
240 var fields
= this.getAssociatedModel().getFields(),
241 field
= fields
.get(foreignKey
);
244 field
= new Ext
.data
.Field({
248 fields
.isDirty
= true;
252 field
= fields
.get(oldForeignKey
);
254 fields
.remove(field
);
255 fields
.isDirty
= true;
262 * @deprecated as of v2.0.0 on an association. Instead use the store configuration.
264 * Creates a function that returns an Ext.data.Store which is configured to load a set of data filtered
265 * by the owner model's primary key - e.g. in a `hasMany` association where Group `hasMany` Users, this function
266 * returns a Store configured to return the filtered set of a single Group's Users.
267 * @return {Function} The store-generating function.
269 applyStore: function(storeConfig
) {
271 associatedModel
= me
.getAssociatedModel(),
272 storeName
= me
.getStoreName(),
273 foreignKey
= me
.getForeignKey(),
274 primaryKey
= me
.getPrimaryKey(),
275 filterProperty
= me
.getFilterProperty(),
276 autoLoad
= me
.getAutoLoad(),
277 autoSync
= me
.getAutoSync();
281 config
, filter
, store
,
284 addrecords
: me
.onAddRecords
,
285 removerecords
: me
.onRemoveRecords
,
289 if (record
[storeName
] === undefined) {
290 if (filterProperty
) {
292 property
: filterProperty
,
293 value
: record
.get(filterProperty
),
298 property
: foreignKey
,
299 value
: record
.get(primaryKey
),
304 modelDefaults
[foreignKey
] = record
.get(primaryKey
);
306 config
= Ext
.apply({}, storeConfig
, {
307 model
: associatedModel
,
311 modelDefaults
: modelDefaults
314 store
= record
[storeName
] = Ext
.create('Ext.data.Store', config
);
315 store
.boundTo
= record
;
317 store
.onAfter(listeners
);
320 record
[storeName
].load();
324 return record
[storeName
];
328 onAddRecords: function(store
, records
) {
329 var ln
= records
.length
,
330 id
= store
.boundTo
.getId(),
333 for (i
= 0; i
< ln
; i
++) {
335 record
.set(this.getForeignKey(), id
);
337 this.updateInverseInstances(store
.boundTo
);
340 onRemoveRecords: function(store
, records
) {
341 var ln
= records
.length
,
343 for (i
= 0; i
< ln
; i
++) {
345 record
.set(this.getForeignKey(), null);
349 updateStore: function(store
) {
350 this.getOwnerModel().prototype[this.getName()] = store
;
354 * Read associated data
356 * @param {Ext.data.Model} record The record we're writing to.
357 * @param {Ext.data.reader.Reader} reader The reader for the associated model.
358 * @param {Object} associationData The raw associated data.
360 read: function(record
, reader
, associationData
) {
361 var store
= record
[this.getName()](),
362 records
= reader
.read(associationData
).getRecords();
367 updateInverseInstances: function(record
) {
368 var store
= record
[this.getName()](),
369 inverse
= this.getInverseAssociation();
371 //if the inverse association was found, set it now on each record we've just created
373 store
.each(function(associatedRecord
) {
374 associatedRecord
[inverse
.getInstanceName()] = record
;
379 getInverseAssociation: function() {
380 var ownerName
= this.getOwnerModel().modelName
;
382 //now that we've added the related records to the hasMany association, set the inverse belongsTo
383 //association on each of them if it exists
384 return this.getAssociatedModel().associations
.findBy(function(assoc
) {
385 return assoc
.getType().toLowerCase() === 'belongsto' && assoc
.getAssociatedModel().modelName
=== ownerName
;
389 // <deprecated product=touch since=2.0>
392 * @cfg {Object} storeConfig
393 * @deprecated 2.0.0 Use `store` instead.
395 Ext
.deprecateProperty(this, 'storeConfig', 'store');