PDA

View Full Version : Help getting associated data working in Etjs 4.2 MVC



delebash
13 May 2013, 4:41 AM
Scenerio,

Grid view that displays nested data. Not sure of the correct way to get this working in 4.2. I have it working by adding my nested field into the parent model. Parent model is Contact associated is ContactType, if I add ContactType.name to fields in Contact and add ContactType.name to grid, then nested data is displayed.

1) Is this the proper way to show nested data in a grid or should it be done via association?

2) Edit view popup load combobox with ContactTypes, this is working, but need to set selected value via the nested value ContactTypes from grid record

3)Save data back to server, both primary Contact and associated ContactType. When saving I am trying to use record.getAssociatedData() in my writer getRecordData function but no associated data is available, so I am not sure how I am setting up my associations wrong?

Grid is working, displaying nested data is working, loading combobox is working. Setting selected value not sure how. Setting up association so I can get all data to send back to server not working.

Here is my code that I am trying

Contacts Controller

Ext.define('SimplyFundraising.controller.Contacts', {
extend: 'Ext.app.Controller',


views: ['contacts.List', 'contacts.Edit'],
init: function () {
this.control({
'contactslist': {
itemdblclick: this.editContact,
removeitem: this.removeContact
},
'contactslist > toolbar > button[action=create]': {
click: this.onCreateContact
},
// 'contactsadd button[action=save]': {
// click: this.doCreateContact
// },
'contactsedit button[action=save]': {
click: this.updateContact
}
});
},
list: function () {

var mystore = this.getStore('Contacts')
mystore.proxy.extraParams = { $expand: 'ContactType'};
mystore.load({
params: {
},
callback: function(r,options,success) {
// debugger;
} //callback
}); //store.load
// mystore.proxy.extraParams = { $expand: 'ContactType'};
// var User = this.getContactModel();
// User.load(258, {
// success: function (user) {
// console.log("Loaded user 258: " + user.get('lastName'));
// }
// });
},
editContact: function (grid, record) {
var store = this.getStore('ContactTypes');
store.load({
params: {
},
callback: function(r,options,success) {
// debugger;
} //callback
}); //store.load

var view = Ext.widget('contactsedit');
view.down('form').loadRecord(record);
this.addnew = false
},
removeContact: function (Contact) {
Ext.Msg.confirm('Remove Contact ' + Contact.data.lastName, 'Are you sure?', function (button) {
if (button == 'yes') {
this.getContactsStore().remove(Contact);
}
}, this);
},
onCreateContact: function () {
var view = Ext.widget('contactsedit');
this.addnew = true
},
// doCreateContact: function (button) {
// var win = button.up('window'),
// form = win.down('form'),
// values = form.getValues(),
// store = this.getContactsStore();
// if (form.getForm().isValid()) {
// store.add(values);
// win.close();
// }
// },
updateContact: function (button) {
var win = button.up('window'),
form = win.down('form'),
record = form.getRecord(),
values = form.getValues(),
store = this.getStore('Contacts')
if (form.getForm().isValid()) {
if (this.addnew == true) {
store.add(values);
} else {
record.set(values);
}
store.sync();
win.close();
}
}
});


Contact Model

Ext.define('SimplyFundraising.model.Contact', {
extend : 'Wakanda.model',
fields: ['firstName', 'middleName','lastName'],
associations: [{
type: 'hasOne',
model: 'SimplyFundraising.model.ContactType',
name: 'contacttypes',
associationKey: 'ContactType',
reader: {
type: 'json',
record: 'ContactType',
idProperty: '__KEY',
root: '__ENTITIES'
}}
]


associations: [{ type: 'hasOne', name: 'contacttype', model: 'SimplyFundraising.model.ContactType',associationKey: 'ContactType' }]
});

In contact model I have tried not specifying reader or root as in the examples and tried type as memory and json. Since my Contact model should already inherit the root property not sure if I need to respecify a reader again, either way it isnt working.

ContactType model

Ext.define('SimplyFundraising.model.ContactType', {
extend : 'Wakanda.model',
fields: ['name',]
});

ContactsStore

Ext.define('SimplyFundraising.store.Contacts', {
extend: 'Ext.data.Store',
model: 'SimplyFundraising.model.Contact',
autoLoad: false,
autoSync: false


});

ContactTypesStore

Ext.define('SimplyFundraising.store.ContactTypes', {
extend: 'Ext.data.Store',
model: 'SimplyFundraising.model.ContactType',
autoLoad: false,
autoSync: false

});

Contact list view

