How can I set up a complex model association in ExtJS 4? - database

Here's the basic table structure in the database:
Users <-> UserGroups <-> Groups
Basically, a group can contain many users, and a user can be a part of many groups. The UserGroups table is just an association.
How can I represent this same relationship using ExtJS 4 models?
I'm thinking the properties need to be something like:
Users belongsTo UserGroups
UserGroups hasMany Users and Groups
Groups belongsTo UserGroups
I'm just unsure if that will work as I need it to (since I will need to save the association between users and groups to the database).
I'm going to continue researching, but I hope either someone knows how to accomplish this, or at least has a good idea!

This is quite simple to do now, check this:
Ext.define('Group', {
extend: 'Ext.data.Model',
fields: ['id', 'user_id', 'group_id'],
belongsTo: 'UserGroups'
});
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['id', 'name', 'group_id'],
belongsTo: 'UserGroup'
});
Ext.define('UserGroup', {
extend: 'Ext.data.Model',
fields: ['id', 'name'],
hasMany : [{model: 'User', name: 'users'},
{model: 'Group', name: 'groups'}]
});
That should get you starting.

Related

ExtJS Grid column with dataIndex from referenced model

I have an ExtJS gridanel with a viewmodel store bound to it. The model of that store is a defined model with a field that references another model.
For the sake of simplicity here's an example:
Ext.define('User', {
extend: 'Ext.data.Model',
fields: ['name']
});
Ext.define('Order', {
extend: 'Ext.data.Model',
fields: ['date', {
name: 'user',
reference: 'User'
}]
});
viewmodel: {
store: {
order: {
model: 'Order',
}
}
}
In my gridpanel I have 2 columns. In one column, dataIndex: 'date' works correctly, displaying the date. However, in my second column I want to display the name of the user. I have no idea how to do that. The user field itself is on object so declaring dataIndex: user doesn't work. I tried dataIndex: {user.name} but that doesn't work either. I even tried
bind: {
data: '{user.name}'
}
also to no avail. I have found a solution using renderer config of the column but it is really ugly and beats the point of having a model reference another if I have to manually traverse the data structure for the appropriate field.
TL;DR
I'm looking for a way to declare a column's dataIndex to be a field from a reference model.

Loading Model from another model file in extjs

formGrid=me.abstractComponent.query('grid[itemId=grid]')[0],
Objmodel= Ext.create('Ext.document.model.GridModel');
formStore = Ext.create('Ext.data.Store',{
model:Objmodel,
data: data
});
formGrid.bindStore(formStore);
formStore.load();
I've been trying to load the store using the model, but error occurs saying that model not found. Is there any way such that i can load the model from another file in store above?
Store model attribute means you have to give the definition of Model not instance of model
Ext.define('app.model.User', {
extend: 'Ext.data.Model',
fields: ['first', 'last']
});
var store = Ext.create('Ext.data.Store',{
model: 'app.model.User'
});
Now if you want to add data to grid using model
var model = Ext.ModelManager.create({
first: 'Ed',
last: 'Spencer'
}, 'app.model.User');
store.add(model)

How do I override the url field when extending a base model in Extjs?

