PDA

View Full Version : [OPEN-125] Store (using AjaxProxy) duplicates new record when .sync() is called



mark.haylock
30 May 2011, 1:27 PM
Sencha Touch version tested:

1.1.0


Platform tested against:

iOS 4
Android 2.1


Description:

New records are duplicated in a store after .sync() is called, when using an AjaxProxy.


Test Case:



// models/contact.js
app.models.Contact = new Ext.regModel('Contact', {
fields: [
{ name: 'id', type: 'int' },
{ name: 'first_name', type: 'string' },
{ name: 'last_name', type: 'string' },
{ name: 'email', type: 'string' },
{ name: 'phone', type: 'string' }
],
validations: [
{ type: 'presence', field: 'first_name', message: 'none' },
{ type: 'presence', field: 'last_name', message: 'none' },
{ type: 'email', field: 'email', message: 'Please enter a valid e-mail address.' },
{ type: 'phone', field: 'phone', message: 'Please enter a valid phone number.'}
],
proxy: {
type: 'ajax',
url: 'contacts.xml',
reader: {
type: 'xml',
record: 'contact'
},
writer: {
type: 'xml',
record: 'contact'
}
}
});

Ext.regStore('contacts', {
autoLoad: true,
model: 'Contact',
sorters: ['last_name'],
sortOnLoad: true,
getGroupString : function(record) {
return (record.get('last_name') || '#')[0].toUpperCase();
},
});

// in views/contacts/list.js
app.views.ContactsList = Ext.extend(Ext.Panel, {
title: 'Contacts',
layout: 'fit',
store: 'contacts',
initComponent: function () {
this.list = new Ext.List({
xtype: 'list',
id: 'contactslist',
grouped: true,
indexBar: true,
store: this.store,
itemTpl: '{first_name} <strong>{last_name}</strong>'
});
this.items = [this.list];

app.views.ContactsList.superclass.initComponent.apply(this, arguments);
}
});
Ext.reg('contacts/list', app.views.ContactsList);

// in controller:
var contact = Ext.ModelMgr.create(this.form.getValues(), 'Contact');
var store = Ext.getStore('contacts');
store.add(contact);
store.sync(); // Duplicate appears in views list once .sync() asynchronously completes.


Steps to reproduce the problem:

Attach a store (backed by an AjaxProxy) to a list
Add a new (unsaved) record to the same store
Call .sync() on store


The result that was expected:

List should reflect one new item in the store


The result that occurs instead:

List shows the new item twice


Debugging already done:

Determined that the record returned from the server is never matched against the existing record in the store - because the internalId doesn't match.


Possible fix:

Please see this commit on github (https://github.com/resolve/sencha-contacts-demo/commit/5e6c683fbfb6023efda40c4a60a75b100b020f23) for my change to AjaxProxy.js which fixes this for me.
Note that the list isn't sorted properly within each group after my fix, but that might be the fault of the way I'm sorting things at the moment.

mark.haylock
30 May 2011, 1:45 PM
We are currently evaluating Sencha Touch as a platform to move forward with and I've been playing around getting a feel for it's capabilities.

We are pretty keen on what we have seen and will likely purchase a support license, however this duplicate problem caused me some grey hairs, so I have some questions I hope can be answered:


Is this a bug or have I set up things incorrectly?
If this is a bug is it fixed in the version available only to support licensees? Is there any public information about what is in the version available only to support licensees?
If this bug is not fixed in the version available to support licensees then what would be the normal turnaround for such a bug fix to become available (if we had a support license)?
I notice that in this pos (http://www.sencha.com/blog/ext-js-is-migrating-to-git/)t you mention "If we get demand for a github repository featuring just the public releases we may set that up too" - can I add a vote for that? It seems that would make submitting fixes like this easier for both sides if we could generate pull requests on github.

christocracy
22 Jun 2011, 8:16 AM
I experience this issue as well and tracked down the problem.

It's a bug.

Perhaps this override might help someone. It worked for me and my RestProxy.

https://gist.github.com/1040439

Basically, when the DataReader reads a response back from server, it instantiates new record instances, with new internalId set.

When the store has its onWrite callback fired, it tries to replace the original record (which caused the CREATE/UPDATE action) with the new version from server, but it can't possibly find it unless their internalId are set the same.


onProxyWrite: function(operation) {
var data = this.data,
action = operation.action,
records = operation.getRecords(),
length = records.length,
callback = operation.callback,
record, i;

if (operation.wasSuccessful()) {
if (action == 'create' || action == 'update') {
for (i = 0; i < length; i++) {
record = records[i];

record.phantom = false;
record.join(this);
data.replace(record); // <-- HERE. Store tries to replace oldRec with newRec.
}
}

else if (action == 'destroy') {
for (i = 0; i < length; i++) {
record = records[i];

record.unjoin(this);
data.remove(record);
}

this.removed = [];
}

this.fireEvent('datachanged');
}

//this is a callback that would have been passed to the 'create', 'update' or 'destroy' function and is optional
if (typeof callback == 'function') {
callback.call(operation.scope || this, records, operation, operation.wasSuccessful());
}
},

edspencer
5 Jul 2011, 5:35 PM
This is a recurring issue with the architecture around creating Model instances locally and saving them remotely. At the moment it's rather difficult to accurately match each record returned by the server against the record we sent to the server.

We're formulating a better solution to this internally at the moment and will keep you updated.