Define mapping in reader - extjs

I have defined a custom ComboBox component, that I want to re-use with multiple Stores. It has a simple config as below.
Ext.define('Myapp.CustomCombo', {
extend: 'Ext.form.ComboBox',
valueField: 'id',
displayField: 'name'
});
The model is
Ext.define('Myapp.model.ComboModel',{
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string' },
{ name: 'linkCls', type: 'string' }
]
});
I define multiple stores each with its own proxy config & identified by a unique storeId, as below
Ext.create('Myapp.store.EmployeeComboStore', {
model: 'Myapp.model.ComboModel',
storeId: 'employeeLOVComboStore',
proxy: {
type: 'rest',
url: '/employees/getLovData',
reader: {
type: 'json',
rootProperty: 'data'
}
}
});
The server responds with a json as below
{
"data" : [
{"employeeId": 1, "employeeName": "Chris"},
{"employeeId": 2, "employeeName": "Jack"},
]
}
I can have multiple such stores, like departmentStore with a different proxy URL and where in, the server response could be
{
"data" : [
{"departmentId": 1, "departmentName": "Sales"},
{"departmentId": 2, "departmentName": "Marketing"},
]
}
I want to define a mapping in the Reader, with instructions to map data differently in different stores, as in.. employeeId should be mapped to 'id' & employeeName to 'name' in employeeStore, whereas departmentId should be mapped to 'id' & departmentName to 'name' in departmentStore.
I've seen that there are options to define mapping for each field in a Model, but I would want to define a mapping in a Reader, since the server responds with data from a relational database where field names would be the columnNames.

What you can do, is to send along metaData from the server to the client.
The json response can look like this:
{
data: [{ ... }],
msg: "...",
total: 99,
metaData: {
fields: [{ ... }],
columns: [{ ... }],
idProperty: "id",
messageProperty: "msg",
root: "data"
}
}
In you case it would be sufficient if the json response would look like this
{
data: [{ ... }],
metaData: {
fields: [
{ name: 'Id', type: 'int', mapping: 'departmentId' },
{ name: 'Name', type: 'string', mapping: 'departmentName' },
]
}
}
Here is also a good example how it works: Basic Meta Data Config

Related

ExtJS load data to Form from Store with association

I have a single form that has fields for a Employee Model and fields for Person Model.
Employee Model
Ext.define('App.model.Employee', {
extend: 'Ext.data.Model',
fields: [{
name: 'EmployeeId',
type: 'int'
},
{
name: 'PersonId',
type: 'int'
},
'EmployeeNumber',
'JoinDate'
],
belongsTo: 'App.model.Person'
});
Person Model
Ext.define('App.mode.Person', {
extend: 'Ext.data.Model',
fields: [{
name: 'PersonId',
type: 'int'
},
'FirstName',
'LastName'
],
hasMany: {
model: 'App.model.Employee',
name: 'Employees'
}
});
My Store:
var store = Ext.create('Ext.data.Store', {
model: 'App.model.Employee',
autoLoad: false,
autoSync: true,
proxy: {
type: 'ajax',
api: {
read: 'api/employee/getemployee'
},
reader: {
type: 'json',
successProperty: 'success',
root: 'data',
messageProperty: 'message'
}
}
});
This is my actual JSON
"data": [{
"EmployeeId": 5,
"PersonId": 1,
"EmployeeNumber": 2001,
"JoinDate" : "",
"Person": {
"PersonId": 1,
"FathersLast": "SMITH",
"FirstName": "JOHN"
}
}],
My question is how does my json response (from server) should be structured in order to load store successful?
You can find explanation and sample code in Ext data package guide section "Loading Nested Data". Other example is in the heading of Association documentation. Both with sample JSON files showing the required structure.
After looking at your code and json structure i can recognize that each employee has a person object, so you need to correct your mapping. Instead of Defining hasMany relationship in person model, you should define hasOne relationship for person in employee.
hasOne: {
model: 'App.mode.Person',
name: 'Person'
}
Please try this out. Your json structure looks good.

Extjs binding a form to a model that has associations

