Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / src / data / ScriptTagProxy.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.data.ScriptTagProxy
9  * @extends Ext.data.DataProxy
10  * An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
11  * other than the originating domain of the running page.<br>
12  * <p>
13  * <b>Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
14  * of the running page, you must use this class, rather than HttpProxy.</b><br>
15  * <p>
16  * The content passed back from a server resource requested by a ScriptTagProxy <b>must</b> be executable JavaScript
17  * source code because it is used as the source inside a &lt;script> tag.<br>
18  * <p>
19  * In order for the browser to process the returned data, the server must wrap the data object
20  * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
21  * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
22  * depending on whether the callback name was passed:
23  * <p>
24  * <pre><code>
25 boolean scriptTag = false;
26 String cb = request.getParameter("callback");
27 if (cb != null) {
28     scriptTag = true;
29     response.setContentType("text/javascript");
30 } else {
31     response.setContentType("application/x-json");
32 }
33 Writer out = response.getWriter();
34 if (scriptTag) {
35     out.write(cb + "(");
36 }
37 out.print(dataBlock.toJsonString());
38 if (scriptTag) {
39     out.write(");");
40 }
41 </code></pre>
42  * <p>Below is a PHP example to do the same thing:</p><pre><code>
43 $callback = $_REQUEST['callback'];
44
45 // Create the output object.
46 $output = array('a' => 'Apple', 'b' => 'Banana');
47
48 //start output
49 if ($callback) {
50     header('Content-Type: text/javascript');
51     echo $callback . '(' . json_encode($output) . ');';
52 } else {
53     header('Content-Type: application/x-json');
54     echo json_encode($output);
55 }
56 </code></pre>
57  * <p>Below is the ASP.Net code to do the same thing:</p><pre><code>
58 String jsonString = "{success: true}";
59 String cb = Request.Params.Get("callback");
60 String responseString = "";
61 if (!String.IsNullOrEmpty(cb)) {
62     responseString = cb + "(" + jsonString + ")";
63 } else {
64     responseString = jsonString;
65 }
66 Response.Write(responseString);
67 </code></pre>
68  *
69  * @constructor
70  * @param {Object} config A configuration object.
71  */
72 Ext.data.ScriptTagProxy = function(config){
73     Ext.apply(this, config);
74
75     Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
76
77     this.head = document.getElementsByTagName("head")[0];
78
79     /**
80      * @event loadexception
81      * <b>Deprecated</b> in favor of 'exception' event.
82      * Fires if an exception occurs in the Proxy during data loading.  This event can be fired for one of two reasons:
83      * <ul><li><b>The load call timed out.</b>  This means the load callback did not execute within the time limit
84      * specified by {@link #timeout}.  In this case, this event will be raised and the
85      * fourth parameter (read error) will be null.</li>
86      * <li><b>The load succeeded but the reader could not read the response.</b>  This means the server returned
87      * data, but the configured Reader threw an error while reading the data.  In this case, this event will be
88      * raised and the caught error will be passed along as the fourth parameter of this event.</li></ul>
89      * Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
90      * on any Store instance.
91      * @param {Object} this
92      * @param {Object} options The loading options that were specified (see {@link #load} for details).  If the load
93      * call timed out, this parameter will be null.
94      * @param {Object} arg The callback's arg object passed to the {@link #load} function
95      * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
96      * If the remote request returns success: false, this parameter will be null.
97      */
98 };
99
100 Ext.data.ScriptTagProxy.TRANS_ID = 1000;
101
102 Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
103     /**
104      * @cfg {String} url The URL from which to request the data object.
105      */
106     /**
107      * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
108      */
109     timeout : 30000,
110     /**
111      * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
112      * the server the name of the callback function set up by the load call to process the returned data object.
113      * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
114      * javascript output which calls this named function passing the data object as its only parameter.
115      */
116     callbackParam : "callback",
117     /**
118      *  @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
119      * name to the request.
120      */
121     nocache : true,
122
123     /**
124      * HttpProxy implementation of DataProxy#doRequest
125      * @param {String} action
126      * @param {Ext.data.Record/Ext.data.Record[]} rs If action is <tt>read</tt>, rs will be null
127      * @param {Object} params An object containing properties which are to be used as HTTP parameters
128      * for the request to the remote server.
129      * @param {Ext.data.DataReader} reader The Reader object which converts the data
130      * object into a block of Ext.data.Records.
131      * @param {Function} callback The function into which to pass the block of Ext.data.Records.
132      * The function must be passed <ul>
133      * <li>The Record block object</li>
134      * <li>The "arg" argument from the load function</li>
135      * <li>A boolean success indicator</li>
136      * </ul>
137      * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
138      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
139      */
140     doRequest : function(action, rs, params, reader, callback, scope, arg) {
141         var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
142
143         var url = this.buildUrl(action, rs);
144         if (!url) {
145             throw new Ext.data.Api.Error('invalid-url', url);
146         }
147         url = Ext.urlAppend(url, p);
148
149         if(this.nocache){
150             url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
151         }
152         var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
153         var trans = {
154             id : transId,
155             action: action,
156             cb : "stcCallback"+transId,
157             scriptId : "stcScript"+transId,
158             params : params,
159             arg : arg,
160             url : url,
161             callback : callback,
162             scope : scope,
163             reader : reader
164         };
165         window[trans.cb] = this.createCallback(action, rs, trans);
166         url += String.format("&{0}={1}", this.callbackParam, trans.cb);
167         if(this.autoAbort !== false){
168             this.abort();
169         }
170
171         trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
172
173         var script = document.createElement("script");
174         script.setAttribute("src", url);
175         script.setAttribute("type", "text/javascript");
176         script.setAttribute("id", trans.scriptId);
177         this.head.appendChild(script);
178
179         this.trans = trans;
180     },
181
182     // @private createCallback
183     createCallback : function(action, rs, trans) {
184         var self = this;
185         return function(res) {
186             self.trans = false;
187             self.destroyTrans(trans, true);
188             if (action === Ext.data.Api.actions.read) {
189                 self.onRead.call(self, action, trans, res);
190             } else {
191                 self.onWrite.call(self, action, trans, res, rs);
192             }
193         };
194     },
195     /**
196      * Callback for read actions
197      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
198      * @param {Object} trans The request transaction object
199      * @param {Object} res The server response
200      * @protected
201      */
202     onRead : function(action, trans, res) {
203         var result;
204         try {
205             result = trans.reader.readRecords(res);
206         }catch(e){
207             // @deprecated: fire loadexception
208             this.fireEvent("loadexception", this, trans, res, e);
209
210             this.fireEvent('exception', this, 'response', action, trans, res, e);
211             trans.callback.call(trans.scope||window, null, trans.arg, false);
212             return;
213         }
214         if (result.success === false) {
215             // @deprecated: fire old loadexception for backwards-compat.
216             this.fireEvent('loadexception', this, trans, res);
217
218             this.fireEvent('exception', this, 'remote', action, trans, res, null);
219         } else {
220             this.fireEvent("load", this, res, trans.arg);
221         }
222         trans.callback.call(trans.scope||window, result, trans.arg, result.success);
223     },
224     /**
225      * Callback for write actions
226      * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
227      * @param {Object} trans The request transaction object
228      * @param {Object} res The server response
229      * @protected
230      */
231     onWrite : function(action, trans, response, rs) {
232         var reader = trans.reader;
233         try {
234             // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.
235             var res = reader.readResponse(action, response);
236         } catch (e) {
237             this.fireEvent('exception', this, 'response', action, trans, res, e);
238             trans.callback.call(trans.scope||window, null, res, false);
239             return;
240         }
241         if(!res.success === true){
242             this.fireEvent('exception', this, 'remote', action, trans, res, rs);
243             trans.callback.call(trans.scope||window, null, res, false);
244             return;
245         }
246         this.fireEvent("write", this, action, res.data, res, rs, trans.arg );
247         trans.callback.call(trans.scope||window, res.data, res, true);
248     },
249
250     // private
251     isLoading : function(){
252         return this.trans ? true : false;
253     },
254
255     /**
256      * Abort the current server request.
257      */
258     abort : function(){
259         if(this.isLoading()){
260             this.destroyTrans(this.trans);
261         }
262     },
263
264     // private
265     destroyTrans : function(trans, isLoaded){
266         this.head.removeChild(document.getElementById(trans.scriptId));
267         clearTimeout(trans.timeoutId);
268         if(isLoaded){
269             window[trans.cb] = undefined;
270             try{
271                 delete window[trans.cb];
272             }catch(e){}
273         }else{
274             // if hasn't been loaded, wait for load to remove it to prevent script error
275             window[trans.cb] = function(){
276                 window[trans.cb] = undefined;
277                 try{
278                     delete window[trans.cb];
279                 }catch(e){}
280             };
281         }
282     },
283
284     // private
285     handleFailure : function(trans){
286         this.trans = false;
287         this.destroyTrans(trans, false);
288         if (trans.action === Ext.data.Api.actions.read) {
289             // @deprecated firing loadexception
290             this.fireEvent("loadexception", this, null, trans.arg);
291         }
292
293         this.fireEvent('exception', this, 'response', trans.action, {
294             response: null,
295             options: trans.arg
296         });
297         trans.callback.call(trans.scope||window, null, trans.arg, false);
298     },
299
300     // inherit docs
301     destroy: function(){
302         this.abort();
303         Ext.data.ScriptTagProxy.superclass.destroy.call(this);
304     }
305 });