Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / data / JsonP.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  * @class Ext.data.JsonP
17  * @singleton
18  * This class is used to create JSONP requests. JSONP is a mechanism that allows for making
19  * requests for data cross domain. More information is available here:
20  * http://en.wikipedia.org/wiki/JSONP
21  */
22 Ext.define('Ext.data.JsonP', {
23     
24     /* Begin Definitions */
25     
26     singleton: true,
27     
28     statics: {
29         requestCount: 0,
30         requests: {}
31     },
32     
33     /* End Definitions */
34     
35     /**
36      * @property timeout
37      * @type Number
38      * A default timeout for any JsonP requests. If the request has not completed in this time the
39      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
40      */
41     timeout: 30000,
42     
43     /**
44      * @property disableCaching
45      * @type Boolean
46      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
47      */
48     disableCaching: true,
49    
50     /**
51      * @property disableCachingParam 
52      * @type String
53      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
54      */
55     disableCachingParam: '_dc',
56    
57     /**
58      * @property callbackKey
59      * @type String
60      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
61      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
62      * url?callback=Ext.data.JsonP.callback1
63      */
64     callbackKey: 'callback',
65    
66     /**
67      * Makes a JSONP request.
68      * @param {Object} options An object which may contain the following properties. Note that options will
69      * take priority over any defaults that are specified in the class.
70      * <ul>
71      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
72      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
73      * key value pairs that will be sent along with the request.</div></li>
74      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
75      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
76      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
77      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
78      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
79      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
80      * in each JsonP request that you make.</div></li>
81      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
82      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
83      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
84      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
85      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request 
86      * completes, whether it is a success or failure.</div></li>
87      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
88      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
89      * </ul>
90      * @return {Object} request An object containing the request details.
91      */
92     request: function(options){
93         options = Ext.apply({}, options);
94        
95         //<debug>
96         if (!options.url) {
97             Ext.Error.raise('A url must be specified for a JSONP request.');
98         }
99         //</debug>
100         
101         var me = this, 
102             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching, 
103             cacheParam = options.disableCachingParam || me.disableCachingParam, 
104             id = ++me.statics().requestCount, 
105             callbackName = options.callbackName || 'callback' + id, 
106             callbackKey = options.callbackKey || me.callbackKey, 
107             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout, 
108             params = Ext.apply({}, options.params), 
109             url = options.url,
110             request, 
111             script;
112             
113         params[callbackKey] = 'Ext.data.JsonP.' + callbackName;
114         if (disableCaching) {
115             params[cacheParam] = new Date().getTime();
116         }
117         
118         script = me.createScript(url, params);
119         
120         me.statics().requests[id] = request = {
121             url: url,
122             params: params,
123             script: script,
124             id: id,
125             scope: options.scope,
126             success: options.success,
127             failure: options.failure,
128             callback: options.callback,
129             callbackName: callbackName
130         };
131         
132         if (timeout > 0) {
133             request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
134         }
135         
136         me.setupErrorHandling(request);
137         me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
138         Ext.getHead().appendChild(script);
139         return request;
140     },
141     
142     /**
143      * Abort a request. If the request parameter is not specified all open requests will
144      * be aborted.
145      * @param {Object/String} request (Optional) The request to abort
146      */
147     abort: function(request){
148         var requests = this.statics().requests,
149             key;
150             
151         if (request) {
152             if (!request.id) {
153                 request = requests[request];
154             }
155             this.abort(request);
156         } else {
157             for (key in requests) {
158                 if (requests.hasOwnProperty(key)) {
159                     this.abort(requests[key]);
160                 }
161             }
162         }
163     },
164     
165     /**
166      * Sets up error handling for the script
167      * @private
168      * @param {Object} request The request
169      */
170     setupErrorHandling: function(request){
171         request.script.onerror = Ext.bind(this.handleError, this, [request]);
172     },
173     
174     /**
175      * Handles any aborts when loading the script
176      * @private
177      * @param {Object} request The request
178      */
179     handleAbort: function(request){
180         request.errorType = 'abort';
181         this.handleResponse(null, request);
182     },
183     
184     /**
185      * Handles any script errors when loading the script
186      * @private
187      * @param {Object} request The request
188      */
189     handleError: function(request){
190         request.errorType = 'error';
191         this.handleResponse(null, request);
192     },
193  
194     /**
195      * Cleans up anu script handling errors
196      * @private
197      * @param {Object} request The request
198      */
199     cleanupErrorHandling: function(request){
200         request.script.onerror = null;
201     },
202  
203     /**
204      * Handle any script timeouts
205      * @private
206      * @param {Object} request The request
207      */
208     handleTimeout: function(request){
209         request.errorType = 'timeout';
210         this.handleResponse(null, request);
211     },
212  
213     /**
214      * Handle a successful response
215      * @private
216      * @param {Object} result The result from the request
217      * @param {Object} request The request
218      */
219     handleResponse: function(result, request){
220  
221         var success = true;
222  
223         if (request.timeout) {
224             clearTimeout(request.timeout);
225         }
226         delete this[request.callbackName];
227         delete this.statics()[request.id];
228         this.cleanupErrorHandling(request);
229         Ext.fly(request.script).remove();
230  
231         if (request.errorType) {
232             success = false;
233             Ext.callback(request.failure, request.scope, [request.errorType]);
234         } else {
235             Ext.callback(request.success, request.scope, [result]);
236         }
237         Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
238     },
239     
240     /**
241      * Create the script tag
242      * @private
243      * @param {String} url The url of the request
244      * @param {Object} params Any extra params to be sent
245      */
246     createScript: function(url, params) {
247         var script = document.createElement('script');
248         script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
249         script.setAttribute("async", true);
250         script.setAttribute("type", "text/javascript");
251         return script;
252     }
253 });
254