I have the following JSON structure:
userAuth
----userAccount -- hasOne
----userAccountDetails -- hasMany
I want to show a form that shows data from userAuth and userAccount and then a grid with a row for each row in userAccountDetails
Here is the code for userAuth model:
Ext.define('xxxx.model.UserAuth', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id' },
{ name: 'email' },
{ name: 'userName' },
{ name: 'refIdStr' },
{ name: 'displayName' },
{ name: 'modifiedDate', type: 'date', }
],
requires: [
'xxxx.model.UserAccount',
'xxxx.model.UserAuthDetails',
],
hasMany: [
{
model: 'xxxx.model.UserAuthDetails',
name: 'authDetails',
foreignKey: 'userAuthId',
associationKey: 'userAuthDetails',
}
],
hasOne: [
{
model: 'xxxx.model.UserAccount',
name: 'userAcount',
associationKey: 'userAccount',
getterName: 'getUserAccount',
setterName: 'setUserAccount',
}
],
proxy: {
type: 'ajax',
startParam: 'skip',
limitParam: 'take',
url: settings.ApiUrl + '/model/UserAuth/?format=json',
baseParams: {
skip: '0',
take: '10',
},
reader: {
type: 'json',
root: 'results',
successProperty: 'success',
totalProperty: 'totalCount'
}
}
});
And userAccount model
Ext.define('xxxx.model.UserAccount', {
extend: 'Ext.data.Model',
fields: [
{ name: 'username' },
{ name: 'id' },
{ name: 'name' },
{ name: 'email' },
{ name: 'slug' },
{ name: 'facebookId' }
],
belongsTo: 'xxxx.model.UserAuth',
proxy: {
type: 'ajax',
startParam: 'skip',
limitParam: 'take',
url: settings.ApiUrl + '/model/UserAccount/?format=json',
baseParams: {
skip: '0',
take: '10',
},
reader: {
type: 'json',
root: 'results',
successProperty: 'success',
totalProperty: 'totalCount'
}
}
});
and userAuthDetails:
Ext.define('xxxx.model.UserAuthDetails', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id' },
{ name: 'userAuthId' },
{ name: 'provider' },
{ name: 'userId' },
{ name: 'userName' },
{ name: 'displayName' },
{ name: 'firstName' },
{ name: 'lastName' },
{ name: 'email' },
{ name: 'modifiedDate' },
{ name: 'provider' },
],
belongsTo: [
{
model: 'xxxx.model.UserAuth',
isntanceName: 'userAuth',
getterName: 'getUserAuthDetails',
setterName: 'setUserAuthDetails',
associationKey: 'userAuthDetails'
}
],
proxy: {
type: 'ajax',
startParam: 'skip',
limitParam: 'take',
url: settings.ApiUrl + '/model/UserAuthDetails/?format=json',
baseParams: {
skip: '0',
take: '10',
},
reader: {
type: 'json',
root: 'results',
successProperty: 'success',
totalProperty: 'totalCount'
}
}
});
Loading the store works and the associations work - when i call record.getUserAccount().get('email') it returns what I'd expect - so all that is working.
Now, my question is - how the hell do i get that data into the form described above?
I have tried doing things like:
{
xtype: 'textfield',
fieldLabel: 'username',
allowBlank: false,
maxLength: 100,
name: 'userAccount.userName'
}
thinking the association name might be a good hint for it - but that doesn't work...
Data from the top most model (userAuth) works ok - I just can't seem to pull in the hasOne
into the form.
I haven't even tried binding the userAuthDetails to a grid yet but suspect that will be equally as challenging
Has anyone managed to get this to work? I only went down the associations route because i thought it would make this sort of stuff cleaner / simpler!
[EDIT]
Giving up trying to do this with a form as I am happy for this data to be read only for now. I am now trying to show this data in an xtemplate within a row expander.
I've tried the following but it does not work!!!
plugins: [
{
ptype: 'rowexpander',
rowBodyTpl: [
'<p><b>Username:</b> {userName}</p>',
'<p><b>Email:</b> {userAccount.email}</p>',
'<tpl for="userAuthDetails">',
'<p>{provider}</p>',
'</tpl>'
]
}],
What sort of voodoo do I need to make this work? It is not obvious at all!!!
ATM I am thinking I won't be using ExtJs again - I've found myself in too many dark holes like this recently where things blatantly should just work the way you'd expect - just googling this shows everyone expects this to work. I've used ExtJs on and off for years but I think now is the time to finally admit there are better js application frameworks out there.
Unfortunately, there is no automatic, config-only way of how to do that. If you look at the loadRecord source you see that it just gets data from the passed record and calls setValues on the form.
You would need to override loadRecord method to dig the required values from the associated record(s).

Is Sencha ExtJS association POST wrong?

