/** * @author Ed Spencer * @class Ext.data.proxy.JsonP * @extends Ext.data.proxy.Server * * <p>JsonPProxy is useful when you need to load data from a domain other than the one your application is running * on. If your application is running on http://domainA.com it cannot use {@link Ext.data.proxy.Ajax Ajax} to load its * data from http://domainB.com because cross-domain ajax requests are prohibited by the browser.</p> * * <p>We can get around this using a JsonPProxy. JsonPProxy injects a <script> tag into the DOM whenever * an AJAX request would usually be made. Let's say we want to load data from http://domainB.com/users - the script tag * that would be injected might look like this:</p> * <pre><code> <script src="http://domainB.com/users?callback=someCallback"></script> </code></pre> * * <p>When we inject the tag above, the browser makes a request to that url and includes the response as if it was any * other type of JavaScript include. By passing a callback in the url above, we're telling domainB's server that we * want to be notified when the result comes in and that it should call our callback function with the data it sends * back. So long as the server formats the response to look like this, everything will work:</p> * <pre><code> someCallback({ users: [ { id: 1, name: "Ed Spencer", email: "ed@sencha.com" } ] }); </code></pre> * * <p>As soon as the script finishes loading, the 'someCallback' function that we passed in the url is called with the * JSON object that the server returned.</p> * * <p>JsonPProxy takes care of all of this automatically. It formats the url you pass, adding the callback * parameter automatically. It even creates a temporary callback function, waits for it to be called and then puts * the data into the Proxy making it look just like you loaded it through a normal {@link Ext.data.proxy.Ajax AjaxProxy}. * Here's how we might set that up:</p> * <pre><code> Ext.define('User', { extend: 'Ext.data.Model', fields: ['id', 'name', 'email'] }); var store = new Ext.data.Store({ model: 'User', proxy: { type: 'jsonp', url : 'http://domainB.com/users' } }); store.load(); </code></pre> * * <p>That's all we need to do - JsonPProxy takes care of the rest. In this case the Proxy will have injected a * script tag like this: * <pre><code> <script src="http://domainB.com/users?callback=stcCallback001" id="stcScript001"></script> </code></pre> * * <p><u>Customization</u></p> * * <p>Most parts of this script tag can be customized using the {@link #callbackParam}, {@link #callbackPrefix} and * {@link #scriptIdPrefix} configurations. For example: * <pre><code> var store = new Ext.data.Store({ model: 'User', proxy: { type: 'jsonp', url : 'http://domainB.com/users', callbackParam: 'theCallbackFunction', callbackPrefix: 'ABC', scriptIdPrefix: 'injectedScript' } }); store.load(); </code></pre> * * <p>Would inject a script tag like this:</p> * <pre><code> <script src="http://domainB.com/users?theCallbackFunction=ABC001" id="injectedScript001"></script> </code></pre> * * <p><u>Implementing on the server side</u></p> * * <p>The remote server side needs to be configured to return data in this format. Here are suggestions for how you * might achieve this using Java, PHP and ASP.net:</p> * * <p>Java:</p> * <pre><code> boolean jsonP = false; String cb = request.getParameter("callback"); if (cb != null) { jsonP = true; response.setContentType("text/javascript"); } else { response.setContentType("application/x-json"); } Writer out = response.getWriter(); if (jsonP) { out.write(cb + "("); } out.print(dataBlock.toJsonString()); if (jsonP) { out.write(");"); } </code></pre> * * <p>PHP:</p> * <pre><code> $callback = $_REQUEST['callback']; // Create the output object. $output = array('a' => 'Apple', 'b' => 'Banana'); //start output if ($callback) { header('Content-Type: text/javascript'); echo $callback . '(' . json_encode($output) . ');'; } else { header('Content-Type: application/x-json'); echo json_encode($output); } </code></pre> * * <p>ASP.net:</p> * <pre><code> String jsonString = "{success: true}"; String cb = Request.Params.Get("callback"); String responseString = ""; if (!String.IsNullOrEmpty(cb)) { responseString = cb + "(" + jsonString + ")"; } else { responseString = jsonString; } Response.Write(responseString); </code></pre> * */ Ext.define('Ext.data.proxy.JsonP', { extend: 'Ext.data.proxy.Server', alternateClassName: 'Ext.data.ScriptTagProxy', alias: ['proxy.jsonp', 'proxy.scripttag'], requires: ['Ext.data.JsonP'], defaultWriterType: 'base', /** * @cfg {String} callbackKey (Optional) See {@link Ext.data.JsonP#callbackKey}. */ callbackKey : 'callback', /** * @cfg {String} recordParam * The param name to use when passing records to the server (e.g. 'records=someEncodedRecordString'). * Defaults to 'records' */ recordParam: 'records', /** * @cfg {Boolean} autoAppendParams True to automatically append the request's params to the generated url. Defaults to true */ autoAppendParams: true, constructor: function(){ this.addEvents( /** * @event exception * Fires when the server returns an exception * @param {Ext.data.proxy.Proxy} this * @param {Ext.data.Request} request The request that was sent * @param {Ext.data.Operation} operation The operation that triggered the request */ 'exception' ); this.callParent(arguments); }, /** * @private * Performs the read request to the remote domain. JsonPProxy does not actually create an Ajax request, * instead we write out a <script> tag based on the configuration of the internal Ext.data.Request object * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute * @param {Function} callback A callback function to execute when the Operation has been completed * @param {Object} scope The scope to execute the callback in */ doRequest: function(operation, callback, scope) { //generate the unique IDs for this request var me = this, writer = me.getWriter(), request = me.buildRequest(operation), params = request.params; if (operation.allowWrite()) { request = writer.write(request); } //apply JsonPProxy-specific attributes to the Request Ext.apply(request, { callbackKey: me.callbackKey, timeout: me.timeout, scope: me, disableCaching: false, // handled by the proxy callback: me.createRequestCallback(request, operation, callback, scope) }); // prevent doubling up if (me.autoAppendParams) { request.params = {}; } request.jsonp = Ext.data.JsonP.request(request); // restore on the request request.params = params; operation.setStarted(); me.lastRequest = request; return request; }, /** * @private * Creates and returns the function that is called when the request has completed. The returned function * should accept a Response object, which contains the response to be read by the configured Reader. * The third argument is the callback that should be called after the request has been completed and the Reader has decoded * the response. This callback will typically be the callback passed by a store, e.g. in proxy.read(operation, theCallback, scope) * theCallback refers to the callback argument received by this function. * See {@link #doRequest} for details. * @param {Ext.data.Request} request The Request object * @param {Ext.data.Operation} operation The Operation being executed * @param {Function} callback The callback function to be called when the request completes. This is usually the callback * passed to doRequest * @param {Object} scope The scope in which to execute the callback function * @return {Function} The callback function */ createRequestCallback: function(request, operation, callback, scope) { var me = this; return function(success, response, errorType) { delete me.lastRequest; me.processResponse(success, operation, request, response, callback, scope); }; }, // inherit docs setException: function(operation, response) { operation.setException(operation.request.jsonp.errorType); }, /** * Generates a url based on a given Ext.data.Request object. Adds the params and callback function name to the url * @param {Ext.data.Request} request The request object * @return {String} The url */ buildUrl: function(request) { var me = this, url = me.callParent(arguments), params = Ext.apply({}, request.params), filters = params.filters, records, filter, i; delete params.filters; if (me.autoAppendParams) { url = Ext.urlAppend(url, Ext.Object.toQueryString(params)); } if (filters && filters.length) { for (i = 0; i < filters.length; i++) { filter = filters[i]; if (filter.value) { url = Ext.urlAppend(url, filter.property + "=" + filter.value); } } } //if there are any records present, append them to the url also records = request.records; if (Ext.isArray(records) && records.length > 0) { url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.recordParam, me.encodeRecords(records))); } return url; }, //inherit docs destroy: function() { this.abort(); this.callParent(); }, /** * Aborts the current server request if one is currently running */ abort: function() { var lastRequest = this.lastRequest; if (lastRequest) { Ext.data.JsonP.abort(lastRequest.jsonp); } }, /** * Encodes an array of records into a string suitable to be appended to the script src url. This is broken * out into its own function so that it can be easily overridden. * @param {Array} records The records array * @return {String} The encoded records string */ encodeRecords: function(records) { var encoded = "", i = 0, len = records.length; for (; i < len; i++) { encoded += Ext.Object.toQueryString(records[i].data); } return encoded; } });