PDA

View Full Version : [FEATREQ][3.0rc1] Batch-save should work like the autosave



crp_spaeth
5 May 2009, 3:06 AM
Hi there Since i am working on a solution for a generic .net Proxy and Router for the Direct Class I need to know wich changes in Ext.Direct Philosophie are planed.

I dived into the Ext Code again and find it kind of confusing that the batch methode works different than the automatically mechanism.


Since Server-side validation for example can prevent a Record to be saved but it may stops one record to be saved it may saves other records just fine. So there ocurs an error while saving records on the server-side but there a still a few records saved...
So how can the server tell the client wicht record has been saved and wich not?
It would be much easier to have a transaction for every record to save. That would make it possible to return an exception when an error orcur and it would be much easier to rollback the records that failed to save.


Ext.data.Store:



/**
* Send all {@link #getModifiedRecords modifiedRecords} to the server using the
* api's configured save url.
* @param {Object} options
*/
save : function(rs) {
rs = rs || this.getModifiedRecords();
if (!rs.length && !rs instanceof Ext.data.Record && !this.removed.length) {
return false;
}
var action = 'save';
if (this.removed.length) {
try {
this.execute('destroy', this.removed);
}
catch (e) {
throw e; // <-- just re-throw it for now...
}
}
try {
if (Ext.isArray(rs)) {
for (var i = rs.length-1; i >= 0; i--) {
if (rs[i].phantom === true) {
var rec = rs.splice(i, 1).shift();
if (rec.isValid()) {
this.execute('create', rec);
}
}
}
}
else if (rs.phantom) {
if (!rs.isValid()) {
return false;
}
action = 'create';
}
if (Ext.isArray(rs) && rs.length == 1) {
rs = rs[0];
}
if (rs instanceof Ext.data.Record || rs.length > 0) {
this.execute(action, rs);
return true;
}
else {
// no more actions to execute. They may have been spliced-out by create actions above. just return true.
return true;
}
}
catch (e) {
throw e;
}
return true;
},



I would end up with something like this...

/**
* Send all {@link #getModifiedRecords modifiedRecords} to the server using the
* api's configured save url.
* @param {Object} options
*/
save : function(rs) {
rs = rs || this.getModifiedRecords();
if (!rs.length && !rs instanceof Ext.data.Record && !this.removed.length) {
return false;
}
var action = 'save';
if (this.removed.length) {
try {
this.execute('destroy', this.removed);
}
catch (e) {
throw e; // <-- just re-throw it for now...
}
}
try {
if (Ext.isArray(rs)) {
for (var i = rs.length-1; i >= 0; i--) {
var rec = rs.splice(i, 1).shift();
if (rec.phantom === true) {
if (rec.isValid()) {
this.execute('create', rec);
}
} else {
if (rec.isValid()) {
this.execute('save', rec);
}
}

}
}
else if (rs.phantom) {
if (!rs.isValid()) {
return false;
}
action = 'create';
} else {
if (rs.isValid()) {
this.execute('save', rs);
}
}
else {
// no more actions to execute. They may have been spliced-out by create actions above. just return true.
return true;
}
}
catch (e) {
throw e;
}
return true;
},


(falls under the same category like http://extjs.com/forum/showthread.php?p=324928#post324928)

christocracy
11 May 2009, 12:30 PM
In fact, that's how I originally wrote Store#save. The Writer package was initially designed for Ext.Direct then expanded to handle all proxies. Since Ext.Direct will queue the transaction into one Ajax request, it worked great. I'll see what I can do once things settle down a bit.

christocracy
11 May 2009, 12:34 PM
Oh, and that code you posted is rather dated. Store#save looks like this now:



save : function() {
if (!this.writer) {
throw new Ext.data.Store.Error('writer-undefined', 'Store.js');
}

// First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
if (this.removed.length) {
try {
this.execute(Ext.data.Api.DESTROY, this.removed);
} catch (e) {
this.handleException(e);
}
}

// Check for modified records. Bail-out if empty...
var rs = this.getModifiedRecords();
if (!rs.length) {
return true;
}

// Next check for phantoms within rs. splice-off and execute create.
var phantoms = [];
for (var i = rs.length-1; i >= 0; i--) {
if (rs[i].phantom === true) {
var rec = rs.splice(i, 1).shift();
if (rec.isValid()) {
phantoms.push(rec);
}
}
else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records
rs.splice(i,1);
}
}
// If we have valid phantoms, create them...
if (phantoms.length) {
try {
this.execute(Ext.data.Api.CREATE, phantoms);
} catch (e) {
this.handleException(e);
}
}

// And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
if (rs.length) {
try {
this.execute(Ext.data.Api.UPDATE, rs);
} catch (e) {
this.handleException(e);
}
}
return true;
},

christocracy
12 May 2009, 2:58 PM
Ok, Ext.data.DirectStore does what you requested, wraps each record-action into a distinct Ext.Direct transaction.

As it happens, DirectStore will actually work with an HttpProxy -- you'll get an Ajax request for each record though! I highly recommend using Ext.Direct for this.

Committed to SVN.