So, I have a problem using Sencha ExtJs 4.1 Associations.
I have something like:
Models
Ext.define('Tpl.model.User', {
extend: 'Ext.data.Model',
requires: ['Tpl.model.PostTemplate'],
fields: [
{ name: 'id', type: 'int' },
{ name: 'name', type: 'string' },
{ name: 'postTemplate', type: 'int' }
],
associations: [
{ type: 'belongsTo', name: 'postTemplate', model:'Tpl.model.PostTemplate', associationKey: 'postTemplate', primaryKey: 'id', foreignKey: 'postTemplate' }
]
});
and
Ext.define('Tpl.model.PostTemplate', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id', type: 'int' },
{ name: 'blah', type: 'string' }
],
associations: [
{ type: 'hasMany', model: 'Tpl.model.User' }
]
});
Stores
Ext.define('Tpl.store.Users', {
extend: 'Ext.data.Store',
model: 'Tpl.model.User',
autoLoad: true,
proxy: {
type: 'rest',
url: '../users',
reader: {
type: 'json',
root: ''
},
writer: {
type: 'json'
}
}
});
Ext.define('Tpl.store.PostTemplate', {
extend: 'Ext.data.Store',
model: 'Tpl.model.PostTemplate',
autoLoad: true,
proxy: {
type: 'rest',
//appendId: true,
url: '../postTemplates/',
reader: {
type: 'json',
root: ''
},
writer: {
type: 'json'
}
}
});
The problem is that I'm getting a POST like this:
{
"postTemplate": 1,
"id": 0,
"name": "foo"
}
But I need a POST like this:
{
"postTemplate": {
"id": 1,
"blah": "foo"
},
"id": 0,
"name": "bar"
}
Also, the assessor function like "setPostTemplate" doesn't exist and I think it should be created automatically.
Already tried to something like " record.data.postTemplate = (...) " but I got an infinite loop throwing an execption...
Can someone please advise? Also, if you need something else that could be useful for the answer let me know.
Thanks!
Out of the box the Associated objects are not serialized back to the server as you expect them . This issue has been brought up many times. The best thing might be to follow this thread:
http://www.sencha.com/forum/showthread.php?141957-Saving-objects-that-are-linked-hasMany-relation-with-a-single-Store
This thread talks about overriding getRecordData in the JSON writer class.

ExtJS 4.1 - Nested hasOne associated in JSON

I have JSON in the following format:
{
"id": 1,
"arbitraryAttribute": "arbitraryValue",
"thing": {
"thingKey1": "thingValue1",
"thinkKey2": "thingValue2"
}
}
This model is represented like so:
Ext.define("MyModel", {
extends: "Ext.data.Model",
fields: [
{name: "id", type: "string"},
{name: "arbitraryAttribute", type: "string"},
],
hasOne: [
{
model: "Thing",
name: "thing"
}
]
});
Ext.define("Thing", {
extends: "Ext.data.Model",
fields: [
{name: "thingKey1", type: "string"},
{name: "thingKey2", type: "string"}
]
});
The proxy is a simple json proxy. The JSON it is getting looks like that which I presented, but my record seems to have no knowledge of the Thing model. Is there any additional plumbing I need to set up to get MyModel to pull the nested Thing json?
Your forgot to set the thing_id in MyModel. Also this doesnt work at all in ExtJs.
You can now set the thing_id via JSON but not the whole object as you do (and it should).
It will autoLoad the complete object via the models proxy if required.
Ext.define('User', {
extend:'Ext.data.Model',
fields: ['id', 'name', 'status'],
associations: [
{ type: 'hasOne', model: 'Status', associationKey: 'status' }
]
});
Ext.define('Status', {
extend:'Ext.data.Model',
fields: ['id', 'title'],
});
Demo here

EXTJS4--Why don't my associated stores load child data?

