Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / data / proxy / Ajax.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.proxy.Ajax
18  * @extends Ext.data.proxy.Server
19  * 
20  * <p>AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to 
21  * load data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical
22  * setup. Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a 
23  * {@link Ext.data.Model Model}:</p>
24  * 
25 <pre><code>
26 Ext.define('User', {
27     extend: 'Ext.data.Model',
28     fields: ['id', 'name', 'email']
29 });
30
31 //The Store contains the AjaxProxy as an inline configuration
32 var store = new Ext.data.Store({
33     model: 'User',
34     proxy: {
35         type: 'ajax',
36         url : 'users.json'
37     }
38 });
39
40 store.load();
41 </code></pre>
42  * 
43  * <p>Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model}
44  * with the fields that we expect the server to return. Next we set up the Store itself, along with a {@link #proxy}
45  * configuration. This configuration was automatically turned into an Ext.data.proxy.Ajax instance, with the url we
46  * specified being passed into AjaxProxy's constructor. It's as if we'd done this:</p>
47  * 
48 <pre><code>
49 new Ext.data.proxy.Ajax({
50     url: 'users.json',
51     model: 'User',
52     reader: 'json'
53 });
54 </code></pre>
55  * 
56  * <p>A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default 
57  * when we create the proxy via the Store - the Store already knows about the Model, and Proxy's default 
58  * {@link Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.</p>
59  * 
60  * <p>Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
61  * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see {@link #actionMethods}
62  * to customize this - by default any kind of read will be sent as a GET request and any kind of write will be sent as a
63  * POST request).</p>
64  * 
65  * <p><u>Limitations</u></p>
66  * 
67  * <p>AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com
68  * it cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
69  * talking to each other via AJAX.</p>
70  * 
71  * <p>If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
72  * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
73  * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with 
74  * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
75  * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.</p>
76  * 
77  * <p><u>Readers and Writers</u></p>
78  * 
79  * <p>AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response. If
80  * no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader configuration
81  * can be passed in as a simple object, which the Proxy automatically turns into a {@link Ext.data.reader.Reader Reader}
82  * instance:</p>
83  * 
84 <pre><code>
85 var proxy = new Ext.data.proxy.Ajax({
86     model: 'User',
87     reader: {
88         type: 'xml',
89         root: 'users'
90     }
91 });
92
93 proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
94 </code></pre>
95  * 
96  * <p><u>Url generation</u></p>
97  * 
98  * <p>AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
99  * each request. These are controlled with the following configuration options:</p>
100  * 
101  * <ul style="list-style-type: disc; padding-left: 20px;">
102  *     <li>{@link #pageParam} - controls how the page number is sent to the server 
103  *     (see also {@link #startParam} and {@link #limitParam})</li>
104  *     <li>{@link #sortParam} - controls how sort information is sent to the server</li>
105  *     <li>{@link #groupParam} - controls how grouping information is sent to the server</li>
106  *     <li>{@link #filterParam} - controls how filter information is sent to the server</li>
107  * </ul>
108  * 
109  * <p>Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can 
110  * customize the generated urls, let's say we're loading the Proxy with the following Operation:</p>
111  * 
112 <pre><code>
113 var operation = new Ext.data.Operation({
114     action: 'read',
115     page  : 2
116 });
117 </code></pre>
118  * 
119  * <p>Now we'll issue the request for this Operation by calling {@link #read}:</p>
120  * 
121 <pre><code>
122 var proxy = new Ext.data.proxy.Ajax({
123     url: '/users'
124 });
125
126 proxy.read(operation); //GET /users?page=2
127 </code></pre>
128  * 
129  * <p>Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is
130  * sent to the server:</p>
131  * 
132 <pre><code>
133 var proxy = new Ext.data.proxy.Ajax({
134     url: '/users',
135     pagePage: 'pageNumber'
136 });
137
138 proxy.read(operation); //GET /users?pageNumber=2
139 </code></pre>
140  * 
141  * <p>Alternatively, our Operation could have been configured to send start and limit parameters instead of page:</p>
142  * 
143 <pre><code>
144 var operation = new Ext.data.Operation({
145     action: 'read',
146     start : 50,
147     limit : 25
148 });
149
150 var proxy = new Ext.data.proxy.Ajax({
151     url: '/users'
152 });
153
154 proxy.read(operation); //GET /users?start=50&limit=25
155 </code></pre>
156  * 
157  * <p>Again we can customize this url:</p>
158  * 
159 <pre><code>
160 var proxy = new Ext.data.proxy.Ajax({
161     url: '/users',
162     startParam: 'startIndex',
163     limitParam: 'limitIndex'
164 });
165
166 proxy.read(operation); //GET /users?startIndex=50&limitIndex=25
167 </code></pre>
168  * 
169  * <p>AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a
170  * more expressive Operation object:</p>
171  * 
172 <pre><code>
173 var operation = new Ext.data.Operation({
174     action: 'read',
175     sorters: [
176         new Ext.util.Sorter({
177             property : 'name',
178             direction: 'ASC'
179         }),
180         new Ext.util.Sorter({
181             property : 'age',
182             direction: 'DESC'
183         })
184     ],
185     filters: [
186         new Ext.util.Filter({
187             property: 'eyeColor',
188             value   : 'brown'
189         })
190     ]
191 });
192 </code></pre>
193  * 
194  * <p>This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters
195  * and filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like
196  * this (note that the url is escaped before sending the request, but is left unescaped here for clarity):</p>
197  * 
198 <pre><code>
199 var proxy = new Ext.data.proxy.Ajax({
200     url: '/users'
201 });
202
203 proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter=[{"property":"eyeColor","value":"brown"}]
204 </code></pre>
205  * 
206  * <p>We can again customize how this is created by supplying a few configuration options. Let's say our server is set 
207  * up to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
208  * that format like this:</p>
209  * 
210  <pre><code>
211  var proxy = new Ext.data.proxy.Ajax({
212      url: '/users',
213      sortParam: 'sortBy',
214      filterParam: 'filterBy',
215
216      //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
217      encodeSorters: function(sorters) {
218          var length   = sorters.length,
219              sortStrs = [],
220              sorter, i;
221
222          for (i = 0; i < length; i++) {
223              sorter = sorters[i];
224
225              sortStrs[i] = sorter.property + '#' + sorter.direction
226          }
227
228          return sortStrs.join(",");
229      }
230  });
231
232  proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy=[{"property":"eyeColor","value":"brown"}]
233  </code></pre>
234  * 
235  * <p>We can also provide a custom {@link #encodeFilters} function to encode our filters.</p>
236  * 
237  * @constructor
238  * 
239  * <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
240  * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
241  * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
242  * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
243  * used to pass parameters known at instantiation time.</p>
244  * 
245  * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
246  * the request.</p>
247  */
248 Ext.define('Ext.data.proxy.Ajax', {
249     requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
250     extend: 'Ext.data.proxy.Server',
251     alias: 'proxy.ajax',
252     alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
253     
254     /**
255      * @property actionMethods
256      * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions and 'POST' 
257      * for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the correct RESTful methods.
258      */
259     actionMethods: {
260         create : 'POST',
261         read   : 'GET',
262         update : 'POST',
263         destroy: 'POST'
264     },
265     
266     /**
267      * @cfg {Object} headers Any headers to add to the Ajax request. Defaults to <tt>undefined</tt>.
268      */
269     
270     /**
271      * @ignore
272      */
273     doRequest: function(operation, callback, scope) {
274         var writer  = this.getWriter(),
275             request = this.buildRequest(operation, callback, scope);
276             
277         if (operation.allowWrite()) {
278             request = writer.write(request);
279         }
280         
281         Ext.apply(request, {
282             headers       : this.headers,
283             timeout       : this.timeout,
284             scope         : this,
285             callback      : this.createRequestCallback(request, operation, callback, scope),
286             method        : this.getMethod(request),
287             disableCaching: false // explicitly set it to false, ServerProxy handles caching
288         });
289         
290         Ext.Ajax.request(request);
291         
292         return request;
293     },
294     
295     /**
296      * Returns the HTTP method name for a given request. By default this returns based on a lookup on {@link #actionMethods}.
297      * @param {Ext.data.Request} request The request object
298      * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
299      */
300     getMethod: function(request) {
301         return this.actionMethods[request.action];
302     },
303     
304     /**
305      * @private
306      * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
307      * of code duplication inside the returned function so we need to find a way to DRY this up.
308      * @param {Ext.data.Request} request The Request object
309      * @param {Ext.data.Operation} operation The Operation being executed
310      * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
311      * passed to doRequest
312      * @param {Object} scope The scope in which to execute the callback function
313      * @return {Function} The callback function
314      */
315     createRequestCallback: function(request, operation, callback, scope) {
316         var me = this;
317         
318         return function(options, success, response) {
319             me.processResponse(success, operation, request, response, callback, scope);
320         };
321     }
322 }, function() {
323     //backwards compatibility, remove in Ext JS 5.0
324     Ext.data.HttpProxy = this;
325 });
326