ExtJS Grid column with dataIndex from referenced model - extjs

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.

Related

Why doesn't a store declared in a ViewModel get loaded?

This question is related to Trying to bind a store to a ViewModel, but is a different question.
I'm declaring a store in a viewmodel like this:
Ext.define('Mb.view.rma.DetailsModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.rma-details',
requires: ['Mb.model.rma.Detail'],
data: {
id: 0
},
stores:{
details: {
//store: 'rma.Details',
type: 'rmaDetails', // correction as suggested by #scebotari
filters: [{
property: 'rma',
value: '{id}'
}],
remoteFilter: true
}
}
});
When I instantiate the view, the value id is correctly updated in the viewmodel (I can see this because the View reflects it through the bound title):
Ext.define('Mb.view.rma.Details', {
extend: 'Ext.grid.Panel',
requires: [
'Mb.view.rma.DetailsModel'
],
viewModel: {
type: 'rma-details'
},
bind: {
title: 'Retour n° {id}',
store: '{details}'
},
The problem I'm facing is that the store does not get loaded, even though there is the remoteFilter: true property in the viewmodel (setting remoteFilter: true in the store class instead of the viewmodel does not change the behavior).
To complete the code, here is the store (nothing special):
Ext.define('Mb.store.rma.Details', {
extend: 'Ext.data.Store',
model: 'Mb.model.rma.Detail',
alias: 'rmaDetails', // correction as suggested by #scebotari
proxy: {
(...)
},
remoteFilter: true
});
Main question: Why doesn't the store get loaded ?
Subsidiary question: It looks as a frequent problem to show a details grid like the lines of an invoice for instance. I'm not sure if what I'm trying is the recommended approach using MVVM for this problem. MVVM seems better than MVC, because it gives the possibility to open more than one details instance at a time. I didn't find an example for this case. Is there a recommended way to solve this problem ?
Notes:
The filter gets applied as it should.
The store {details} doesn't seem to be a chained store. In fact, it becomes a chained store when changing store: 'rma.Details' into source: 'rma.Details' in the viewmodel.
As far as I am aware, this is not how you use stores declared in separate classes in the viewmodels.
When declaring a store that you want to use in a view model, you should specify the "alias" config like so:
Ext.define('Mb.store.rma.Details', {
extend: 'Ext.data.Store',
model: 'Mb.model.rma.Detail',
alias: 'store.rmaDetails',
proxy: {
(...)
}
});
And then use this alias in the viewmodel in the "type" store config:
stores:{
details: {
type: 'rmaDetails',
filters: [{
property: 'rma',
value: '{id}'
}],
remoteFilter: true,
autoLoad: true
}
}
I don't know why this is not documented yet. Maybe because this capability was added later (https://www.sencha.com/forum/showthread.php?284012-Use-existing-Store-in-ViewModel).
The data will load automatically when setting autoLoad: true. The store configs in the viewmodel extend the store class store.rmaDetails. Each view instance has its own store instance associated (that is different from the default instance .getStore('rma.Details').

Trying to get a basic combo to work in ExtJS

I want to use a very simple combo box in ExtJS, but I was surprised to learn that it seems as though I have to complexify things by using a store.
I have a single array of data :
var states = [
{"name":"Alabama"},
{"name":"Alaska"}
]
I create my model 'State' linking to the 'name' field, and then I create my store linking to the model, and the array of data.
Ext.regModel('State', {
fields: [
{type: 'string', name: 'name'}
]
});
var store1 = Ext.create('Ext.data.Store', {
model: 'State',
data: states
});
Now I create my combo, as a field in my panel :
var f = Ext.create('Ext.form.Panel', {
items: [
{
fieldLabel: 'hi there',
xtype: 'combobox',
name: 'XXXX',
store:store1,
maxLength: 64,
allowBlank: false
}
]
})
Nothing tells me that I am doing anything wrong, but I get an 'Uncaught TypeError: Cannot read property 'indexOf' of undefined ' whenever I try and open the combo.
My fiddle is here :
http://jsfiddle.net/sr61tpmd/1/
Another aside to my question is, what is the simplest way I can present a combobox in ExtjS?
As long as you only want a combo box with same value as displayed, it is entirely possible to define the store as an array.
xtype:'combo',
store:['Alabama','Arkansas',...]
A real extjs store is necessary where your displayed text differs from the value. You can see a working example for this (also using the us states, actually) in the ext docs: http://docs.sencha.com/extjs/4.2.2/#!/api/Ext.form.field.ComboBox

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.

extjs combobox model

Is there a way to bind a store to a ExtJS ComboBox without creating a js model (ExtJS 4.1)?
To populate a users combobox I'll always need to set up the model first? I would like to skip the following code for each combobox:
Ext.define('User',{extend:'Ext.data.Model',idProperty:'Id',fields:['Id','Name']});
You're right, Neil!
I've found how to use it:
var myStore = Ext.create('Ext.data.Store',{
fields:['Id','Name'],
data:[
{Id:0,Name:'Yes'},
{Id:1,Name:'No'},
{Id:2,Name:'Maybe'}
]
});
var pnl = Ext.create('Ext.panel.Panel', {
xtype: 'panel',
title: 'My Panel',
items: [{
id:'cboField1'
xtype:'combobox',
fieldLabel:'My Field',
displayField:'Name',
valueField:'Id',
queryMode:'local',
store: myStore
}]
});
If you're using Architect you can specify an array in the store property for the ComboBox. There is no point to create extra stores & models if you just want a static 'Title' ComboBox.
xtype:'combo',
fieldLabel:'Title',
name:'division',
queryMode:'local',
store:['Mr','Mrs','Ms'],
displayField:'title',
autoSelect:true,
forceSelection:true
p.s. In order to change the store property to a local array in Architect you need to select the little icon on the left of the store text and change it to an array instead of a store.
I found it useful to create simple model with two fields id, name and then use this model on all static stores (which I use for comboboxes) where list of values is predefined.
You do not need to set a model in a store in any situation in Extjs. Set the fields property of the store.
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.data.AbstractStore-cfg-fields
Also, consider the data property of the store for local data.
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.data.Store-property-data

extjs 3.4 editable grid: how to separate displayField and ValueField in column

I'm trying to make something that looks like: http://dev.sencha.com/deploy/ext-3.4.0/examples/grid/edit-grid.html
But I want to change Light column:
I want it to contain ids instead of actual values.
I can force combobox to separate values from presentation but not the actual column value (in fact I don't know where to store id-value mapping for column (not just for the editor)):
new Ext.grid.EditorGridPanel({
...
store: new Ext.data.Store ({
...
fields: [
'MagicId',
...
]
})
columns: [
{
header: 'Magic',
dataIndex: 'MagicId',
editor: new Ext.form.ComboBox({
store: new Ext.data.Store({
...
fields: ['id', 'title']}),
valueField: 'id',
displayField: 'title',
editable: 'false'
})
},
...
]
When I select "Magic title" in combobox I get MagicId in my grid anyway. I understand why it's happening but can't make it work the way I need it to work...
I tried to replace all unnecessary code with ... to help you reading.
Thank you for your attention.
Keep the ID field in your grid/store, then use the "renderer" property to display something else. ID-text mapping could be stored in an array or an object:
{
header: 'Magic',
dataIndex: 'MagicId',
renderer: function(value) {
return magicIdValueArray[value];
}
...
}
EDIT:
Since you already have the ID-value mapping in the combo store, I would use that store to fetch the value (it needs to be declared outside the combobox).
renderer: function(value) {
var record = comboStore.findRecord('id', value);
return record.title;
}

Resources