Thx again flanders, but I dont find the right code position to do so.
I find a solution that works for my problem. I add a listener to the master store which adds a datachange event to all childstores.

DeepJson writer

DeepJson writer

This still seems to be an issue in the current version available to me: 4.1.1a. Thanks to Chrisface and others on this thread for the helpful feedback. Using Chrisface's code and modifying it with feedback & fixes from others, I came up with the extended class below, which I post here in case it saves anyone else some time:

Code:

Ext.define('MetaTools.lib.data.writer.DeepJson', {
extend: 'Ext.data.writer.Json',
getRecordData: function (record, operation) {
//Setup variables
var me = this,
i,
association,
childStore,
data;
data = me.callParent(arguments);
//Iterate over all the hasMany associations
for (i = 0; i < record.associations.length; i++) {
association = record.associations.get(i);
if (association.type == 'hasMany') {
data[association.name] = null;
childStore = record[association.storeName];
//Iterate over all the children in the current association
childStore.each(function (childRecord) {
if (!data[association.name]) {
data[association.name] = [];
}
//Recursively get the record data for children (depth first)
var childData = this.getRecordData.call(this, childRecord);
/*
* If the child was marked dirty or phantom it must be added. If there was data returned that was neither
* dirty or phantom, this means that the depth first recursion has detected that it has a child which is
* either dirty or phantom. For this child to be put into the prepared data, it's parents must be in place whether
* they were modified or not.
*/
if (childRecord.dirty || childRecord.phantom || (childData != null)) {
data[association.name].push(childData);
record.setDirty();
}
}, me);
/*
* Iterate over all the removed records and add them to the preparedData. Set a flag on them to show that
* they are to be deleted
*/
Ext.each(childStore.removed, function (removedChildRecord) {
//Set a flag here to identify removed records
removedChildRecord.set('forDeletion', true);
var removedChildData = this.getRecordData.call(this, removedChildRecord);
data[association.name].push(removedChildData);
record.setDirty();
}, me);
}
}
//Only return data if it was dirty, new or marked for deletion.
if (record.dirty || record.phantom || record.get('forDeletion')) {
return data;
}
return null;
}
});

Just from quickly scanning through the class, it looks like it would return null, "Only return data if it was dirty, new or marked for deletion," so if your record was neither new nor deleted nor dirty (edited), then this code would return null. It is designed to only return data that has changed and therefore needs to be synced with its remote source.

If you wanted it to return all data regardless of its status, you could alter the last few lines, replacing

This is something I had to do as well myself and I took the same approach of overriding the getRecordData function to take into account the associations. I also added some code to take care of deleted records as well. I'm adding a forDeletion property only for deleted records so that I can keep it out of the fields on the models.

Code:

Ext.data.writer.Json.override({
/*
* This function overrides the default implementation of json writer. Any hasMany relationships will be submitted
* as nested objects. When preparing the data, only children which have been newly created, modified or marked for
* deletion will be added. To do this, a depth first bottom -> up recursive technique was used.
*/
getRecordData: function(record) {
//Setup variables
var me = this, i, association, childStore, data = record.data;
//Iterate over all the hasMany associations
for (i = 0; i < record.associations.length; i++) {
association = record.associations.get(i);
data[association.name] = null;
childStore = record[association.storeName];
//Iterate over all the children in the current association
childStore.each(function(childRecord) {
if (!data[association.name]){
data[association.name] = [];
}
//Recursively get the record data for children (depth first)
var childData = this.getRecordData.call(this, childRecord);
/*
* If the child was marked dirty or phantom it must be added. If there was data returned that was neither
* dirty or phantom, this means that the depth first recursion has detected that it has a child which is
* either dirty or phantom. For this child to be put into the prepared data, it's parents must be in place whether
* they were modified or not.
*/
if (childRecord.dirty | childRecord.phantom | (childData != null)){
data[association.name].push(childData);
record.setDirty();
}
}, me);
/*
* Iterate over all the removed records and add them to the preparedData. Set a flag on them to show that
* they are to be deleted
*/
Ext.each(childStore.removed, function(removedChildRecord) {
//Set a flag here to identify removed records
removedChildRecord.set('forDeletion', true);
var removedChildData = this.getRecordData.call(this, removedChildRecord);
data[association.name].push(removedChildData);
record.setDirty();
}, me);
}
//Only return data if it was dirty, new or marked for deletion.
if (record.dirty | record.phantom | record.get('forDeletion')){
return data;
}
}
});

