Write-store

From Jsorm

Jump to: navigation, search

Contents

Overview

write-store is an extension to the excellent ExtJS library by Jack Slocum. It answers several shortcoming of ExtJS data Stores, which are used to present data:

  • ExtJS stores are primarily read-only with respect to the server, rather than read-write. You can change data in the Store, but cannot transmit it back to the server.
  • ExtJS stores do not have a full transaction support. If you make several changes and then decide you want to undo them, modern RDBMS systems will let you reject or roll back the transaction; ExtJS stores do not support this.
  • ExtJS stores cannot use other Stores as their data source. This becomes important to reduce round-trip requests to/from the server in a time-sensitive Ajax environment. For example, if a single query returns a list of persons, and each person has several telephone numbers, it makes sense from a presentation to the user perspective to have the persons in one Store, perhaps displayed by one grid, and the telephone numbers in another Store, perhaps displayed bny a separate grid. ExtJS currently requires you to retrieve the persons in one request and each set of telephone numbers, when desired, in separate requests.

write-store extends ExtJS to resolve these shortcomings.

It is strongly recommended that you understand how to use ExtJS before using write-store.

Release information is available.

When To Use It

You should use write-store if you want to do any of the following:

  • Persist modified data in an Ext.data.Store back to persistent storage on the server. Ext.data.Store does not currently support this, but write-store does.
  • Use full transactions on the data in an Ext.data.Store, including commit and rollback. The current store does not support this, but write-store does. You can make any changes, and then commit or reject the changes, rolling back to the last commit or reject.
  • Use one store as the source of data for another. This is particularly useful when you have multiple stores but one source of data on the server. Existing proxies and readers require you to go back to the server for each one. This will give you more centralized control over data and much reduced network traffic.

You should not use write-store if you:

  • Have no need of transactions; and
  • You have no data to persist from your store, i.e. store data is read-only; and
  • You have few or no stores with a common data source.

That having been said, we recommend you read this wiki and try write-store even if you think you have no need. You may discover better ways to work within ExtJS, and you may come up with recommendations for how we can help you.

Usage

write-store adds these features by creating several new classes. These are:

  • Ext.ux.WriteStore, which extends Ext.data.Store, can be used in any place an Ext.data.Store is used, and provides transactions and write features.
  • Ext.ux.WriteRecord, which extends Ext.data.Record, is used internally by Ext.ux.WriteStore, and provides for update by update rejection/rollback of changes.
  • Ext.ux.JsonWriterReader, which extends Ext.data.JsonReader, can be used in any place an Ext.data.JsonReader is used, and provides write features.
  • Ext.ux.HttpWriteProxy, which extends Ext.data.HttpProxy, can be used in any place an Ext.data.HttpProxy is used, and provides write features.
  • Ext.ux.StoreProxy, which extends Ext.data.DataProxy, can be used anywhere a DataProxy is normally used, and provides the ability to use an Ext.data.Store as the data source for another Ext.data.Store.

Transactions

To use transactions, you only need to use an Ext.ux.WriteStore in place of an Ext.data.Store. All updates to the Store, using the normal Ext.data.Store methods, are considered part of a single transaction. A journal of all changes is kept until the transaction ends and a new transaction begins. Because of this journal, you can commit all changes since the transaction began, reject all changes since the transaction began, or undo some of the changes (backwards historically sequentially) since the transaction began. A transaction ends only when WriteStore is told the transaction has ended. At that point, one of two things happens:

  • The transaction is committed, meaning all changes are considered accepted, the journal is discarded, and a new transaction is begun. A transaction is committed by calling writeStore.commitChanges().
  • The transaction is rejected, meaning all changes are considered to be invalid, all changes are rolled back, the journal is emptied, and a new transaction is begun. A transaction is rejected by calling writeStore.rejectChanges().