I am new to Extjs and am following an Extjs 5 example on creating a model hierarchy:
(http://docs.sencha.com/extjs/5.0/core_concepts/data_package.html)
I created a base model which holds a proxy with a url field. I now want to extend this base model and only override the url part below:
Ext.define('MyPortal.model.Base', {
extend: 'Ext.data.Model',
fields: [{
name: 'id',
type: 'int'
}],
schema: {
namespace: 'MyPortal.model', // generate auto entityName
proxy: {
type: 'ajax'
,url : '/portal-web/{entityName}'
,reader: {
type:'json',
rootProperty:'{entityName:lowercase}',
idProperty: 'id'
}
}
}
});
Here is a child model:
Ext.define('MyPortal.model.Account', {
extend : 'MyPortal.model.Base'
,fields: [
{name: 'accountId', type: 'string'},
{name: 'name', type: 'string'}
]
, //add something here to override the url from the base model above?
});
I tried adding a url field or function to the child model, but these are ignored. Is it possible for a child model to override the parent's url param?
Thanks!
The proxy option in the schema is what Ext5 calls an ObjectTemplate. Simply put, it is used as a default configuration for the models of the schema.
You can override this defaults in the proxy configuration of the model itself (one proxy instance will be created by model class -- not by model instance). Note that the model class itself doesn't accept an url option, it must be set in the proxy.
For you, that would give something like this:
Ext.define('MyPortal.model.Account', {
extend : 'MyPortal.model.Base' // I guess you had a typo here
,fields: [
{name: 'accountId', type: 'string'},
{name: 'name', type: 'string'},
]
// Override proxy URL (the other options of the schema's proxy
// will be used)
,proxy {
url: 'path/to/accounts'
}
});
FYI, the reader doesn't have an idProperty option (so says the docs), it's in the model this time...
I have a slightly different problem: my data model is generated, so i am using override: to extend model classes like this:
Ext.define('app.override.Foo', {
override: 'app.data.Foo',
proxy: {url: 'rest/foo',type: 'rest'}
}
this proxy override does not take effect in extjs 5 (works great in 4.x)

Implementing Many-to-Many Associations in ExtJS 4

This question has been asked twice before in on SO:
https://stackoverflow.com/questions/10244753/extjs-many-to-many-association-how
Extjs 4.1 many-to-many model association
BUT neither of these questions have an actual answer, so I'm going to try again!
Let's say I have two models, User and Group. A user can be in many groups, and groups can contain many users. Here's the model code for User:
Ext.define('User', {
extend: 'Ext.data.Model',
alias: 'model.User',
fields: [
{name: 'username', type: 'string'},
...
],
proxy: {
// Omitted for brevity...
},
});
And Group:
Ext.define('Group', {
extend: 'Ext.data.Model',
alias: 'model.Group',
fields: [
{name: 'name', type: 'string'},
...
],
proxy: {
// Omitted for brevity...
},
});
Now, let's say I wanted a Grid which lists my groups, and allows me to double-click a group and edit which users are in that group in second grid.
Let's also say there's a lot of users per group, so I don't want to load all the associated users when I load the groups.
I want to be able get a store of users for a particular group, and give that to my grid, which will load data as needed (using the usual pagination that a grid does).
I see two potential approaches here. There may another better way, but I will outline what I've tried do so far below.
Intermediate model
Add another joining model
Add hasMany associations from User and Group to that model
Add belongsTo associations from my joining model back the way to User and Group.
Joining model code:
Ext.define('GroupUsers', {
extend: 'Ext.data.Model',
alias: 'model.GroupUsers',
fields: [
{name: 'group_id', type: 'int'},
{name: 'user_id', type: 'int'},
],
associations: [
{type: 'belongsTo', model: 'User'},
{type: 'belongsTo', model: 'Group'}
],
...
});
Association in Group:
associations: [
{type: 'hasMany', model: 'GroupUsers', name: 'group_users'}
],
I will now be able to access a store of GroupUsers for a particular Group:
group.group_users()
The problem with this approach, is that I can't just bind a store of GroupUsers to my second grid, because I want to display things like the user's name. I could iterate the store's items, fetch each User object with getUser(), add them to another store, and use that for my Grid, but that results in a server request per item! Alternatively, I could use my store of GroupUsers directly, but then would need to do something with renderers and I still need to fetch each User individually.
Direct association
Associate User and Group directly with a hasMany association on each
Associations on Group:
associations: [
{type: 'hasMany', model: 'User', name: 'users', foreignKey: '???'}
],
I can now get a store of actual User objects for a given group:
group.users()
Which would be great, except there's nothing for me to set the foreignKey of the association to. User can't have a group_id field, because a User can have many Groups!
Maybe this is not the answer you look for, but this is how I would solve this issue :
I would not link the groups and the users with extjs store associations, but rather on the server side.
In the controller of your grid put something like this :
init: function(){
this.control({
'grid': {itemdblclick: this.onGridItemdblclick}
})
},
onGridItemdblclick: function(grid, record){
var group_id = record.getId(),
usersStore = this.getStore('Users');
usersStore.load({params: {group_id: group_id}});
var win = Ext.widget('UsersGrid'); // adapt the code to your naming scheme
win.show();
}
The request to load the Users store will be sent with an extra parameter group_id. On the server side, your can use this extra parameter to filter your users.

How to insert association/child data into Ext.data.list as separate entries

I'm using ST2 and using MVC. I'm very new to ExtJS and Sencha, so am not au fair the best practices for many things - and on this issue I've hit a dead end despite research.
I'll use a toy example to illustrate my issue below, but essentially I have a relationship as follows (which all works correctly from an association perspective).
Business X -- Location A
|
-- Location N
The Problem
I want to then populate the data into (for instance) an Ext.dataview.List, but to process it such that each location (i.e. child location) has its own separate entry in the table; not just a simple itemTpl formatting a single entry. However, at present I can't find any way to do that. Is it possible to hook into a List and format the data as I want, or should I be creating a new store? Ideally I want to make best use of the associations.
As a rough example, each entry would look like this, with some parent data and some child data:
---------------------------
|Smith Co - 1 Smith Street|
---------------------------
|Smith Co - 24 High Street|
---------------------------
|Tea[...] - 12 Tea Leaf |
---------------------------
|Tea[...] - 3 Bis Kit |
---------------------------
Example Code
Raw data
[
{
"id":1,
"name":"Smith Co",
"locations":[
{
"address":"1 Smith Street"
},
{
"address":"24 High Street"
}
]
},
{
"id":2,
"name":"Tea So Good",
"locations":[
{
"address":"12 Tea Leaf"
},
{
"address":"3 Bis Kit"
}
]
}
]
Location Model
Ext.define('Example.model.Location', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'address', type: 'string' }
],
proxy: { ... }, // Rest proxy that loads data as shown above.
BelongsTo: 'Example.model.Company'
}
});
Company Model
Ext.define('Example.model.Company', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string' }
],
proxy: { ... }, // Rest proxy that loads data as shown above.
hasMany: { model: 'Example.model.Location', name: 'locations' }
}
});
Store
Ext.define('Example.store.Companies', {
extend: 'Ext.data.Store',
require: 'Example.model.Company',
config: {
model: 'Example.model.Company'
}
});
Controller
// (works correctly, relationships are traversable)
// Companies store is looked up and loaded in #launch()
View
Ext.define('Example.view.CompaniesList', {
extend: 'Ext.List',
xtype: 'companieslist',
config: {
layout: 'fit',
store: 'Businesses',
itemTpl: [
// Tpl is only to format inside each element
]
}
});
Solution Edit (15th Sept 2013):
Solution
I used the solution #rixo suggested (and I had been hoping to avoid in the original question).
I created a separate store for the list, and loaded the data I need into it by using a load listener on the Companies store. This seems to be the most graceful solution available, although it means you may need to add extra logic in various places to ensure it remains satisfactorily synchronised.
By pushing the location objects themselves into the new store the associations remain intact (i.e. you can still do location.getCompany()).
Yes, create another store for locations.
You may have tried a template like this:
itemTpl: [
'{name}',
'<tpl for="locations">',
', {address}',
'</tpl>'
]
But that will indeed only display the information, it won't let you interact with each location as an individual list item.
You could get it working by hacking the view's doRefresh method, but that's just going against the lib's intention and other developer' expectations.
Maybe the problem is that you can get the data only in this format, that is with locations as children of companies, and you can't get the server to send you a flat list of companies. In that case, I think the most meaningful approach would be to customize a reader to flatten locations from companies, and feed a standalone location store. The extractData method seems a very promising start for that (see how the JSON reader uses it to implements its root property).

Resources