I'm not sure if this code will work under every condition but it's been fine for me so far. If you have any suggestions for improvements or a better way of doing it I'd like to hear your thoughts.

Any ideas on how to configure json reader to handle deepjson response from model.save() or store.sync()?

I have deepjson writer functional and working. Server receives all data, creates, updates, deletes as necessary ... And responds with the same data structure as it received, except that phantom records now have IDs, creation dates are there, and maybe some other parameters are present that are set on the server when record is first created.

So my question is ... how do you apply data returned from deepjson writer to the existing data and mark all those records !dirty and !phantom? Is json reader even capable of doing that? Will store.sync() take care of everything if readers are set up properly? If yes, how to configure reader?

Any comments from Sencha support on this issue. Handling change tracking using associated data, ie form has parent store and associated data store and store.save is for parent. So it only tracks parent fields dirty and not associated. Others have manually coded to handle some of this, but really I would think by now using 4.2 this would be built in?

I think that if you set clientIdProperty: 'clientId' on Ext.data.Model and then return clientId parameter in the response object together with newly assigned idProperty, the framework should able to figure out which records are new and which phantom record to they belong to, even for deeply nested responses.

I'm not 100% sure, it was very late when I tested it, so maybe you can give it a try and confirm this. Hopefully I'll find some time this week to thoroughly test this.

As for sending all data, just create a custom json writer. In one of my projects, I have a model with multiple hasMany associations and they have hasMany who have hasMany ... I just send the entire object graph with custom json writer, process data on server, and return complete object graph back. I know it's an overhead to send all data, but it works Of course in the future I plan to send only modified/new data ...

The model association framework still needs some work, especially with reading/writing deeply nested data. And some more documentation with advanced examples wouldn't hurt. But I'm sure that Sencha team will improve this

I think I get the custom writer part, and have seen the examples here on how to implement. The problem I am having is just getting a simple combobox on an edit form that has its primary record data loaded via from.load from the grid or list view. The problem is 2 stores 1 that loads the primary record 1 that loads the data for the combobox, they are not linked, so for example when the form loads the data and I set the value of the combobox manually from the primary models associated data, when I change the value of the combobox the primary store is not marked as dirty and therefore does not sync when I try to save. Since no sync event fired I don't even get to the writer part.

In addition I am setting selected value of combobox manually but shouldn't this be automatic since I have association setup and valueField of combobox with the key id equal to key id of associated record? Tried using form.load twice once for primary and once for associated as I read some post that this may work, but it doesn't

Really I am a more than a bit frustrated this should be a simple task. I feel like I must be missing something major as this seems very difficult to achieve. I just want to have a simple grid view that a user clicks on a row, then edit view pops up with primary record fields set and a combobox that sets the associated records value and save back to server. This is my first time building an app with ExtJs, you guys who have already developed apps with Extjs, the scenario I am describing at least I think is very common, most basic add/edit with grid and edit form, nothing fancy.

How have you all accomplished this task?

Should I not use associations?

If not use associations, what is the best practice for this using Extjs 4.2 mvc, any working examples, I would so very much appreciate it!

Just to clarify, I have two tables Contact with basic fname and lname, then a ContactsType, this is just a lookup table, like a states or countries table. In fact I would add two more comboboxes just like ContactType with Countries and States as associated hasOne relations if I can get it working easily.

Really need some help, spent hours and hours reading forums, google, docs, ect. Trying different things here and there. One thing is I don't want to just hack this problem with my javascript. Gotta be a best practices example for this somewhere, right?

Sorry for the rant? Just beating my head against a brick wall right now........