Initial commit

This commit is contained in:
Eike Foken
2011-04-16 13:57:38 +02:00
parent cd3750db95
commit b570685ae8
924 changed files with 172926 additions and 0 deletions

View File

@@ -0,0 +1,285 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.Api
* @extends Object
* Ext.data.Api is a singleton designed to manage the data API including methods
* for validating a developer's DataProxy API. Defines variables for CRUD actions
* create, read, update and destroy in addition to a mapping of RESTful HTTP methods
* GET, POST, PUT and DELETE to CRUD actions.
* @singleton
*/
Ext.data.Api = (function() {
// private validActions. validActions is essentially an inverted hash of Ext.data.Api.actions, where value becomes the key.
// Some methods in this singleton (e.g.: getActions, getVerb) will loop through actions with the code <code>for (var verb in this.actions)</code>
// For efficiency, some methods will first check this hash for a match. Those methods which do acces validActions will cache their result here.
// We cannot pre-define this hash since the developer may over-ride the actions at runtime.
var validActions = {};
return {
/**
* Defined actions corresponding to remote actions:
* <pre><code>
actions: {
create : 'create', // Text representing the remote-action to create records on server.
read : 'read', // Text representing the remote-action to read/load data from server.
update : 'update', // Text representing the remote-action to update records on server.
destroy : 'destroy' // Text representing the remote-action to destroy records on server.
}
* </code></pre>
* @property actions
* @type Object
*/
actions : {
create : 'create',
read : 'read',
update : 'update',
destroy : 'destroy'
},
/**
* Defined {CRUD action}:{HTTP method} pairs to associate HTTP methods with the
* corresponding actions for {@link Ext.data.DataProxy#restful RESTful proxies}.
* Defaults to:
* <pre><code>
restActions : {
create : 'POST',
read : 'GET',
update : 'PUT',
destroy : 'DELETE'
},
* </code></pre>
*/
restActions : {
create : 'POST',
read : 'GET',
update : 'PUT',
destroy : 'DELETE'
},
/**
* Returns true if supplied action-name is a valid API action defined in <code>{@link #actions}</code> constants
* @param {String} action Action to test for availability.
* @return {Boolean}
*/
isAction : function(action) {
return (Ext.data.Api.actions[action]) ? true : false;
},
/**
* Returns the actual CRUD action KEY "create", "read", "update" or "destroy" from the supplied action-name. This method is used internally and shouldn't generally
* need to be used directly. The key/value pair of Ext.data.Api.actions will often be identical but this is not necessarily true. A developer can override this naming
* convention if desired. However, the framework internally calls methods based upon the KEY so a way of retreiving the the words "create", "read", "update" and "destroy" is
* required. This method will cache discovered KEYS into the private validActions hash.
* @param {String} name The runtime name of the action.
* @return {String||null} returns the action-key, or verb of the user-action or null if invalid.
* @nodoc
*/
getVerb : function(name) {
if (validActions[name]) {
return validActions[name]; // <-- found in cache. return immediately.
}
for (var verb in this.actions) {
if (this.actions[verb] === name) {
validActions[name] = verb;
break;
}
}
return (validActions[name] !== undefined) ? validActions[name] : null;
},
/**
* Returns true if the supplied API is valid; that is, check that all keys match defined actions
* otherwise returns an array of mistakes.
* @return {String[]|true}
*/
isValid : function(api){
var invalid = [];
var crud = this.actions; // <-- cache a copy of the actions.
for (var action in api) {
if (!(action in crud)) {
invalid.push(action);
}
}
return (!invalid.length) ? true : invalid;
},
/**
* Returns true if the supplied verb upon the supplied proxy points to a unique url in that none of the other api-actions
* point to the same url. The question is important for deciding whether to insert the "xaction" HTTP parameter within an
* Ajax request. This method is used internally and shouldn't generally need to be called directly.
* @param {Ext.data.DataProxy} proxy
* @param {String} verb
* @return {Boolean}
*/
hasUniqueUrl : function(proxy, verb) {
var url = (proxy.api[verb]) ? proxy.api[verb].url : null;
var unique = true;
for (var action in proxy.api) {
if ((unique = (action === verb) ? true : (proxy.api[action].url != url) ? true : false) === false) {
break;
}
}
return unique;
},
/**
* This method is used internally by <tt>{@link Ext.data.DataProxy DataProxy}</tt> and should not generally need to be used directly.
* Each action of a DataProxy api can be initially defined as either a String or an Object. When specified as an object,
* one can explicitly define the HTTP method (GET|POST) to use for each CRUD action. This method will prepare the supplied API, setting
* each action to the Object form. If your API-actions do not explicitly define the HTTP method, the "method" configuration-parameter will
* be used. If the method configuration parameter is not specified, POST will be used.
<pre><code>
new Ext.data.HttpProxy({
method: "POST", // <-- default HTTP method when not specified.
api: {
create: 'create.php',
load: 'read.php',
save: 'save.php',
destroy: 'destroy.php'
}
});
// Alternatively, one can use the object-form to specify the API
new Ext.data.HttpProxy({
api: {
load: {url: 'read.php', method: 'GET'},
create: 'create.php',
destroy: 'destroy.php',
save: 'update.php'
}
});
</code></pre>
*
* @param {Ext.data.DataProxy} proxy
*/
prepare : function(proxy) {
if (!proxy.api) {
proxy.api = {}; // <-- No api? create a blank one.
}
for (var verb in this.actions) {
var action = this.actions[verb];
proxy.api[action] = proxy.api[action] || proxy.url || proxy.directFn;
if (typeof(proxy.api[action]) == 'string') {
proxy.api[action] = {
url: proxy.api[action],
method: (proxy.restful === true) ? Ext.data.Api.restActions[action] : undefined
};
}
}
},
/**
* Prepares a supplied Proxy to be RESTful. Sets the HTTP method for each api-action to be one of
* GET, POST, PUT, DELETE according to the defined {@link #restActions}.
* @param {Ext.data.DataProxy} proxy
*/
restify : function(proxy) {
proxy.restful = true;
for (var verb in this.restActions) {
proxy.api[this.actions[verb]].method ||
(proxy.api[this.actions[verb]].method = this.restActions[verb]);
}
// TODO: perhaps move this interceptor elsewhere? like into DataProxy, perhaps? Placed here
// to satisfy initial 3.0 final release of REST features.
proxy.onWrite = proxy.onWrite.createInterceptor(function(action, o, response, rs) {
var reader = o.reader;
var res = new Ext.data.Response({
action: action,
raw: response
});
switch (response.status) {
case 200: // standard 200 response, send control back to HttpProxy#onWrite by returning true from this intercepted #onWrite
return true;
break;
case 201: // entity created but no response returned
if (Ext.isEmpty(res.raw.responseText)) {
res.success = true;
} else {
//if the response contains data, treat it like a 200
return true;
}
break;
case 204: // no-content. Create a fake response.
res.success = true;
res.data = null;
break;
default:
return true;
break;
}
if (res.success === true) {
this.fireEvent("write", this, action, res.data, res, rs, o.request.arg);
} else {
this.fireEvent('exception', this, 'remote', action, o, res, rs);
}
o.request.callback.call(o.request.scope, res.data, res, res.success);
return false; // <-- false to prevent intercepted function from running.
}, proxy);
}
};
})();
/**
* Ext.data.Response
* Experimental. Do not use directly.
*/
Ext.data.Response = function(params, response) {
Ext.apply(this, params, {
raw: response
});
};
Ext.data.Response.prototype = {
message : null,
success : false,
status : null,
root : null,
raw : null,
getMessage : function() {
return this.message;
},
getSuccess : function() {
return this.success;
},
getStatus : function() {
return this.status;
},
getRoot : function() {
return this.root;
},
getRawResponse : function() {
return this.raw;
}
};
/**
* @class Ext.data.Api.Error
* @extends Ext.Error
* Error class for Ext.data.Api errors
*/
Ext.data.Api.Error = Ext.extend(Ext.Error, {
constructor : function(message, arg) {
this.arg = arg;
Ext.Error.call(this, message);
},
name: 'Ext.data.Api'
});
Ext.apply(Ext.data.Api.Error.prototype, {
lang: {
'action-url-undefined': 'No fallback url defined for this action. When defining a DataProxy api, please be sure to define an url for each CRUD action in Ext.data.Api.actions or define a default url in addition to your api-configuration.',
'invalid': 'received an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions',
'invalid-url': 'Invalid url. Please review your proxy configuration.',
'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"'
}
});

View File

