-
20 Mar 2012 4:59 PM #1
Setting the callbackName property of the Ext.data.JsonP class to any name
Setting the callbackName property of the Ext.data.JsonP class to any name
Hi Sencha,
firstly congratulations on doing a great job with Sencha Touch 2. The framework is quite amazing.
I noticed that when specifying the callbackName property of the Ext.data.JsonP class the callback name is set to Ext.data.JsonP.MyCallbackName
This configuration means the data being returned from the http request must be in the format of Ext.data.JsonP.MyCallbackName({"SomeData":101954}) and any other formats will fail to execute the callback script, e.g . MyCallbackName({"SomeData":101954})
To be truly configurable it would be great if you could modify the Ext.data.JsonP class so that the exact callbackName specified is configured as the callback name in the script tag url.
Below is my modified version of the Ext.data.JsonP class that attempts to solve this issue. Edits are in green font.
Thanks
Francesco
Code:Ext.define('Ext.data.JsonP', { alternateClassName: 'Ext.util.JSONP', /* Begin Definitions */ singleton: true, statics: { requestCount: 0, requests: {} }, /* End Definitions */ /** * @property {Number} [timeout=30000] * A default timeout for any JsonP requests. If the request has not completed in this time the failure callback will * be fired. The timeout is in ms. Defaults to 30000. */ timeout: 30000, /** * @property {Boolean} [disableCaching=true] * True to add a unique cache-buster param to requests. Defaults to true. */ disableCaching: true, /** * @property {String} [disableCachingParam="_dc"] * Change the parameter which is sent went disabling caching through a cache buster. Defaults to '_dc'. */ disableCachingParam: '_dc', /** * @property {String} [callbackKey="callback"] * Specifies the GET parameter that will be sent to the server containing the function name to be executed when the * request completes. Defaults to callback. Thus, a common request will be in the form of * url?callback=Ext.data.JsonP.callback1 */ callbackKey: 'callback', /** * Makes a JSONP request. * @param {Object} options An object which may contain the following properties. Note that options will take * priority over any defaults that are specified in the class. * * @param {String} options.url The URL to request. * @param {Object} [options.params] An object containing a series of key value pairs that will be sent along with the request. * @param {Number} [options.timeout] See {@link #timeout} * @param {String} [options.callbackKey] See {@link #callbackKey} * @param {String} [options.callbackName] See {@link #callbackKey} * The function name to use for this request. By default this name will be auto-generated: Ext.data.JsonP.callback1, * Ext.data.JsonP.callback2, etc. Setting this option to "my_name" will force the function name to be * Ext.data.JsonP.my_name. Use this if you want deterministic behavior, but be careful - the callbackName should be * different in each JsonP request that you make. * @param {Boolean} [options.disableCaching] See {@link #disableCaching} * @param {String} [options.disableCachingParam] See {@link #disableCachingParam} * @param {Function} [options.success] A function to execute if the request succeeds. * @param {Function} [options.failure] A function to execute if the request fails. * @param {Function} [options.callback] A function to execute when the request completes, whether it is a success or failure. * @param {Object} [options.scope] The scope in which to execute the callbacks: The "this" object for the * callback function. Defaults to the browser window. * * @return {Object} request An object containing the request details. */ request: function(options){ options = Ext.apply({}, options); //<debug> if (!options.url) { Ext.Logger.error('A url must be specified for a JSONP request.'); } //</debug> var me = this, disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching, cacheParam = options.disableCachingParam || me.disableCachingParam, id = ++me.statics().requestCount, callbackName = options.callbackName || 'callback' + id, callbackKey = options.callbackKey || me.callbackKey, timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout, params = Ext.apply({}, options.params), url = options.url, name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext', request, script; //Edited to handle custom callback params[callbackKey] = options.callbackName || name + '.data.JsonP.' + callbackName; if (disableCaching) { params[cacheParam] = new Date().getTime(); } script = me.createScript(url, params, options); me.statics().requests[id] = request = { url: url, params: params, script: script, id: id, scope: options.scope, success: options.success, failure: options.failure, callback: options.callback, callbackKey: callbackKey, callbackName: callbackName }; if (timeout > 0) { request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout); } me.setupErrorHandling(request); //Edited to handle custom callback if (options.callbackName){ window[callbackName] = Ext.bind(me.handleResponse, me, [request], true); }else{ me[callbackName] = Ext.bind(me.handleResponse, me, [request], true); } me.loadScript(request); return request; }, /** * Abort a request. If the request parameter is not specified all open requests will be aborted. * @param {Object/String} request The request to abort */ abort: function(request){ var requests = this.statics().requests, key; if (request) { if (!request.id) { request = requests[request]; } this.abort(request); } else { for (key in requests) { if (requests.hasOwnProperty(key)) { this.abort(requests[key]); } } } }, /** * Sets up error handling for the script * @private * @param {Object} request The request */ setupErrorHandling: function(request){ request.script.onerror = Ext.bind(this.handleError, this, [request]); }, /** * Handles any aborts when loading the script * @private * @param {Object} request The request */ handleAbort: function(request){ request.errorType = 'abort'; this.handleResponse(null, request); }, /** * Handles any script errors when loading the script * @private * @param {Object} request The request */ handleError: function(request){ request.errorType = 'error'; this.handleResponse(null, request); }, /** * Cleans up anu script handling errors * @private * @param {Object} request The request */ cleanupErrorHandling: function(request){ request.script.onerror = null; }, /** * Handle any script timeouts * @private * @param {Object} request The request */ handleTimeout: function(request){ request.errorType = 'timeout'; this.handleResponse(null, request); }, /** * Handle a successful response * @private * @param {Object} result The result from the request * @param {Object} request The request */ handleResponse: function(result, request){ var success = true; if (request.timeout) { clearTimeout(request.timeout); } delete this[request.callbackName]; delete this.statics()[request.id]; this.cleanupErrorHandling(request); Ext.fly(request.script).destroy(); if (request.errorType) { success = false; Ext.callback(request.failure, request.scope, [request.errorType]); } else { Ext.callback(request.success, request.scope, [result]); } Ext.callback(request.callback, request.scope, [success, result, request.errorType]); }, /** * Create the script tag given the specified url, params and options. The options * parameter is passed to allow an override to access it. * @private * @param {String} url The url of the request * @param {Object} params Any extra params to be sent * @param {Object} options The object passed to {@link #request}. */ createScript: function(url, params, options) { var script = document.createElement('script'); script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params))); script.setAttribute("async", true); script.setAttribute("type", "text/javascript"); return script; }, /** * Loads the script for the given request by appending it to the HEAD element. This is * its own method so that users can override it (as well as {@link #createScript}). * @private * @param request The request object. */ loadScript: function (request) { Ext.getHead().appendChild(request.script); } });
-
21 Mar 2012 5:56 AM #2Sencha - Senior Forum Manager
- Join Date
- Mar 2007
- Location
- St. Louis, MO
- Posts
- 33,684
- Vote Rating
- 435
The docs do explain this and maybe there is a good reason why we do that but I can't think of one. I'm sure Jamie or Tommy will weigh in.
Mitchell Simoens @SenchaMitch
Sencha Inc, Senior Forum Manager
________________
http://www.JSONPLint.com - Source to lint your JSONP!
Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
https://github.com/mitchellsimoens
Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/
Need more help with your app? Hire Sencha Services services@sencha.com
Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is almost in print!
When posting code, please use BBCode's CODE tags.
-
21 Mar 2012 9:19 PM #3
Hi Mitchell,
thanks for looking at this. There's a jQuery plugin that also achieves this: http://code.google.com/p/jquery-jsonp/ I've been using this technique for a few years now with no noticeable issues.Will be interesting to see what the others think of this technique.
cheers
Francesco
-
22 Mar 2012 3:23 AM #4
Similar Issues
Similar Issues
I have been struggling with exactly the same issue, over the past few days with the main version of ExtJS and would like to second these comments.
Francesco, how are you passing the callbackName into your updated code from the store configuration?
Thanks
David
-
22 Mar 2012 4:26 AM #5Sencha - Senior Forum Manager
- Join Date
- Mar 2007
- Location
- St. Louis, MO
- Posts
- 33,684
- Vote Rating
- 435
Personally I would think if you specify a callback name it should use it and not change it but if you don't then you get the generated one. This is how other things in the framework work like id. If you specify it, we won't touch it but if you don't then we will create one for you. On thing to note is that I'm sure the callback needs to be unique for each request and the reason we add a number to the end of that callbackName.
Mitchell Simoens @SenchaMitch
Sencha Inc, Senior Forum Manager
________________
http://www.JSONPLint.com - Source to lint your JSONP!
Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
https://github.com/mitchellsimoens
Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/
Need more help with your app? Hire Sencha Services services@sencha.com
Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is almost in print!
When posting code, please use BBCode's CODE tags.
-
22 Mar 2012 6:36 PM #6
Hi David,
I don't use the data stores in my application, at the moment I access my data directly using Ext.data.JsonP.request or Ext.Ajax.request and render the results to a template.
I haven't looked into this and I'm sure Mitchell will be able to guide you but I'd imagine that If you wanted to use the modified version of the class Ext.data.JsonP included at the beginning of this discussion in a store you'd have to either create a new version of the class Ext.data.Proxy.JsonP and modify it so that it uses your modified version of the Ext.data.JsonP class. Then in your store you'd refer (and perhaps add a requires) to your new proxy class in the config. Eg:
hope this helpsCode:var store =Ext.create('Ext.data.Store',{ model:'User', proxy:{ type:'jsonp2', url :'http://domainB.com/users'}});
cheers
Francesco
-
25 Oct 2012 3:14 PM #7
I find myself needing a similar solution for ExtJS 4.1.1a
I find myself needing a similar solution for ExtJS 4.1.1a
This thread is from May...this capability has not been exposed in ExtJS 4.1.1a (the ability to provide a custom callback name).
I find that I need it because the remote service I'm working with mistakenly thinks that "Ext.data.Json.callback1" is NOT a valid function name.
I've reported the bug, but it may never be fixed, so I need a local solution.
Do I need to create my own X.data.JsonP and X.data.proxy.JsonP classes to implement this functionality?
Thanks.
-
25 Oct 2012 3:30 PM #8
Hello,
basically I copied Sencha's implementation of Ext.data.JsonP and modified to handle custom callback names to create MyProject.util.JsonP. Then I use my custom implementation to call the JsonP endpoint eg:
Code:Ext.define('MyProject.util.Data', { requires: [ 'Ext.MessageBox' ,'Ext.data.Connection' ,'Ext.Ajax' ,'MyProject.util.JsonP' ], singleton : true, config: { }, constructor : function(config) { this.callParent(arguments); this.initConfig(config); }, requestJsonP : function(options){ options = Ext.apply({}, options); options.successCallback = Ext.isDefined(options.successCallback) ? options.successCallback : Ext.emptyFn; options.failureCallback = Ext.isDefined(options.failureCallback) ? options.failureCallback : this.onError; options.callback = Ext.isDefined(options.callback) ? options.callback : Ext.emptyFn; options.scope = Ext.isDefined(options.scope) ? options.scope : this; // Make the JsonP request MyProject.util.JsonP.request({ url: options.url, disableCaching : true, timeout : 60000, scope : options.scope, callbackKey: options.callbackKey || 'callback', callbackName : options.callbackName, params: Ext.apply({}, options.params), success: function(result) { options.successCallback.call(options.scope, result) }, failure : function(result){ options.failureCallback.call(options.scope, result) }, callback : function(result){ options.callback.call(options.scope, result) } }); } });Hope this helpsCode:Ext.define('MyProject.util.JsonP', { alternateClassName: 'MyProject.util.JsonP', /* Begin Definitions */ singleton: true, statics: { requestCount: 0, requests: {} }, /* End Definitions */ /** * @property {Number} [timeout=30000] * A default timeout for any JsonP requests. If the request has not completed in this time the failure callback will * be fired. The timeout is in ms. Defaults to 30000. */ timeout: 30000, /** * @property {Boolean} [disableCaching=true] * True to add a unique cache-buster param to requests. Defaults to true. */ disableCaching: true, /** * @property {String} [disableCachingParam="_dc"] * Change the parameter which is sent went disabling caching through a cache buster. Defaults to '_dc'. */ disableCachingParam: '_dc', /** * @property {String} [callbackKey="callback"] * Specifies the GET parameter that will be sent to the server containing the function name to be executed when the * request completes. Defaults to callback. Thus, a common request will be in the form of * url?callback=Ext.data.JsonP.callback1 */ callbackKey: 'callback', /** * Makes a JSONP request. * @param {Object} options An object which may contain the following properties. Note that options will take * priority over any defaults that are specified in the class. * * @param {String} options.url The URL to request. * @param {Object} [options.params] An object containing a series of key value pairs that will be sent along with the request. * @param {Number} [options.timeout] See {@link #timeout} * @param {String} [options.callbackKey] See {@link #callbackKey} * @param {String} [options.callbackName] See {@link #callbackKey} * The function name to use for this request. By default this name will be auto-generated: Ext.data.JsonP.callback1, * Ext.data.JsonP.callback2, etc. Setting this option to "my_name" will force the function name to be * Ext.data.JsonP.my_name. Use this if you want deterministic behavior, but be careful - the callbackName should be * different in each JsonP request that you make. * @param {Boolean} [options.disableCaching] See {@link #disableCaching} * @param {String} [options.disableCachingParam] See {@link #disableCachingParam} * @param {Function} [options.success] A function to execute if the request succeeds. * @param {Function} [options.failure] A function to execute if the request fails. * @param {Function} [options.callback] A function to execute when the request completes, whether it is a success or failure. * @param {Object} [options.scope] The scope in which to execute the callbacks: The "this" object for the * callback function. Defaults to the browser window. * * @return {Object} request An object containing the request details. */ request: function(options){ options = Ext.apply({}, options); //<debug> if (!options.url) { Ext.Logger.error('A url must be specified for a JSONP request.'); } //</debug> var me = this, disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching, cacheParam = options.disableCachingParam || me.disableCachingParam, id = ++me.statics().requestCount, callbackName = options.callbackName || 'callback' + id, callbackKey = options.callbackKey || me.callbackKey, timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout, params = Ext.apply({}, options.params), url = options.url, name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext', request, script; //Francesco - edited to handle custom callback params[callbackKey] = options.callbackName || 'MyProject' + '.util.JsonP.' + callbackName; if (disableCaching) { params[cacheParam] = new Date().getTime(); } script = me.createScript(url, params, options); me.statics().requests[id] = request = { url: url, params: params, script: script, id: id, scope: options.scope, success: options.success, failure: options.failure, callback: options.callback, callbackKey: callbackKey, callbackName: callbackName }; if (timeout > 0) { request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout); } me.setupErrorHandling(request); //Francesco - edited to handle custom callback if (options.callbackName){ window[callbackName] = Ext.bind(me.handleResponse, me, [request], true); }else{ me[callbackName] = Ext.bind(me.handleResponse, me, [request], true); } me.loadScript(request); return request; }, /** * Abort a request. If the request parameter is not specified all open requests will be aborted. * @param {Object/String} request The request to abort */ abort: function(request){ var requests = this.statics().requests, key; if (request) { if (!request.id) { request = requests[request]; } this.abort(request); } else { for (key in requests) { if (requests.hasOwnProperty(key)) { this.abort(requests[key]); } } } }, /** * Sets up error handling for the script * @private * @param {Object} request The request */ setupErrorHandling: function(request){ request.script.onerror = Ext.bind(this.handleError, this, [request]); }, /** * Handles any aborts when loading the script * @private * @param {Object} request The request */ handleAbort: function(request){ request.errorType = 'abort'; this.handleResponse(null, request); }, /** * Handles any script errors when loading the script * @private * @param {Object} request The request */ handleError: function(request){ request.errorType = 'error'; this.handleResponse(null, request); }, /** * Cleans up anu script handling errors * @private * @param {Object} request The request */ cleanupErrorHandling: function(request){ request.script.onerror = null; }, /** * Handle any script timeouts * @private * @param {Object} request The request */ handleTimeout: function(request){ request.errorType = 'timeout'; this.handleResponse(null, request); }, /** * Handle a successful response * @private * @param {Object} result The result from the request * @param {Object} request The request */ handleResponse: function(result, request){ var success = true; if (request.timeout) { clearTimeout(request.timeout); } delete this[request.callbackName]; delete this.statics()[request.id]; this.cleanupErrorHandling(request); Ext.fly(request.script).destroy(); if (request.errorType) { success = false; Ext.callback(request.failure, request.scope, [request.errorType]); } else { Ext.callback(request.success, request.scope, [result]); } Ext.callback(request.callback, request.scope, [success, result, request.errorType]); }, /** * Create the script tag given the specified url, params and options. The options * parameter is passed to allow an override to access it. * @private * @param {String} url The url of the request * @param {Object} params Any extra params to be sent * @param {Object} options The object passed to {@link #request}. */ createScript: function(url, params, options) { var script = document.createElement('script'); script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params))); script.setAttribute("async", true); script.setAttribute("type", "text/javascript"); return script; }, /** * Loads the script for the given request by appending it to the HEAD element. This is * its own method so that users can override it (as well as {@link #createScript}). * @private * @param request The request object. */ loadScript: function (request) { Ext.getHead().appendChild(request.script); } });
cheers
Francesco
-
25 Oct 2012 3:55 PM #9
Thanks Francesco.
I was hoping that they'd fixed the problem in the source...doesn't seem like it should be that hard to do
Thanks again!
-Chris
-
28 Oct 2012 3:40 AM #10
i solved this by modifying the minified javascript
//params[callbackKey] = name + '.data.JsonP.' + callbackName; //drew
params[callbackKey] = callbackName;
not that the above is minified but you get the idea.
i have a php framework that would supernova with something like url?callback=Ext.data.JsonP.callback1
You found a bug! We've classified it as
TOUCH-2541
.
We encourage you to continue the discussion and to find an acceptable workaround while we work on a permanent fix.


Reply With Quote