To undo the last x changes in a transaction, you call writeStore.rejectChanges(x), where x is the number of changes, or journal entries, to undo. If x is empty, less than or equal to 0, or greater than or equal to the total number of changes in the transaction, all changes are rejected, as if you simply called writeStore.rejectChanges(). You can determine how many changes there are by calling writeStore.getModifiedCount(), which returns an integer. If you prefer a simple boolean "are there any uncommitted changes?" method, use writeStore.isDirty().

When transactions are committed, one of several events occurs:

  • If the WriteStore is not configured to write its changes to the server, changes are immediately committed, a "commit" event is triggered, and a new transaction is begun.
  • If the WriteStore is configured to write its changes to the server, the changes are first sent to the server.
    • If the server successfully accepts the changes, the commit is complete, a commit event is triggered, and a new transaction is begun.
    • If the server can not be reached due to network or other errors, the commit is not complete, a writeexception event is triggered, a new transaction is not begun, and all changes remain in the journal.
    • If the server can be reached, normally defined as an HTTP response of 200, but the server program rejects the changes, the commit is not complete, a new transaction is not begun, and all changes remain in the journal.

The events launched by WriteStore, which, as an extension of DataStore, is Observable, are as follows.

  • If the WriteStore is not configured to write changes to the server, a commit event is triggered immediately after committing.
  • If the WriteStore is configured to write changes to the server, the following events are triggered:
    • At the beginning of a commitChanges(), the beforewrite event is triggered. Like all before type events, the write will occur unless a registered handler returns false.
    • After the write, one of the following two events is triggered.
      • In case of successful connection, normally defined as an HTTP status code of 200 from the server, a write event is triggered. If not registered event handlers exist, or no handler returns false, the commit is completed locally, a commit event is triggered, and a new transaction is begun.
      • In case of a failure to reach the server/page, a writeexception event is triggered, the commit is not completed locally, and no new transaction is begun.

This structure enables an application to check the response to write from the server, and determine if it was successful or not, and thus enable the local commit to proceed or prevent it.

For example, assume a server will send JSON as follows:

{
  'person':
    [
      {firstName: 'John', lastName: 'Smith'},
      {firstName: 'Jill', lastName: 'Smith'},
      {firstName: 'Justin', lastName: 'Smith'},
      {firstName: 'Juliet', lastName: 'Smith'}
    ]
}

The following code demonstrates a commit.

var Person = Ext.data.Record.create([{name: 'firstName'},{name: 'lastName'},{name: 'address'}]);
var storeA = new Ext.ux.WriteStore({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	updateProxy: new Ext.ux.HttpProxy({url: someUrl}),
	reader: new Ext.ux.JsonReader({
		root: 'person'
	},Person)
});
storeA.load();
 
// make some changes
storeA.remove(storeA.getAt(1));
storeA.getAt(0).set('lastName','Butler');
 
// commit the changes
storeA.commitChanges();

At this point, the data in storeA will look as follows:

{
  'person':
    [
      {firstName: 'John', lastName: 'Butler'},
      {firstName: 'Justin', lastName: 'Smith'},
      {firstName: 'Juliet', lastName: 'Smith'}
    ]
}

The following code demonstrates a reject. Assume we begin with the same data as in the commit example.

var Person = Ext.data.Record.create([{name: 'firstName'},{name: 'lastName'},{name: 'address'}]);
var storeA = new Ext.ux.WriteStore({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	updateProxy: new Ext.ux.HttpProxy({url: someUrl}),
	reader: new Ext.ux.JsonReader({
		root: 'person'
	},Person)
});
storeA.load();
 
// make some changes
storeA.remove(storeA.getAt(1));
storeA.getAt(0).set('lastName','Butler');

At this point, the data in the store is as we expect:

{
  'person':
    [
      {firstName: 'John', lastName: 'Butler'},
      {firstName: 'Justin', lastName: 'Smith'},
      {firstName: 'Juliet', lastName: 'Smith'}
    ]
}

However, we are still in the same transaction, and have not yet committed. If we reject the changes...

storeA.rejectChanges();

... the data in the store will revert back to:

{
  'person':
    [
      {firstName: 'John', lastName: 'Smith'},
      {firstName: 'Jill', lastName: 'Smith'},
      {firstName: 'Justin', lastName: 'Smith'},
      {firstName: 'Juliet', lastName: 'Smith'}
    ]
}

Store as Data Source

Suppose you have information that is embedded inside other information. This is often the case with the result of complex relational queries or objects that may have other objects as members. If the result, in JSON, looks as follows:

{
  'person':
    [
      {firstName: 'John', lastName: 'Smith',telephone: [{number: '+1-212-555-1212',type: 'work'},{number: '+1-212-555-1213', type: 'home'},{number: '+1-212-555-1214',type: 'mobile'}]},
      {firstName: 'Jill', lastName: 'Smith',telephone: [{number: '+1-202-555-1212',type: 'work'},{number: '+1-202-555-1213', type: 'home'},{number: '+1-202-555-1214',type: 'mobile'}]},
      {firstName: 'Justin', lastName: 'Smith',telephone: [{number: '+1-213-555-1212',type: 'work'},{number: '+1-213-555-1213', type: 'home'},{number: '+1-213-555-1214',type: 'mobile'}]},
      {firstName: 'Juliet', lastName: 'Smith',telephone: [{number: '+1-312-555-1212',type: 'work'},{number: '+1-312-555-1213', type: 'home'},{number: '+1-312-555-1214',type: 'mobile'}]}
    ]
}

The primary data Store, backing the primary Grid, is one that has one line for each person. However, when a person is selected, a secondary grid representing the telephone numbers should be presented by loading its secondary store, so that the telephone data can be edited. The current method for doing so in ExtJS is as follows:

var Person = Ext.data.Record.create([{name: 'firstName'},{name: 'lastName'}]);
var Phone = Ext.data.Record.create([{name: 'number'},{name: 'type'}])
var personStore = new Ext.data.Store({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	reader: new Ext.data.JsonReader({
		root: 'person'
	},Person)
});
personStore.load();
 
var phoneStore = new Ext.data.Store({
	proxy: new Ext.data.HttpProxy({url: someUrlToDirectlyAccessById}),
	reader: new Ext.data.JsonReader({
		root: 'telephone'
	},Phone)
});
// when a person is selected in the grid, not described here, load the second store
// set up the click to load the details
personsGrid.on('rowclick',function(grid,rowIndex,e){
    var record = grid.getStore().getAt(rowIndex);
    phoneStore.load({params: {id: record.id}});
});

Note that the above requires going back to the server for each selection, not an efficient method

write-store adds the ability to use one Record in another store as the source for its data. Using the example above, phoneStore still runs a load, but it loads from personStore, which is already in memory.

var Person = Ext.data.Record.create([{name: 'firstName'},{name: 'lastName'},{name: 'telephone'}]);
var Phone = Ext.data.Record.create([{name: 'number'},{name: 'type'}])
var personStore = new Ext.data.Store({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	reader: new Ext.data.JsonReader({
		root: 'person'
	},Person)
});
personStore.load();
// do some stuff with storeA
 
// configure phoneStore to get its information from personStore, using the field in a record of telephone
var phoneStore = new Ext.ux.WriteStore({
	proxy: new Ext.ux.StoreProxy({store: personStore, field: 'telephone'}),
	reader: new Ext.ux.ObjectReader({},Phone)
});
 
// set up the click to load the details
personsGrid.on('rowclick',function(grid,rowIndex,e){
    var record = grid.getStore().getAt(rowIndex);
    phoneStore.load({params: {id: record.id}});
});

Notice that phoneStore need not go to the server. Rather, personStore is the master Store, containing all the data. phoneStore simply goes to personStore to get the information it needs, and can write back to it as well.

Using a StoreProxy

There are two steps to a useful StoreProxy: creating it and using it.

  • To create a StoreProxy, you simple create one with the new command, passing it a config object as an argument. The config object is expected to have two fields:
    • store: the Ext.data.Store to use as the source.
    • field: the name of the field within a record in the source store that will hold the data for records.
var sp = new Ext.ux.StoreProxy({store: otherStore, field: someField});