@@ -0,0 +1,103 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.ArrayReader
* @extends Ext.data.JsonReader
* <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an Array.
* Each element of that Array represents a row of data fields. The
* fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
* of the field definition if it exists, or the field's ordinal position in the definition.</p>
* <p>Example code:</p>
* <pre><code>
var Employee = Ext.data.Record.create([
{name: 'name', mapping: 1}, // "mapping" only needed if an "id" field is present which
{name: 'occupation', mapping: 2} // precludes using the ordinal position as the index.
]);
var myReader = new Ext.data.ArrayReader({
{@link #idIndex}: 0
}, Employee);
</code></pre>
* <p>This would consume an Array like this:</p>
* <pre><code>
[ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
* </code></pre>
* @constructor
* Create a new ArrayReader
* @param {Object} meta Metadata configuration options.
* @param {Array/Object} recordType
* <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
* will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
* constructor created from {@link Ext.data.Record#create}.</p>
*/
Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
/**
* @cfg {String} successProperty
* @hide
*/
/**
* @cfg {Number} id (optional) The subscript within row Array that provides an ID for the Record.
* Deprecated. Use {@link #idIndex} instead.
*/
/**
* @cfg {Number} idIndex (optional) The subscript within row Array that provides an ID for the Record.
*/
/**
* Create a data block containing Ext.data.Records from an Array.
* @param {Object} o An Array of row objects which represents the dataset.
* @return {Object} data A data block which is used by an Ext.data.Store object as
* a cache of Ext.data.Records.
*/
readRecords : function(o){
this.arrayData = o;
var s = this.meta,
sid = s ? Ext.num(s.idIndex, s.id) : null,
recordType = this.recordType,
fields = recordType.prototype.fields,
records = [],
success = true,
v;
var root = this.getRoot(o);
for(var i = 0, len = root.length; i < len; i++) {
var n = root[i],
values = {},
id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
for(var j = 0, jlen = fields.length; j < jlen; j++) {
var f = fields.items[j],
k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
v = n[k] !== undefined ? n[k] : f.defaultValue;
v = f.convert(v, n);
values[f.name] = v;
}
var record = new recordType(values, id);
record.json = n;
records[records.length] = record;
}
var totalRecords = records.length;
if(s.totalProperty) {
v = parseInt(this.getTotal(o), 10);
if(!isNaN(v)) {
totalRecords = v;
}
}
if(s.successProperty){
v = this.getSuccess(o);
if(v === false || v === 'false'){
success = false;
}
}
return {
success : success,
records : records,
totalRecords : totalRecords
};
}
});

View File

@@ -0,0 +1,70 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.ArrayStore
* @extends Ext.data.Store
* <p>Formerly known as "SimpleStore".</p>
* <p>Small helper class to make creating {@link Ext.data.Store}s from Array data easier.
* An ArrayStore will be automatically configured with a {@link Ext.data.ArrayReader}.</p>
* <p>A store configuration would be something like:<pre><code>
var store = new Ext.data.ArrayStore({
// store configs
autoDestroy: true,
storeId: 'myStore',
// reader configs
idIndex: 0,
fields: [
'company',
{name: 'price', type: 'float'},
{name: 'change', type: 'float'},
{name: 'pctChange', type: 'float'},
{name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
]
});
* </code></pre></p>
* <p>This store is configured to consume a returned object of the form:<pre><code>
var myData = [
['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
];
* </code></pre>
* An object literal of this form could also be used as the {@link #data} config option.</p>
* <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
* <b>{@link Ext.data.ArrayReader ArrayReader}</b>.</p>
* @constructor
* @param {Object} config
* @xtype arraystore
*/
Ext.data.ArrayStore = Ext.extend(Ext.data.Store, {
/**
* @cfg {Ext.data.DataReader} reader @hide
*/
constructor: function(config){
Ext.data.ArrayStore.superclass.constructor.call(this, Ext.apply(config, {
reader: new Ext.data.ArrayReader(config)
}));
},
loadData : function(data, append){
if(this.expandData === true){
var r = [];
for(var i = 0, len = data.length; i < len; i++){
r[r.length] = [data[i]];
}
data = r;
}
Ext.data.ArrayStore.superclass.loadData.call(this, data, append);
}
});
Ext.reg('arraystore', Ext.data.ArrayStore);
// backwards compat
Ext.data.SimpleStore = Ext.data.ArrayStore;
Ext.reg('simplestore', Ext.data.SimpleStore);

View File

@@ -0,0 +1,203 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.Field
* <p>This class encapsulates the field definition information specified in the field definition objects
* passed to {@link Ext.data.Record#create}.</p>
* <p>Developers do not need to instantiate this class. Instances are created by {@link Ext.data.Record.create}
* and cached in the {@link Ext.data.Record#fields fields} property of the created Record constructor's <b>prototype.</b></p>
*/
Ext.data.Field = Ext.extend(Object, {
constructor : function(config){
if(Ext.isString(config)){
config = {name: config};
}
Ext.apply(this, config);
var types = Ext.data.Types,
st = this.sortType,
t;
if(this.type){
if(Ext.isString(this.type)){
this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
}
}else{
this.type = types.AUTO;
}
// named sortTypes are supported, here we look them up
if(Ext.isString(st)){
this.sortType = Ext.data.SortTypes[st];
}else if(Ext.isEmpty(st)){
this.sortType = this.type.sortType;
}
if(!this.convert){
this.convert = this.type.convert;
}
},
/**
* @cfg {String} name
* The name by which the field is referenced within the Record. This is referenced by, for example,
* the <code>dataIndex</code> property in column definition objects passed to {@link Ext.grid.ColumnModel}.
* <p>Note: In the simplest case, if no properties other than <code>name</code> are required, a field
* definition may consist of just a String for the field name.</p>
*/
/**
* @cfg {Mixed} type
* (Optional) The data type for automatic conversion from received data to the <i>stored</i> value if <code>{@link Ext.data.Field#convert convert}</code>
* has not been specified. This may be specified as a string value. Possible values are
* <div class="mdetail-params"><ul>
* <li>auto (Default, implies no conversion)</li>
* <li>string</li>
* <li>int</li>
* <li>float</li>
* <li>boolean</li>
* <li>date</li></ul></div>
* <p>This may also be specified by referencing a member of the {@link Ext.data.Types} class.</p>
* <p>Developers may create their own application-specific data types by defining new members of the
* {@link Ext.data.Types} class.</p>
*/
/**
* @cfg {Function} convert
* (Optional) A function which converts the value provided by the Reader into an object that will be stored
* in the Record. It is passed the following parameters:<div class="mdetail-params"><ul>
* <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
* the configured <code>{@link Ext.data.Field#defaultValue defaultValue}</code>.</div></li>
* <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
* Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
* ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
* </ul></div>
* <pre><code>
// example of convert function
function fullName(v, record){
return record.name.last + ', ' + record.name.first;
}
function location(v, record){
return !record.city ? '' : (record.city + ', ' + record.state);
}
var Dude = Ext.data.Record.create([
{name: 'fullname', convert: fullName},
{name: 'firstname', mapping: 'name.first'},
{name: 'lastname', mapping: 'name.last'},
{name: 'city', defaultValue: 'homeless'},
'state',
{name: 'location', convert: location}
]);
// create the data store
var store = new Ext.data.Store({
reader: new Ext.data.JsonReader(
{
idProperty: 'key',
root: 'daRoot',
totalProperty: 'total'
},
Dude // recordType
)
});
var myData = [
{ key: 1,
name: { first: 'Fat', last: 'Albert' }
// notice no city, state provided in data object
},
{ key: 2,
name: { first: 'Barney', last: 'Rubble' },
city: 'Bedrock', state: 'Stoneridge'
},
{ key: 3,
name: { first: 'Cliff', last: 'Claven' },
city: 'Boston', state: 'MA'
}
];
* </code></pre>
*/
/**
* @cfg {String} dateFormat
* <p>(Optional) Used when converting received data into a Date when the {@link #type} is specified as <code>"date"</code>.</p>
* <p>A format string for the {@link Date#parseDate Date.parseDate} function, or "timestamp" if the
* value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
* javascript millisecond timestamp. See {@link Date}</p>
*/
dateFormat: null,
/**
* @cfg {Boolean} useNull
* <p>(Optional) Use when converting received data into a Number type (either int or float). If the value cannot be parsed,
* null will be used if useNull is true, otherwise the value will be 0. Defaults to <tt>false</tt>
*/
useNull: false,
/**
* @cfg {Mixed} defaultValue
* (Optional) The default value used <b>when a Record is being created by a {@link Ext.data.Reader Reader}</b>
* when the item referenced by the <code>{@link Ext.data.Field#mapping mapping}</code> does not exist in the data
* object (i.e. undefined). (defaults to "")
*/
defaultValue: "",
/**
* @cfg {String/Number} mapping
* <p>(Optional) A path expression for use by the {@link Ext.data.DataReader} implementation
* that is creating the {@link Ext.data.Record Record} to extract the Field value from the data object.
* If the path expression is the same as the field name, the mapping may be omitted.</p>
* <p>The form of the mapping expression depends on the Reader being used.</p>
* <div class="mdetail-params"><ul>
* <li>{@link Ext.data.JsonReader}<div class="sub-desc">The mapping is a string containing the javascript
* expression to reference the data from an element of the data item's {@link Ext.data.JsonReader#root root} Array. Defaults to the field name.</div></li>
* <li>{@link Ext.data.XmlReader}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
* item relative to the DOM element that represents the {@link Ext.data.XmlReader#record record}. Defaults to the field name.</div></li>
* <li>{@link Ext.data.ArrayReader}<div class="sub-desc">The mapping is a number indicating the Array index
* of the field's value. Defaults to the field specification's Array position.</div></li>
* </ul></div>
* <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
* function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
* return the desired data.</p>
*/
mapping: null,
/**
* @cfg {Function} sortType
* (Optional) A function which converts a Field's value to a comparable value in order to ensure
* correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
* sort example:<pre><code>
// current sort after sort we want
// +-+------+ +-+------+
// |1|First | |1|First |
// |2|Last | |3|Second|
// |3|Second| |2|Last |
// +-+------+ +-+------+
sortType: function(value) {
switch (value.toLowerCase()) // native toLowerCase():
{
case 'first': return 1;
case 'second': return 2;
default: return 3;
}
}
* </code></pre>
*/
sortType : null,
/**
* @cfg {String} sortDir
* (Optional) Initial direction to sort (<code>"ASC"</code> or <code>"DESC"</code>). Defaults to
* <code>"ASC"</code>.
*/
sortDir : "ASC",
/**
* @cfg {Boolean} allowBlank
* (Optional) Used for validating a {@link Ext.data.Record record}, defaults to <code>true</code>.
* An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid}
* to evaluate to <code>false</code>.
*/
allowBlank : true
});

View File

@@ -0,0 +1,511 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.DataProxy
* @extends Ext.util.Observable
* <p>Abstract base class for implementations which provide retrieval of unformatted data objects.
* This class is intended to be extended and should not be created directly. For existing implementations,
* see {@link Ext.data.DirectProxy}, {@link Ext.data.HttpProxy}, {@link Ext.data.ScriptTagProxy} and
* {@link Ext.data.MemoryProxy}.</p>
* <p>DataProxy implementations are usually used in conjunction with an implementation of {@link Ext.data.DataReader}
* (of the appropriate type which knows how to parse the data object) to provide a block of
* {@link Ext.data.Records} to an {@link Ext.data.Store}.</p>
* <p>The parameter to a DataProxy constructor may be an {@link Ext.data.Connection} or can also be the
* config object to an {@link Ext.data.Connection}.</p>
* <p>Custom implementations must implement either the <code><b>doRequest</b></code> method (preferred) or the
* <code>load</code> method (deprecated). See
* {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#doRequest doRequest} or
* {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#load load} for additional details.</p>
* <p><b><u>Example 1</u></b></p>
* <pre><code>
proxy: new Ext.data.ScriptTagProxy({
{@link Ext.data.Connection#url url}: 'http://extjs.com/forum/topics-remote.php'
}),
* </code></pre>
* <p><b><u>Example 2</u></b></p>
* <pre><code>
proxy : new Ext.data.HttpProxy({
{@link Ext.data.Connection#method method}: 'GET',
{@link Ext.data.HttpProxy#prettyUrls prettyUrls}: false,
{@link Ext.data.Connection#url url}: 'local/default.php', // see options parameter for {@link Ext.Ajax#request}
{@link #api}: {
// all actions except the following will use above url
create : 'local/new.php',
update : 'local/update.php'
}
}),
* </code></pre>
* <p>And <b>new in Ext version 3</b>, attach centralized event-listeners upon the DataProxy class itself! This is a great place
* to implement a <i>messaging system</i> to centralize your application's user-feedback and error-handling.</p>
* <pre><code>
// Listen to all "beforewrite" event fired by all proxies.
Ext.data.DataProxy.on('beforewrite', function(proxy, action) {
console.log('beforewrite: ', action);
});
// Listen to "write" event fired by all proxies
Ext.data.DataProxy.on('write', function(proxy, action, data, res, rs) {
console.info('write: ', action);
});
// Listen to "exception" event fired by all proxies
Ext.data.DataProxy.on('exception', function(proxy, type, action, exception) {
console.error(type + action + ' exception);
});
* </code></pre>
* <b>Note:</b> These three events are all fired with the signature of the corresponding <i>DataProxy instance</i> event {@link #beforewrite beforewrite}, {@link #write write} and {@link #exception exception}.
*/
Ext.data.DataProxy = function(conn){
// make sure we have a config object here to support ux proxies.
// All proxies should now send config into superclass constructor.
conn = conn || {};
// This line caused a bug when people use custom Connection object having its own request method.
// http://extjs.com/forum/showthread.php?t=67194. Have to set DataProxy config
//Ext.applyIf(this, conn);
this.api = conn.api;
this.url = conn.url;
this.restful = conn.restful;
this.listeners = conn.listeners;
// deprecated
this.prettyUrls = conn.prettyUrls;
/**
* @cfg {Object} api
* Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
* Defaults to:<pre><code>
api: {
read : undefined,
create : undefined,
update : undefined,
destroy : undefined
}
* </code></pre>
* <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
* using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
* configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>
* <p>For example:</p>
* <pre><code>
api: {
load : '/controller/load',
create : '/controller/new', // Server MUST return idProperty of new record
save : '/controller/update',
destroy : '/controller/destroy_action'
}
// Alternatively, one can use the object-form to specify each API-action
api: {
load: {url: 'read.php', method: 'GET'},
create: 'create.php',
destroy: 'destroy.php',
save: 'update.php'
}
* </code></pre>
* <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
* will be directed to the configured <tt>{@link Ext.data.Connection#url url}</tt>.</p>
* <br><p><b>Note</b>: To modify the URL for an action dynamically the appropriate API
* property should be modified before the action is requested using the corresponding before
* action event. For example to modify the URL associated with the load action:
* <pre><code>
// modify the url for the action
myStore.on({
beforeload: {
fn: function (store, options) {
// use <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> to change the URL for *just* this request.
store.proxy.setUrl('changed1.php');
// set optional second parameter to true to make this URL change
// permanent, applying this URL for all subsequent requests.
store.proxy.setUrl('changed1.php', true);
// Altering the proxy API should be done using the public
// method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt>.
store.proxy.setApi('read', 'changed2.php');
// Or set the entire API with a config-object.
// When using the config-object option, you must redefine the <b>entire</b>
// API -- not just a specific action of it.
store.proxy.setApi({
read : 'changed_read.php',
create : 'changed_create.php',
update : 'changed_update.php',
destroy : 'changed_destroy.php'
});
}
}
});
* </code></pre>
* </p>
*/
this.addEvents(
/**
* @event exception
* <p>Fires if an exception occurs in the Proxy during a remote request. This event is relayed
* through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},
* so any Store instance may observe this event.</p>
* <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
* through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of exception events from <b>all</b>
* DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
* <p>This event can be fired for one of two reasons:</p>
* <div class="mdetail-params"><ul>
* <li>remote-request <b>failed</b> : <div class="sub-desc">
* The server did not return status === 200.
* </div></li>
* <li>remote-request <b>succeeded</b> : <div class="sub-desc">
* The remote-request succeeded but the reader could not read the response.
* This means the server returned data, but the configured Reader threw an
* error while reading the response. In this case, this event will be
* raised and the caught error will be passed along into this event.
* </div></li>
* </ul></div>
* <br><p>This event fires with two different contexts based upon the 2nd
* parameter <tt>type [remote|response]</tt>. The first four parameters
* are identical between the two contexts -- only the final two parameters
* differ.</p>
* @param {DataProxy} this The proxy that sent the request
* @param {String} type
* <p>The value of this parameter will be either <tt>'response'</tt> or <tt>'remote'</tt>.</p>
* <div class="mdetail-params"><ul>
* <li><b><tt>'response'</tt></b> : <div class="sub-desc">
* <p>An <b>invalid</b> response from the server was returned: either 404,
* 500 or the response meta-data does not match that defined in the DataReader
* (e.g.: root, idProperty, successProperty).</p>
* </div></li>
* <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
* <p>A <b>valid</b> response was returned from the server having
* successProperty === false. This response might contain an error-message
* sent from the server. For example, the user may have failed
* authentication/authorization or a database validation error occurred.</p>
* </div></li>
* </ul></div>
* @param {String} action Name of the action (see {@link Ext.data.Api#actions}.
* @param {Object} options The options for the action that were specified in the {@link #request}.
* @param {Object} response
* <p>The value of this parameter depends on the value of the <code>type</code> parameter:</p>
* <div class="mdetail-params"><ul>
* <li><b><tt>'response'</tt></b> : <div class="sub-desc">
* <p>The raw browser response object (e.g.: XMLHttpRequest)</p>
* </div></li>
* <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
* <p>The decoded response object sent from the server.</p>
* </div></li>
* </ul></div>
* @param {Mixed} arg
* <p>The type and value of this parameter depends on the value of the <code>type</code> parameter:</p>
* <div class="mdetail-params"><ul>
* <li><b><tt>'response'</tt></b> : Error<div class="sub-desc">
* <p>The JavaScript Error object caught if the configured Reader could not read the data.
* If the remote request returns success===false, this parameter will be null.</p>
* </div></li>
* <li><b><tt>'remote'</tt></b> : Record/Record[]<div class="sub-desc">
* <p>This parameter will only exist if the <tt>action</tt> was a <b>write</b> action
* (Ext.data.Api.actions.create|update|destroy).</p>
* </div></li>
* </ul></div>
*/
'exception',
/**
* @event beforeload
* Fires before a request to retrieve a data object.
* @param {DataProxy} this The proxy for the request
* @param {Object} params The params object passed to the {@link #request} function
*/
'beforeload',
/**
* @event load
* Fires before the load method's callback is called.
* @param {DataProxy} this The proxy for the request
* @param {Object} o The request transaction object
* @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
*/
'load',
/**
* @event loadexception
* <p>This event is <b>deprecated</b>. The signature of the loadexception event
* varies depending on the proxy, use the catch-all {@link #exception} event instead.
* This event will fire in addition to the {@link #exception} event.</p>
* @param {misc} misc See {@link #exception}.
* @deprecated
*/
'loadexception',
/**
* @event beforewrite
* <p>Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy</p>
* <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
* through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of beforewrite events from <b>all</b>
* DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
* @param {DataProxy} this The proxy for the request
* @param {String} action [Ext.data.Api.actions.create|update|destroy]
* @param {Record/Record[]} rs The Record(s) to create|update|destroy.
* @param {Object} params The request <code>params</code> object. Edit <code>params</code> to add parameters to the request.
*/
'beforewrite',
/**
* @event write
* <p>Fires before the request-callback is called</p>
* <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
* through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of write events from <b>all</b>
* DataProxies by attaching a listener to the Ext.data.DataProxy class itself.</p>
* @param {DataProxy} this The proxy that sent the request
* @param {String} action [Ext.data.Api.actions.create|upate|destroy]
* @param {Object} data The data object extracted from the server-response
* @param {Object} response The decoded response from server
* @param {Record/Record[]} rs The Record(s) from Store
* @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
*/
'write'
);
Ext.data.DataProxy.superclass.constructor.call(this);
// Prepare the proxy api. Ensures all API-actions are defined with the Object-form.
try {
Ext.data.Api.prepare(this);
} catch (e) {
if (e instanceof Ext.data.Api.Error) {
e.toConsole();
}
}
// relay each proxy's events onto Ext.data.DataProxy class for centralized Proxy-listening
Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']);
};
Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {
/**
* @cfg {Boolean} restful
* <p>Defaults to <tt>false</tt>. Set to <tt>true</tt> to operate in a RESTful manner.</p>
* <br><p> Note: this parameter will automatically be set to <tt>true</tt> if the
* {@link Ext.data.Store} it is plugged into is set to <code>restful: true</code>. If the
* Store is RESTful, there is no need to set this option on the proxy.</p>
* <br><p>RESTful implementations enable the serverside framework to automatically route
* actions sent to one url based upon the HTTP method, for example:
* <pre><code>
store: new Ext.data.Store({
restful: true,
proxy: new Ext.data.HttpProxy({url:'/users'}); // all requests sent to /users
...
)}
* </code></pre>
* If there is no <code>{@link #api}</code> specified in the configuration of the proxy,
* all requests will be marshalled to a single RESTful url (/users) so the serverside
* framework can inspect the HTTP Method and act accordingly:
* <pre>
<u>Method</u> <u>url</u> <u>action</u>
POST /users create
GET /users read
PUT /users/23 update
DESTROY /users/23 delete
* </pre></p>
* <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's
* {@link Ext.data.Record#id id} will be appended to the url. Some MVC (e.g., Ruby on Rails,
* Merb and Django) support segment based urls where the segments in the URL follow the
* Model-View-Controller approach:<pre><code>
* someSite.com/controller/action/id
* </code></pre>
* Where the segments in the url are typically:<div class="mdetail-params"><ul>
* <li>The first segment : represents the controller class that should be invoked.</li>
* <li>The second segment : represents the class function, or method, that should be called.</li>
* <li>The third segment : represents the ID (a variable typically passed to the method).</li>
* </ul></div></p>
* <br><p>Refer to <code>{@link Ext.data.DataProxy#api}</code> for additional information.</p>
*/
restful: false,
/**
* <p>Redefines the Proxy's API or a single action of an API. Can be called with two method signatures.</p>
* <p>If called with an object as the only parameter, the object should redefine the <b>entire</b> API, e.g.:</p><pre><code>
proxy.setApi({
read : '/users/read',
create : '/users/create',
update : '/users/update',
destroy : '/users/destroy'
});
</code></pre>
* <p>If called with two parameters, the first parameter should be a string specifying the API action to
* redefine and the second parameter should be the URL (or function if using DirectProxy) to call for that action, e.g.:</p><pre><code>
proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url');
</code></pre>
* @param {String/Object} api An API specification object, or the name of an action.
* @param {String/Function} url The URL (or function if using DirectProxy) to call for the action.
*/
setApi : function() {
if (arguments.length == 1) {
var valid = Ext.data.Api.isValid(arguments[0]);
if (valid === true) {
this.api = arguments[0];
}
else {
throw new Ext.data.Api.Error('invalid', valid);
}
}
else if (arguments.length == 2) {
if (!Ext.data.Api.isAction(arguments[0])) {
throw new Ext.data.Api.Error('invalid', arguments[0]);
}
this.api[arguments[0]] = arguments[1];
}
Ext.data.Api.prepare(this);
},
/**
* Returns true if the specified action is defined as a unique action in the api-config.
* request. If all API-actions are routed to unique urls, the xaction parameter is unecessary. However, if no api is defined
* and all Proxy actions are routed to DataProxy#url, the server-side will require the xaction parameter to perform a switch to
* the corresponding code for CRUD action.
* @param {String [Ext.data.Api.CREATE|READ|UPDATE|DESTROY]} action
* @return {Boolean}
*/
isApiAction : function(action) {
return (this.api[action]) ? true : false;
},
/**
* All proxy actions are executed through this method. Automatically fires the "before" + action event
* @param {String} action Name of the action
* @param {Ext.data.Record/Ext.data.Record[]/null} rs Will be null when action is 'load'
* @param {Object} params
* @param {Ext.data.DataReader} reader
* @param {Function} callback
* @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the Proxy object.
* @param {Object} options Any options specified for the action (e.g. see {@link Ext.data.Store#load}.
*/
request : function(action, rs, params, reader, callback, scope, options) {
if (!this.api[action] && !this.load) {
throw new Ext.data.DataProxy.Error('action-undefined', action);
}
params = params || {};
if ((action === Ext.data.Api.actions.read) ? this.fireEvent("beforeload", this, params) : this.fireEvent("beforewrite", this, action, rs, params) !== false) {
this.doRequest.apply(this, arguments);
}
else {
callback.call(scope || this, null, options, false);
}
},
/**
* <b>Deprecated</b> load method using old method signature. See {@doRequest} for preferred method.
* @deprecated
* @param {Object} params
* @param {Object} reader
* @param {Object} callback
* @param {Object} scope
* @param {Object} arg
*/
load : null,
/**
* @cfg {Function} doRequest Abstract method that should be implemented in all subclasses. <b>Note:</b> Should only be used by custom-proxy developers.
* (e.g.: {@link Ext.data.HttpProxy#doRequest HttpProxy.doRequest},
* {@link Ext.data.DirectProxy#doRequest DirectProxy.doRequest}).
*/
doRequest : function(action, rs, params, reader, callback, scope, options) {
// default implementation of doRequest for backwards compatibility with 2.0 proxies.
// If we're executing here, the action is probably "load".
// Call with the pre-3.0 method signature.
this.load(params, reader, callback, scope, options);
},
/**
* @cfg {Function} onRead Abstract method that should be implemented in all subclasses. <b>Note:</b> Should only be used by custom-proxy developers. Callback for read {@link Ext.data.Api#actions action}.
* @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
* @param {Object} o The request transaction object
* @param {Object} res The server response
* @fires loadexception (deprecated)
* @fires exception
* @fires load
* @protected
*/
onRead : Ext.emptyFn,
/**
* @cfg {Function} onWrite Abstract method that should be implemented in all subclasses. <b>Note:</b> Should only be used by custom-proxy developers. Callback for <i>create, update and destroy</i> {@link Ext.data.Api#actions actions}.
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
* @param {Object} trans The request transaction object
* @param {Object} res The server response
* @fires exception
* @fires write
* @protected
*/
onWrite : Ext.emptyFn,
/**
* buildUrl
* Sets the appropriate url based upon the action being executed. If restful is true, and only a single record is being acted upon,
* url will be built Rails-style, as in "/controller/action/32". restful will aply iff the supplied record is an
* instance of Ext.data.Record rather than an Array of them.
* @param {String} action The api action being executed [read|create|update|destroy]
* @param {Ext.data.Record/Ext.data.Record[]} record The record or Array of Records being acted upon.
* @return {String} url
* @private
*/
buildUrl : function(action, record) {
record = record || null;
// conn.url gets nullified after each request. If it's NOT null here, that means the user must have intervened with a call
// to DataProxy#setUrl or DataProxy#setApi and changed it before the request was executed. If that's the case, use conn.url,
// otherwise, build the url from the api or this.url.
var url = (this.conn && this.conn.url) ? this.conn.url : (this.api[action]) ? this.api[action].url : this.url;
if (!url) {
throw new Ext.data.Api.Error('invalid-url', action);
}
// look for urls having "provides" suffix used in some MVC frameworks like Rails/Merb and others. The provides suffice informs
// the server what data-format the client is dealing with and returns data in the same format (eg: application/json, application/xml, etc)
// e.g.: /users.json, /users.xml, etc.
// with restful routes, we need urls like:
// PUT /users/1.json
// DELETE /users/1.json
var provides = null;
var m = url.match(/(.*)(\.json|\.xml|\.html)$/);
if (m) {
provides = m[2]; // eg ".json"
url = m[1]; // eg: "/users"
}
// prettyUrls is deprectated in favor of restful-config
if ((this.restful === true || this.prettyUrls === true) && record instanceof Ext.data.Record && !record.phantom) {
url += '/' + record.id;
}
return (provides === null) ? url : url + provides;
},
/**
* Destroys the proxy by purging any event listeners and cancelling any active requests.
*/
destroy: function(){
this.purgeListeners();
}
});
// Apply the Observable prototype to the DataProxy class so that proxy instances can relay their
// events to the class. Allows for centralized listening of all proxy instances upon the DataProxy class.
Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype);
Ext.util.Observable.call(Ext.data.DataProxy);
/**
* @class Ext.data.DataProxy.Error
* @extends Ext.Error
* DataProxy Error extension.
* constructor
* @param {String} message Message describing the error.
* @param {Record/Record[]} arg
*/
Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {
constructor : function(message, arg) {
this.arg = arg;
Ext.Error.call(this, message);
},
name: 'Ext.data.DataProxy'
});
Ext.apply(Ext.data.DataProxy.Error.prototype, {
lang: {
'action-undefined': "DataProxy attempted to execute an API-action but found an undefined url / function. Please review your Proxy url/api-configuration.",
'api-invalid': 'Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'
}
});

View File

@@ -0,0 +1,228 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.DataReader
* Abstract base class for reading structured data from a data source and converting
* it into an object containing {@link Ext.data.Record} objects and metadata for use
* by an {@link Ext.data.Store}. This class is intended to be extended and should not
* be created directly. For existing implementations, see {@link Ext.data.ArrayReader},
* {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}.
* @constructor Create a new DataReader
* @param {Object} meta Metadata configuration options (implementation-specific).
* @param {Array/Object} recordType
* <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
* will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
* constructor created using {@link Ext.data.Record#create}.</p>
*/
Ext.data.DataReader = function(meta, recordType){
/**
* This DataReader's configured metadata as passed to the constructor.
* @type Mixed
* @property meta
*/
this.meta = meta;
/**
* @cfg {Array/Object} fields
* <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
* will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
* constructor created from {@link Ext.data.Record#create}.</p>
*/
this.recordType = Ext.isArray(recordType) ?
Ext.data.Record.create(recordType) : recordType;
// if recordType defined make sure extraction functions are defined
if (this.recordType){
this.buildExtractors();
}
};
Ext.data.DataReader.prototype = {
/**
* @cfg {String} messageProperty [undefined] Optional name of a property within a server-response that represents a user-feedback message.
*/
/**
* Abstract method created in extension's buildExtractors impl.
*/
getTotal: Ext.emptyFn,
/**
* Abstract method created in extension's buildExtractors impl.
*/
getRoot: Ext.emptyFn,
/**
* Abstract method created in extension's buildExtractors impl.
*/
getMessage: Ext.emptyFn,
/**
* Abstract method created in extension's buildExtractors impl.
*/
getSuccess: Ext.emptyFn,
/**
* Abstract method created in extension's buildExtractors impl.
*/
getId: Ext.emptyFn,
/**
* Abstract method, overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
*/
buildExtractors : Ext.emptyFn,
/**
* Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
*/
extractValues : Ext.emptyFn,
/**
* Used for un-phantoming a record after a successful database insert. Sets the records pk along with new data from server.
* You <b>must</b> return at least the database pk using the idProperty defined in your DataReader configuration. The incoming
* data from server will be merged with the data in the local record.
* In addition, you <b>must</b> return record-data from the server in the same order received.
* Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed.
* @param {Record/Record[]} record The phantom record to be realized.
* @param {Object/Object[]} data The new record data to apply. Must include the primary-key from database defined in idProperty field.
*/
realize: function(rs, data){
if (Ext.isArray(rs)) {
for (var i = rs.length - 1; i >= 0; i--) {
// recurse
if (Ext.isArray(data)) {
this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
}
else {
// weird...rs is an array but data isn't?? recurse but just send in the whole invalid data object.
// the else clause below will detect !this.isData and throw exception.
this.realize(rs.splice(i,1).shift(), data);
}
}
}
else {
// If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
if (Ext.isArray(data) && data.length == 1) {
data = data.shift();
}
if (!this.isData(data)) {
// TODO: Let exception-handler choose to commit or not rather than blindly rs.commit() here.
//rs.commit();
throw new Ext.data.DataReader.Error('realize', rs);
}
rs.phantom = false; // <-- That's what it's all about
rs._phid = rs.id; // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords
rs.id = this.getId(data);
rs.data = data;
rs.commit();
}
},
/**
* Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save.
* If returning data from multiple-records after a batch-update, you <b>must</b> return record-data from the server in
* the same order received. Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be
* suppressed as the record receives fresh new data-hash
* @param {Record/Record[]} rs
* @param {Object/Object[]} data
*/
update : function(rs, data) {
if (Ext.isArray(rs)) {
for (var i=rs.length-1; i >= 0; i--) {
if (Ext.isArray(data)) {
this.update(rs.splice(i,1).shift(), data.splice(i,1).shift());
}
else {
// weird...rs is an array but data isn't?? recurse but just send in the whole data object.
// the else clause below will detect !this.isData and throw exception.
this.update(rs.splice(i,1).shift(), data);
}
}
}
else {
// If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
if (Ext.isArray(data) && data.length == 1) {
data = data.shift();
}
if (this.isData(data)) {
rs.data = Ext.apply(rs.data, data);
}
rs.commit();
}
},
/**
* returns extracted, type-cast rows of data. Iterates to call #extractValues for each row
* @param {Object[]/Object} data-root from server response
* @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record
* @private
*/
extractData : function(root, returnRecords) {
// A bit ugly this, too bad the Record's raw data couldn't be saved in a common property named "raw" or something.
var rawName = (this instanceof Ext.data.JsonReader) ? 'json' : 'node';
var rs = [];
// Had to add Check for XmlReader, #isData returns true if root is an Xml-object. Want to check in order to re-factor
// #extractData into DataReader base, since the implementations are almost identical for JsonReader, XmlReader
if (this.isData(root) && !(this instanceof Ext.data.XmlReader)) {
root = [root];
}
var f = this.recordType.prototype.fields,
fi = f.items,
fl = f.length,
rs = [];
if (returnRecords === true) {
var Record = this.recordType;
for (var i = 0; i < root.length; i++) {
var n = root[i];
var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
record[rawName] = n; // <-- There's implementation of ugly bit, setting the raw record-data.
rs.push(record);
}
}
else {
for (var i = 0; i < root.length; i++) {
var data = this.extractValues(root[i], fi, fl);
data[this.meta.idProperty] = this.getId(root[i]);
rs.push(data);
}
}
return rs;
},
/**
* Returns true if the supplied data-hash <b>looks</b> and quacks like data. Checks to see if it has a key
* corresponding to idProperty defined in your DataReader config containing non-empty pk.
* @param {Object} data
* @return {Boolean}
*/
isData : function(data) {
return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false;
},
// private function a store will createSequence upon
onMetaChange : function(meta){
delete this.ef;
this.meta = meta;
this.recordType = Ext.data.Record.create(meta.fields);
this.buildExtractors();
}
};
/**
* @class Ext.data.DataReader.Error
* @extends Ext.Error
* General error class for Ext.data.DataReader
*/
Ext.data.DataReader.Error = Ext.extend(Ext.Error, {
constructor : function(message, arg) {
this.arg = arg;
Ext.Error.call(this, message);
},
name: 'Ext.data.DataReader'
});
Ext.apply(Ext.data.DataReader.Error.prototype, {
lang : {
'update': "#update received invalid data from server. Please see docs for DataReader#update and review your DataReader configuration.",
'realize': "#realize was called with invalid remote-data. Please see the docs for DataReader#realize and review your DataReader configuration.",
'invalid-response': "#readResponse received an invalid response from the server."
}
});

View File

@@ -0,0 +1,210 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.DataWriter
* <p>Ext.data.DataWriter facilitates create, update, and destroy actions between
* an Ext.data.Store and a server-side framework. A Writer enabled Store will
* automatically manage the Ajax requests to perform CRUD actions on a Store.</p>
* <p>Ext.data.DataWriter is an abstract base class which is intended to be extended
* and should not be created directly. For existing implementations, see
* {@link Ext.data.JsonWriter}.</p>
* <p>Creating a writer is simple:</p>
* <pre><code>
var writer = new Ext.data.JsonWriter({
encode: false // &lt;--- false causes data to be printed to jsonData config-property of Ext.Ajax#reqeust
});
* </code></pre>
* * <p>Same old JsonReader as Ext-2.x:</p>
* <pre><code>
var reader = new Ext.data.JsonReader({idProperty: 'id'}, [{name: 'first'}, {name: 'last'}, {name: 'email'}]);
* </code></pre>
*
* <p>The proxy for a writer enabled store can be configured with a simple <code>url</code>:</p>
* <pre><code>
// Create a standard HttpProxy instance.
var proxy = new Ext.data.HttpProxy({
url: 'app.php/users' // &lt;--- Supports "provides"-type urls, such as '/users.json', '/products.xml' (Hello Rails/Merb)
});
* </code></pre>
* <p>For finer grained control, the proxy may also be configured with an <code>API</code>:</p>
* <pre><code>
// Maximum flexibility with the API-configuration
var proxy = new Ext.data.HttpProxy({
api: {
read : 'app.php/users/read',
create : 'app.php/users/create',
update : 'app.php/users/update',
destroy : { // &lt;--- Supports object-syntax as well
url: 'app.php/users/destroy',
method: "DELETE"
}
}
});
* </code></pre>
* <p>Pulling it all together into a Writer-enabled Store:</p>
* <pre><code>
var store = new Ext.data.Store({
proxy: proxy,
reader: reader,
writer: writer,
autoLoad: true,
autoSave: true // -- Cell-level updates.
});
* </code></pre>
* <p>Initiating write-actions <b>automatically</b>, using the existing Ext2.0 Store/Record API:</p>
* <pre><code>
var rec = store.getAt(0);
rec.set('email', 'foo@bar.com'); // &lt;--- Immediately initiates an UPDATE action through configured proxy.
store.remove(rec); // &lt;---- Immediately initiates a DESTROY action through configured proxy.
* </code></pre>
* <p>For <b>record/batch</b> updates, use the Store-configuration {@link Ext.data.Store#autoSave autoSave:false}</p>
* <pre><code>
var store = new Ext.data.Store({
proxy: proxy,
reader: reader,
writer: writer,
autoLoad: true,
autoSave: false // -- disable cell-updates
});
var urec = store.getAt(0);
urec.set('email', 'foo@bar.com');
var drec = store.getAt(1);
store.remove(drec);
// Push the button!
store.save();
* </code></pre>
* @constructor Create a new DataWriter
* @param {Object} meta Metadata configuration options (implementation-specific)
* @param {Object} recordType Either an Array of field definition objects as specified
* in {@link Ext.data.Record#create}, or an {@link Ext.data.Record} object created
* using {@link Ext.data.Record#create}.
*/
Ext.data.DataWriter = function(config){
Ext.apply(this, config);
};
Ext.data.DataWriter.prototype = {
/**
* @cfg {Boolean} writeAllFields
* <tt>false</tt> by default. Set <tt>true</tt> to have DataWriter return ALL fields of a modified
* record -- not just those that changed.
* <tt>false</tt> to have DataWriter only request modified fields from a record.
*/
writeAllFields : false,
/**
* @cfg {Boolean} listful
* <tt>false</tt> by default. Set <tt>true</tt> to have the DataWriter <b>always</b> write HTTP params as a list,
* even when acting upon a single record.
*/
listful : false, // <-- listful is actually not used internally here in DataWriter. @see Ext.data.Store#execute.
/**
* Compiles a Store recordset into a data-format defined by an extension such as {@link Ext.data.JsonWriter} or {@link Ext.data.XmlWriter} in preparation for a {@link Ext.data.Api#actions server-write action}. The first two params are similar similar in nature to {@link Ext#apply},
* Where the first parameter is the <i>receiver</i> of paramaters and the second, baseParams, <i>the source</i>.
* @param {Object} params The request-params receiver.
* @param {Object} baseParams as defined by {@link Ext.data.Store#baseParams}. The baseParms must be encoded by the extending class, eg: {@link Ext.data.JsonWriter}, {@link Ext.data.XmlWriter}.
* @param {String} action [{@link Ext.data.Api#actions create|update|destroy}]
* @param {Record/Record[]} rs The recordset to write, the subject(s) of the write action.
*/
apply : function(params, baseParams, action, rs) {
var data = [],
renderer = action + 'Record';
// TODO implement @cfg listful here
if (Ext.isArray(rs)) {
Ext.each(rs, function(rec){
data.push(this[renderer](rec));
}, this);
}
else if (rs instanceof Ext.data.Record) {
data = this[renderer](rs);
}
this.render(params, baseParams, data);
},
/**
* abstract method meant to be overridden by all DataWriter extensions. It's the extension's job to apply the "data" to the "params".
* The data-object provided to render is populated with data according to the meta-info defined in the user's DataReader config,
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
* @param {Record[]} rs Store recordset
* @param {Object} params Http params to be sent to server.
* @param {Object} data object populated according to DataReader meta-data.
*/
render : Ext.emptyFn,
/**
* @cfg {Function} updateRecord Abstract method that should be implemented in all subclasses
* (e.g.: {@link Ext.data.JsonWriter#updateRecord JsonWriter.updateRecord}
*/
updateRecord : Ext.emptyFn,
/**
* @cfg {Function} createRecord Abstract method that should be implemented in all subclasses
* (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord})
*/
createRecord : Ext.emptyFn,
/**
* @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses
* (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord})
*/
destroyRecord : Ext.emptyFn,
/**
* Converts a Record to a hash, taking into account the state of the Ext.data.Record along with configuration properties
* related to its rendering, such as {@link #writeAllFields}, {@link Ext.data.Record#phantom phantom}, {@link Ext.data.Record#getChanges getChanges} and
* {@link Ext.data.DataReader#idProperty idProperty}
* @param {Ext.data.Record} rec The Record from which to create a hash.
* @param {Object} config <b>NOT YET IMPLEMENTED</b>. Will implement an exlude/only configuration for fine-control over which fields do/don't get rendered.
* @return {Object}
* @protected
* TODO Implement excludes/only configuration with 2nd param?
*/
toHash : function(rec, config) {
var map = rec.fields.map,
data = {},
raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
m;
Ext.iterate(raw, function(prop, value){
if((m = map[prop])){
data[m.mapping ? m.mapping : m.name] = value;
}
});
// we don't want to write Ext auto-generated id to hash. Careful not to remove it on Models not having auto-increment pk though.
// We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty.
// we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix.
if (rec.phantom) {
if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) {
delete data[this.meta.idProperty];
}
} else {
data[this.meta.idProperty] = rec.id;
}
return data;
},
/**
* Converts a {@link Ext.data.DataWriter#toHash Hashed} {@link Ext.data.Record} to fields-array array suitable
* for encoding to xml via XTemplate, eg:
<code><pre>&lt;tpl for=".">&lt;{name}>{value}&lt;/{name}&lt;/tpl></pre></code>
* eg, <b>non-phantom</b>:
<code><pre>{id: 1, first: 'foo', last: 'bar'} --> [{name: 'id', value: 1}, {name: 'first', value: 'foo'}, {name: 'last', value: 'bar'}]</pre></code>
* {@link Ext.data.Record#phantom Phantom} records will have had their idProperty omitted in {@link #toHash} if determined to be auto-generated.
* Non AUTOINCREMENT pks should have been protected.
* @param {Hash} data Hashed by Ext.data.DataWriter#toHash
* @return {[Object]} Array of attribute-objects.
* @protected
*/
toArray : function(data) {
var fields = [];
Ext.iterate(data, function(k, v) {fields.push({name: k, value: v});},this);
return fields;
}
};

View File

@@ -0,0 +1,173 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.DirectProxy
* @extends Ext.data.DataProxy
*/
Ext.data.DirectProxy = function(config){
Ext.apply(this, config);
if(typeof this.paramOrder == 'string'){
this.paramOrder = this.paramOrder.split(/[\s,|]/);
}
Ext.data.DirectProxy.superclass.constructor.call(this, config);
};
Ext.extend(Ext.data.DirectProxy, Ext.data.DataProxy, {
/**
* @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. A list of params to be executed
* server side. Specify the params in the order in which they must be executed on the server-side
* as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
* comma, or pipe. For example,
* any of the following would be acceptable:<pre><code>
paramOrder: ['param1','param2','param3']
paramOrder: 'param1 param2 param3'
paramOrder: 'param1,param2,param3'
paramOrder: 'param1|param2|param'
</code></pre>
*/
paramOrder: undefined,
/**
* @cfg {Boolean} paramsAsHash
* Send parameters as a collection of named arguments (defaults to <tt>true</tt>). Providing a
* <tt>{@link #paramOrder}</tt> nullifies this configuration.
*/
paramsAsHash: true,
/**
* @cfg {Function} directFn
* Function to call when executing a request. directFn is a simple alternative to defining the api configuration-parameter
* for Store's which will not implement a full CRUD api.
*/
directFn : undefined,
/**
* DirectProxy implementation of {@link Ext.data.DataProxy#doRequest}
* @param {String} action The crud action type (create, read, update, destroy)
* @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
* @param {Object} params An object containing properties which are to be used as HTTP parameters
* for the request to the remote server.
* @param {Ext.data.DataReader} reader The Reader object which converts the data
* object into a block of Ext.data.Records.
* @param {Function} callback
* <div class="sub-desc"><p>A function to be called after the request.
* The <tt>callback</tt> is passed the following arguments:<ul>
* <li><tt>r</tt> : Ext.data.Record[] The block of Ext.data.Records.</li>
* <li><tt>options</tt>: Options object from the action request</li>
* <li><tt>success</tt>: Boolean success indicator</li></ul></p></div>
* @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
* @param {Object} arg An optional argument which is passed to the callback as its second parameter.
* @protected
*/
doRequest : function(action, rs, params, reader, callback, scope, options) {
var args = [],
directFn = this.api[action] || this.directFn;
switch (action) {
case Ext.data.Api.actions.create:
args.push(params.jsonData); // <-- create(Hash)
break;
case Ext.data.Api.actions.read:
// If the method has no parameters, ignore the paramOrder/paramsAsHash.
if(directFn.directCfg.method.len > 0){
if(this.paramOrder){
for(var i = 0, len = this.paramOrder.length; i < len; i++){
args.push(params[this.paramOrder[i]]);
}
}else if(this.paramsAsHash){
args.push(params);
}
}
break;
case Ext.data.Api.actions.update:
args.push(params.jsonData); // <-- update(Hash/Hash[])
break;
case Ext.data.Api.actions.destroy:
args.push(params.jsonData); // <-- destroy(Int/Int[])
break;
}
var trans = {
params : params || {},
request: {
callback : callback,
scope : scope,
arg : options
},
reader: reader
};
args.push(this.createCallback(action, rs, trans), this);
directFn.apply(window, args);
},
// private
createCallback : function(action, rs, trans) {
var me = this;
return function(result, res) {
if (!res.status) {
// @deprecated fire loadexception
if (action === Ext.data.Api.actions.read) {
me.fireEvent("loadexception", me, trans, res, null);
}
me.fireEvent('exception', me, 'remote', action, trans, res, null);
trans.request.callback.call(trans.request.scope, null, trans.request.arg, false);
return;
}
if (action === Ext.data.Api.actions.read) {
me.onRead(action, trans, result, res);
} else {
me.onWrite(action, trans, result, res, rs);
}
};
},
/**
* Callback for read actions
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
* @param {Object} trans The request transaction object
* @param {Object} result Data object picked out of the server-response.
* @param {Object} res The server response
* @protected
*/
onRead : function(action, trans, result, res) {
var records;
try {
records = trans.reader.readRecords(result);
}
catch (ex) {
// @deprecated: Fire old loadexception for backwards-compat.
this.fireEvent("loadexception", this, trans, res, ex);
this.fireEvent('exception', this, 'response', action, trans, res, ex);
trans.request.callback.call(trans.request.scope, null, trans.request.arg, false);
return;
}
this.fireEvent("load", this, res, trans.request.arg);
trans.request.callback.call(trans.request.scope, records, trans.request.arg, true);
},
/**
* Callback for write actions
* @param {String} action [{@link Ext.data.Api#actions create|read|update|destroy}]
* @param {Object} trans The request transaction object
* @param {Object} result Data object picked out of the server-response.
* @param {Object} res The server response
* @param {Ext.data.Record/[Ext.data.Record]} rs The Store resultset associated with the action.
* @protected
*/
onWrite : function(action, trans, result, res, rs) {
var data = trans.reader.extractData(trans.reader.getRoot(result), false);
var success = trans.reader.getSuccess(result);
success = (success !== false);
if (success){
this.fireEvent("write", this, action, data, res, rs, trans.request.arg);
}else{
this.fireEvent('exception', this, 'remote', action, trans, result, rs);
}
trans.request.callback.call(trans.request.scope, data, res, success);
}
});

View File

@@ -0,0 +1,54 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.DirectStore
* @extends Ext.data.Store
* <p>Small helper class to create an {@link Ext.data.Store} configured with an
* {@link Ext.data.DirectProxy} and {@link Ext.data.JsonReader} to make interacting
* with an {@link Ext.Direct} Server-side {@link Ext.direct.Provider Provider} easier.
* To create a different proxy/reader combination create a basic {@link Ext.data.Store}
* configured as needed.</p>
*
* <p><b>*Note:</b> Although they are not listed, this class inherits all of the config options of:</p>
* <div><ul class="mdetail-params">
* <li><b>{@link Ext.data.Store Store}</b></li>
* <div class="sub-desc"><ul class="mdetail-params">
*
* </ul></div>
* <li><b>{@link Ext.data.JsonReader JsonReader}</b></li>
* <div class="sub-desc"><ul class="mdetail-params">
* <li><tt><b>{@link Ext.data.JsonReader#root root}</b></tt></li>
* <li><tt><b>{@link Ext.data.JsonReader#idProperty idProperty}</b></tt></li>
* <li><tt><b>{@link Ext.data.JsonReader#totalProperty totalProperty}</b></tt></li>
* </ul></div>
*
* <li><b>{@link Ext.data.DirectProxy DirectProxy}</b></li>
* <div class="sub-desc"><ul class="mdetail-params">
* <li><tt><b>{@link Ext.data.DirectProxy#directFn directFn}</b></tt></li>
* <li><tt><b>{@link Ext.data.DirectProxy#paramOrder paramOrder}</b></tt></li>
* <li><tt><b>{@link Ext.data.DirectProxy#paramsAsHash paramsAsHash}</b></tt></li>
* </ul></div>
* </ul></div>
*
* @xtype directstore
*
* @constructor
* @param {Object} config
*/
Ext.data.DirectStore = Ext.extend(Ext.data.Store, {
constructor : function(config){
// each transaction upon a singe record will generate a distinct Direct transaction since Direct queues them into one Ajax request.
var c = Ext.apply({}, {
batchTransactions: false
}, config);
Ext.data.DirectStore.superclass.constructor.call(this, Ext.apply(c, {
proxy: Ext.isDefined(c.proxy) ? c.proxy : new Ext.data.DirectProxy(Ext.copyTo({}, c, 'paramOrder,paramsAsHash,directFn,api')),
reader: (!Ext.isDefined(c.reader) && c.fields) ? new Ext.data.JsonReader(Ext.copyTo({}, c, 'totalProperty,root,idProperty'), c.fields) : c.reader
}));
}
});
Ext.reg('directstore', Ext.data.DirectStore);

View File

@@ -0,0 +1,263 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.GroupingStore
* @extends Ext.data.Store
* A specialized store implementation that provides for grouping records by one of the available fields. This
* is usually used in conjunction with an {@link Ext.grid.GroupingView} to provide the data model for
* a grouped GridPanel.
*
* Internally, GroupingStore is simply a normal Store with multi sorting enabled from the start. The grouping field
* and direction are always injected as the first sorter pair. GroupingView picks up on the configured groupField and
* builds grid rows appropriately.
*
* @constructor
* Creates a new GroupingStore.
* @param {Object} config A config object containing the objects needed for the Store to access data,
* and read the data into Records.
* @xtype groupingstore
*/
Ext.data.GroupingStore = Ext.extend(Ext.data.Store, {
//inherit docs
constructor: function(config) {
config = config || {};
//We do some preprocessing here to massage the grouping + sorting options into a single
//multi sort array. If grouping and sorting options are both presented to the constructor,
//the sorters array consists of the grouping sorter object followed by the sorting sorter object
//see Ext.data.Store's sorting functions for details about how multi sorting works
this.hasMultiSort = true;
this.multiSortInfo = this.multiSortInfo || {sorters: []};
var sorters = this.multiSortInfo.sorters,
groupField = config.groupField || this.groupField,
sortInfo = config.sortInfo || this.sortInfo,
groupDir = config.groupDir || this.groupDir;
//add the grouping sorter object first
if(groupField){
sorters.push({
field : groupField,
direction: groupDir
});
}
//add the sorting sorter object if it is present
if (sortInfo) {
sorters.push(sortInfo);
}
Ext.data.GroupingStore.superclass.constructor.call(this, config);
this.addEvents(
/**
* @event groupchange
* Fired whenever a call to store.groupBy successfully changes the grouping on the store
* @param {Ext.data.GroupingStore} store The grouping store
* @param {String} groupField The field that the store is now grouped by
*/
'groupchange'
);
this.applyGroupField();
},
/**
* @cfg {String} groupField
* The field name by which to sort the store's data (defaults to '').
*/
/**
* @cfg {Boolean} remoteGroup
* True if the grouping should apply on the server side, false if it is local only (defaults to false). If the
* grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
* helper, automatically sending the grouping field name as the 'groupBy' param with each XHR call.
*/
remoteGroup : false,
/**
* @cfg {Boolean} groupOnSort
* True to sort the data on the grouping field when a grouping operation occurs, false to sort based on the
* existing sort info (defaults to false).
*/
groupOnSort:false,
/**
* @cfg {String} groupDir
* The direction to sort the groups. Defaults to <tt>'ASC'</tt>.
*/
groupDir : 'ASC',
/**
* Clears any existing grouping and refreshes the data using the default sort.
*/
clearGrouping : function(){
this.groupField = false;
if(this.remoteGroup){
if(this.baseParams){
delete this.baseParams.groupBy;
delete this.baseParams.groupDir;
}
var lo = this.lastOptions;
if(lo && lo.params){
delete lo.params.groupBy;
delete lo.params.groupDir;
}
this.reload();
}else{
this.sort();
this.fireEvent('datachanged', this);
}
},
/**
* Groups the data by the specified field.
* @param {String} field The field name by which to sort the store's data
* @param {Boolean} forceRegroup (optional) True to force the group to be refreshed even if the field passed
* in is the same as the current grouping field, false to skip grouping on the same field (defaults to false)
*/
groupBy : function(field, forceRegroup, direction) {
direction = direction ? (String(direction).toUpperCase() == 'DESC' ? 'DESC' : 'ASC') : this.groupDir;
if (this.groupField == field && this.groupDir == direction && !forceRegroup) {
return; // already grouped by this field
}
//check the contents of the first sorter. If the field matches the CURRENT groupField (before it is set to the new one),
//remove the sorter as it is actually the grouper. The new grouper is added back in by this.sort
var sorters = this.multiSortInfo.sorters;
if (sorters.length > 0 && sorters[0].field == this.groupField) {
sorters.shift();
}
this.groupField = field;
this.groupDir = direction;
this.applyGroupField();
var fireGroupEvent = function() {
this.fireEvent('groupchange', this, this.getGroupState());
};
if (this.groupOnSort) {
this.sort(field, direction);
fireGroupEvent.call(this);
return;
}
if (this.remoteGroup) {
this.on('load', fireGroupEvent, this, {single: true});
this.reload();
} else {
this.sort(sorters);
fireGroupEvent.call(this);
}
},
//GroupingStore always uses multisorting so we intercept calls to sort here to make sure that our grouping sorter object
//is always injected first.
sort : function(fieldName, dir) {
if (this.remoteSort) {
return Ext.data.GroupingStore.superclass.sort.call(this, fieldName, dir);
}
var sorters = [];
//cater for any existing valid arguments to this.sort, massage them into an array of sorter objects
if (Ext.isArray(arguments[0])) {
sorters = arguments[0];
} else if (fieldName == undefined) {
//we preserve the existing sortInfo here because this.sort is called after
//clearGrouping and there may be existing sorting
sorters = this.sortInfo ? [this.sortInfo] : [];
} else {
//TODO: this is lifted straight from Ext.data.Store's singleSort function. It should instead be
//refactored into a common method if possible
var field = this.fields.get(fieldName);
if (!field) return false;
var name = field.name,
sortInfo = this.sortInfo || null,
sortToggle = this.sortToggle ? this.sortToggle[name] : null;
if (!dir) {
if (sortInfo && sortInfo.field == name) { // toggle sort dir
dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC');
} else {
dir = field.sortDir;
}
}
this.sortToggle[name] = dir;
this.sortInfo = {field: name, direction: dir};
sorters = [this.sortInfo];
}
//add the grouping sorter object as the first multisort sorter
if (this.groupField) {
sorters.unshift({direction: this.groupDir, field: this.groupField});
}
return this.multiSort.call(this, sorters, dir);
},
/**
* @private
* Saves the current grouping field and direction to this.baseParams and this.lastOptions.params
* if we're using remote grouping. Does not actually perform any grouping - just stores values
*/
applyGroupField: function(){
if (this.remoteGroup) {
if(!this.baseParams){
this.baseParams = {};
}
Ext.apply(this.baseParams, {
groupBy : this.groupField,
groupDir: this.groupDir
});
var lo = this.lastOptions;
if (lo && lo.params) {
lo.params.groupDir = this.groupDir;
//this is deleted because of a bug reported at http://www.extjs.com/forum/showthread.php?t=82907
delete lo.params.groupBy;
}
}
},
/**
* @private
* TODO: This function is apparently never invoked anywhere in the framework. It has no documentation
* and should be considered for deletion
*/
applyGrouping : function(alwaysFireChange){
if(this.groupField !== false){
this.groupBy(this.groupField, true, this.groupDir);
return true;
}else{
if(alwaysFireChange === true){
this.fireEvent('datachanged', this);
}
return false;
}
},
/**
* @private
* Returns the grouping field that should be used. If groupOnSort is used this will be sortInfo's field,
* otherwise it will be this.groupField
* @return {String} The group field
*/
getGroupState : function(){
return this.groupOnSort && this.groupField !== false ?
(this.sortInfo ? this.sortInfo.field : undefined) : this.groupField;
}
});
Ext.reg('groupingstore', Ext.data.GroupingStore);

View File

@@ -0,0 +1,261 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.HttpProxy
* @extends Ext.data.DataProxy
* <p>An implementation of {@link Ext.data.DataProxy} that processes data requests within the same
* domain of the originating page.</p>
* <p><b>Note</b>: this class cannot be used to retrieve data from a domain other
* than the domain from which the running page was served. For cross-domain requests, use a
* {@link Ext.data.ScriptTagProxy ScriptTagProxy}.</p>
* <p>Be aware that to enable the browser to parse an XML document, the server must set
* the Content-Type header in the HTTP response to "<tt>text/xml</tt>".</p>
* @constructor
* @param {Object} conn
* An {@link Ext.data.Connection} object, or options parameter to {@link Ext.Ajax#request}.
* <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
* Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
* options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
* or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
* used to pass parameters known at instantiation time.</p>
* <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
* the request.</p>
*/
Ext.data.HttpProxy = function(conn){
Ext.data.HttpProxy.superclass.constructor.call(this, conn);
/**
* The Connection object (Or options parameter to {@link Ext.Ajax#request}) which this HttpProxy
* uses to make requests to the server. Properties of this object may be changed dynamically to
* change the way data is requested.
* @property
*/
this.conn = conn;
// nullify the connection url. The url param has been copied to 'this' above. The connection
// url will be set during each execution of doRequest when buildUrl is called. This makes it easier for users to override the
// connection url during beforeaction events (ie: beforeload, beforewrite, etc).
// Url is always re-defined during doRequest.
this.conn.url = null;
this.useAjax = !conn || !conn.events;
// A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]
var actions = Ext.data.Api.actions;
this.activeRequest = {};
for (var verb in actions) {
this.activeRequest[actions[verb]] = undefined;
}
};
Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {
/**
* Return the {@link Ext.data.Connection} object being used by this Proxy.
* @return {Connection} The Connection object. This object may be used to subscribe to events on
* a finer-grained basis than the DataProxy events.
*/
getConnection : function() {
return this.useAjax ? Ext.Ajax : this.conn;
},
/**
* Used for overriding the url used for a single request. Designed to be called during a beforeaction event. Calling setUrl
* will override any urls set via the api configuration parameter. Set the optional parameter makePermanent to set the url for
* all subsequent requests. If not set to makePermanent, the next request will use the same url or api configuration defined
* in the initial proxy configuration.
* @param {String} url
* @param {Boolean} makePermanent (Optional) [false]
*
* (e.g.: beforeload, beforesave, etc).
*/
setUrl : function(url, makePermanent) {
this.conn.url = url;
if (makePermanent === true) {
this.url = url;
this.api = null;
Ext.data.Api.prepare(this);
}
},
/**
* HttpProxy implementation of DataProxy#doRequest
* @param {String} action The crud action type (create, read, update, destroy)
* @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
* @param {Object} params An object containing properties which are to be used as HTTP parameters
* for the request to the remote server.
* @param {Ext.data.DataReader} reader The Reader object which converts the data
* object into a block of Ext.data.Records.
* @param {Function} callback
* <div class="sub-desc"><p>A function to be called after the request.
* The <tt>callback</tt> is passed the following arguments:<ul>
* <li><tt>r</tt> : Ext.data.Record[] The block of Ext.data.Records.</li>
* <li><tt>options</tt>: Options object from the action request</li>
* <li><tt>success</tt>: Boolean success indicator</li></ul></p></div>
* @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
* @param {Object} arg An optional argument which is passed to the callback as its second parameter.
* @protected
*/
doRequest : function(action, rs, params, reader, cb, scope, arg) {
var o = {
method: (this.api[action]) ? this.api[action]['method'] : undefined,
request: {
callback : cb,
scope : scope,
arg : arg
},
reader: reader,
callback : this.createCallback(action, rs),
scope: this
};
// If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.).
// Use std HTTP params otherwise.
if (params.jsonData) {
o.jsonData = params.jsonData;
} else if (params.xmlData) {
o.xmlData = params.xmlData;
} else {
o.params = params || {};
}
// Set the connection url. If this.conn.url is not null here,
// the user must have overridden the url during a beforewrite/beforeload event-handler.
// this.conn.url is nullified after each request.
this.conn.url = this.buildUrl(action, rs);
if(this.useAjax){
Ext.applyIf(o, this.conn);
// If a currently running request is found for this action, abort it.
if (this.activeRequest[action]) {
////
// Disabled aborting activeRequest while implementing REST. activeRequest[action] will have to become an array
// TODO ideas anyone?
//
//Ext.Ajax.abort(this.activeRequest[action]);
}
this.activeRequest[action] = Ext.Ajax.request(o);
}else{
this.conn.request(o);
}
// request is sent, nullify the connection url in preparation for the next request
this.conn.url = null;
},
/**
* Returns a callback function for a request. Note a special case is made for the
* read action vs all the others.
* @param {String} action [create|update|delete|load]
* @param {Ext.data.Record[]} rs The Store-recordset being acted upon
* @private
*/
createCallback : function(action, rs) {
return function(o, success, response) {
this.activeRequest[action] = undefined;
if (!success) {
if (action === Ext.data.Api.actions.read) {
// @deprecated: fire loadexception for backwards compat.
// TODO remove
this.fireEvent('loadexception', this, o, response);
}
this.fireEvent('exception', this, 'response', action, o, response);
o.request.callback.call(o.request.scope, null, o.request.arg, false);
return;
}
if (action === Ext.data.Api.actions.read) {
this.onRead(action, o, response);
} else {
this.onWrite(action, o, response, rs);
}
};
},
/**
* Callback for read action
* @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
* @param {Object} o The request transaction object
* @param {Object} res The server response
* @fires loadexception (deprecated)
* @fires exception
* @fires load
* @protected
*/
onRead : function(action, o, response) {
var result;
try {
result = o.reader.read(response);
}catch(e){
// @deprecated: fire old loadexception for backwards-compat.
// TODO remove
this.fireEvent('loadexception', this, o, response, e);
this.fireEvent('exception', this, 'response', action, o, response, e);
o.request.callback.call(o.request.scope, null, o.request.arg, false);
return;
}
if (result.success === false) {
// @deprecated: fire old loadexception for backwards-compat.
// TODO remove
this.fireEvent('loadexception', this, o, response);
// Get DataReader read-back a response-object to pass along to exception event
var res = o.reader.readResponse(action, response);
this.fireEvent('exception', this, 'remote', action, o, res, null);
}
else {
this.fireEvent('load', this, o, o.request.arg);
}
// TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
// the calls to request.callback(...) in each will have to be made identical.
// NOTE reader.readResponse does not currently return Ext.data.Response
o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
},
/**
* Callback for write actions
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
* @param {Object} trans The request transaction object
* @param {Object} res The server response
* @fires exception
* @fires write
* @protected
*/
onWrite : function(action, o, response, rs) {
var reader = o.reader;
var res;
try {
res = reader.readResponse(action, response);
} catch (e) {
this.fireEvent('exception', this, 'response', action, o, response, e);
o.request.callback.call(o.request.scope, null, o.request.arg, false);
return;
}
if (res.success === true) {
this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);
} else {
this.fireEvent('exception', this, 'remote', action, o, res, rs);
}
// TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
// the calls to request.callback(...) in each will have to be made similar.
// NOTE reader.readResponse does not currently return Ext.data.Response
o.request.callback.call(o.request.scope, res.data, res, res.success);
},
// inherit docs
destroy: function(){
if(!this.useAjax){
this.conn.abort();
}else if(this.activeRequest){
var actions = Ext.data.Api.actions;
for (var verb in actions) {
if(this.activeRequest[actions[verb]]){
Ext.Ajax.abort(this.activeRequest[actions[verb]]);
}
}
}
Ext.data.HttpProxy.superclass.destroy.call(this);
}
});

View File

@@ -0,0 +1,358 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.JsonReader
* @extends Ext.data.DataReader
* <p>Data reader class to create an Array of {@link Ext.data.Record} objects
* from a JSON packet based on mappings in a provided {@link Ext.data.Record}
* constructor.</p>
* <p>Example code:</p>
* <pre><code>
var myReader = new Ext.data.JsonReader({
// metadata configuration options:
{@link #idProperty}: 'id'
{@link #root}: 'rows',
{@link #totalProperty}: 'results',
{@link Ext.data.DataReader#messageProperty}: "msg" // The element within the response that provides a user-feedback message (optional)
// the fields config option will internally create an {@link Ext.data.Record}
// constructor that provides mapping for reading the record data objects
{@link Ext.data.DataReader#fields fields}: [
// map Record&#39;s 'firstname' field to data object&#39;s key of same name
{name: 'name', mapping: 'firstname'},
// map Record&#39;s 'job' field to data object&#39;s 'occupation' key
{name: 'job', mapping: 'occupation'}
]
});
</code></pre>
* <p>This would consume a JSON data object of the form:</p><pre><code>
{
results: 2000, // Reader&#39;s configured {@link #totalProperty}
rows: [ // Reader&#39;s configured {@link #root}
// record data objects:
{ {@link #idProperty id}: 1, firstname: 'Bill', occupation: 'Gardener' },
{ {@link #idProperty id}: 2, firstname: 'Ben' , occupation: 'Horticulturalist' },
...
]
}
</code></pre>
* <p><b><u>Automatic configuration using metaData</u></b></p>
* <p>It is possible to change a JsonReader's metadata at any time by including
* a <b><tt>metaData</tt></b> property in the JSON data object. If the JSON data
* object has a <b><tt>metaData</tt></b> property, a {@link Ext.data.Store Store}
* object using this Reader will reconfigure itself to use the newly provided
* field definition and fire its {@link Ext.data.Store#metachange metachange}
* event. The metachange event handler may interrogate the <b><tt>metaData</tt></b>
* property to perform any configuration required.</p>
* <p>Note that reconfiguring a Store potentially invalidates objects which may
* refer to Fields or Records which no longer exist.</p>
* <p>To use this facility you would create the JsonReader like this:</p><pre><code>
var myReader = new Ext.data.JsonReader();
</code></pre>
* <p>The first data packet from the server would configure the reader by
* containing a <b><tt>metaData</tt></b> property <b>and</b> the data. For
* example, the JSON data object might take the form:</p><pre><code>
{
metaData: {
"{@link #idProperty}": "id",
"{@link #root}": "rows",
"{@link #totalProperty}": "results"
"{@link #successProperty}": "success",
"{@link Ext.data.DataReader#fields fields}": [
{"name": "name"},
{"name": "job", "mapping": "occupation"}
],
// used by store to set its sortInfo
"sortInfo":{
"field": "name",
"direction": "ASC"
},
// {@link Ext.PagingToolbar paging data} (if applicable)
"start": 0,
"limit": 2,
// custom property
"foo": "bar"
},
// Reader&#39;s configured {@link #successProperty}
"success": true,
// Reader&#39;s configured {@link #totalProperty}
"results": 2000,
// Reader&#39;s configured {@link #root}
// (this data simulates 2 results {@link Ext.PagingToolbar per page})
"rows": [ // <b>*Note:</b> this must be an Array
{ "id": 1, "name": "Bill", "occupation": "Gardener" },
{ "id": 2, "name": "Ben", "occupation": "Horticulturalist" }
]
}
* </code></pre>
* <p>The <b><tt>metaData</tt></b> property in the JSON data object should contain:</p>
* <div class="mdetail-params"><ul>
* <li>any of the configuration options for this class</li>
* <li>a <b><tt>{@link Ext.data.Record#fields fields}</tt></b> property which
* the JsonReader will use as an argument to the
* {@link Ext.data.Record#create data Record create method} in order to
* configure the layout of the Records it will produce.</li>
* <li>a <b><tt>{@link Ext.data.Store#sortInfo sortInfo}</tt></b> property
* which the JsonReader will use to set the {@link Ext.data.Store}'s
* {@link Ext.data.Store#sortInfo sortInfo} property</li>
* <li>any custom properties needed</li>
* </ul></div>
*
* @constructor
* Create a new JsonReader
* @param {Object} meta Metadata configuration options.
* @param {Array/Object} recordType
* <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
* will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
* constructor created from {@link Ext.data.Record#create}.</p>
*/
Ext.data.JsonReader = function(meta, recordType){
meta = meta || {};
/**
* @cfg {String} idProperty [id] Name of the property within a row object
* that contains a record identifier value. Defaults to <tt>id</tt>
*/
/**
* @cfg {String} successProperty [success] Name of the property from which to
* retrieve the success attribute. Defaults to <tt>success</tt>. See
* {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
* for additional information.
*/
/**
* @cfg {String} totalProperty [total] Name of the property from which to
* retrieve the total number of records in the dataset. This is only needed
* if the whole dataset is not passed in one go, but is being paged from
* the remote server. Defaults to <tt>total</tt>.
*/
/**
* @cfg {String} root [undefined] <b>Required</b>. The name of the property
* which contains the Array of row objects. Defaults to <tt>undefined</tt>.
* An exception will be thrown if the root property is undefined. The data
* packet value for this property should be an empty array to clear the data
* or show no data.
*/
Ext.applyIf(meta, {
idProperty: 'id',
successProperty: 'success',
totalProperty: 'total'
});
Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
};
Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
/**
* This JsonReader's metadata as passed to the constructor, or as passed in
* the last data packet's <b><tt>metaData</tt></b> property.
* @type Mixed
* @property meta
*/
/**
* This method is only used by a DataProxy which has retrieved data from a remote server.
* @param {Object} response The XHR object which contains the JSON data in its responseText.
* @return {Object} data A data block which is used by an Ext.data.Store object as
* a cache of Ext.data.Records.
*/
read : function(response){
var json = response.responseText;
var o = Ext.decode(json);
if(!o) {
throw {message: 'JsonReader.read: Json object not found'};
}
return this.readRecords(o);
},
/*
* TODO: refactor code between JsonReader#readRecords, #readResponse into 1 method.
* there's ugly duplication going on due to maintaining backwards compat. with 2.0. It's time to do this.
*/
/**
* Decode a JSON response from server.
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
* @param {Object} response The XHR object returned through an Ajax server request.
* @return {Response} A {@link Ext.data.Response Response} object containing the data response, and also status information.
*/
readResponse : function(action, response) {
var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
if(!o) {
throw new Ext.data.JsonReader.Error('response');
}
var root = this.getRoot(o);
if (action === Ext.data.Api.actions.create) {
var def = Ext.isDefined(root);
if (def && Ext.isEmpty(root)) {
throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
}
else if (!def) {
throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
}
}
// instantiate response object
var res = new Ext.data.Response({
action: action,
success: this.getSuccess(o),
data: (root) ? this.extractData(root, false) : [],
message: this.getMessage(o),
raw: o
});
// blow up if no successProperty
if (Ext.isEmpty(res.success)) {
throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
}
return res;
},
/**
* Create a data block containing Ext.data.Records from a JSON object.
* @param {Object} o An object which contains an Array of row objects in the property specified
* in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
* which contains the total size of the dataset.
* @return {Object} data A data block which is used by an Ext.data.Store object as
* a cache of Ext.data.Records.
*/
readRecords : function(o){
/**
* After any data loads, the raw JSON data is available for further custom processing. If no data is
* loaded or there is a load exception this property will be undefined.
* @type Object
*/
this.jsonData = o;
if(o.metaData){
this.onMetaChange(o.metaData);
}
var s = this.meta, Record = this.recordType,
f = Record.prototype.fields, fi = f.items, fl = f.length, v;
var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
if(s.totalProperty){
v = parseInt(this.getTotal(o), 10);
if(!isNaN(v)){
totalRecords = v;
}
}
if(s.successProperty){
v = this.getSuccess(o);
if(v === false || v === 'false'){
success = false;
}
}
// TODO return Ext.data.Response instance instead. @see #readResponse
return {
success : success,
records : this.extractData(root, true), // <-- true to return [Ext.data.Record]
totalRecords : totalRecords
};
},
// private
buildExtractors : function() {
if(this.ef){
return;
}
var s = this.meta, Record = this.recordType,
f = Record.prototype.fields, fi = f.items, fl = f.length;
if(s.totalProperty) {
this.getTotal = this.createAccessor(s.totalProperty);
}
if(s.successProperty) {
this.getSuccess = this.createAccessor(s.successProperty);
}
if (s.messageProperty) {
this.getMessage = this.createAccessor(s.messageProperty);
}
this.getRoot = s.root ? this.createAccessor(s.root) : function(p){return p;};
if (s.id || s.idProperty) {
var g = this.createAccessor(s.id || s.idProperty);
this.getId = function(rec) {
var r = g(rec);
return (r === undefined || r === '') ? null : r;
};
} else {
this.getId = function(){return null;};
}
var ef = [];
for(var i = 0; i < fl; i++){
f = fi[i];
var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
ef.push(this.createAccessor(map));
}
this.ef = ef;
},
/**
* @ignore
* TODO This isn't used anywhere?? Don't we want to use this where possible instead of complex #createAccessor?
*/
simpleAccess : function(obj, subsc) {
return obj[subsc];
},
/**
* @ignore
*/
createAccessor : function(){
var re = /[\[\.]/;
return function(expr) {
if(Ext.isEmpty(expr)){
return Ext.emptyFn;
}
if(Ext.isFunction(expr)){
return expr;
}
var i = String(expr).search(re);
if(i >= 0){
return new Function('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
}
return function(obj){
return obj[expr];
};
};
}(),
/**
* type-casts a single row of raw-data from server
* @param {Object} data
* @param {Array} items
* @param {Integer} len
* @private
*/
extractValues : function(data, items, len) {
var f, values = {};
for(var j = 0; j < len; j++){
f = items[j];
var v = this.ef[j](data);
values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
}
return values;
}
});
/**
* @class Ext.data.JsonReader.Error
* Error class for JsonReader
*/
Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
constructor : function(message, arg) {
this.arg = arg;
Ext.Error.call(this, message);
},
name : 'Ext.data.JsonReader'
});
Ext.apply(Ext.data.JsonReader.Error.prototype, {
lang: {
'response': 'An error occurred while json-decoding your server response',
'successProperty-response': 'Could not locate your "successProperty" in your server response. Please review your JsonReader config to ensure the config-property "successProperty" matches the property in your server-response. See the JsonReader docs.',
'root-undefined-config': 'Your JsonReader was configured without a "root" property. Please review your JsonReader config and make sure to define the root property. See the JsonReader docs.',
'idProperty-undefined' : 'Your JsonReader was configured without an "idProperty" Please review your JsonReader configuration and ensure the "idProperty" is set (e.g.: "id"). See the JsonReader docs.',
'root-empty': 'Data was expected to be returned by the server in the "root" property of the response. Please review your JsonReader configuration to ensure the "root" property matches that returned in the server-response. See JsonReader docs.'
}
});

View File

@@ -0,0 +1,49 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.JsonStore
* @extends Ext.data.Store
* <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
* A JsonStore will be automatically configured with a {@link Ext.data.JsonReader}.</p>
* <p>A store configuration would be something like:<pre><code>
var store = new Ext.data.JsonStore({
// store configs
autoDestroy: true,
url: 'get-images.php',
storeId: 'myStore',
// reader configs
root: 'images',
idProperty: 'name',
fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
});
* </code></pre></p>
* <p>This store is configured to consume a returned object of the form:<pre><code>
{
images: [
{name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
{name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
]
}
* </code></pre>
* An object literal of this form could also be used as the {@link #data} config option.</p>
* <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
* <b>{@link Ext.data.JsonReader JsonReader}</b>.</p>
* @constructor
* @param {Object} config
* @xtype jsonstore
*/
Ext.data.JsonStore = Ext.extend(Ext.data.Store, {
/**
* @cfg {Ext.data.DataReader} reader @hide
*/
constructor: function(config){
Ext.data.JsonStore.superclass.constructor.call(this, Ext.apply(config, {
reader: new Ext.data.JsonReader(config)
}));
}
});
Ext.reg('jsonstore', Ext.data.JsonStore);

View File

@@ -0,0 +1,96 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.JsonWriter
* @extends Ext.data.DataWriter
* DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action.
*/
Ext.data.JsonWriter = Ext.extend(Ext.data.DataWriter, {
/**
* @cfg {Boolean} encode <p><tt>true</tt> to {@link Ext.util.JSON#encode JSON encode} the
* {@link Ext.data.DataWriter#toHash hashed data} into a standard HTTP parameter named after this
* Reader's <code>meta.root</code> property which, by default is imported from the associated Reader. Defaults to <tt>true</tt>.</p>
* <p>If set to <code>false</code>, the hashed data is {@link Ext.util.JSON#encode JSON encoded}, along with
* the associated {@link Ext.data.Store}'s {@link Ext.data.Store#baseParams baseParams}, into the POST body.</p>
* <p>When using {@link Ext.data.DirectProxy}, set this to <tt>false</tt> since Ext.Direct.JsonProvider will perform
* its own json-encoding. In addition, if you're using {@link Ext.data.HttpProxy}, setting to <tt>false</tt>
* will cause HttpProxy to transmit data using the <b>jsonData</b> configuration-params of {@link Ext.Ajax#request}
* instead of <b>params</b>.</p>
* <p>When using a {@link Ext.data.Store#restful} Store, some serverside frameworks are
* tuned to expect data through the jsonData mechanism. In those cases, one will want to set <b>encode: <tt>false</tt></b>, as in
* let the lower-level connection object (eg: Ext.Ajax) do the encoding.</p>
*/
encode : true,
/**
* @cfg {Boolean} encodeDelete False to send only the id to the server on delete, true to encode it in an object
* literal, eg: <pre><code>
{id: 1}
* </code></pre> Defaults to <tt>false</tt>
*/
encodeDelete: false,
constructor : function(config){
Ext.data.JsonWriter.superclass.constructor.call(this, config);
},
/**
* <p>This method should not need to be called by application code, however it may be useful on occasion to
* override it, or augment it with an {@link Function#createInterceptor interceptor} or {@link Function#createSequence sequence}.</p>
* <p>The provided implementation encodes the serialized data representing the Store's modified Records into the Ajax request's
* <code>params</code> according to the <code>{@link #encode}</code> setting.</p>
* @param {Object} Ajax request params object to write into.
* @param {Object} baseParams as defined by {@link Ext.data.Store#baseParams}. The baseParms must be encoded by the extending class, eg: {@link Ext.data.JsonWriter}, {@link Ext.data.XmlWriter}.
* @param {Object/Object[]} data Data object representing the serialized modified records from the Store. May be either a single object,
* or an Array of objects - user implementations must handle both cases.
*/
render : function(params, baseParams, data) {
if (this.encode === true) {
// Encode here now.
Ext.apply(params, baseParams);
params[this.meta.root] = Ext.encode(data);
} else {
// defer encoding for some other layer, probably in {@link Ext.Ajax#request}. Place everything into "jsonData" key.
var jdata = Ext.apply({}, baseParams);
jdata[this.meta.root] = data;
params.jsonData = jdata;
}
},
/**
* Implements abstract Ext.data.DataWriter#createRecord
* @protected
* @param {Ext.data.Record} rec
* @return {Object}
*/
createRecord : function(rec) {
return this.toHash(rec);
},
/**
* Implements abstract Ext.data.DataWriter#updateRecord
* @protected
* @param {Ext.data.Record} rec
* @return {Object}
*/
updateRecord : function(rec) {
return this.toHash(rec);
},
/**
* Implements abstract Ext.data.DataWriter#destroyRecord
* @protected
* @param {Ext.data.Record} rec
* @return {Object}
*/
destroyRecord : function(rec){
if(this.encodeDelete){
var data = {};
data[this.meta.idProperty] = rec.id;
return data;
}else{
return rec.id;
}
}
});

View File

@@ -0,0 +1,69 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.MemoryProxy
* @extends Ext.data.DataProxy
* An implementation of Ext.data.DataProxy that simply passes the data specified in its constructor
* to the Reader when its load method is called.
* @constructor
* @param {Object} data The data object which the Reader uses to construct a block of Ext.data.Records.
*/
Ext.data.MemoryProxy = function(data){
// Must define a dummy api with "read" action to satisfy DataProxy#doRequest and Ext.data.Api#prepare *before* calling super
var api = {};
api[Ext.data.Api.actions.read] = true;
Ext.data.MemoryProxy.superclass.constructor.call(this, {
api: api
});
this.data = data;
};
Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
/**
* @event loadexception
* Fires if an exception occurs in the Proxy during data loading. Note that this event is also relayed
* through {@link Ext.data.Store}, so you can listen for it directly on any Store instance.
* @param {Object} this
* @param {Object} arg The callback's arg object passed to the {@link #load} function
* @param {Object} null This parameter does not apply and will always be null for MemoryProxy
* @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data
*/
/**
* MemoryProxy implementation of DataProxy#doRequest
* @param {String} action
* @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
* @param {Object} params An object containing properties which are to be used as HTTP parameters
* for the request to the remote server.
* @param {Ext.data.DataReader} reader The Reader object which converts the data
* object into a block of Ext.data.Records.
* @param {Function} callback The function into which to pass the block of Ext.data.Records.
* The function must be passed <ul>
* <li>The Record block object</li>
* <li>The "arg" argument from the load function</li>
* <li>A boolean success indicator</li>
* </ul>
* @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
* @param {Object} arg An optional argument which is passed to the callback as its second parameter.
*/
doRequest : function(action, rs, params, reader, callback, scope, arg) {
// No implementation for CRUD in MemoryProxy. Assumes all actions are 'load'
params = params || {};
var result;
try {
result = reader.readRecords(this.data);
}catch(e){
// @deprecated loadexception
this.fireEvent("loadexception", this, null, arg, e);
this.fireEvent('exception', this, 'response', action, arg, null, e);
callback.call(scope, null, arg, false);
return;
}
callback.call(scope, result, arg, true);
}
});

View File

@@ -0,0 +1,415 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.Record
* <p>Instances of this class encapsulate both Record <em>definition</em> information, and Record
* <em>value</em> information for use in {@link Ext.data.Store} objects, or any code which needs
* to access Records cached in an {@link Ext.data.Store} object.</p>
* <p>Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
* Instances are usually only created by {@link Ext.data.Reader} implementations when processing unformatted data
* objects.</p>
* <p>Note that an instance of a Record class may only belong to one {@link Ext.data.Store Store} at a time.
* In order to copy data from one Store to another, use the {@link #copy} method to create an exact
* copy of the Record, and insert the new instance into the other Store.</p>
* <p>When serializing a Record for submission to the server, be aware that it contains many private
* properties, and also a reference to its owning Store which in turn holds references to its Records.
* This means that a whole Record may not be encoded using {@link Ext.util.JSON.encode}. Instead, use the
* <code>{@link #data}</code> and <code>{@link #id}</code> properties.</p>
* <p>Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.</p>
* @constructor
* <p>This constructor should not be used to create Record objects. Instead, use {@link #create} to
* generate a subclass of Ext.data.Record configured with information about its constituent fields.<p>
* <p><b>The generated constructor has the same signature as this constructor.</b></p>
* @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
* fields. If not specified the <code>{@link Ext.data.Field#defaultValue defaultValue}</code>
* for each field will be assigned.
* @param {Object} id (Optional) The id of the Record. The id is used by the
* {@link Ext.data.Store} object which owns the Record to index its collection
* of Records (therefore this id should be unique within each store). If an
* <code>id</code> is not specified a <b><code>{@link #phantom}</code></b>
* Record will be created with an {@link #Record.id automatically generated id}.
*/
Ext.data.Record = function(data, id){
// if no id, call the auto id method
this.id = (id || id === 0) ? id : Ext.data.Record.id(this);
this.data = data || {};
};
/**
* Generate a constructor for a specific Record layout.
* @param {Array} o An Array of <b>{@link Ext.data.Field Field}</b> definition objects.
* The constructor generated by this method may be used to create new Record instances. The data
* object must contain properties named after the {@link Ext.data.Field field}
* <b><tt>{@link Ext.data.Field#name}s</tt></b>. Example usage:<pre><code>
// create a Record constructor from a description of the fields
var TopicRecord = Ext.data.Record.create([ // creates a subclass of Ext.data.Record
{{@link Ext.data.Field#name name}: 'title', {@link Ext.data.Field#mapping mapping}: 'topic_title'},
{name: 'author', mapping: 'username', allowBlank: false},
{name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
{name: 'lastPost', mapping: 'post_time', type: 'date'},
{name: 'lastPoster', mapping: 'user2'},
{name: 'excerpt', mapping: 'post_text', allowBlank: false},
// In the simplest case, if no properties other than <tt>name</tt> are required,
// a field definition may consist of just a String for the field name.
'signature'
]);
// create Record instance
var myNewRecord = new TopicRecord(
{
title: 'Do my job please',
author: 'noobie',
totalPosts: 1,
lastPost: new Date(),
lastPoster: 'Animal',
excerpt: 'No way dude!',
signature: ''
},
id // optionally specify the id of the record otherwise {@link #Record.id one is auto-assigned}
);
myStore.{@link Ext.data.Store#add add}(myNewRecord);
</code></pre>
* @method create
* @return {Function} A constructor which is used to create new Records according
* to the definition. The constructor has the same signature as {@link #Record}.
* @static
*/
Ext.data.Record.create = function(o){
var f = Ext.extend(Ext.data.Record, {});
var p = f.prototype;
p.fields = new Ext.util.MixedCollection(false, function(field){
return field.name;
});
for(var i = 0, len = o.length; i < len; i++){
p.fields.add(new Ext.data.Field(o[i]));
}
f.getField = function(name){
return p.fields.get(name);
};
return f;
};
Ext.data.Record.PREFIX = 'ext-record';
Ext.data.Record.AUTO_ID = 1;
Ext.data.Record.EDIT = 'edit';
Ext.data.Record.REJECT = 'reject';
Ext.data.Record.COMMIT = 'commit';
/**
* Generates a sequential id. This method is typically called when a record is {@link #create}d
* and {@link #Record no id has been specified}. The returned id takes the form:
* <tt>&#123;PREFIX}-&#123;AUTO_ID}</tt>.<div class="mdetail-params"><ul>
* <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.PREFIX</tt>
* (defaults to <tt>'ext-record'</tt>)</p></li>
* <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.AUTO_ID</tt>
* (defaults to <tt>1</tt> initially)</p></li>
* </ul></div>
* @param {Record} rec The record being created. The record does not exist, it's a {@link #phantom}.
* @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
*/
Ext.data.Record.id = function(rec) {
rec.phantom = true;
return [Ext.data.Record.PREFIX, '-', Ext.data.Record.AUTO_ID++].join('');
};
Ext.data.Record.prototype = {
/**
* <p><b>This property is stored in the Record definition's <u>prototype</u></b></p>
* A MixedCollection containing the defined {@link Ext.data.Field Field}s for this Record. Read-only.
* @property fields
* @type Ext.util.MixedCollection
*/
/**
* An object hash representing the data for this Record. Every field name in the Record definition
* is represented by a property of that name in this object. Note that unless you specified a field
* with {@link Ext.data.Field#name name} "id" in the Record definition, this will <b>not</b> contain
* an <tt>id</tt> property.
* @property data
* @type {Object}
*/
/**
* The unique ID of the Record {@link #Record as specified at construction time}.
* @property id
* @type {Object}
*/
/**
* <p><b>Only present if this Record was created by an {@link Ext.data.XmlReader XmlReader}</b>.</p>
* <p>The XML element which was the source of the data for this Record.</p>
* @property node
* @type {XMLElement}
*/
/**
* <p><b>Only present if this Record was created by an {@link Ext.data.ArrayReader ArrayReader} or a {@link Ext.data.JsonReader JsonReader}</b>.</p>
* <p>The Array or object which was the source of the data for this Record.</p>
* @property json
* @type {Array|Object}
*/
/**
* Readonly flag - true if this Record has been modified.
* @type Boolean
*/
dirty : false,
editing : false,
error : null,
/**
* This object contains a key and value storing the original values of all modified
* fields or is null if no fields have been modified.
* @property modified
* @type {Object}
*/
modified : null,
/**
* <tt>true</tt> when the record does not yet exist in a server-side database (see
* {@link #markDirty}). Any record which has a real database pk set as its id property
* is NOT a phantom -- it's real.
* @property phantom
* @type {Boolean}
*/
phantom : false,
// private
join : function(store){
/**
* The {@link Ext.data.Store} to which this Record belongs.
* @property store
* @type {Ext.data.Store}
*/
this.store = store;
},
/**
* Set the {@link Ext.data.Field#name named field} to the specified value. For example:
* <pre><code>
// record has a field named 'firstname'
var Employee = Ext.data.Record.{@link #create}([
{name: 'firstname'},
...
]);
// update the 2nd record in the store:
var rec = myStore.{@link Ext.data.Store#getAt getAt}(1);
// set the value (shows dirty flag):
rec.set('firstname', 'Betty');
// commit the change (removes dirty flag):
rec.{@link #commit}();
// update the record in the store, bypass setting dirty flag,
// and do not store the change in the {@link Ext.data.Store#getModifiedRecords modified records}
rec.{@link #data}['firstname'] = 'Wilma'; // updates record, but not the view
rec.{@link #commit}(); // updates the view
* </code></pre>
* <b>Notes</b>:<div class="mdetail-params"><ul>
* <li>If the store has a writer and <code>autoSave=true</code>, each set()
* will execute an XHR to the server.</li>
* <li>Use <code>{@link #beginEdit}</code> to prevent the store's <code>update</code>
* event firing while using set().</li>
* <li>Use <code>{@link #endEdit}</code> to have the store's <code>update</code>
* event fire.</li>
* </ul></div>
* @param {String} name The {@link Ext.data.Field#name name of the field} to set.
* @param {String/Object/Array} value The value to set the field to.
*/
set : function(name, value){
var encode = Ext.isPrimitive(value) ? String : Ext.encode;
if(encode(this.data[name]) == encode(value)) {
return;
}
this.dirty = true;
if(!this.modified){
this.modified = {};
}
if(this.modified[name] === undefined){
this.modified[name] = this.data[name];
}
this.data[name] = value;
if(!this.editing){
this.afterEdit();
}
},
// private
afterEdit : function(){
if (this.store != undefined && typeof this.store.afterEdit == "function") {
this.store.afterEdit(this);
}
},
// private
afterReject : function(){
if(this.store){
this.store.afterReject(this);
}
},
// private
afterCommit : function(){
if(this.store){
this.store.afterCommit(this);
}
},
/**
* Get the value of the {@link Ext.data.Field#name named field}.
* @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
* @return {Object} The value of the field.
*/
get : function(name){
return this.data[name];
},
/**
* Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
* are relayed to the containing store.
* See also: <code>{@link #endEdit}</code> and <code>{@link #cancelEdit}</code>.
*/
beginEdit : function(){
this.editing = true;
this.modified = this.modified || {};
},
/**
* Cancels all changes made in the current edit operation.
*/
cancelEdit : function(){
this.editing = false;
delete this.modified;
},
/**
* End an edit. If any data was modified, the containing store is notified
* (ie, the store's <code>update</code> event will fire).
*/
endEdit : function(){
this.editing = false;
if(this.dirty){
this.afterEdit();
}
},
/**
* Usually called by the {@link Ext.data.Store} which owns the Record.
* Rejects all changes made to the Record since either creation, or the last commit operation.
* Modified fields are reverted to their original values.
* <p>Developers should subscribe to the {@link Ext.data.Store#update} event
* to have their code notified of reject operations.</p>
* @param {Boolean} silent (optional) True to skip notification of the owning
* store of the change (defaults to false)
*/
reject : function(silent){
var m = this.modified;
for(var n in m){
if(typeof m[n] != "function"){
this.data[n] = m[n];
}
}
this.dirty = false;
delete this.modified;
this.editing = false;
if(silent !== true){
this.afterReject();
}
},
/**
* Usually called by the {@link Ext.data.Store} which owns the Record.
* Commits all changes made to the Record since either creation, or the last commit operation.
* <p>Developers should subscribe to the {@link Ext.data.Store#update} event
* to have their code notified of commit operations.</p>
* @param {Boolean} silent (optional) True to skip notification of the owning
* store of the change (defaults to false)
*/
commit : function(silent){
this.dirty = false;
delete this.modified;
this.editing = false;
if(silent !== true){
this.afterCommit();
}
},
/**
* Gets a hash of only the fields that have been modified since this Record was created or commited.
* @return Object
*/
getChanges : function(){
var m = this.modified, cs = {};
for(var n in m){
if(m.hasOwnProperty(n)){
cs[n] = this.data[n];
}
}
return cs;
},
// private
hasError : function(){
return this.error !== null;
},
// private
clearError : function(){
this.error = null;
},
/**
* Creates a copy (clone) of this Record.
* @param {String} id (optional) A new Record id, defaults to the id
* of the record being copied. See <code>{@link #id}</code>.
* To generate a phantom record with a new id use:<pre><code>
var rec = record.copy(); // clone the record
Ext.data.Record.id(rec); // automatically generate a unique sequential id
* </code></pre>
* @return {Record}
*/
copy : function(newId) {
return new this.constructor(Ext.apply({}, this.data), newId || this.id);
},
/**
* Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
* since the load or last commit.
* @param {String} fieldName {@link Ext.data.Field.{@link Ext.data.Field#name}
* @return {Boolean}
*/
isModified : function(fieldName){
return !!(this.modified && this.modified.hasOwnProperty(fieldName));
},
/**
* By default returns <tt>false</tt> if any {@link Ext.data.Field field} within the
* record configured with <tt>{@link Ext.data.Field#allowBlank} = false</tt> returns
* <tt>true</tt> from an {@link Ext}.{@link Ext#isEmpty isempty} test.
* @return {Boolean}
*/
isValid : function() {
return this.fields.find(function(f) {
return (f.allowBlank === false && Ext.isEmpty(this.data[f.name])) ? true : false;
},this) ? false : true;
},
/**
* <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>. This method
* is used interally when adding <code>{@link #phantom}</code> records to a
* {@link Ext.data.Store#writer writer enabled store}.</p>
* <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
* be returned by {@link Ext.data.Store#getModifiedRecords} where it will
* have a create action composed for it during {@link Ext.data.Store#save store save}
* operations.</p>
*/
markDirty : function(){
this.dirty = true;
if(!this.modified){
this.modified = {};
}
this.fields.each(function(f) {
this.modified[f.name] = this.data[f.name];
},this);
}
};

View File

@@ -0,0 +1,41 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.Request
* A simple Request class used internally to the data package to provide more generalized remote-requests
* to a DataProxy.
* TODO Not yet implemented. Implement in Ext.data.Store#execute
*/
Ext.data.Request = function(params) {
Ext.apply(this, params);
};
Ext.data.Request.prototype = {
/**
* @cfg {String} action
*/
action : undefined,
/**
* @cfg {Ext.data.Record[]/Ext.data.Record} rs The Store recordset associated with the request.
*/
rs : undefined,
/**
* @cfg {Object} params HTTP request params
*/
params: undefined,
/**
* @cfg {Function} callback The function to call when request is complete
*/
callback : Ext.emptyFn,
/**
* @cfg {Object} scope The scope of the callback funtion
*/
scope : undefined,
/**
* @cfg {Ext.data.DataReader} reader The DataReader instance which will parse the received response
*/
reader : undefined
};

View File

@@ -0,0 +1,39 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.Response
* A generic response class to normalize response-handling internally to the framework.
*/
Ext.data.Response = function(params) {
Ext.apply(this, params);
};
Ext.data.Response.prototype = {
/**
* @cfg {String} action {@link Ext.data.Api#actions}
*/
action: undefined,
/**
* @cfg {Boolean} success
*/
success : undefined,
/**
* @cfg {String} message
*/
message : undefined,
/**
* @cfg {Array/Object} data
*/
data: undefined,
/**
* @cfg {Object} raw The raw response returned from server-code
*/
raw: undefined,
/**
* @cfg {Ext.data.Record/Ext.data.Record[]} records related to the Request action
*/
records: undefined
};

View File

@@ -0,0 +1,305 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.ScriptTagProxy
* @extends Ext.data.DataProxy
* An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
* other than the originating domain of the running page.<br>
* <p>
* <b>Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
* of the running page, you must use this class, rather than HttpProxy.</b><br>
* <p>
* The content passed back from a server resource requested by a ScriptTagProxy <b>must</b> be executable JavaScript
* source code because it is used as the source inside a &lt;script> tag.<br>
* <p>
* In order for the browser to process the returned data, the server must wrap the data object
* with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
* Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
* depending on whether the callback name was passed:
* <p>
* <pre><code>
boolean scriptTag = false;
String cb = request.getParameter("callback");
if (cb != null) {
scriptTag = true;
response.setContentType("text/javascript");
} else {
response.setContentType("application/x-json");
}
Writer out = response.getWriter();
if (scriptTag) {
out.write(cb + "(");
}
out.print(dataBlock.toJsonString());
if (scriptTag) {
out.write(");");
}
</code></pre>
* <p>Below is a PHP example to do the same thing:</p><pre><code>
$callback = $_REQUEST['callback'];
// Create the output object.
$output = array('a' => 'Apple', 'b' => 'Banana');
//start output
if ($callback) {
header('Content-Type: text/javascript');
echo $callback . '(' . json_encode($output) . ');';
} else {
header('Content-Type: application/x-json');
echo json_encode($output);
}
</code></pre>
* <p>Below is the ASP.Net code to do the same thing:</p><pre><code>
String jsonString = "{success: true}";
String cb = Request.Params.Get("callback");
String responseString = "";
if (!String.IsNullOrEmpty(cb)) {
responseString = cb + "(" + jsonString + ")";
} else {
responseString = jsonString;
}
Response.Write(responseString);
</code></pre>
*
* @constructor
* @param {Object} config A configuration object.
*/
Ext.data.ScriptTagProxy = function(config){
Ext.apply(this, config);
Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
this.head = document.getElementsByTagName("head")[0];
/**
* @event loadexception
* <b>Deprecated</b> in favor of 'exception' event.
* Fires if an exception occurs in the Proxy during data loading. This event can be fired for one of two reasons:
* <ul><li><b>The load call timed out.</b> This means the load callback did not execute within the time limit
* specified by {@link #timeout}. In this case, this event will be raised and the
* fourth parameter (read error) will be null.</li>
* <li><b>The load succeeded but the reader could not read the response.</b> This means the server returned
* data, but the configured Reader threw an error while reading the data. In this case, this event will be
* raised and the caught error will be passed along as the fourth parameter of this event.</li></ul>
* Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
* on any Store instance.
* @param {Object} this
* @param {Object} options The loading options that were specified (see {@link #load} for details). If the load
* call timed out, this parameter will be null.
* @param {Object} arg The callback's arg object passed to the {@link #load} function
* @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
* If the remote request returns success: false, this parameter will be null.
*/
};
Ext.data.ScriptTagProxy.TRANS_ID = 1000;
Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
/**
* @cfg {String} url The URL from which to request the data object.
*/
/**
* @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
*/
timeout : 30000,
/**
* @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
* the server the name of the callback function set up by the load call to process the returned data object.
* Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
* javascript output which calls this named function passing the data object as its only parameter.
*/
callbackParam : "callback",
/**
* @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
* name to the request.
*/
nocache : true,
/**
* HttpProxy implementation of DataProxy#doRequest
* @param {String} action
* @param {Ext.data.Record/Ext.data.Record[]} rs If action is <tt>read</tt>, rs will be null
* @param {Object} params An object containing properties which are to be used as HTTP parameters
* for the request to the remote server.
* @param {Ext.data.DataReader} reader The Reader object which converts the data
* object into a block of Ext.data.Records.
* @param {Function} callback The function into which to pass the block of Ext.data.Records.
* The function must be passed <ul>
* <li>The Record block object</li>
* <li>The "arg" argument from the load function</li>
* <li>A boolean success indicator</li>
* </ul>
* @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
* @param {Object} arg An optional argument which is passed to the callback as its second parameter.
*/
doRequest : function(action, rs, params, reader, callback, scope, arg) {
var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
var url = this.buildUrl(action, rs);
if (!url) {
throw new Ext.data.Api.Error('invalid-url', url);
}
url = Ext.urlAppend(url, p);
if(this.nocache){
url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
}
var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
var trans = {
id : transId,
action: action,
cb : "stcCallback"+transId,
scriptId : "stcScript"+transId,
params : params,
arg : arg,
url : url,
callback : callback,
scope : scope,
reader : reader
};
window[trans.cb] = this.createCallback(action, rs, trans);
url += String.format("&{0}={1}", this.callbackParam, trans.cb);
if(this.autoAbort !== false){
this.abort();
}
trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
var script = document.createElement("script");
script.setAttribute("src", url);
script.setAttribute("type", "text/javascript");
script.setAttribute("id", trans.scriptId);
this.head.appendChild(script);
this.trans = trans;
},
// @private createCallback
createCallback : function(action, rs, trans) {
var self = this;
return function(res) {
self.trans = false;
self.destroyTrans(trans, true);
if (action === Ext.data.Api.actions.read) {
self.onRead.call(self, action, trans, res);
} else {
self.onWrite.call(self, action, trans, res, rs);
}
};
},
/**
* Callback for read actions
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
* @param {Object} trans The request transaction object
* @param {Object} res The server response
* @protected
*/
onRead : function(action, trans, res) {
var result;
try {
result = trans.reader.readRecords(res);
}catch(e){
// @deprecated: fire loadexception
this.fireEvent("loadexception", this, trans, res, e);
this.fireEvent('exception', this, 'response', action, trans, res, e);
trans.callback.call(trans.scope||window, null, trans.arg, false);
return;
}
if (result.success === false) {
// @deprecated: fire old loadexception for backwards-compat.
this.fireEvent('loadexception', this, trans, res);
this.fireEvent('exception', this, 'remote', action, trans, res, null);
} else {
this.fireEvent("load", this, res, trans.arg);
}
trans.callback.call(trans.scope||window, result, trans.arg, result.success);
},
/**
* Callback for write actions
* @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
* @param {Object} trans The request transaction object
* @param {Object} res The server response
* @protected
*/
onWrite : function(action, trans, response, rs) {
var reader = trans.reader;
try {
// though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.
var res = reader.readResponse(action, response);
} catch (e) {
this.fireEvent('exception', this, 'response', action, trans, res, e);
trans.callback.call(trans.scope||window, null, res, false);
return;
}
if(!res.success === true){
this.fireEvent('exception', this, 'remote', action, trans, res, rs);
trans.callback.call(trans.scope||window, null, res, false);
return;
}
this.fireEvent("write", this, action, res.data, res, rs, trans.arg );
trans.callback.call(trans.scope||window, res.data, res, true);
},
// private
isLoading : function(){
return this.trans ? true : false;
},
/**
* Abort the current server request.
*/
abort : function(){
if(this.isLoading()){
this.destroyTrans(this.trans);
}
},
// private
destroyTrans : function(trans, isLoaded){
this.head.removeChild(document.getElementById(trans.scriptId));
clearTimeout(trans.timeoutId);
if(isLoaded){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
}else{
// if hasn't been loaded, wait for load to remove it to prevent script error
window[trans.cb] = function(){
window[trans.cb] = undefined;
try{
delete window[trans.cb];
}catch(e){}
};
}
},
// private
handleFailure : function(trans){
this.trans = false;
this.destroyTrans(trans, false);
if (trans.action === Ext.data.Api.actions.read) {
// @deprecated firing loadexception
this.fireEvent("loadexception", this, null, trans.arg);
}
this.fireEvent('exception', this, 'response', trans.action, {
response: null,
options: trans.arg
});
trans.callback.call(trans.scope||window, null, trans.arg, false);
},
// inherit docs
destroy: function(){
this.abort();
Ext.data.ScriptTagProxy.superclass.destroy.call(this);
}
});

View File

@@ -0,0 +1,91 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.SortTypes
* @singleton
* Defines the default sorting (casting?) comparison functions used when sorting data.
*/
Ext.data.SortTypes = {
/**
* Default sort that does nothing
* @param {Mixed} s The value being converted
* @return {Mixed} The comparison value
*/
none : function(s){
return s;
},
/**
* The regular expression used to strip tags
* @type {RegExp}
* @property
*/
stripTagsRE : /<\/?[^>]+>/gi,
/**
* Strips all HTML tags to sort on text only
* @param {Mixed} s The value being converted
* @return {String} The comparison value
*/
asText : function(s){
return String(s).replace(this.stripTagsRE, "");
},
/**
* Strips all HTML tags to sort on text only - Case insensitive
* @param {Mixed} s The value being converted
* @return {String} The comparison value
*/
asUCText : function(s){
return String(s).toUpperCase().replace(this.stripTagsRE, "");
},
/**
* Case insensitive string
* @param {Mixed} s The value being converted
* @return {String} The comparison value
*/
asUCString : function(s) {
return String(s).toUpperCase();
},
/**
* Date sorting
* @param {Mixed} s The value being converted
* @return {Number} The comparison value
*/
asDate : function(s) {
if(!s){
return 0;
}
if(Ext.isDate(s)){
return s.getTime();
}
return Date.parse(String(s));
},
/**
* Float sorting
* @param {Mixed} s The value being converted
* @return {Float} The comparison value
*/
asFloat : function(s) {
var val = parseFloat(String(s).replace(/,/g, ""));
return isNaN(val) ? 0 : val;
},
/**
* Integer sorting
* @param {Mixed} s The value being converted
* @return {Number} The comparison value
*/
asInt : function(s) {
var val = parseInt(String(s).replace(/,/g, ""), 10);
return isNaN(val) ? 0 : val;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,72 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.StoreMgr
* @extends Ext.util.MixedCollection
* The default global group of stores.
* @singleton
*/
Ext.StoreMgr = Ext.apply(new Ext.util.MixedCollection(), {
/**
* @cfg {Object} listeners @hide
*/
/**
* Registers one or more Stores with the StoreMgr. You do not normally need to register stores
* manually. Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
* @param {Ext.data.Store} store1 A Store instance
* @param {Ext.data.Store} store2 (optional)
* @param {Ext.data.Store} etc... (optional)
*/
register : function(){
for(var i = 0, s; (s = arguments[i]); i++){
this.add(s);
}
},
/**
* Unregisters one or more Stores with the StoreMgr
* @param {String/Object} id1 The id of the Store, or a Store instance
* @param {String/Object} id2 (optional)
* @param {String/Object} etc... (optional)
*/
unregister : function(){
for(var i = 0, s; (s = arguments[i]); i++){
this.remove(this.lookup(s));
}
},
/**
* Gets a registered Store by id
* @param {String/Object} id The id of the Store, or a Store instance
* @return {Ext.data.Store}
*/
lookup : function(id){
if(Ext.isArray(id)){
var fields = ['field1'], expand = !Ext.isArray(id[0]);
if(!expand){
for(var i = 2, len = id[0].length; i <= len; ++i){
fields.push('field' + i);
}
}
return new Ext.data.ArrayStore({
fields: fields,
data: id,
expandData: expand,
autoDestroy: true,
autoCreated: true
});
}
return Ext.isObject(id) ? (id.events ? id : Ext.create(id, 'store')) : this.get(id);
},
// getKey implementation for MixedCollection
getKey : function(o){
return o.storeId;
}
});

View File

@@ -0,0 +1,833 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.Tree
* @extends Ext.util.Observable
* Represents a tree data structure and bubbles all the events for its nodes. The nodes
* in the tree have most standard DOM functionality.
* @constructor
* @param {Node} root (optional) The root node
*/
Ext.data.Tree = Ext.extend(Ext.util.Observable, {
constructor: function(root){
this.nodeHash = {};
/**
* The root node for this tree
* @type Node
*/
this.root = null;
if(root){
this.setRootNode(root);
}
this.addEvents(
/**
* @event append
* Fires when a new child node is appended to a node in this tree.
* @param {Tree} tree The owner tree
* @param {Node} parent The parent node
* @param {Node} node The newly appended node
* @param {Number} index The index of the newly appended node
*/
"append",
/**
* @event remove
* Fires when a child node is removed from a node in this tree.
* @param {Tree} tree The owner tree
* @param {Node} parent The parent node
* @param {Node} node The child node removed
*/
"remove",
/**
* @event move
* Fires when a node is moved to a new location in the tree
* @param {Tree} tree The owner tree
* @param {Node} node The node moved
* @param {Node} oldParent The old parent of this node
* @param {Node} newParent The new parent of this node
* @param {Number} index The index it was moved to
*/
"move",
/**
* @event insert
* Fires when a new child node is inserted in a node in this tree.
* @param {Tree} tree The owner tree
* @param {Node} parent The parent node
* @param {Node} node The child node inserted
* @param {Node} refNode The child node the node was inserted before
*/
"insert",
/**
* @event beforeappend
* Fires before a new child is appended to a node in this tree, return false to cancel the append.
* @param {Tree} tree The owner tree
* @param {Node} parent The parent node
* @param {Node} node The child node to be appended
*/
"beforeappend",
/**
* @event beforeremove
* Fires before a child is removed from a node in this tree, return false to cancel the remove.
* @param {Tree} tree The owner tree
* @param {Node} parent The parent node
* @param {Node} node The child node to be removed
*/
"beforeremove",
/**
* @event beforemove
* Fires before a node is moved to a new location in the tree. Return false to cancel the move.
* @param {Tree} tree The owner tree
* @param {Node} node The node being moved
* @param {Node} oldParent The parent of the node
* @param {Node} newParent The new parent the node is moving to
* @param {Number} index The index it is being moved to
*/
"beforemove",
/**
* @event beforeinsert
* Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
* @param {Tree} tree The owner tree
* @param {Node} parent The parent node
* @param {Node} node The child node to be inserted
* @param {Node} refNode The child node the node is being inserted before
*/
"beforeinsert"
);
Ext.data.Tree.superclass.constructor.call(this);
},
/**
* @cfg {String} pathSeparator
* The token used to separate paths in node ids (defaults to '/').
*/
pathSeparator: "/",
// private
proxyNodeEvent : function(){
return this.fireEvent.apply(this, arguments);
},
/**
* Returns the root node for this tree.
* @return {Node}
*/
getRootNode : function(){
return this.root;
},
/**
* Sets the root node for this tree.
* @param {Node} node
* @return {Node}
*/
setRootNode : function(node){
this.root = node;
node.ownerTree = this;
node.isRoot = true;
this.registerNode(node);
return node;
},
/**
* Gets a node in this tree by its id.
* @param {String} id
* @return {Node}
*/
getNodeById : function(id){
return this.nodeHash[id];
},
// private
registerNode : function(node){
this.nodeHash[node.id] = node;
},
// private
unregisterNode : function(node){
delete this.nodeHash[node.id];
},
toString : function(){
return "[Tree"+(this.id?" "+this.id:"")+"]";
}
});
/**
* @class Ext.data.Node
* @extends Ext.util.Observable
* @cfg {Boolean} leaf true if this node is a leaf and does not have children
* @cfg {String} id The id for this node. If one is not specified, one is generated.
* @constructor
* @param {Object} attributes The attributes/config for the node
*/
Ext.data.Node = Ext.extend(Ext.util.Observable, {
constructor: function(attributes){
/**
* The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
* @type {Object}
*/
this.attributes = attributes || {};
this.leaf = this.attributes.leaf;
/**
* The node id. @type String
*/
this.id = this.attributes.id;
if(!this.id){
this.id = Ext.id(null, "xnode-");
this.attributes.id = this.id;
}
/**
* All child nodes of this node. @type Array
*/
this.childNodes = [];
/**
* The parent node for this node. @type Node
*/
this.parentNode = null;
/**
* The first direct child node of this node, or null if this node has no child nodes. @type Node
*/
this.firstChild = null;
/**
* The last direct child node of this node, or null if this node has no child nodes. @type Node
*/
this.lastChild = null;
/**
* The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
*/
this.previousSibling = null;
/**
* The node immediately following this node in the tree, or null if there is no sibling node. @type Node
*/
this.nextSibling = null;
this.addEvents({
/**
* @event append
* Fires when a new child node is appended
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} node The newly appended node
* @param {Number} index The index of the newly appended node
*/
"append" : true,
/**
* @event remove
* Fires when a child node is removed
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} node The removed node
*/
"remove" : true,
/**
* @event move
* Fires when this node is moved to a new location in the tree
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} oldParent The old parent of this node
* @param {Node} newParent The new parent of this node
* @param {Number} index The index it was moved to
*/
"move" : true,
/**
* @event insert
* Fires when a new child node is inserted.
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} node The child node inserted
* @param {Node} refNode The child node the node was inserted before
*/
"insert" : true,
/**
* @event beforeappend
* Fires before a new child is appended, return false to cancel the append.
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} node The child node to be appended
*/
"beforeappend" : true,
/**
* @event beforeremove
* Fires before a child is removed, return false to cancel the remove.
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} node The child node to be removed
*/
"beforeremove" : true,
/**
* @event beforemove
* Fires before this node is moved to a new location in the tree. Return false to cancel the move.
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} oldParent The parent of this node
* @param {Node} newParent The new parent this node is moving to
* @param {Number} index The index it is being moved to
*/
"beforemove" : true,
/**
* @event beforeinsert
* Fires before a new child is inserted, return false to cancel the insert.
* @param {Tree} tree The owner tree
* @param {Node} this This node
* @param {Node} node The child node to be inserted
* @param {Node} refNode The child node the node is being inserted before
*/
"beforeinsert" : true
});
this.listeners = this.attributes.listeners;
Ext.data.Node.superclass.constructor.call(this);
},
// private
fireEvent : function(evtName){
// first do standard event for this node
if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
return false;
}
// then bubble it up to the tree if the event wasn't cancelled
var ot = this.getOwnerTree();
if(ot){
if(ot.proxyNodeEvent.apply(ot, arguments) === false){
return false;
}
}
return true;
},
/**
* Returns true if this node is a leaf
* @return {Boolean}
*/
isLeaf : function(){
return this.leaf === true;
},
// private
setFirstChild : function(node){
this.firstChild = node;
},
//private
setLastChild : function(node){
this.lastChild = node;
},
/**
* Returns true if this node is the last child of its parent
* @return {Boolean}
*/
isLast : function(){
return (!this.parentNode ? true : this.parentNode.lastChild == this);
},
/**
* Returns true if this node is the first child of its parent
* @return {Boolean}
*/
isFirst : function(){
return (!this.parentNode ? true : this.parentNode.firstChild == this);
},
/**
* Returns true if this node has one or more child nodes, else false.
* @return {Boolean}
*/
hasChildNodes : function(){
return !this.isLeaf() && this.childNodes.length > 0;
},
/**
* Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
* node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
* @return {Boolean}
*/
isExpandable : function(){
return this.attributes.expandable || this.hasChildNodes();
},
/**
* Insert node(s) as the last child node of this node.
* @param {Node/Array} node The node or Array of nodes to append
* @return {Node} The appended node if single append, or null if an array was passed
*/
appendChild : function(node){
var multi = false;
if(Ext.isArray(node)){
multi = node;
}else if(arguments.length > 1){
multi = arguments;
}
// if passed an array or multiple args do them one by one
if(multi){
for(var i = 0, len = multi.length; i < len; i++) {
this.appendChild(multi[i]);
}
}else{
if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
return false;
}
var index = this.childNodes.length;
var oldParent = node.parentNode;
// it's a move, make sure we move it cleanly
if(oldParent){
if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
return false;
}
oldParent.removeChild(node);
}
index = this.childNodes.length;
if(index === 0){
this.setFirstChild(node);
}
this.childNodes.push(node);
node.parentNode = this;
var ps = this.childNodes[index-1];
if(ps){
node.previousSibling = ps;
ps.nextSibling = node;
}else{
node.previousSibling = null;
}
node.nextSibling = null;
this.setLastChild(node);
node.setOwnerTree(this.getOwnerTree());
this.fireEvent("append", this.ownerTree, this, node, index);
if(oldParent){
node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
}
return node;
}
},
/**
* Removes a child node from this node.
* @param {Node} node The node to remove
* @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
* @return {Node} The removed node
*/
removeChild : function(node, destroy){
var index = this.childNodes.indexOf(node);
if(index == -1){
return false;
}
if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
return false;
}
// remove it from childNodes collection
this.childNodes.splice(index, 1);
// update siblings
if(node.previousSibling){
node.previousSibling.nextSibling = node.nextSibling;
}
if(node.nextSibling){
node.nextSibling.previousSibling = node.previousSibling;
}
// update child refs
if(this.firstChild == node){
this.setFirstChild(node.nextSibling);
}
if(this.lastChild == node){
this.setLastChild(node.previousSibling);
}
this.fireEvent("remove", this.ownerTree, this, node);
if(destroy){
node.destroy(true);
}else{
node.clear();
}
return node;
},
// private
clear : function(destroy){
// clear any references from the node
this.setOwnerTree(null, destroy);
this.parentNode = this.previousSibling = this.nextSibling = null;
if(destroy){
this.firstChild = this.lastChild = null;
}
},
/**
* Destroys the node.
*/
destroy : function(/* private */ silent){
/*
* Silent is to be used in a number of cases
* 1) When setRootNode is called.
* 2) When destroy on the tree is called
* 3) For destroying child nodes on a node
*/
if(silent === true){
this.purgeListeners();
this.clear(true);
Ext.each(this.childNodes, function(n){
n.destroy(true);
});
this.childNodes = null;
}else{
this.remove(true);
}
},
/**
* Inserts the first node before the second node in this nodes childNodes collection.
* @param {Node} node The node to insert
* @param {Node} refNode The node to insert before (if null the node is appended)
* @return {Node} The inserted node
*/
insertBefore : function(node, refNode){
if(!refNode){ // like standard Dom, refNode can be null for append
return this.appendChild(node);
}
// nothing to do
if(node == refNode){
return false;
}
if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
return false;
}
var index = this.childNodes.indexOf(refNode);
var oldParent = node.parentNode;
var refIndex = index;
// when moving internally, indexes will change after remove
if(oldParent == this && this.childNodes.indexOf(node) < index){
refIndex--;
}
// it's a move, make sure we move it cleanly
if(oldParent){
if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
return false;
}
oldParent.removeChild(node);
}
if(refIndex === 0){
this.setFirstChild(node);
}
this.childNodes.splice(refIndex, 0, node);
node.parentNode = this;
var ps = this.childNodes[refIndex-1];
if(ps){
node.previousSibling = ps;
ps.nextSibling = node;
}else{
node.previousSibling = null;
}
node.nextSibling = refNode;
refNode.previousSibling = node;
node.setOwnerTree(this.getOwnerTree());
this.fireEvent("insert", this.ownerTree, this, node, refNode);
if(oldParent){
node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
}
return node;
},
/**
* Removes this node from its parent
* @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
* @return {Node} this
*/
remove : function(destroy){
if (this.parentNode) {
this.parentNode.removeChild(this, destroy);
}
return this;
},
/**
* Removes all child nodes from this node.
* @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
* @return {Node} this
*/
removeAll : function(destroy){
var cn = this.childNodes,
n;
while((n = cn[0])){
this.removeChild(n, destroy);
}
return this;
},
/**
* Returns the child node at the specified index.
* @param {Number} index
* @return {Node}
*/
item : function(index){
return this.childNodes[index];
},
/**
* Replaces one child node in this node with another.
* @param {Node} newChild The replacement node
* @param {Node} oldChild The node to replace
* @return {Node} The replaced node
*/
replaceChild : function(newChild, oldChild){
var s = oldChild ? oldChild.nextSibling : null;
this.removeChild(oldChild);
this.insertBefore(newChild, s);
return oldChild;
},
/**
* Returns the index of a child node
* @param {Node} node
* @return {Number} The index of the node or -1 if it was not found
*/
indexOf : function(child){
return this.childNodes.indexOf(child);
},
/**
* Returns the tree this node is in.
* @return {Tree}
*/
getOwnerTree : function(){
// if it doesn't have one, look for one
if(!this.ownerTree){
var p = this;
while(p){
if(p.ownerTree){
this.ownerTree = p.ownerTree;
break;
}
p = p.parentNode;
}
}
return this.ownerTree;
},
/**
* Returns depth of this node (the root node has a depth of 0)
* @return {Number}
*/
getDepth : function(){
var depth = 0;
var p = this;
while(p.parentNode){
++depth;
p = p.parentNode;
}
return depth;
},
// private
setOwnerTree : function(tree, destroy){
// if it is a move, we need to update everyone
if(tree != this.ownerTree){
if(this.ownerTree){
this.ownerTree.unregisterNode(this);
}
this.ownerTree = tree;
// If we're destroying, we don't need to recurse since it will be called on each child node
if(destroy !== true){
Ext.each(this.childNodes, function(n){
n.setOwnerTree(tree);
});
}
if(tree){
tree.registerNode(this);
}
}
},
/**
* Changes the id of this node.
* @param {String} id The new id for the node.
*/
setId: function(id){
if(id !== this.id){
var t = this.ownerTree;
if(t){
t.unregisterNode(this);
}
this.id = this.attributes.id = id;
if(t){
t.registerNode(this);
}
this.onIdChange(id);
}
},
// private
onIdChange: Ext.emptyFn,
/**
* Returns the path for this node. The path can be used to expand or select this node programmatically.
* @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
* @return {String} The path
*/
getPath : function(attr){
attr = attr || "id";
var p = this.parentNode;
var b = [this.attributes[attr]];
while(p){
b.unshift(p.attributes[attr]);
p = p.parentNode;
}
var sep = this.getOwnerTree().pathSeparator;
return sep + b.join(sep);
},
/**
* Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
* will be the args provided or the current node. If the function returns false at any point,
* the bubble is stopped.
* @param {Function} fn The function to call
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
* @param {Array} args (optional) The args to call the function with (default to passing the current Node)
*/
bubble : function(fn, scope, args){
var p = this;
while(p){
if(fn.apply(scope || p, args || [p]) === false){
break;
}
p = p.parentNode;
}
},
/**
* Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
* will be the args provided or the current node. If the function returns false at any point,
* the cascade is stopped on that branch.
* @param {Function} fn The function to call
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
* @param {Array} args (optional) The args to call the function with (default to passing the current Node)
*/
cascade : function(fn, scope, args){
if(fn.apply(scope || this, args || [this]) !== false){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
cs[i].cascade(fn, scope, args);
}
}
},
/**
* Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
* will be the args provided or the current node. If the function returns false at any point,
* the iteration stops.
* @param {Function} fn The function to call
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
* @param {Array} args (optional) The args to call the function with (default to passing the current Node)
*/
eachChild : function(fn, scope, args){
var cs = this.childNodes;
for(var i = 0, len = cs.length; i < len; i++) {
if(fn.apply(scope || cs[i], args || [cs[i]]) === false){
break;
}
}
},
/**
* Finds the first child that has the attribute with the specified value.
* @param {String} attribute The attribute name
* @param {Mixed} value The value to search for
* @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
* @return {Node} The found child or null if none was found
*/
findChild : function(attribute, value, deep){
return this.findChildBy(function(){
return this.attributes[attribute] == value;
}, null, deep);
},
/**
* Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
* @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
* @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
* @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
* @return {Node} The found child or null if none was found
*/
findChildBy : function(fn, scope, deep){
var cs = this.childNodes,
len = cs.length,
i = 0,
n,
res;
for(; i < len; i++){
n = cs[i];
if(fn.call(scope || n, n) === true){
return n;
}else if (deep){
res = n.findChildBy(fn, scope, deep);
if(res != null){
return res;
}
}
}
return null;
},
/**
* Sorts this nodes children using the supplied sort function.
* @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
* @param {Object} scope (optional)The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
*/
sort : function(fn, scope){
var cs = this.childNodes;
var len = cs.length;
if(len > 0){
var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
cs.sort(sortFn);
for(var i = 0; i < len; i++){
var n = cs[i];
n.previousSibling = cs[i-1];
n.nextSibling = cs[i+1];
if(i === 0){
this.setFirstChild(n);
}
if(i == len-1){
this.setLastChild(n);
}
}
}
},
/**
* Returns true if this node is an ancestor (at any point) of the passed node.
* @param {Node} node
* @return {Boolean}
*/
contains : function(node){
return node.isAncestor(this);
},
/**
* Returns true if the passed node is an ancestor (at any point) of this node.
* @param {Node} node
* @return {Boolean}
*/
isAncestor : function(node){
var p = this.parentNode;
while(p){
if(p == node){
return true;
}
p = p.parentNode;
}
return false;
},
toString : function(){
return "[Node"+(this.id?" "+this.id:"")+"]";
}
});

View File

@@ -0,0 +1,187 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.Types
* <p>This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
* <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
* test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
* of this class.</p>
* <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
* each type definition must contain three properties:</p>
* <div class="mdetail-params"><ul>
* <li><code>convert</code> : <i>Function</i><div class="sub-desc">A function to convert raw data values from a data block into the data
* to be stored in the Field. The function is passed the collowing parameters:
* <div class="mdetail-params"><ul>
* <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
* the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
* <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
* Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
* ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
* </ul></div></div></li>
* <li><code>sortType</code> : <i>Function</i> <div class="sub-desc">A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.</div></li>
* <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
* </ul></div>
* <p>For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
* which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
*<pre><code>
// Add a new Field data type which stores a VELatLong object in the Record.
Ext.data.Types.VELATLONG = {
convert: function(v, data) {
return new VELatLong(data.lat, data.long);
},
sortType: function(v) {
return v.Latitude; // When sorting, order by latitude
},
type: 'VELatLong'
};
</code></pre>
* <p>Then, when declaring a Record, use <pre><code>
var types = Ext.data.Types; // allow shorthand type access
UnitRecord = Ext.data.Record.create([
{ name: 'unitName', mapping: 'UnitName' },
{ name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
{ name: 'latitude', mapping: 'lat', type: types.FLOAT },
{ name: 'latitude', mapping: 'lat', type: types.FLOAT },
{ name: 'position', type: types.VELATLONG }
]);
</code></pre>
* @singleton
*/
Ext.data.Types = new function(){
var st = Ext.data.SortTypes;
Ext.apply(this, {
/**
* @type Regexp
* @property stripRe
* A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
* This should be overridden for localization.
*/
stripRe: /[\$,%]/g,
/**
* @type Object.
* @property AUTO
* This data type means that no conversion is applied to the raw data before it is placed into a Record.
*/
AUTO: {
convert: function(v){ return v; },
sortType: st.none,
type: 'auto'
},
/**
* @type Object.
* @property STRING
* This data type means that the raw data is converted into a String before it is placed into a Record.
*/
STRING: {
convert: function(v){ return (v === undefined || v === null) ? '' : String(v); },
sortType: st.asUCString,
type: 'string'
},
/**
* @type Object.
* @property INT
* This data type means that the raw data is converted into an integer before it is placed into a Record.
* <p>The synonym <code>INTEGER</code> is equivalent.</p>
*/
INT: {
convert: function(v){
return v !== undefined && v !== null && v !== '' ?
parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
},
sortType: st.none,
type: 'int'
},
/**
* @type Object.
* @property FLOAT
* This data type means that the raw data is converted into a number before it is placed into a Record.
* <p>The synonym <code>NUMBER</code> is equivalent.</p>
*/
FLOAT: {
convert: function(v){
return v !== undefined && v !== null && v !== '' ?
parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
},
sortType: st.none,
type: 'float'
},
/**
* @type Object.
* @property BOOL
* <p>This data type means that the raw data is converted into a boolean before it is placed into
* a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
* <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
*/
BOOL: {
convert: function(v){ return v === true || v === 'true' || v == 1; },
sortType: st.none,
type: 'bool'
},
/**
* @type Object.
* @property DATE
* This data type means that the raw data is converted into a Date before it is placed into a Record.
* The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
* being applied.
*/
DATE: {
convert: function(v){
var df = this.dateFormat;
if(!v){
return null;
}
if(Ext.isDate(v)){
return v;
}
if(df){
if(df == 'timestamp'){
return new Date(v*1000);
}
if(df == 'time'){
return new Date(parseInt(v, 10));
}
return Date.parseDate(v, df);
}
var parsed = Date.parse(v);
return parsed ? new Date(parsed) : null;
},
sortType: st.asDate,
type: 'date'
}
});
Ext.apply(this, {
/**
* @type Object.
* @property BOOLEAN
* <p>This data type means that the raw data is converted into a boolean before it is placed into
* a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
* <p>The synonym <code>BOOL</code> is equivalent.</p>
*/
BOOLEAN: this.BOOL,
/**
* @type Object.
* @property INTEGER
* This data type means that the raw data is converted into an integer before it is placed into a Record.
* <p>The synonym <code>INT</code> is equivalent.</p>
*/
INTEGER: this.INT,
/**
* @type Object.
* @property NUMBER
* This data type means that the raw data is converted into a number before it is placed into a Record.
* <p>The synonym <code>FLOAT</code> is equivalent.</p>
*/
NUMBER: this.FLOAT
});
};

View File

@@ -0,0 +1,259 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.XmlReader
* @extends Ext.data.DataReader
* <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an XML document
* based on mappings in a provided {@link Ext.data.Record} constructor.</p>
* <p><b>Note</b>: that in order for the browser to parse a returned XML document, the Content-Type
* header in the HTTP response must be set to "text/xml" or "application/xml".</p>
* <p>Example code:</p>
* <pre><code>
var Employee = Ext.data.Record.create([
{name: 'name', mapping: 'name'}, // "mapping" property not needed if it is the same as "name"
{name: 'occupation'} // This field will use "occupation" as the mapping.
]);
var myReader = new Ext.data.XmlReader({
totalProperty: "results", // The element which contains the total dataset size (optional)
record: "row", // The repeated element which contains row information
idProperty: "id" // The element within the row that provides an ID for the record (optional)
messageProperty: "msg" // The element within the response that provides a user-feedback message (optional)
}, Employee);
</code></pre>
* <p>
* This would consume an XML file like this:
* <pre><code>
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;dataset>
&lt;results>2&lt;/results>
&lt;row>
&lt;id>1&lt;/id>
&lt;name>Bill&lt;/name>
&lt;occupation>Gardener&lt;/occupation>
&lt;/row>
&lt;row>
&lt;id>2&lt;/id>
&lt;name>Ben&lt;/name>
&lt;occupation>Horticulturalist&lt;/occupation>
&lt;/row>
&lt;/dataset>
</code></pre>
* @cfg {String} totalProperty The DomQuery path from which to retrieve the total number of records
* in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
* paged from the remote server.
* @cfg {String} record The DomQuery path to the repeated element which contains record information.
* @cfg {String} record The DomQuery path to the repeated element which contains record information.
* @cfg {String} successProperty The DomQuery path to the success attribute used by forms.
* @cfg {String} idPath The DomQuery path relative from the record element to the element that contains
* a record identifier value.
* @constructor
* Create a new XmlReader.
* @param {Object} meta Metadata configuration options
* @param {Object} recordType Either an Array of field definition objects as passed to
* {@link Ext.data.Record#create}, or a Record constructor object created using {@link Ext.data.Record#create}.
*/
Ext.data.XmlReader = function(meta, recordType){
meta = meta || {};
// backwards compat, convert idPath or id / success
Ext.applyIf(meta, {
idProperty: meta.idProperty || meta.idPath || meta.id,
successProperty: meta.successProperty || meta.success
});
Ext.data.XmlReader.superclass.constructor.call(this, meta, recordType || meta.fields);
};
Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
/**
* This method is only used by a DataProxy which has retrieved data from a remote server.
* @param {Object} response The XHR object which contains the parsed XML document. The response is expected
* to contain a property called <tt>responseXML</tt> which refers to an XML document object.
* @return {Object} records A data block which is used by an {@link Ext.data.Store} as
* a cache of Ext.data.Records.
*/
read : function(response){
var doc = response.responseXML;
if(!doc) {
throw {message: "XmlReader.read: XML Document not available"};
}
return this.readRecords(doc);
},
/**
* Create a data block containing Ext.data.Records from an XML document.
* @param {Object} doc A parsed XML document.
* @return {Object} records A data block which is used by an {@link Ext.data.Store} as
* a cache of Ext.data.Records.
*/
readRecords : function(doc){
/**
* After any data loads/reads, the raw XML Document is available for further custom processing.
* @type XMLDocument
*/
this.xmlData = doc;
var root = doc.documentElement || doc,
q = Ext.DomQuery,
totalRecords = 0,
success = true;
if(this.meta.totalProperty){
totalRecords = this.getTotal(root, 0);
}
if(this.meta.successProperty){
success = this.getSuccess(root);
}
var records = this.extractData(q.select(this.meta.record, root), true); // <-- true to return Ext.data.Record[]
// TODO return Ext.data.Response instance. @see #readResponse
return {
success : success,
records : records,
totalRecords : totalRecords || records.length
};
},
/**
* Decode an XML response from server.
* @param {String} action [{@link Ext.data.Api#actions} create|read|update|destroy]
* @param {Object} response HTTP Response object from browser.
* @return {Ext.data.Response} An instance of {@link Ext.data.Response}
*/
readResponse : function(action, response) {
var q = Ext.DomQuery,
doc = response.responseXML,
root = doc.documentElement || doc;
// create general Response instance.
var res = new Ext.data.Response({
action: action,
success : this.getSuccess(root),
message: this.getMessage(root),
data: this.extractData(q.select(this.meta.record, root) || q.select(this.meta.root, root), false),
raw: doc
});
if (Ext.isEmpty(res.success)) {
throw new Ext.data.DataReader.Error('successProperty-response', this.meta.successProperty);
}
// Create actions from a response having status 200 must return pk
if (action === Ext.data.Api.actions.create) {
var def = Ext.isDefined(res.data);
if (def && Ext.isEmpty(res.data)) {
throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
}
else if (!def) {
throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
}
}
return res;
},
getSuccess : function() {
return true;
},
/**
* build response-data extractor functions.
* @private
* @ignore
*/
buildExtractors : function() {
if(this.ef){
return;
}
var s = this.meta,
Record = this.recordType,
f = Record.prototype.fields,
fi = f.items,
fl = f.length;
if(s.totalProperty) {
this.getTotal = this.createAccessor(s.totalProperty);
}
if(s.successProperty) {
this.getSuccess = this.createAccessor(s.successProperty);
}
if (s.messageProperty) {
this.getMessage = this.createAccessor(s.messageProperty);
}
this.getRoot = function(res) {
return (!Ext.isEmpty(res[this.meta.record])) ? res[this.meta.record] : res[this.meta.root];
};
if (s.idPath || s.idProperty) {
var g = this.createAccessor(s.idPath || s.idProperty);
this.getId = function(rec) {
var id = g(rec) || rec.id;
return (id === undefined || id === '') ? null : id;
};
} else {
this.getId = function(){return null;};
}
var ef = [];
for(var i = 0; i < fl; i++){
f = fi[i];
var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
ef.push(this.createAccessor(map));
}
this.ef = ef;
},
/**
* Creates a function to return some particular key of data from a response.
* @param {String} key
* @return {Function}
* @private
* @ignore
*/
createAccessor : function(){
var q = Ext.DomQuery;
return function(key) {
if (Ext.isFunction(key)) {
return key;
}
switch(key) {
case this.meta.totalProperty:
return function(root, def){
return q.selectNumber(key, root, def);
};
break;
case this.meta.successProperty:
return function(root, def) {
var sv = q.selectValue(key, root, true);
var success = sv !== false && sv !== 'false';
return success;
};
break;
default:
return function(root, def) {
return q.selectValue(key, root, def);
};
break;
}
};
}(),
/**
* extracts values and type-casts a row of data from server, extracted by #extractData
* @param {Hash} data
* @param {Ext.data.Field[]} items
* @param {Number} len
* @private
* @ignore
*/
extractValues : function(data, items, len) {
var f, values = {};
for(var j = 0; j < len; j++){
f = items[j];
var v = this.ef[j](data);
values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
}
return values;
}
});

View File

@@ -0,0 +1,75 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.XmlStore
* @extends Ext.data.Store
* <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
* A XmlStore will be automatically configured with a {@link Ext.data.XmlReader}.</p>
* <p>A store configuration would be something like:<pre><code>
var store = new Ext.data.XmlStore({
// store configs
autoDestroy: true,
storeId: 'myStore',
url: 'sheldon.xml', // automatically configures a HttpProxy
// reader configs
record: 'Item', // records will have an "Item" tag
idPath: 'ASIN',
totalRecords: '@TotalResults'
fields: [
// set up the fields mapping into the xml doc
// The first needs mapping, the others are very basic
{name: 'Author', mapping: 'ItemAttributes > Author'},
'Title', 'Manufacturer', 'ProductGroup'
]
});
* </code></pre></p>
* <p>This store is configured to consume a returned object of the form:<pre><code>
&#60?xml version="1.0" encoding="UTF-8"?>
&#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
&#60Items>
&#60Request>
&#60IsValid>True&#60/IsValid>
&#60ItemSearchRequest>
&#60Author>Sidney Sheldon&#60/Author>
&#60SearchIndex>Books&#60/SearchIndex>
&#60/ItemSearchRequest>
&#60/Request>
&#60TotalResults>203&#60/TotalResults>
&#60TotalPages>21&#60/TotalPages>
&#60Item>
&#60ASIN>0446355453&#60/ASIN>
&#60DetailPageURL>
http://www.amazon.com/
&#60/DetailPageURL>
&#60ItemAttributes>
&#60Author>Sidney Sheldon&#60/Author>
&#60Manufacturer>Warner Books&#60/Manufacturer>
&#60ProductGroup>Book&#60/ProductGroup>
&#60Title>Master of the Game&#60/Title>
&#60/ItemAttributes>
&#60/Item>
&#60/Items>
&#60/ItemSearchResponse>
* </code></pre>
* An object literal of this form could also be used as the {@link #data} config option.</p>
* <p><b>Note:</b> Although not listed here, this class accepts all of the configuration options of
* <b>{@link Ext.data.XmlReader XmlReader}</b>.</p>
* @constructor
* @param {Object} config
* @xtype xmlstore
*/
Ext.data.XmlStore = Ext.extend(Ext.data.Store, {
/**
* @cfg {Ext.data.DataReader} reader @hide
*/
constructor: function(config){
Ext.data.XmlStore.superclass.constructor.call(this, Ext.apply(config, {
reader: new Ext.data.XmlReader(config)
}));
}
});
Ext.reg('xmlstore', Ext.data.XmlStore);

View File

@@ -0,0 +1,158 @@
/*!
* Ext JS Library 3.3.1
* Copyright(c) 2006-2010 Sencha Inc.
* licensing@sencha.com
* http://www.sencha.com/license
*/
/**
* @class Ext.data.XmlWriter
* @extends Ext.data.DataWriter
* DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action via XML.
* XmlWriter uses an instance of {@link Ext.XTemplate} for maximum flexibility in defining your own custom XML schema if the default schema is not appropriate for your needs.
* See the {@link #tpl} configuration-property.
*/
Ext.data.XmlWriter = function(params) {
Ext.data.XmlWriter.superclass.constructor.apply(this, arguments);
// compile the XTemplate for rendering XML documents.
this.tpl = (typeof(this.tpl) === 'string') ? new Ext.XTemplate(this.tpl).compile() : this.tpl.compile();
};
Ext.extend(Ext.data.XmlWriter, Ext.data.DataWriter, {
/**
* @cfg {String} documentRoot [xrequest] (Optional) The name of the XML document root-node. <b>Note:</b>
* this parameter is required </b>only when</b> sending extra {@link Ext.data.Store#baseParams baseParams} to the server
* during a write-request -- if no baseParams are set, the {@link Ext.data.XmlReader#record} meta-property can
* suffice as the XML document root-node for write-actions involving just a <b>single record</b>. For requests
* involving <b>multiple</b> records and <b>NO</b> baseParams, the {@link Ext.data.XmlWriter#root} property can
* act as the XML document root.
*/
documentRoot: 'xrequest',
/**
* @cfg {Boolean} forceDocumentRoot [false] Set to <tt>true</tt> to force XML documents having a root-node as defined
* by {@link #documentRoot}, even with no baseParams defined.
*/
forceDocumentRoot: false,
/**
* @cfg {String} root [records] The name of the containing element which will contain the nodes of an write-action involving <b>multiple</b> records. Each
* xml-record written to the server will be wrapped in an element named after {@link Ext.data.XmlReader#record} property.
* eg:
<code><pre>
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;user>&lt;first>Barney&lt;/first>&lt;/user>
</code></pre>
* However, when <b>multiple</b> records are written in a batch-operation, these records must be wrapped in a containing
* Element.
* eg:
<code><pre>
&lt;?xml version="1.0" encoding="UTF-8"?>
&lt;records>
&lt;first>Barney&lt;/first>&lt;/user>
&lt;records>&lt;first>Barney&lt;/first>&lt;/user>
&lt;/records>
</code></pre>
* Defaults to <tt>records</tt>. Do not confuse the nature of this property with that of {@link #documentRoot}
*/
root: 'records',
/**
* @cfg {String} xmlVersion [1.0] The <tt>version</tt> written to header of xml documents.
<code><pre>&lt;?xml version="1.0" encoding="ISO-8859-15"?></pre></code>
*/
xmlVersion : '1.0',
/**
* @cfg {String} xmlEncoding [ISO-8859-15] The <tt>encoding</tt> written to header of xml documents.
<code><pre>&lt;?xml version="1.0" encoding="ISO-8859-15"?></pre></code>
*/
xmlEncoding: 'ISO-8859-15',
/**
* @cfg {String/Ext.XTemplate} tpl The XML template used to render {@link Ext.data.Api#actions write-actions} to your server.
* <p>One can easily provide his/her own custom {@link Ext.XTemplate#constructor template-definition} if the default does not suffice.</p>
* <p>Defaults to:</p>
<code><pre>
&lt;?xml version="{version}" encoding="{encoding}"?>
&lt;tpl if="documentRoot">&lt;{documentRoot}>
&lt;tpl for="baseParams">
&lt;tpl for=".">
&lt;{name}>{value}&lt;/{name}>
&lt;/tpl>
&lt;/tpl>
&lt;tpl if="records.length &gt; 1">&lt;{root}>',
&lt;tpl for="records">
&lt;{parent.record}>
&lt;tpl for=".">
&lt;{name}>{value}&lt;/{name}>
&lt;/tpl>
&lt;/{parent.record}>
&lt;/tpl>
&lt;tpl if="records.length &gt; 1">&lt;/{root}>&lt;/tpl>
&lt;tpl if="documentRoot">&lt;/{documentRoot}>&lt;/tpl>
</pre></code>
* <p>Templates will be called with the following API</p>
* <ul>
* <li>{String} version [1.0] The xml version.</li>
* <li>{String} encoding [ISO-8859-15] The xml encoding.</li>
* <li>{String/false} documentRoot The XML document root-node name or <tt>false</tt> if not required. See {@link #documentRoot} and {@link #forceDocumentRoot}.</li>
* <li>{String} record The meta-data parameter defined on your {@link Ext.data.XmlReader#record} configuration represents the name of the xml-tag containing each record.</li>
* <li>{String} root The meta-data parameter defined by {@link Ext.data.XmlWriter#root} configuration-parameter. Represents the name of the xml root-tag when sending <b>multiple</b> records to the server.</li>
* <li>{Array} records The records being sent to the server, ie: the subject of the write-action being performed. The records parameter will be always be an array, even when only a single record is being acted upon.
* Each item within the records array will contain an array of field objects having the following properties:
* <ul>
* <li>{String} name The field-name of the record as defined by your {@link Ext.data.Record#create Ext.data.Record definition}. The "mapping" property will be used, otherwise it will match the "name" property. Use this parameter to define the XML tag-name of the property.</li>
* <li>{Mixed} value The record value of the field enclosed within XML tags specified by name property above.</li>
* </ul></li>
* <li>{Array} baseParams. The baseParams as defined upon {@link Ext.data.Store#baseParams}. Note that the baseParams have been converted into an array of [{name : "foo", value: "bar"}, ...] pairs in the same manner as the <b>records</b> parameter above. See {@link #documentRoot} and {@link #forceDocumentRoot}.</li>
* </ul>
*/
// Encoding the ? here in case it's being included by some kind of page that will parse it (eg. PHP)
tpl: '<tpl for="."><\u003fxml version="{version}" encoding="{encoding}"\u003f><tpl if="documentRoot"><{documentRoot}><tpl for="baseParams"><tpl for="."><{name}>{value}</{name}></tpl></tpl></tpl><tpl if="records.length&gt;1"><{root}></tpl><tpl for="records"><{parent.record}><tpl for="."><{name}>{value}</{name}></tpl></{parent.record}></tpl><tpl if="records.length&gt;1"></{root}></tpl><tpl if="documentRoot"></{documentRoot}></tpl></tpl>',
/**
* XmlWriter implementation of the final stage of a write action.
* @param {Object} params Transport-proxy's (eg: {@link Ext.Ajax#request}) params-object to write-to.
* @param {Object} baseParams as defined by {@link Ext.data.Store#baseParams}. The baseParms must be encoded by the extending class, eg: {@link Ext.data.JsonWriter}, {@link Ext.data.XmlWriter}.
* @param {Object/Object[]} data Data-object representing the compiled Store-recordset.
*/
render : function(params, baseParams, data) {
baseParams = this.toArray(baseParams);
params.xmlData = this.tpl.applyTemplate({
version: this.xmlVersion,
encoding: this.xmlEncoding,
documentRoot: (baseParams.length > 0 || this.forceDocumentRoot === true) ? this.documentRoot : false,
record: this.meta.record,
root: this.root,
baseParams: baseParams,
records: (Ext.isArray(data[0])) ? data : [data]
});
},
/**
* createRecord
* @protected
* @param {Ext.data.Record} rec
* @return {Array} Array of <tt>name:value</tt> pairs for attributes of the {@link Ext.data.Record}. See {@link Ext.data.DataWriter#toHash}.
*/
createRecord : function(rec) {
return this.toArray(this.toHash(rec));
},
/**
* updateRecord
* @protected
* @param {Ext.data.Record} rec
* @return {Array} Array of {name:value} pairs for attributes of the {@link Ext.data.Record}. See {@link Ext.data.DataWriter#toHash}.
*/
updateRecord : function(rec) {
return this.toArray(this.toHash(rec));
},
/**
* destroyRecord
* @protected
* @param {Ext.data.Record} rec
* @return {Array} Array containing a attribute-object (name/value pair) representing the {@link Ext.data.DataReader#idProperty idProperty}.
*/
destroyRecord : function(rec) {
var data = {};
data[this.meta.idProperty] = rec.id;
return this.toArray(data);
}
});