So I have a parent and child store, illustrated here:
Parent Model
Ext.define('APP.model.Client', {
extend: 'Ext.data.Model',
requires: [
'APP.model.Website', 'Ext.data.association.HasMany', 'Ext.data.association.BelongsTo'],
fields: [{
name: 'id',
type: 'string'
}, {
name: 'name',
type: 'string'
}, {
name: 'slug',
type: 'string'
}, {
name: 'active',
type: 'boolean'
}, {
name: 'current',
type: 'boolean'
}],
hasMany: {
model: 'APP.model.Website',
name: 'websites'
}
});
Child Model
Ext.define('APP.model.Website', {
extend: 'Ext.data.Model',
fields: [{
name: 'id',
type: 'string'
}, {
name: 'client_id',
type: 'string'
}, {
name: 'sub_domain',
type: 'string'
}, {
name: 'active',
type: 'boolean'
}],
belongsTo: 'APP.model.Client'
});
Using an AJAX call via the server, I am loading the Clients store, and that is loading fine. But the Websites store isn't populated, and when I breakpoint on the Clients store on.load function, to see what it's populated with, the Client store is only populated with the client data, but in the raw property for that store, I can see all the websites data. So it's being returned correctly, but my extjs isn't correct. Here are the stores:
Client Store
Ext.define('APP.store.Clients', {
extend: 'Ext.data.Store',
autoLoad: false,
model: 'APP.model.Client',
proxy: {
type: 'ajax',
url: '/client/list',
reader: {
type: 'json',
root: 'items'
}
},
sorters: [{
property: 'name',
direction: 'ASC'
}]
});
Websites Store
Ext.define('APP.store.Websites', {
extend: 'Ext.data.Store',
requires: ['Ext.ux.Msg'],
autoLoad: false,
model: 'APP.model.Website',
proxy: {
type: 'ajax',
url: '/client/list',
reader: {
type: 'json',
root: 'items'
},
writer: {
type: 'json'
}
},
sorters: [{
property: 'sub_domain',
direction: 'ASC'
}]
});
My final result is...I would like to populate both stores so I can click on an element, and when it loads something from the parent store, I can access the child store(s) (there will be more when I figure out this problem) to populate a couple grid(s) in tabs.
What am I missing as far as my setup? I just downloaded extjs4 a couple days ago, so I am on 4.1.
Put your proxies in your models, unless you have a good reason not to [1]
Make sure you require the related model(s), either in the same file, or earlier in the application
Use foreignKey if you want to load the related data at will (i.e. with a later network request).
Use associationKey if the related data is loaded in the same (nested) response
Or just use both
Always name your relationships (otherwise the name will be weird if using namespaces).
Always use the fully qualified model name for the model property in your relationships
Working code:
model/Contact.js:
Ext.define('Assoc.model.Contact', {
extend:'Ext.data.Model',
requires:[
'Assoc.model.PhoneNumber'
],
fields:[
'name' /* automatically has an 'id' field */
],
hasMany:[
{
model:'Assoc.model.PhoneNumber', /*use the fully-qualified name here*/
name:'phoneNumbers',
foreignKey:'contact_id',
associationKey:'phoneNumbers'
}
],
proxy:{
type:'ajax',
url:'assoc/data/contacts.json',
reader:{
type:'json',
root:'data'
}
}
});
model/PhoneNumber.js:
Ext.define('Assoc.model.PhoneNumber', {
extend:'Ext.data.Model',
fields:[
'number',
'contact_id'
],
proxy:{
type:'ajax',
url:'assoc/data/phone-numbers.json',
reader:{
type:'json',
root:'data'
}
}
});
data/contacts.json:
{
"data":[
{
"id":1,
"name":"neil",
"phoneNumbers":[
{
"id":999,
"contact_id":1,
"number":"9005551234"
}
]
}
]
}
data/phone-numbers.json
{
"data":[
{
"id":7,
"contact_id":1,
"number":"6045551212"
},
{
"id":88,
"contact_id":1,
"number":"8009996541"
},
]
}
app.js:
Ext.Loader.setConfig({
enabled:true
});
Ext.application({
requires:[
'Assoc.model.Contact'
],
name:'Assoc',
appFolder:'Assoc',
launch:function(){
/* load child models that are in the response (uses associationKey): */
Assoc.model.Contact.load(1, {
success: function(record){
console.log(record.phoneNumbers());
}
});
/* load child models at will (uses foreignKey). this overwrites child model that are in the first load response */
Assoc.model.Contact.load(1, {
success: function(record){
record.phoneNumbers().load({
callback:function(){
console.log(arguments);
}
});
}
});
}
});
[1] A store will use its model's proxy. You can always override the store's proxy if need be. You won't be able to use Model.load() if the model has no proxy.
Your assumption is wrong. You expect that the WebSite store loads itself but that is not how it works. What you can do is the following (this is untested but is how I do it in all my projects):
In your clients grid add a listener for the itemclick event to call the following method (showWebSites). It will receive the selected client record containing the selected APP.model.Client instance. Then, and given that each client has a set of WebSites, the method will load the APP.store.Websites store and the client´s websites will be displayed in your view.
showWebSites: function (sender, selectedClient) {
Ext.StoreManager.lookup('APP.store.Websites')
.loadData(selectedClient.data.WebSites);
}
This is the way. I hope you find it useful.

Resources