For example, if the data structure in store myStore is as follows:

{person:
  [
    {id: 1, name: 'John Smith',address: [{type: 'home', detail: '123 Cherry Lane, Some City, KS'},{type: 'work', detail: '987 Main St, Some City, KS'}]},
    {id: 2, name: 'Jill Smythe',address: [{type: 'home', detail: '123 Rose Lane, Some City, KS'},{type: 'work', detail: '987 Center St, Some City, KS'}]}
  ]
}

Then an appropriate creation of a StoreProxy to retrieve addresses would be

var sp = new Ext.ux.StoreProxy({store: myStore, field: 'address'});
  • To use the StoreProxy, you tell the store for which the StoreProxy is the proxy to load in the normal fashion:
newStore.load();

However, you need to indicate to the StoreProxy which record from the source store you wish to use. This is done by using the params argument to the Ext.data.Store load() method, and passing the ID you wish to load. Continuing the example above, if we wish to load 'Jill Smythe':

newStore.load({params: {id: 2}});

The contents of the given field, 'address' in the above example, will be passed to the Ext.data.Store's configured reader for interpretation.

Writing Data

Overview

Many times, the user or application will change the data in a Store. For example, the Store is the standard backing to a Grid. If the Grid is editable, the user may change data, with the expectation that the data changes will be persistent. ExtJS does not currently support sending changes back to the server. write-store adds the ability to write back to the data source one of:

  • The complete updated state of the Store.
  • The changes that occurred in the Store.

The data source can either be the server via HTTP or another Store via StoreProxy.

Setup

In order to use the write functionality in WriteStore, you must take several steps.

  1. Use Ext.ux.WriteStore as your Store, instead of Ext.data.Store. Ext.ux.WriteStore extends Ext.data.Store, so it is usable anywhere Ext.data.Store is.
  2. Configure WriteStore with a new parameter called updateProxy. updateProxy may be set as a parameter of the config object parameter at construction time, or it may be set on the fly by changing the public property updateProxy. The updateProxy itself must be an Ext.data.DataProxy child that is capable of writing data as well as reading data. The write-proxy package includes two such proxies:
    • Ext.ux.HttpWriteProxy extends Ext.data.HttpProxy, providing all the same functionality in addition to writing
    • Ext.ux.StoreProxy extends Ext.data.DataProxy, providing all the same functionality in addition to reading from and writing to another Ext.data.Store.
  3. Configure WriteStore with a reader that supports writing. The reader must extend a standard Ext.data.DataReader. The write-store package includes two such readers:
    • Ext.ux.JsonWriterReader extends Ext.data.JsonReader and will both read and write JSON.
    • Ext.ux.ObjectReader extends Ext.data.DataReader and will both read and write in-memory objects.

The following is a sample configuration:

var Person = Ext.data.Record.create([{name: 'firstName'},{name: 'lastName'}]);
var store = new Ext.ux.WriteStore({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	updateProxy: new Ext.ux.HttpWriteProxy({url: somePostUrl}),
	reader: new Ext.ux.JsonWriterReader({
		root: 'person'
	},Person)
});
store.load();

Alternatively, you can set updateProxy as follows:

store.updateProxy = new Ext.ux.HttpWriteProxy({url: somePostUrl});

If updateProxy is null or undefined, WriteStore's write behaviour is disabled, and it functions exactly like Ext.data.Store.

Writing Data

Data writing from WriteStore to its data source happens when a transaction is committed. For an overview of transactions, commit and reject, see #Transactions. In the case of a reject, no changes are written to the data source. Writes occur only in the case of a commit.

When you call commitChanges() and updateProxy has been defined, the Store does the following:

  1. send out a 'beforewrite' event
  2. use configured reader's write() method to convert from Records to raw data (e.g. JSON)
  3. use updateProxy's update() method to send the data back to the server
  4. Complete the commit:
    • If successful in sending the write from a network-layer perspective, send out a 'write' event. There are two possible outcomes to the write event.
      • If there are no registered handlers, or no handler returns false, commit the changes in the store, purge the journal, and trigger a commit event.
      • If there are registered handlers and at least one handler returns false, consider the write to have failed from an application perspective, do not commit the changes or start a new transaction.
    • If not successful in sending the write from a network-layer perspective, do not commit the changes locally, do not purge the journal, send out a writeexception event.