Ext.define('SimplyFundraising.view.contacts.List', {
extend : 'Ext.grid.Panel',
xtype : 'contactslist',
title : 'All Contacts',
store : 'Contacts',
autoHeight: true,
autoScroll : true,
viewConfig : {
loadMask : true
},
initComponent : function() {
this.tbar = [{
text : 'Create Contact',
action : 'create'
}];
this.columns = [{
header : 'Id',
dataIndex : '__KEY',
width : 50
}, {
header : 'First Name',
dataIndex : 'firstName',
flex : 1
}, {
header : 'Middle Name',
dataIndex : 'middleName',
flex : 1
}, {
header : 'Last Name',
dataIndex : 'lastName',
flex : 1
},
{
header : 'Type',
dataIndex : 'ContactType.name',
flex : 1
}];
this.addEvents('removeitem');
this.actions = {
removeitem : Ext.create('Ext.Action', {
text : 'Remove Contact',
handler : function() {
this.fireEvent('removeitem', this.getSelected())
},
scope : this
})
};
var contextMenu = Ext.create('Ext.menu.Menu', {
items : [this.actions.removeitem]
});
this.on({
itemcontextmenu : function(view, rec, node, index, e) {
e.stopEvent();
contextMenu.showAt(e.getXY());
return false;
}
});
this.callParent(arguments);
},
getSelected : function() {
var sm = this.getSelectionModel();
var rs = sm.getSelection();
if (rs.length) {
return rs[0];
}
return null;
}
});


Contact edit view

Ext.define('SimplyFundraising.view.contacts.Edit', {
extend: 'Ext.window.Window',
xtype: 'contactsedit',
title: 'Edit Contacts',
layout: 'fit',
stores: ['ContactTypes','Contacts'],
autoShow: true,
initComponent: function () {
this.items = [
{
xtype: 'form',
bodyStyle: {
background: 'none',
padding: '10px',
border: '0'
},
items: [
{
xtype: 'textfield',
name: 'firstName',
allowBlank: false,
fieldLabel: 'Name'
},
{
xtype: 'textfield',
name: 'lastName',
allowBlank: false,
fieldLabel: 'Last Name'
},
{
xtype: 'combobox',
fieldLabel: 'Contact Type',

store: 'ContactTypes',

displayField: 'name',
valueField: '__KEY',
typeAhead: true,
queryMode: 'local',
emptyText: 'Select a type...'
}
]
}
];
this.buttons = [
{
text: 'Save',
action: 'save'
},
{
text: 'Cancel',
scope: this,
handler: this.close
}
];
this.callParent(arguments);
}
});

Code that my model,proxy,reader,writer inherit used to get data in correct format to and from server

Wakanda Model

Ext.define('Wakanda.model', {
requires: ['Wakanda.proxy'],
extend: 'Ext.data.Model',
fields: ['__KEY',{name: '__STAMP',persist: false}],
idProperty: '__KEY',

stampProperty: '__STAMP',

proxy: 'wakanda',
// constructor: function () {
// this.callParent(arguments)
// return this;
// },
onClassExtended: function (cls, data) {
// debugger;
// cls.apply(this)
// var parts = data.$className.split('.');
// var entity = parts[2]
// var catalog = this.prototype.getCatalog(entity),
// attributes = catalog.attributes;
// for (var i = 0, l = attributes.length; i < l; i++) {
// if (attributes[i].name === 'ID') {
// attributes[i].persist = false;
// }
// }
// attributes.push({name: this.prototype.idProperty});
// attributes.push({name: this.prototype.stampProperty});
// // data.fields = attributes;
// // debugger;
// //this.setFields(data.fields)
// // var mymodel = Ext.ModelManager.getModel(data.$className);
// debugger;
// Ext.appy(this);
// //this.superclass.superclass.$onExtended.apply(this, arguments);
// this.triggerExtended.apply(this,arguments)
// cls.apply(this,arguments)
},

getCatalog: function (className) {
var catalog;
Ext.Ajax.request({
async: false,
url: 'http://127.0.0.1:8081/cors/$catalog/' + className,
success: function (response) {
catalog = Ext.decode(response.responseText);
}
});
return catalog;
}

});


Wakanda proxy

Ext.define('Wakanda.proxy', {
requires: ['Wakanda.reader', 'Wakanda.writer'],
extend: 'Ext.data.proxy.Rest',

// alternateClassName: 'SimplyFundraising.data.WakandaProxy',

alias: 'proxy.wakanda',

sortParam: '$orderby',

filterParam: '$filter',

startParam: '$skip',

groupParam: '$group',

limitParam: '$top',

// groupersParam: '$group',

reader: 'wakanda',

writer: 'wakanda',

actionMethods: {
create: 'POST',
read: 'GET',
update: 'POST',
destroy: 'POST'
},

buildUrl: function (request) {
// debugger;
// var modelName = this.model.modelName,
var parts = this.model.modelName.split('.');
var modelName = parts[2]

operation = request.operation,
records = operation.records || [],
record = records[0],
id = record ? record.getId() : operation.id,
url = 'http://127.0.0.1:8081/cors/' + modelName,
action = request.action;

if (this.appendId && id && (action === 'read' || action === 'destroy')) {
url += '(' + id + ')';
}

request.url = url;

// console.log("buildUrl", this, arguments, request.url);


if (action !== 'read') {
if (action === 'create') action = 'update';
else if (action === 'destroy') action = 'delete';
url = Ext.urlAppend(url, '$method=' + action);
}

if (this.noCache) {
url = Ext.urlAppend(url, Ext.String.format("{0}={1}", this.cacheString, Ext.Date.now()));
}

return url;
},

encodeSorters: function (sorters) {
var min = [],
length = sorters.length,
i = 0, sort = '';

for (; i < length; i++) {
sort += sorters[i].property + ' ' + sorters[i].direction + ' ';
}

return sort;
},

encodeFilters: function (filters) {
var min = [],
length = filters.length,
i = 0, filter = '';

for (; i < length; i++) {
filter += filters[i].property + ' eq ' + filters[i].value + '@ ';
}
return filter;
}

});


