Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / data / reader / Xml.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @author Ed Spencer
17  * @class Ext.data.reader.Xml
18  * @extends Ext.data.reader.Reader
19  * 
20  * <p>The XML Reader is used by a Proxy to read a server response that is sent back in XML format. This usually
21  * happens as a result of loading a Store - for example we might create something like this:</p>
22  * 
23 <pre><code>
24 Ext.define('User', {
25     extend: 'Ext.data.Model',
26     fields: ['id', 'name', 'email']
27 });
28
29 var store = new Ext.data.Store({
30     model: 'User',
31     proxy: {
32         type: 'ajax',
33         url : 'users.xml',
34         reader: {
35             type: 'xml',
36             record: 'user'
37         }
38     }
39 });
40 </code></pre>
41  * 
42  * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
43  * not already familiar with them.</p>
44  * 
45  * <p>We created the simplest type of XML Reader possible by simply telling our {@link Ext.data.Store Store}'s 
46  * {@link Ext.data.proxy.Proxy Proxy} that we want a XML Reader. The Store automatically passes the configured model to the
47  * Store, so it is as if we passed this instead:
48  * 
49 <pre><code>
50 reader: {
51     type : 'xml',
52     model: 'User',
53     record: 'user'
54 }
55 </code></pre>
56  * 
57  * <p>The reader we set up is ready to read data from our server - at the moment it will accept a response like this:</p>
58  *
59 <pre><code>
60 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
61 &lt;user&gt;
62     &lt;id&gt;1&lt;/id&gt;
63     &lt;name&gt;Ed Spencer&lt;/name&gt;
64     &lt;email&gt;ed@sencha.com&lt;/email&gt;
65 &lt;/user&gt;
66 &lt;user&gt;
67     &lt;id&gt;2&lt;/id&gt;
68     &lt;name&gt;Abe Elias&lt;/name&gt;
69     &lt;email&gt;abe@sencha.com&lt;/email&gt;
70 &lt;/user&gt;
71 </code></pre>
72  * 
73  * <p>The XML Reader uses the configured {@link #record} option to pull out the data for each record - in this case we
74  * set record to 'user', so each &lt;user&gt; above will be converted into a User model.</p>
75  * 
76  * <p><u>Reading other XML formats</u></p>
77  * 
78  * <p>If you already have your XML format defined and it doesn't look quite like what we have above, you can usually
79  * pass XmlReader a couple of configuration options to make it parse your format. For example, we can use the 
80  * {@link #root} configuration to parse data that comes back like this:</p>
81  * 
82 <pre><code>
83 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
84 &lt;users&gt;
85     &lt;user&gt;
86         &lt;id&gt;1&lt;/id&gt;
87         &lt;name&gt;Ed Spencer&lt;/name&gt;
88         &lt;email&gt;ed@sencha.com&lt;/email&gt;
89     &lt;/user&gt;
90     &lt;user&gt;
91         &lt;id&gt;2&lt;/id&gt;
92         &lt;name&gt;Abe Elias&lt;/name&gt;
93         &lt;email&gt;abe@sencha.com&lt;/email&gt;
94     &lt;/user&gt;
95 &lt;/users&gt;
96 </code></pre>
97  * 
98  * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
99  * 
100 <pre><code>
101 reader: {
102     type  : 'xml',
103     root  : 'users',
104     record: 'user'
105 }
106 </code></pre>
107  * 
108  * <p>Note that XmlReader doesn't care whether your {@link #root} and {@link #record} elements are nested deep inside
109  * a larger structure, so a response like this will still work:
110  * 
111 <pre><code>
112 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
113 &lt;deeply&gt;
114     &lt;nested&gt;
115         &lt;xml&gt;
116             &lt;users&gt;
117                 &lt;user&gt;
118                     &lt;id&gt;1&lt;/id&gt;
119                     &lt;name&gt;Ed Spencer&lt;/name&gt;
120                     &lt;email&gt;ed@sencha.com&lt;/email&gt;
121                 &lt;/user&gt;
122                 &lt;user&gt;
123                     &lt;id&gt;2&lt;/id&gt;
124                     &lt;name&gt;Abe Elias&lt;/name&gt;
125                     &lt;email&gt;abe@sencha.com&lt;/email&gt;
126                 &lt;/user&gt;
127             &lt;/users&gt;
128         &lt;/xml&gt;
129     &lt;/nested&gt;
130 &lt;/deeply&gt;
131 </code></pre>
132  * 
133  * <p><u>Response metadata</u></p>
134  * 
135  * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records} 
136  * and the {@link #successProperty success status of the response}. These are typically included in the XML response
137  * like this:</p>
138  * 
139 <pre><code>
140 &lt;?xml version="1.0" encoding="UTF-8"?&gt;
141 &lt;total&gt;100&lt;/total&gt;
142 &lt;success&gt;true&lt;/success&gt;
143 &lt;users&gt;
144     &lt;user&gt;
145         &lt;id&gt;1&lt;/id&gt;
146         &lt;name&gt;Ed Spencer&lt;/name&gt;
147         &lt;email&gt;ed@sencha.com&lt;/email&gt;
148     &lt;/user&gt;
149     &lt;user&gt;
150         &lt;id&gt;2&lt;/id&gt;
151         &lt;name&gt;Abe Elias&lt;/name&gt;
152         &lt;email&gt;abe@sencha.com&lt;/email&gt;
153     &lt;/user&gt;
154 &lt;/users&gt;
155 </code></pre>
156  * 
157  * <p>If these properties are present in the XML response they can be parsed out by the XmlReader and used by the
158  * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration 
159  * options:</p>
160  * 
161 <pre><code>
162 reader: {
163     type: 'xml',
164     root: 'users',
165     totalProperty  : 'total',
166     successProperty: 'success'
167 }
168 </code></pre>
169  * 
170  * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
171  * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
172  * returned.</p>
173  * 
174  * <p><u>Response format</u></p>
175  * 
176  * <p><b>Note:</b> in order for the browser to parse a returned XML document, the Content-Type header in the HTTP 
177  * response must be set to "text/xml" or "application/xml". This is very important - the XmlReader will not
178  * work correctly otherwise.</p>
179  */
180 Ext.define('Ext.data.reader.Xml', {
181     extend: 'Ext.data.reader.Reader',
182     alternateClassName: 'Ext.data.XmlReader',
183     alias : 'reader.xml',
184     
185     /**
186      * @cfg {String} record The DomQuery path to the repeated element which contains record information.
187      */
188
189     /**
190      * @private
191      * Creates a function to return some particular key of data from a response. The totalProperty and
192      * successProperty are treated as special cases for type casting, everything else is just a simple selector.
193      * @param {String} key
194      * @return {Function}
195      */
196     createAccessor: function(expr) {
197         var me = this;
198         
199         if (Ext.isEmpty(expr)) {
200             return Ext.emptyFn;
201         }
202         
203         if (Ext.isFunction(expr)) {
204             return expr;
205         }
206         
207         return function(root) {
208             return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
209         };
210     },
211     
212     getNodeValue: function(node) {
213         if (node && node.firstChild) {
214             return node.firstChild.nodeValue;
215         }
216         return undefined;
217     },
218
219     //inherit docs
220     getResponseData: function(response) {
221         var xml = response.responseXML;
222
223         //<debug>
224         if (!xml) {
225             Ext.Error.raise({
226                 response: response,
227                 msg: 'XML data not found in the response'
228             });
229         }
230         //</debug>
231
232         return xml;
233     },
234
235     /**
236      * Normalizes the data object
237      * @param {Object} data The raw data object
238      * @return {Object} Returns the documentElement property of the data object if present, or the same object if not
239      */
240     getData: function(data) {
241         return data.documentElement || data;
242     },
243
244     /**
245      * @private
246      * Given an XML object, returns the Element that represents the root as configured by the Reader's meta data
247      * @param {Object} data The XML data object
248      * @return {Element} The root node element
249      */
250     getRoot: function(data) {
251         var nodeName = data.nodeName,
252             root     = this.root;
253         
254         if (!root || (nodeName && nodeName == root)) {
255             return data;
256         } else if (Ext.DomQuery.isXml(data)) {
257             // This fix ensures we have XML data
258             // Related to TreeStore calling getRoot with the root node, which isn't XML
259             // Probably should be resolved in TreeStore at some point
260             return Ext.DomQuery.selectNode(root, data);
261         }
262     },
263
264     /**
265      * @private
266      * We're just preparing the data for the superclass by pulling out the record nodes we want
267      * @param {Element} root The XML root node
268      * @return {Array} The records
269      */
270     extractData: function(root) {
271         var recordName = this.record;
272         
273         //<debug>
274         if (!recordName) {
275             Ext.Error.raise('Record is a required parameter');
276         }
277         //</debug>
278         
279         if (recordName != root.nodeName) {
280             root = Ext.DomQuery.select(recordName, root);
281         } else {
282             root = [root];
283         }
284         return this.callParent([root]);
285     },
286     
287     /**
288      * @private
289      * See Ext.data.reader.Reader's getAssociatedDataRoot docs
290      * @param {Mixed} data The raw data object
291      * @param {String} associationName The name of the association to get data for (uses associationKey if present)
292      * @return {Mixed} The root
293      */
294     getAssociatedDataRoot: function(data, associationName) {
295         return Ext.DomQuery.select(associationName, data)[0];
296     },
297
298     /**
299      * Parses an XML document and returns a ResultSet containing the model instances
300      * @param {Object} doc Parsed XML document
301      * @return {Ext.data.ResultSet} The parsed result set
302      */
303     readRecords: function(doc) {
304         //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
305         if (Ext.isArray(doc)) {
306             doc = doc[0];
307         }
308         
309         /**
310          * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
311          * @property xmlData
312          * @type Object
313          */
314         this.xmlData = doc;
315         return this.callParent([doc]);
316     }
317 });