Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / pkgs / data-xml-debug.js
1 /*!
2  * Ext JS Library 3.0.3
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.data.XmlWriter
9  * @extends Ext.data.DataWriter
10  * DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action via XML.
11  */
12 Ext.data.XmlWriter = function(params) {
13     Ext.data.XmlWriter.superclass.constructor.apply(this, arguments);
14     this.tpl = new Ext.XTemplate(this.tpl).compile();
15 };
16 Ext.extend(Ext.data.XmlWriter, Ext.data.DataWriter, {
17     /**
18      * @cfg {String} root [records] The name of the root element when writing <b>multiple</b> records to the server.  Each
19      * xml-record written to the server will be wrapped in an element named after {@link Ext.data.XmlReader#record} property.
20      * eg:
21 <code><pre>
22 &lt;?xml version="1.0" encoding="UTF-8"?>
23 &lt;user>&lt;first>Barney&lt;/first>&lt;/user>
24 </code></pre>
25      * However, when <b>multiple</b> records are written in a batch-operation, these records must be wrapped in a containing
26      * Element.
27      * eg:
28 <code><pre>
29 &lt;?xml version="1.0" encoding="UTF-8"?>
30     &lt;records>
31         &lt;first>Barney&lt;/first>&lt;/user>
32         &lt;records>&lt;first>Barney&lt;/first>&lt;/user>
33     &lt;/records>
34 </code></pre>
35      * Defaults to <tt>records</tt>
36      */
37     root: 'records',
38     /**
39      * @cfg {String} xmlVersion [1.0] The <tt>version</tt> written to header of xml documents.
40 <code><pre>&lt;?xml version="1.0" encoding="ISO-8859-15"?></pre></code>
41      */
42     xmlVersion : '1.0',
43     /**
44      * @cfg {String} xmlEncoding [ISO-8859-15] The <tt>encoding</tt> written to header of xml documents.
45 <code><pre>&lt;?xml version="1.0" encoding="ISO-8859-15"?></pre></code>
46      */
47     xmlEncoding: 'ISO-8859-15',
48     /**
49      * @cfg {String} tpl The xml template.  Defaults to
50 <code><pre>
51 &lt;?xml version="{version}" encoding="{encoding}"?>
52     &lt;tpl if="{[values.nodes.length>1]}">&lt;{root}}>',
53     &lt;tpl for="records">
54         &lt;{parent.record}>
55         &lt;tpl for="fields">
56             &lt;{name}>{value}&lt;/{name}>
57         &lt;/tpl>
58         &lt;/{parent.record}>
59     &lt;/tpl>
60     &lt;tpl if="{[values.records.length>1]}">&lt;/{root}}>&lt;/tpl>
61 </pre></code>
62      */
63     // Break up encoding here in case it's being included by some kind of page that will parse it (eg. PHP)
64     tpl: '<tpl for="."><' + '?xml version="{version}" encoding="{encoding}"?' + '><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>',
65
66     /**
67      * Final action of a write event.  Apply the written data-object to params.
68      * @param {String} action [Ext.data.Api.create|read|update|destroy]
69      * @param {Ext.data.Record/Ext.data.Record[]} rs
70      * @param {Object} http params
71      * @param {Object/Object[]} rendered data.
72      */
73     render : function(action, rs, params, data) {
74         params.xmlData = this.tpl.applyTemplate({
75             version: this.xmlVersion,
76             encoding: this.xmlEncoding,
77             record: this.meta.record,
78             root: this.root,
79             records: (Ext.isArray(rs)) ? data : [data]
80         });
81     },
82
83     /**
84      * Converts an Ext.data.Record to xml
85      * @param {Ext.data.Record} rec
86      * @return {String} rendered xml-element
87      * @private
88      */
89     toXml : function(data) {
90         var fields = [];
91         Ext.iterate(data, function(k, v) {
92             fields.push({
93                 name: k,
94                 value: v
95             });
96         },this);
97         return {
98             fields: fields
99         };
100     },
101
102     /**
103      * createRecord
104      * @param {Ext.data.Record} rec
105      * @return {String} xml element
106      * @private
107      */
108     createRecord : function(rec) {
109         return this.toXml(this.toHash(rec));
110     },
111
112     /**
113      * updateRecord
114      * @param {Ext.data.Record} rec
115      * @return {String} xml element
116      * @private
117      */
118     updateRecord : function(rec) {
119         return this.toXml(this.toHash(rec));
120
121     },
122     /**
123      * destroyRecord
124      * @param {Ext.data.Record} rec
125      * @return {String} xml element
126      */
127     destroyRecord : function(rec) {
128         var data = {};
129         data[this.meta.idProperty] = rec.id;
130         return this.toXml(data);
131     }
132 });
133
134 /**
135  * @class Ext.data.XmlReader
136  * @extends Ext.data.DataReader
137  * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an XML document
138  * based on mappings in a provided {@link Ext.data.Record} constructor.</p>
139  * <p><b>Note</b>: that in order for the browser to parse a returned XML document, the Content-Type
140  * header in the HTTP response must be set to "text/xml" or "application/xml".</p>
141  * <p>Example code:</p>
142  * <pre><code>
143 var Employee = Ext.data.Record.create([
144    {name: 'name', mapping: 'name'},     // "mapping" property not needed if it is the same as "name"
145    {name: 'occupation'}                 // This field will use "occupation" as the mapping.
146 ]);
147 var myReader = new Ext.data.XmlReader({
148    totalProperty: "results", // The element which contains the total dataset size (optional)
149    record: "row",           // The repeated element which contains row information
150    idProperty: "id"         // The element within the row that provides an ID for the record (optional)
151    messageProperty: "msg"   // The element within the response that provides a user-feedback message (optional)
152 }, Employee);
153 </code></pre>
154  * <p>
155  * This would consume an XML file like this:
156  * <pre><code>
157 &lt;?xml version="1.0" encoding="UTF-8"?>
158 &lt;dataset>
159  &lt;results>2&lt;/results>
160  &lt;row>
161    &lt;id>1&lt;/id>
162    &lt;name>Bill&lt;/name>
163    &lt;occupation>Gardener&lt;/occupation>
164  &lt;/row>
165  &lt;row>
166    &lt;id>2&lt;/id>
167    &lt;name>Ben&lt;/name>
168    &lt;occupation>Horticulturalist&lt;/occupation>
169  &lt;/row>
170 &lt;/dataset>
171 </code></pre>
172  * @cfg {String} totalProperty The DomQuery path from which to retrieve the total number of records
173  * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
174  * paged from the remote server.
175  * @cfg {String} record The DomQuery path to the repeated element which contains record information.
176  * @cfg {String} record The DomQuery path to the repeated element which contains record information.
177  * @cfg {String} successProperty The DomQuery path to the success attribute used by forms.
178  * @cfg {String} idPath The DomQuery path relative from the record element to the element that contains
179  * a record identifier value.
180  * @constructor
181  * Create a new XmlReader.
182  * @param {Object} meta Metadata configuration options
183  * @param {Object} recordType Either an Array of field definition objects as passed to
184  * {@link Ext.data.Record#create}, or a Record constructor object created using {@link Ext.data.Record#create}.
185  */
186 Ext.data.XmlReader = function(meta, recordType){
187     meta = meta || {};
188
189     // backwards compat, convert idPath to idProperty
190     meta.idProperty = meta.idProperty || meta.idPath;
191
192     Ext.data.XmlReader.superclass.constructor.call(this, meta, recordType || meta.fields);
193 };
194 Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
195     /**
196      * This method is only used by a DataProxy which has retrieved data from a remote server.
197      * @param {Object} response The XHR object which contains the parsed XML document.  The response is expected
198      * to contain a property called <tt>responseXML</tt> which refers to an XML document object.
199      * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
200      * a cache of Ext.data.Records.
201      */
202     read : function(response){
203         var doc = response.responseXML;
204         if(!doc) {
205             throw {message: "XmlReader.read: XML Document not available"};
206         }
207         return this.readRecords(doc);
208     },
209
210     /**
211      * Create a data block containing Ext.data.Records from an XML document.
212      * @param {Object} doc A parsed XML document.
213      * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
214      * a cache of Ext.data.Records.
215      */
216     readRecords : function(doc){
217         /**
218          * After any data loads/reads, the raw XML Document is available for further custom processing.
219          * @type XMLDocument
220          */
221         this.xmlData = doc;
222
223         var root    = doc.documentElement || doc,
224             q       = Ext.DomQuery,
225             totalRecords = 0,
226             success = true;
227
228         if(this.meta.totalProperty){
229             totalRecords = this.getTotal(root, 0);
230         }
231         if(this.meta.successProperty){
232             success = this.getSuccess(root);
233         }
234
235         var records = this.extractData(q.select(this.meta.record, root), true); // <-- true to return Ext.data.Record[]
236
237         // TODO return Ext.data.Response instance.  @see #readResponse
238         return {
239             success : success,
240             records : records,
241             totalRecords : totalRecords || records.length
242         };
243     },
244
245     /**
246      * Decode a json response from server.
247      * @param {String} action [{@link Ext.data.Api#actions} create|read|update|destroy]
248      * @param {Ext.data.Response} response Returns an instance of {@link Ext.data.Response}
249      */
250     readResponse : function(action, response) {
251         var q   = Ext.DomQuery,
252         doc     = response.responseXML;
253
254         var res = new Ext.data.Response({
255             action: action,
256             success : this.getSuccess(doc),
257             message: this.getMessage(doc),
258             data: this.extractData(q.select(this.meta.record, doc) || q.select(this.meta.root, doc)),
259             raw: doc
260         });
261
262         if (Ext.isEmpty(res.success)) {
263             throw new Ext.data.DataReader.Error('successProperty-response', this.meta.successProperty);
264         }
265
266         if (action === Ext.data.Api.actions.create) {
267             var def = Ext.isDefined(res.data);
268             if (def && Ext.isEmpty(res.data)) {
269                 throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
270             }
271             else if (!def) {
272                 throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
273             }
274         }
275         return res;
276     },
277
278     getSuccess : function() {
279         return true;
280     },
281
282     /**
283      * build response-data extractor functions.
284      * @private
285      * @ignore
286      */
287     buildExtractors : function() {
288         if(this.ef){
289             return;
290         }
291         var s       = this.meta,
292             Record  = this.recordType,
293             f       = Record.prototype.fields,
294             fi      = f.items,
295             fl      = f.length;
296
297         if(s.totalProperty) {
298             this.getTotal = this.createAccessor(s.totalProperty);
299         }
300         if(s.successProperty) {
301             this.getSuccess = this.createAccessor(s.successProperty);
302         }
303         if (s.messageProperty) {
304             this.getMessage = this.createAccessor(s.messageProperty);
305         }
306         this.getRoot = function(res) {
307             return (!Ext.isEmpty(res[this.meta.record])) ? res[this.meta.record] : res[this.meta.root];
308         }
309         if (s.idPath || s.idProperty) {
310             var g = this.createAccessor(s.idPath || s.idProperty);
311             this.getId = function(rec) {
312                 var id = g(rec) || rec.id;
313                 return (id === undefined || id === '') ? null : id;
314             };
315         } else {
316             this.getId = function(){return null;};
317         }
318         var ef = [];
319         for(var i = 0; i < fl; i++){
320             f = fi[i];
321             var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
322             ef.push(this.createAccessor(map));
323         }
324         this.ef = ef;
325     },
326
327     /**
328      * Creates a function to return some particular key of data from a response.
329      * @param {String} key
330      * @return {Function}
331      * @private
332      * @ignore
333      */
334     createAccessor : function(){
335         var q = Ext.DomQuery;
336         return function(key) {
337             switch(key) {
338                 case this.meta.totalProperty:
339                     return function(root, def){
340                         return q.selectNumber(key, root, def);
341                     }
342                     break;
343                 case this.meta.successProperty:
344                     return function(root, def) {
345                         var sv = q.selectValue(key, root, true);
346                         var success = sv !== false && sv !== 'false';
347                         return success;
348                     }
349                     break;
350                 default:
351                     return function(root, def) {
352                         return q.selectValue(key, root, def);
353                     }
354                     break;
355             }
356         };
357     }(),
358
359     /**
360      * Extracts rows of record-data from server.  iterates and calls #extractValues
361      * TODO I don't care much for method-names of #extractData, #extractValues.
362      * @param {Array} root
363      * @param {Boolean} returnRecords When true, will return instances of Ext.data.Record; otherwise just hashes.
364      * @private
365      * @ignore
366      */
367     extractData : function(root, returnRecords) {
368         var Record  = this.recordType,
369         records     = [],
370         f           = Record.prototype.fields,
371         fi          = f.items,
372         fl          = f.length;
373         if (returnRecords === true) {
374             for (var i = 0, len = root.length; i < len; i++) {
375                 var data = root[i],
376                     record = new Record(this.extractValues(data, fi, fl), this.getId(data));
377                     
378                 record.node = data;
379                 records.push(record);
380             }
381         } else {
382             for (var i = 0, len = root.length; i < len; i++) {
383                 records.push(this.extractValues(root[i], fi, fl));
384             }
385         }
386         return records;
387     },
388
389     /**
390      * extracts values and type-casts a row of data from server, extracted by #extractData
391      * @param {Hash} data
392      * @param {Ext.data.Field[]} items
393      * @param {Number} len
394      * @private
395      * @ignore
396      */
397     extractValues : function(data, items, len) {
398         var f, values = {};
399         for(var j = 0; j < len; j++){
400             f = items[j];
401             var v = this.ef[j](data);
402             values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
403         }
404         return values;
405     }
406 });/**\r
407  * @class Ext.data.XmlStore\r
408  * @extends Ext.data.Store\r
409  * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.\r
410  * A XmlStore will be automatically configured with a {@link Ext.data.XmlReader}.</p>\r
411  * <p>A store configuration would be something like:<pre><code>\r
412 var store = new Ext.data.XmlStore({\r
413     // store configs\r
414     autoDestroy: true,\r
415     storeId: 'myStore',\r
416     url: 'sheldon.xml', // automatically configures a HttpProxy\r
417     // reader configs\r
418     record: 'Item', // records will have an "Item" tag\r
419     idPath: 'ASIN',\r
420     totalRecords: '@TotalResults'\r
421     fields: [\r
422         // set up the fields mapping into the xml doc\r
423         // The first needs mapping, the others are very basic\r
424         {name: 'Author', mapping: 'ItemAttributes > Author'},\r
425         'Title', 'Manufacturer', 'ProductGroup'\r
426     ]\r
427 });\r
428  * </code></pre></p>\r
429  * <p>This store is configured to consume a returned object of the form:<pre><code>\r
430 &#60?xml version="1.0" encoding="UTF-8"?>\r
431 &#60ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">\r
432     &#60Items>\r
433         &#60Request>\r
434             &#60IsValid>True&#60/IsValid>\r
435             &#60ItemSearchRequest>\r
436                 &#60Author>Sidney Sheldon&#60/Author>\r
437                 &#60SearchIndex>Books&#60/SearchIndex>\r
438             &#60/ItemSearchRequest>\r
439         &#60/Request>\r
440         &#60TotalResults>203&#60/TotalResults>\r
441         &#60TotalPages>21&#60/TotalPages>\r
442         &#60Item>\r
443             &#60ASIN>0446355453&#60/ASIN>\r
444             &#60DetailPageURL>\r
445                 http://www.amazon.com/\r
446             &#60/DetailPageURL>\r
447             &#60ItemAttributes>\r
448                 &#60Author>Sidney Sheldon&#60/Author>\r
449                 &#60Manufacturer>Warner Books&#60/Manufacturer>\r
450                 &#60ProductGroup>Book&#60/ProductGroup>\r
451                 &#60Title>Master of the Game&#60/Title>\r
452             &#60/ItemAttributes>\r
453         &#60/Item>\r
454     &#60/Items>\r
455 &#60/ItemSearchResponse>\r
456  * </code></pre>\r
457  * An object literal of this form could also be used as the {@link #data} config option.</p>\r
458  * <p><b>Note:</b> Although not listed here, this class accepts all of the configuration options of \r
459  * <b>{@link Ext.data.XmlReader XmlReader}</b>.</p>\r
460  * @constructor\r
461  * @param {Object} config\r
462  * @xtype xmlstore\r
463  */\r
464 Ext.data.XmlStore = Ext.extend(Ext.data.Store, {\r
465     /**\r
466      * @cfg {Ext.data.DataReader} reader @hide\r
467      */\r
468     constructor: function(config){\r
469         Ext.data.XmlStore.superclass.constructor.call(this, Ext.apply(config, {\r
470             reader: new Ext.data.XmlReader(config)\r
471         }));\r
472     }\r
473 });\r
474 Ext.reg('xmlstore', Ext.data.XmlStore);