ExtJS Model with repeating structures - extjs

What patterns have been successful when mapping a model with a few repeating structures.
I have to model and create a grid view a record with has a few columns which are basically the same repeating structure.
Ext.define('Ep.mm.model.MyComparison', {
var derived = function(val,)
extend: 'Ext.data.Model',
fields : [
{name:'id',type:'string'},
{name:'count',type:'number'},
{name:'special',type:'number'},
{name:'A_a',type:'number'},
{name:'A_b',type:'number'}
{name:'A_derived',renderer = function(val,meta,rec){return rec.get('A_b')-rec.get('A_a')}}
{name:'B_a',type:'number'},
{name:'B_b',type:'number'}
{name:'B_derived',renderer = function(val,meta,rec){return rec.get('B_b')-rec.get('B_a')}}
{name:'C_a',type:'number'},
{name:'C_b',type:'number'}
{name:'C_derived',renderer = function(val,meta,rec){return rec.get('C_b')-rec.get('C_a')}}
]
});
I am likely to end up with Server models/stores that differ only in the number of times this structure repeats (A) (A,B) (A,B,C,D). Is there a way to use a common model for each of A,B,C,D and just re-use the common model as the type. This is something akin to a 'nested' structure.
Ext.define('MyType',{
extend :'Ext.data.Model',
fields : [
{name : 'a', type:'number'},
{name : 'b', type:'number'},
{ name : 'derived',
convert : function(value, record) {
return record.get('a') - record.get('b');
}
}
]
}
Then MyComparison would be defined as
Ext.define('Ep.mm.model.MyComparison',{
extend : 'Ext'data.Model',
fields : [
{name:'A', type:'MyType'}
{name:'B', type:'MyType'}
{name:'C', type:'MyType'}
]
});
I know this isn't quite right, but this is sort of the functionality I am trying to create.
Perhaps using the 'hasOne' association type?

Yes, you can extend your own model classes:
Ext.define('Base', {
extend: 'Ext.data.Model',
fields: ['name']
});
Ext.define('Child', {
extend: 'Base',
fields: ['age']
});
var o = new Child({
name: 'Foo',
age: 100
});
console.log(rec.get('name'), rec.get('age'));
As a sidenote, models don't have renderers. You're probably looking for the convert function on the field, here.

I'd say you want functionality of a field type:
Ext.data.Types.A_DERIVED = {
convert: function(v, data) {
return rec.get('A_b')-rec.get('A_a');
},
type: 'number'
};
Then use it as a type of A_derived field:
Ext.define('Ep.mm.model.MyComparison', {
extend: 'Ext.data.Model',
fields: [
{name:'id',type:'string'},
{name:'A_a',type:'number'},
{name:'A_b',type:'number'},
{name:'A_derived', type: 'Ext.data.Types.A_DERIVED'}
]
});
Afterthoughts:
- Maybe you would like to aggregate all A_* data into one field? See documentation for an example.
- There is a trouble when you want to use the same mechanism with B_derived because there is no way of supplying B into convert function. A dirty hack might be to set the defaultValue to B and to bind the field to null data. Then v would be read from defaultValue.

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.

ExtJS 4 I don't understand models + associations

I don't think the documentation is very clear on this - at least I can't figure out how associations work with models in ExtJS 4. Lets look at a simple example:
the models
Ext.define('app.model.Goo', {
...
fields: ['id', 'foo_id', 'goo_field'],
belongsTo: 'Foo'
});
Ext.define('app.model.Foo', {
...
fields: ['id', 'foo_field'],
hasMany: {model: 'Goo', name: 'goos'} //
});
this allows me to easily write a server response that returns nested data such as:
{
success: true,
foo: {
id: 42
foo_field: 'bacon',
goos: [
{ id: 0, goo_field: 'velociraptor' },
{ id: 1, goo_field: 'spidermonkey' },
...
]
}
}
and parse out the data into their respective models. But what if I want to load nested data lazily? Say I write my server such that it doesn't send any goos field in my returned foo object. What does it mean to write foo.goos().load()? What's being sent to my server then? GET <proxy:url>/<'id' of foo>/goos ?
If you want to lazy load goo you should not send goo in the foo response. But instead you call foo.goos(). This wil return a goo store with a filtervalue foo_id on 42 (primary id from your foo instance). Basicly its doing something like this for you:
Ext.create('Ext.data.Store', {
model: 'app.model.Goo',
filters: [
{
property: 'foo_id',
value: 42
}
]
});
So calling load does the request. Assuming you are using REST proxy, your goo proxy will do the following request: GET <proxy:url>with filter-queryparameters: filter:[{"property":"foo_id","value":42}].
Also I believe you need to specify fully qualified class name (ie. model: 'app.model.Goo').

sencha touch 2: binding associations data to existing store

I have a simple data model that looks something like this (actual code below):
model Game:
fields: id, team_1_id, team_2_id
model GameScore:
fields: id, game_id, team_1_score, team_2_score, is_final, submission_date
model SpiritScore:
fields: id, game_id, team_1_score, team_2_score
What I want seems simple. I already have code that loads Games and GameScores in bulk. I have a 'Game' instance in hand, and can call gameScores(). And I get a store, but it's empty. I have code that will dynamically load it, by placing the store into the model's hasMany definition. But what I would really like is some way to bind the Game.gameScores() call to the my existing GameScores store. Even if it used a normal filter underneath, that gives me a single record that I can bind and use in a view. (Important note: the data does not come in nested form.)
This leads to my second question. Game:GameScores is 1:many, but I only ever display the most recent one (from live score reporting). What is the general approach here? I can also manually build a filter from the game_id, but I can only bind 1 record to a view, so I don't see how I can bring that other information into a view, short of a proper hasMany relationship. Is there another way?
Any and all advice, including telling me to RTFM (with a link to the relevant manual) would be greatly appreciated! I've been pulling my hair out on this (pro bono side project) for the last week.
Cheers!
b
Ext.define('TouchMill.model.Game', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'team_1_id', 'team_2_id' ],
hasMany: {
model: 'TouchMill.model.GameScore',
name: 'gameScores',
},
},
});
Ext.define('TouchMill.model.GameScore', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', 'is_final', 'submission_date', ],
},
// belongsTo necessary? Don't think so unless I want parent func?
});
Ext.define('TouchMill.model.SpiritScore', {
extend: 'Ext.data.Model',
config: {
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', ],
},
},
I've never used touch, so I'm speaking about Ext4 here (4.2 to be precise)... And, your model definitions seem a bit broken to me (is that working with touch?). But whatever, you'll get the general idea. If my code don't work in touch, please try with Ext4.
Also, I understood that you're loading all your scores at once. If that's not the case, my solution will need to be adapted...
So, my general reasoning is the following: if you've loaded all your scores in memory, then why not use a memory proxy that uses the score store's data as the data source for the store generated for the association? I tried that and, quite to my surprise, it worked without a glitch.
To understand this, you need to know that a proxy is an independant data source, that is a proxy can be shared between multiple stores without problem. On the other hand, a store is expected to be bound to a single view or task. For example, if you bind the same store to two different grids, then filtering the first grid will affect the second as well.
And while most proxies do not "contain" their data, memory proxy do. Here's a relevant excerpt of Ext.data.proxy.Memory#read method:
resultSet = operation.resultSet = me.getReader().read(me.data)
So, enough theory, here's the proof of concept (tested in this fiddle):
// I instantiate this proxy myself in order to have a reference available
var masterScoreProxy = Ext.create('Ext.data.proxy.Memory');
Ext.define('TouchMill.model.GameScore', {
extend: 'Ext.data.Model',
fields: [ 'id', 'game_id', 'team_1_score', 'team_2_score', 'is_final', 'submission_date' ],
// I've used a remote server to ensure this all works even asynchronously
proxy: {
// configure your own
}
});
Ext.define('TouchMill.model.Game', {
extend: 'Ext.data.Model'
,fields: [ 'id', 'team_1_id', 'team_2_id' ]
,hasMany: {
model: 'TouchMill.model.GameScore'
,name: 'gameScores'
// required in order to avoid Ext autogenerating it as 'touchmill.model.game_id'
,foreignKey: 'game_id'
// needed if we don't want to have to call gameRecord.gameScores().load()
,autoLoad: true
// first part of the magic: make the generated store use my own proxy
,storeConfig: {
proxy: masterScoreProxy
}
}
});
// Just mocking a store with two games
var gameStore = Ext.create('Ext.data.Store', {
model: 'TouchMill.model.Game'
,data: [{id: 1}, {id: 2}]
,proxy: 'memory'
});
// Creating the "master" score store (that will use the model's proxy)
var scoreStore = Ext.create('Ext.data.Store', {
model: 'TouchMill.model.GameScore'
// second part's in there
,listeners: {
load: function(store, records, success) {
if (success) {
// 1. replace the data of the generated association stores' proxy
// (I must say I'm quite surprised that I didn't had to extract the data of
// every records, nor to configure a reader and all for my shared proxy...
// But hey, that works!)
masterScoreProxy.data = records;
// 2. update already generated stores
// Alternatively, you could call gameRecord.gameScores().load() individually
// before each usage of gameRecord.gameStores()
gameStore.each(function(record) {
var childStore = record.gameScoresStore;
if (childStore) {
childStore.load();
}
});
}
}
}
});
// test first load
scoreStore.load({
callback: function(records, operation, success) {
if (success) {
// and here's to prove it
gameStore.each(function(record) {
record.gameScores().each(function(score) {
console.log('Game ' + record.id + ': ' + JSON.stringify(score.data, undefined, 2));
});
});
testRefreshedData();
}
}
});
function testRefreshedData() {
// test refreshing
scoreStore.load({
callback: function(records, operation, success) {
if (success) {
console.log('--- Scores have changed ---');
gameStore.each(function(record) {
record.gameScores().each(function(score) {
console.log('Game ' + record.id + ': ' + JSON.stringify(score.data, undefined, 2));
});
});
}
}
});
}
Regarding your other questions...
If you have a 1:n for Game:Score, you've got a 1:1 for Game:MostRecentScore... So, I'd try to use that.
As for the view, there should always be a way -- even if hackish -- to access data nested in your records. The way will depend on what you're calling view here... See, for example this question.

Displaying categories/subcategories in sencha

I have recently develop a sencha app about displaying categories/sub categories. It displayed the main categories, but does not display the sub categories on clicking any category.
My store is--
Ext.define('listdemo.store.Sections', {
extend : 'Ext.data.Store',
config : {
autoLoad: true,
model: 'listdemo.model.Sections',
proxy:{
type : 'ajax',
url:'http://localhost/catt.php',
reader:{
type:'json',
rootProperty:'categories'
}
}
}
});
Model code is------
Ext.define('listdemo.model.Sections', {
extend: 'Ext.data.Model',
config: {
fields: ['categories_id', 'categories_name','subcategories'],
}
});
And the view is----
Ext.define('listdemo.view.Main',{
extend: 'Ext.NestedList',
xtype:'main',
requires: [
'Ext.TitleBar',
'Ext.dataview.List',
'Ext.data.Store',
'Ext.dataview.NestedList'
],
config:{
title: 'Categories',
//store:'Sections',
items:[
{
xtype:'list',
itemTpl:'{categories_name}',
title:'Categories',
store:'Sections',
}
]
}
});
My php file returns----
{"categories":[{"categories_id":"1","categories_name":"Hardware","subcategories":[{"categories_id":"4","categories_name":"Graphics
Cards"},{"categories_id":"5","categories_name":"Printers"},{"categories_id":"6","categories_name":"Monitors"},{"categories_id":"7","categories_name":"Speakers"},{"categories_id":"8","categories_name":"Keyboards"},{"categories_id":"9","categories_name":"Mice"},{"categories_id":"16","categories_name":"Memory"},{"categories_id":"17","categories_name":"CDROM
Drives"}]},{"categories_id":"2","categories_name":"Software","subcategories":[{"categories_id":"18","categories_name":"Simulation"},{"categories_id":"19","categories_name":"Action"},{"categories_id":"20","categories_name":"Strategy"}]},{"categories_id":"3","categories_name":"DVD
Movies","subcategories":[{"categories_id":"10","categories_name":"Action"},{"categories_id":"11","categories_name":"Science
Fiction"},{"categories_id":"12","categories_name":"Comedy"},{"categories_id":"13","categories_name":"Cartoons"},{"categories_id":"14","categories_name":"Thriller"},{"categories_id":"15","categories_name":"Drama"}]}]}
What will I do to display the sub categories under main categories.
You need to update your model definition to include field types, and register a custom data type to use for subcategories.
Start by including this before the models load:
Ext.data.Types.ARRAY = {
type: 'Array'
};
Next update your model to look like this:
Ext.define('listdemo.model.Sections', {
extend: 'Ext.data.Model',
config: {
fields: [{
name: 'categories_id',
type: 'int'
},{
name: 'categories_name',
type: 'string'
},{
name: 'subcategories'
type: Ext.data.Types.ARRAY
}]
}
});
Now once your store loads, check the records manually via the web console to ensure the array of subcategories is saved. You will need to add a listener for itemtap on the list, and then grab the subcategories and either:
-Reload the original store manually with the subcategories array. This should refresh the list, but you will lose the original store and there won't be any card animation.
-Set up a second list/store that will be reloaded manually with the subcategories, which will allow for the change card animation and maintain the original store for back navigation.
give your store the Store Id
2.pass the parameter categories_name and select the subcategory whose parent directory is categories_name
success: function (response)
{
searchresponse2 = Ext.JSON.decode(response.responseText);// getting response from server
//searchresponse2 is array storing 1st result in array index[0]
searchresponse2=searchresponse2[0];
var categoriesid=searchresponse2.categories;//getting 1st
values"categories_id":"1","categories_name":"Hardware"
var categoriesname=searchresponse2.categories_name;
del_categoriesid=[];//declaring array
del_categoriesname=[];
for(var i= 0;i<searchresponse2.length;i++)
{
del_categoriesid[i]=searchresponse2[i].categories;
del_categoriesname[i]=searchresponse2[i].categories_name;
}// looping through all values
for(var j= 0;j < searchresponse2.length;j++)
{
Ext.getStore('storeid').add({categories:del_categoriesid[j], categories_name:del_categoriesname[j]});
}// adding the values of the result in store

ExtJS: Display model in Ext.grid.property.Grid

I'd like to display the persistent fields (those defined in my model file) in a property grid.
Property Grid:
Ext.define('ATCOM.view.InspectorProperties', {
extend : 'Ext.grid.property.Grid',
alias : 'widget.inspectorProperties',
cls : 'property-grid',
height : 150,
listeners : {
beforerender : function() {
// Rename the first column
var cols = this.getView().getHeaderCt().getGridColumns();
cols[0].setText("Property");
},
beforeedit : function(e) {
// Read-only
return false;
}
},
source : {} // Start with no items
});
I load items like so in a select event (in a controller), where record is our model object and getInfo() is the property grid:
var source = {};
source.id = record.get('id');
source.start = record.get('start');
source.end = record.get('end');
this.getInfo().setSource(source);
Model:
Ext.define('ATCOM.model.Shift', {
extend : 'Ext.data.Model',
fields : [ 'id', {
name : 'start',
type : 'date',
}, {
name : 'end',
type : 'date',
}, 'position', 'controller' ],
hasMany : {
model : 'ATCOM.model.ShiftAlloc',
name : 'allocations'
}
});
Is there a better way to go about this so all non-associative fields (excluding allocations in my case) are automatically sent to the property grid? It might also be possible to read the fields with ATCOM.model.Shift.getFields() and iterate over those checking for persistent:false; to keep the remaining keys, but how do I get the class reference from an instance - as in, how do I get ATCOM.model.Shift from one of its instances so I can call getFields()?
EDIT:
For finding the class name: http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Base-static-method-getName
It may work to say setSource(record.data). I am just playing with this now; it seems to show the right information, though you may lose control over the details of which fields to enable for editing, etc.

Resources