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