save : function(options) {
if (!this.writer) {
throw new Ext.data.Store.Error('writer-undefined', 'Store.js');
}

// First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
if (this.removed.length) {
for (var i = 0, len = this.removed.length; i < len; i++) {
try {
this.execute(Ext.data.Api.DESTROY, this.removed[i]);
}
catch (e) {
this.handleException(e);
}
}
}

// Check for modified records. Bail-out if empty...
var rs = this.getModifiedRecords();
if (!rs.length) {
return true;
}

// Next create phantoms within rs...
for (var i = rs.length-1; i >= 0; i--) {
if (rs[i].phantom === true) {
var rec = rs.splice(i, 1).shift();
if (rec.isValid()) {
try {
this.execute(Ext.data.Api.CREATE, rec);
} catch (e) {
this.handleException(e);
}
}
}
else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records
rs.splice(i,1);
}
}
// And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
if (rs.length) {
for (var i = 0, len = rs.length; i < len; i++) {
try {
this.execute(Ext.data.Api.UPDATE, rs[i]);
}
catch (e) {
this.handleException(e);
}
}
}
return true;
}

dj
13 May 2009, 6:19 AM
Hi Chris,

thanks for this commit! I have a problem with it though...

Ext.data.DirectStore was merely a small helper that helped you configure an Ext.data.Store for Ext.Direct. Now it has this override for #save and one has to use this helper class.
But I regularly use an Ext.data.GroupingStore for my Ext.Direct-enabled stores because some grids of mine have GroupingViews.

Couldn't you merge Ext.data.DirectStore#save into Ext.data.Store#save and introduce a new @cfg combineUpdates that switches the behavior?

Something like this (copy&paste-only) solution:

Ext.data.Store


//...
/** @cfg {boolean} combineUpdates <tt>true</tt> if store should send all updated records in one transaction. <tt>false</tt> if it should create a new transaction for every modified record.
combineUpdates: true
//...
save : function() {
if (this.combineUpdates) { // normal Ext.data.Store#save
if (!this.writer) {
throw new Ext.data.Store.Error('writer-undefined', 'Store.js');
}

// First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
if (this.removed.length) {
try {
this.execute(Ext.data.Api.DESTROY, this.removed);
} catch (e) {
this.handleException(e);
}
}

// Check for modified records. Bail-out if empty...
var rs = this.getModifiedRecords();
if (!rs.length) {
return true;
}

// Next check for phantoms within rs. splice-off and execute create.
var phantoms = [];
for (var i = rs.length-1; i >= 0; i--) {
if (rs[i].phantom === true) {
var rec = rs.splice(i, 1).shift();
if (rec.isValid()) {
phantoms.push(rec);
}
}
else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records
rs.splice(i,1);
}
}
// If we have valid phantoms, create them...
if (phantoms.length) {
try {
this.execute(Ext.data.Api.CREATE, phantoms);
} catch (e) {
this.handleException(e);
}
}

// And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
if (rs.length) {
try {
this.execute(Ext.data.Api.UPDATE, rs);
} catch (e) {
this.handleException(e);
}
}
return true;
} else { // Ext.data.DirectStore#save
if (!this.writer) {
throw new Ext.data.Store.Error('writer-undefined', 'Store.js');
}

// First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
if (this.removed.length) {
for (var i = 0, len = this.removed.length; i < len; i++) {
try {
this.execute(Ext.data.Api.DESTROY, this.removed[i]);
}
catch (e) {
this.handleException(e);
}
}
}

// Check for modified records. Bail-out if empty...
var rs = this.getModifiedRecords();
if (!rs.length) {
return true;
}

// Next create phantoms within rs...
for (var i = rs.length-1; i >= 0; i--) {
if (rs[i].phantom === true) {
var rec = rs.splice(i, 1).shift();
if (rec.isValid()) {
try {
this.execute(Ext.data.Api.CREATE, rec);
} catch (e) {
this.handleException(e);
}
}
}
else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records
rs.splice(i,1);
}
}
// And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
if (rs.length) {
for (var i = 0, len = rs.length; i < len; i++) {
try {
this.execute(Ext.data.Api.UPDATE, rs[i]);
}
catch (e) {
this.handleException(e);
}
}
}
return true;
}
}
//...


Ext.data.DirectStore


Ext.data.DirectStore = function(c){
Ext.data.DirectStore.superclass.constructor.call(this, Ext.apply(c, {
proxy: (typeof(c.proxy) == 'undefined') ? new Ext.data.DirectProxy(Ext.copyTo({}, c, 'paramOrder,paramsAsHash,directFn,api')) : c.proxy,
reader: (typeof(c.reader) == 'undefined' && typeof(c.fields) == 'object') ? new Ext.data.JsonReader(Ext.copyTo({}, c, 'totalProperty,root,idProperty'), c.fields) : c.reader
}));
};
Ext.extend(Ext.data.DirectStore, Ext.data.Store, {
combineUpdates: false
});
Ext.reg('directstore', Ext.data.DirectStore);

christocracy
13 May 2009, 12:55 PM
But I regularly use an Ext.data.GroupingStore for my Ext.Direct-enabled stores

Right, ok. I'll find a way. I'll consider you config-param combineUpdates.