Upgrade to ExtJS 4.0.7 - Released 10/19/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 <a href="http://en.wikipedia.org/wiki/JSONP">here</a>.
20  */
21 Ext.define('Ext.data.JsonP', {
22
23     /* Begin Definitions */
24
25     singleton: true,
26
27     statics: {
28         requestCount: 0,
29         requests: {}
30     },
31
32     /* End Definitions */
33
34     /**
35      * @property timeout
36      * @type Number
37      * A default timeout for any JsonP requests. If the request has not completed in this time the
38      * failure callback will be fired. The timeout is in ms. Defaults to <tt>30000</tt>.
39      */
40     timeout: 30000,
41
42     /**
43      * @property disableCaching
44      * @type Boolean
45      * True to add a unique cache-buster param to requests. Defaults to <tt>true</tt>.
46      */
47     disableCaching: true,
48
49     /**
50      * @property disableCachingParam
51      * @type String
52      * Change the parameter which is sent went disabling caching through a cache buster. Defaults to <tt>'_dc'</tt>.
53      */
54     disableCachingParam: '_dc',
55
56     /**
57      * @property callbackKey
58      * @type String
59      * Specifies the GET parameter that will be sent to the server containing the function name to be executed when
60      * the request completes. Defaults to <tt>callback</tt>. Thus, a common request will be in the form of
61      * url?callback=Ext.data.JsonP.callback1
62      */
63     callbackKey: 'callback',
64
65     /**
66      * Makes a JSONP request.
67      * @param {Object} options An object which may contain the following properties. Note that options will
68      * take priority over any defaults that are specified in the class.
69      * <ul>
70      * <li><b>url</b> : String <div class="sub-desc">The URL to request.</div></li>
71      * <li><b>params</b> : Object (Optional)<div class="sub-desc">An object containing a series of
72      * key value pairs that will be sent along with the request.</div></li>
73      * <li><b>timeout</b> : Number (Optional) <div class="sub-desc">See {@link #timeout}</div></li>
74      * <li><b>callbackKey</b> : String (Optional) <div class="sub-desc">See {@link #callbackKey}</div></li>
75      * <li><b>callbackName</b> : String (Optional) <div class="sub-desc">The function name to use for this request.
76      * By default this name will be auto-generated: Ext.data.JsonP.callback1, Ext.data.JsonP.callback2, etc.
77      * Setting this option to "my_name" will force the function name to be Ext.data.JsonP.my_name.
78      * Use this if you want deterministic behavior, but be careful - the callbackName should be different
79      * in each JsonP request that you make.</div></li>
80      * <li><b>disableCaching</b> : Boolean (Optional) <div class="sub-desc">See {@link #disableCaching}</div></li>
81      * <li><b>disableCachingParam</b> : String (Optional) <div class="sub-desc">See {@link #disableCachingParam}</div></li>
82      * <li><b>success</b> : Function (Optional) <div class="sub-desc">A function to execute if the request succeeds.</div></li>
83      * <li><b>failure</b> : Function (Optional) <div class="sub-desc">A function to execute if the request fails.</div></li>
84      * <li><b>callback</b> : Function (Optional) <div class="sub-desc">A function to execute when the request
85      * completes, whether it is a success or failure.</div></li>
86      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
87      * which to execute the callbacks: The "this" object for the callback function. Defaults to the browser window.</div></li>
88      * </ul>
89      * @return {Object} request An object containing the request details.
90      */
91     request: function(options){
92         options = Ext.apply({}, options);
93
94         //<debug>
95         if (!options.url) {
96             Ext.Error.raise('A url must be specified for a JSONP request.');
97         }
98         //</debug>
99
100         var me = this,
101             disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
102             cacheParam = options.disableCachingParam || me.disableCachingParam,
103             id = ++me.statics().requestCount,
104             callbackName = options.callbackName || 'callback' + id,
105             callbackKey = options.callbackKey || me.callbackKey,
106             timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
107             params = Ext.apply({}, options.params),
108             url = options.url,
109             name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
110             request,
111             script;
112
113         params[callbackKey] = name + '.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