Wakanda reader

Ext.define('Wakanda.reader', {

extend: 'Ext.data.reader.Json',

//alternateClassName: 'SimplyFundraising.data.WakandaReader',

alias: 'reader.wakanda',

root: '__ENTITIES',

totalProperty: '__COUNT',

getData: function (data) {
// debugger;
if (Ext.isObject(data) && !data[this.root]) {
data = [data];
}
return data;
}

});


Wakanda writer

Ext.define('Wakanda.writer', {

extend: 'Ext.data.writer.Json',

// alternateClassName: 'SimplyFundraising.data.WakandaWriter',

alias: 'writer.wakanda',

writeAllFields: false,

getRecordData: function(record,operation) {
debugger;
Ext.apply(record.data,record.getAssociatedData());
debugger;
var isPhantom = record.phantom === true,
writeAll = this.writeAllFields || isPhantom,
nameProperty = this.nameProperty,
fields = record.fields,
data = {},
changes,
name,
field,
key;

if (writeAll) {
// console.log("getRecordData1", this, arguments);
fields.each(function(field){
if (field.persist) {
debugger;
name = field[nameProperty] || field.name;
data[name] = record.get(field.name);
} else {

}

});
} else {
changes = record.getChanges();
debugger;
// console.log("getRecordData2", this, arguments, changes);
for (key in changes) {
if (changes.hasOwnProperty(key)) {
field = fields.get(key);
name = field[nameProperty] || field.name;
data[name] = changes[key];
}
}
if (!isPhantom) {
debugger;

data[record.idProperty] = record.getId();
if(operation.action !== 'destroy'){
data[record.stampProperty] = record.get(record.stampProperty);
}
}
}
return {'__ENTITIES': [data]};
}

});

Sample json data for loading grid
{

"__entityModel":"Contact",
"__COUNT":21,
"__SENT":21,
"__FIRST":0,
"__ENTITIES":[
{
"__KEY":"289",
"__STAMP":11,
"ID":289,
"firstName":"a",
"middleName":"",
"lastName":"ffddgddf",
"ContactType":{
"__KEY":"2",
"__STAMP":4,
"ID":2,
"name":"Home",
"contactCollection":{
"__deferred":{
"uri":"/cors/ContactType(2)/contactCollection?$expand=contactCollection"
}
}
},
"addressCollection":{
"__deferred":{
"uri":"/cors/Contact(289)/addressCollection?$expand=addressCollection"
}
}
},
{
"__KEY":"267",
"__STAMP":5,
"ID":267,
"firstName":"a",
"middleName":"",
"lastName":"g",
"ContactType":null,
"addressCollection":{
"__deferred":{
"uri":"/cors/Contact(267)/addressCollection?$expand=addressCollection"
}
}
}
]
}

Thanks for any help,
Dan

mitchellsimoens
15 May 2013, 12:13 PM
First, let's start small. You have 1 model Contact that hasOne ContactType model. What does your data look like and are you loading the data for the association all in one?

delebash
16 May 2013, 5:19 PM
Thanks for the reply. Yes I have Contact model with hasOne ContactType

I am getting data all in one, Contact with nested ContactType

Data


{
"__ENTITIES":[
{
"__KEY":"289",
"__STAMP":20,
"ID":289,
"firstName":"a",
"middleName":"",
"lastName":"a",
"ContactType":{
"__KEY":"2",
"__STAMP":4,
"ID":2,
"name":"Home"
}
}
]
}

1) Showing the data in the grid.
I can add ContactType.name to Contact model and as a field in the grid and the grid displays the ContactType, however since I have an association to ContactType I would think that I would not have to add ContactType to my Contact model.

So I tried using the render function for the ContactType field in the gird like so

{
header : 'Contact Type',
renderer: function (val, meta, record) {
return record.getContactType().get('name')
}
}

getContactType() is the getter for the ContactType association. The above code also works, but it also makes a remote call to the server, which is not needed since I already have the data loaded. I tested in the debugger and just ran record.getContactType().get('name') and the correct associated data is returned without making a remote call to the database, not sure why this same code when placed in the render function of a grid field decides to make the remote call.

Then the remaining issues with combobox and sending data back to server, but as you suggested lets start small.

Thanks again very much for your help, I am trying the best I can to understand proper way to make this work :)

If you think it would help I can put the app up on the web for you to look at so you can the full picture easier.