store.on('write',function() {alert('Successful write!');}
store.on('writeexception',function() {alert('Error in write!');}
 
store.commitChanges();

New in 1.1

The options parameter to commitChanges now supports three handlers:

  • success: will be called for success of submission and processing of this transaction
  • processfailure: will be called if any of the write handlers returns false, i.e. application processing has failed
  • writefailure: will be called if a writeexception event would be raised, i.e. network send failure

To use these, set options.success, options.processfailure or options.writefailure when calling commitChanges().


New in 1.1

commitChanges() supports adding your own parameters to the update call. This can be done in one of two ways:

  • options.params: add a params object as a member of the options passed to commitChanges(), and the key-value pairs listed therein will be passed via the update.
  • store.updateParams: add a params object to the WriteStore object itself, either at instantiation or any time later, and all the parameters therein will be passed via the update.

Several important rules are observed when mixing and matching parameters from the three sources, i.e. store.updateParams, options.params and the base ones already part of the update call:

  1. For each call, first the store-wide store.updateParams are taken, and then the per-call options.params are added. The per-call options will override the store-wide options.
  2. The result of the first rule will be applied to the params to be sent via the update(). However, privileged system parameters will not be overridden. Privileged ones are:
    • data - the data to be sent
    • mode - the update mode

Write Modes

Through version 1.0

There are two methods for sending data: update and replace. The default mode is update.

  • Update: In update mode, all the journal entries since the last commit, reject or load, i.e. the entire transaction, is sent back to the server. The server is expected to receive these updates and apply them as it sees fit.
  • Replace: In replace mode, the entire dataset - including unchanged data - is sent back to the server.

Update mode works well when the data source can apply changes, for example an SQL-based system; replace mode works when the data source cannot, for example while updating a flat file. You must use replace mode when using a StoreProxy.

The mode is set in one of several ways:

  • Store: When creating the WriteStore, you can specify a config parameter replaceWrite. If true, every write from the Store will be in replace mode; if false, undefined or null, every write from the Store will be in update mode.
var store = new Ext.ux.WriteStore({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	updateProxy: new Ext.ux.HttpWriteProxy({url: somePostUrl}),
	reader: new Ext.ux.JsonWriterReader({
		root: 'person'
	},Person),
        replaceWrite: true
});
  • Store: After the WriteStore is created, you can set the public property replaceWrite. If true, every write from the Store will be in replace mode; if false, undefined or null, every write from the Store will be in update mode.
store.replaceWrite = false;
  • Transaction: You can override the WriteStore setting for a particular transaction by passing a replace config parameter to the commitChanges() call.
store.commitChanges({replace: true});
Version 1.1
Data Sent

There are now three methods for sending data: update, replace and condensed. The default mode is update. Constants defined in WriteStore.modes represent the three modes.

  • Update: In update mode, all the journal entries since the last commit, reject or load, i.e. the entire transaction, is sent back to the server. The server is expected to receive these updates and apply them as it sees fit. Represented by WriteStore.modes.update.
  • Replace: In replace mode, the entire dataset - including unchanged data - is sent back to the server. Represented by WriteStore.modes.replace.
  • Condensed: Condensed mode works exactly like update mode, with one significant change. All the changes related to a particular record are condensed into a single change. Represented by WriteStore.modes.condensed.

Update mode works well when the data source can apply changes, for example an SQL-based system; replace mode works when the data source cannot, for example while updating a flat file. You must use replace mode when using a StoreProxy.

The mode is set in one of several ways:

  • Store: When creating the WriteStore, you can specify a config parameter writeMode. This parameter can be set to one of the WriteStore.modes constants.
var store = new Ext.ux.WriteStore({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	updateProxy: new Ext.ux.HttpWriteProxy({url: somePostUrl}),
	reader: new Ext.ux.JsonWriterReader({
		root: 'person'
	},Person),
        writeMode: Ext.ux.WriteStore.modes.update // or replace or condensed
});
  • Store: After the WriteStore is created, you can set the public property writeMode to one of the WriteStore.modes constants.
store.writeMode = WriteStore.modes.update; // or replace or condensed
  • Transaction: You can override the WriteStore setting for a particular transaction by passing a mode config parameter to the commitChanges() call.
var myMode = WriteStore.modes.update; // or replace or condensed
store.commitChanges({mode: myMode});
  • Default: If not other settings are provided, the default mode will be used, which is WriteStore.modes.update

The differences between the three modes are highlighted below. In the example, the original data is as follows:

{
	metaData: {
			root: 'person',
			id: 'id',
			fields: [{name: 'firstName'},{name: 'lastName'},{name: 'address'}]
	},
	person: 
 [
   {id: 1, firstName: 'John', lastName: 'Smith', address: [{number: '123', street: 'Main St'},{number: '456', street: 'Elm St'}]},
   {id: 2, firstName: 'Jill', lastName: 'Stein', address: [{number: '789', street: 'Park St'},{number: '012', street: 'Birch St'}]}
 ]
}

The following changes are then made:

store.getAt(0).set('lastName','Smithers');
store.getAt(1).set('lastName','Steiner');
store.getAt(0).set('firstName','Jane');

Note that we make changes to record 0, then to record 1, then back to record 0.

The resultant data transmitted to the server in each of the modes is:

// update mode
{"person":
[
{"type":"u",
"data":
 [{"name":"lastName","old":"Smith","new":"Smithers"}],"id":1},
  {"type":"u","data":[{"name":"lastName","old":"Stein","new":"Steiner"}],"id":2},
  {"type":"u","data":[{"name":"firstName","old":"John","new":"Jane"}],"id":1}
 ]
}
 
// replace mode
{"person":
 [
  {"firstName":"Jane","lastName":"Smithers",
   "address":[{"number":"123","street":"Main St"},{"number":"456","street":"Elm St"}],
   "id":1},
  {"firstName":"Jill","lastName":"Steiner",
    "address":[{"number":"789","street":"Park St"},{"number":"012","street":"Birch St"}],"id":2}
 ]
}
 
// condensed mode
{"person":
 [
 {"type":"u",
  "data":[{"name":"lastName","old":"Smith","new":"Smithers"},{"name":"firstName","old":"John","new":"Jane"}],"id":1},
 {"type":"u",
  "data":[{"name":"lastName","old":"Stein","new":"Steiner"}],"id":2}
 ]
}
MetaData

Some readers for an Ext.data.Store can be configured at construction time or via metadata sent from the server. See the API Docs for JsonReader for more detail. If the metadata which configured this store was read from the server, rather than from the reader creation config object, the metadata will be sent back to the server.

{
 "metaData":{"root":"person","id":"id","fields":[{"name":"firstName"},{"name":"lastName"},{"name":"address"}]},
 "person":
 [{"type":"u","data":[{"name":"lastName","old":"Smith","new":"Smithers"}],"id":1},
 {"type":"u","data":[{"name":"lastName","old":"Stein","new":"Steiner"}],"id":2},
 {"type":"u","data":[{"name":"firstName","old":"John","new":"Jane"}],"id":1}]
}
ID

The ID of the record comes from one of two sources.

  • If the metadata provided - either at construction time or as part of the data from the server - provides an ID field, then that field is treated as a source for IDs, and is stored in the Record's id property.
  • If not id property is provided in the metadata, then the id is auto-generated by ExtJS. It is normally started at 1000, but that can be configured. See the documentation for Ext.data.Store.

WriteStore follows a similar philosophy. If the metadata includes an id property, then the ID property is assumed to be of importance and significant to the server. Thus, when transmitting to the server, the contents of the Record's ID field will be send as a property of the data itself, as named in the id property. If not, not record ID will be sent.

This example explains it. Assume, as before, that our json from the server is as follows.

{
	metaData: {
			root: 'person',
			id: 'id',
			fields: [{name: 'firstName'},{name: 'lastName'},{name: 'address'}]
	},
	person: 
 [
   {id: 1, firstName: 'John', lastName: 'Smith', address: [{number: '123', street: 'Main St'},{number: '456', street: 'Elm St'}]},
   {id: 2, firstName: 'Jill', lastName: 'Stein', address: [{number: '789', street: 'Park St'},{number: '012', street: 'Birch St'}]}
 ]
}

Note that the metaData specifies an id field named (aptly) id, and that each record contains an id property. When transmitting to the server, the results will be as follows.

// update mode
{"person":
[
{"type":"u",
"data":
 [{"name":"lastName","old":"Smith","new":"Smithers"}],"id":1},
  {"type":"u","data":[{"name":"lastName","old":"Stein","new":"Steiner"}],"id":2},
  {"type":"u","data":[{"name":"firstName","old":"John","new":"Jane"}],"id":1}
 ]
}
 
// replace mode
{"person":
 [
  {"firstName":"Jane","lastName":"Smithers",
   "address":[{"number":"123","street":"Main St"},{"number":"456","street":"Elm St"}],
   "id":1},
  {"firstName":"Jill","lastName":"Steiner",
    "address":[{"number":"789","street":"Park St"},{"number":"012","street":"Birch St"}],"id":2}
 ]
}
 
// condensed mode
{"person":
 [
 {"type":"u",
  "data":[{"name":"lastName","old":"Smith","new":"Smithers"},{"name":"firstName","old":"John","new":"Jane"}],"id":1},
 {"type":"u",
  "data":[{"name":"lastName","old":"Stein","new":"Steiner"}],"id":2}
 ]
}

On the other hand, if no id field is provided in the metadata, as follows:

{
	metaData: {
			root: 'person',
			//id: 'id',
			fields: [{name: 'firstName'},{name: 'lastName'},{name: 'address'}]
	},
	person: 
 [
   {id: 1, firstName: 'John', lastName: 'Smith', address: [{number: '123', street: 'Main St'},{number: '456', street: 'Elm St'}]},
   {id: 2, firstName: 'Jill', lastName: 'Stein', address: [{number: '789', street: 'Park St'},{number: '012', street: 'Birch St'}]}
 ]
}

The results will be as follows.

// update mode
{"person":
[
{"type":"u",
"data":
 [{"name":"lastName","old":"Smith","new":"Smithers"}]},
  {"type":"u","data":[{"name":"lastName","old":"Stein","new":"Steiner"}]},
  {"type":"u","data":[{"name":"firstName","old":"John","new":"Jane"}]}
 ]
}
 
// replace mode
{"person":
 [
  {"firstName":"Jane","lastName":"Smithers",
   "address":[{"number":"123","street":"Main St"},{"number":"456","street":"Elm St"}]},
  {"firstName":"Jill","lastName":"Steiner",
    "address":[{"number":"789","street":"Park St"},{"number":"012","street":"Birch St"}]}
 ]
}
 
// condensed mode
{"person":
 [
 {"type":"u",
  "data":[{"name":"lastName","old":"Smith","new":"Smithers"},{"name":"firstName","old":"John","new":"Jane"}]},
 {"type":"u",
  "data":[{"name":"lastName","old":"Stein","new":"Steiner"}]}
 ]
}

Success or Failure

When sending data back to the source in a commit with write, the result is always given by a callback to an event handler. The signature is always as follows:

function(store,o,response);

There are three possible outcomes to a commit with write:

  • The send (e.g. POST) fails. This can be due to application problems server-side, network problems, or a host of other issues. In HTTP parlance, this is anything other than a 200 response. In this case, a writeexception event is triggered by the WriteStore. The response parameter includes response.status to check, like loadexception from Ext.data.HttpProxy.
  • The send (e.g. POST) succeeds, and the processing succeeds. In this case, a write event is triggered by the WriteStore. The response parameter includes response to check, like load from HttpProxy. WriteStore understands that the processing has succeeded because no registered handler exists, or no handler returns false. The local commit completes, a new transaction is begun, and a commit event is triggered.
  • The send (e.g. POST) succeeds, but the processing fails. For example, the POST goes, yet the processing on the server works and decides that it does not accept the changes. This is a network-layer success but an application-layer failure. In this case, a write is triggered by the WriteStore. The response parameter includes response to check, like load from HttpProxy. WriteStore understands that the processing has failed

because a registered handler returns false.

Server Side

In order to support writing from a WriteStore, certain behaviours must follow from the server side. This section delineates those behaviours. The requirements for sending data in any format (text, XML, JSON, etc.) to do a standard store.load() are not covered here, as they are well covered by the ExtJS documentation.

Success or Failure

write-store, relying on ext, can inherently determine if the send succeeds or fails. It cannot determine if the application processing was successful. As such, the client-side application is expected to register a handler for the write event. When the send has succeeded, a write event will be triggered. The application can indicate that processing has failed by returning false in any registered handler, or it can indicate that processing has succeeded by returning true.

The content of the response will be passed as a parameter to the event handlers.

Processing Data

The format of the data sent to the server is configured by the write-capable reader. The only write-capable reader shipping with write-store is Ext.ux.JsonWriterReader. Put in other terms, the data will be transmitted in JSON format. An XML reader is being evaluated at this time.

JsonWriterReader formats its data in two different ways, depending on if replace mode or the default update mode is used.

Replace Mode

In replace mode, the data sent will look identical to the data as it was loaded, obviously with the exception of changes that were made. Continuing the persons example of above, if the source data is as follows:

{
  'person':
    [
      {firstName: 'John', lastName: 'Smith'},
      {firstName: 'Jill', lastName: 'Smith'},
      {firstName: 'Justin', lastName: 'Smith'},
      {firstName: 'Juliet', lastName: 'Smith'}
    ]
}

And the following changes are made.

var Person = Ext.data.Record.create([{name: 'firstName'},{name: 'lastName'}]);
var storeA = new Ext.ux.WriteStore({
	proxy: new Ext.data.HttpProxy({url: someUrl}),
	updateProxy: new Ext.ux.HttpProxy({url: someUrl}),
	reader: new Ext.ux.JsonReader({
		root: 'person'
	},Person)
});
storeA.load();
 
// make some changes
storeA.remove(storeA.getAt(1));
storeA.getAt(0).set('lastName','Butler');
 
// commit the changes
storeA.commitChanges();

The data transmitted to the server will be as follows:

{
  'person':
    [
      {firstName: 'John', lastName: 'Butler'},
      {firstName: 'Justin', lastName: 'Smith'},
      {firstName: 'Juliet', lastName: 'Smith'}
    ]
}

Update Mode

In replace mode, a sequential journal log listing all the changes made during the transaction, is transmitted to the server. All changes are according to the following format:

{root: [journal1,journal2,...,journalN]}

where each journal entry is an object as follows:

{type: updateType,data:{recordData}}

Where:

  • recordData is the record to update in EXACTLY the same format as was sent by the server on load(), but with updated data.
  • updateType is one of: u = change, c = add, d = remove. All types are available at Ext.ux.WriteStore.types. The types are one of
    • Ext.ux.WriteStore.types.change = 'u'
    • Ext.ux.WriteStore.types.add = 'c'
    • Ext.ux.WriteStore.types.remove = 'd'

(For the astute observers, yes, the single character codes are taken from CRUD.)

Continuing the example above, the data transmitted to the server will be as follows:

{person: [
    {'d',{firstName: 'Jill', lastName: 'Smith'}},
    {'u',{firstName: 'John', lastName: 'Butler'}}
]}

Note that the entire record is sent. It is assumed that the record contains sufficient information, such as a unique identifier or key, to enable the server to manage the update.