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.
  1. #1
    Sencha User
    Join Date
    Dec 2010
    Posts
    23
    Vote Rating
    0
    francescosyd is on a distinguished road

      0  

    Default 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);
        }
    });

  2. #2
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,347
    Vote Rating
    846
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    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
    ________________
    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 in print!

    When posting code, please use BBCode's CODE tags.

  3. #3
    Sencha User
    Join Date
    Dec 2010
    Posts
    23
    Vote Rating
    0
    francescosyd is on a distinguished road

      0  

    Default


    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

  4. #4
    Sencha User
    Join Date
    Jan 2012
    Posts
    1
    Vote Rating
    0
    dsmaycock is on a distinguished road

      0  

    Default 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

  5. #5
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,347
    Vote Rating
    846
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    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
    ________________
    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 in print!

    When posting code, please use BBCode's CODE tags.

  6. #6
    Sencha User
    Join Date
    Dec 2010
    Posts
    23
    Vote Rating
    0
    francescosyd is on a distinguished road

      0  

    Default


    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:

    Code:
    var store =Ext.create('Ext.data.Store',{    model:'User',    proxy:{        type:'jsonp2',        url :'http://domainB.com/users'}});
    hope this helps

    cheers

    Francesco

  7. #7
    Ext JS Premium Member cmeans's Avatar
    Join Date
    Jun 2010
    Location
    Chicago, IL USA
    Posts
    112
    Vote Rating
    7
    cmeans is on a distinguished road

      0  

    Default 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.

  8. #8
    Sencha User
    Join Date
    Dec 2010
    Posts
    23
    Vote Rating
    0
    francescosyd is on a distinguished road

      0  

    Default


    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)
                }
            });
    	}
    });
    Code:
    
    
    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);
        }
    });
    Hope this helps

    cheers

    Francesco

  9. #9
    Ext JS Premium Member cmeans's Avatar
    Join Date
    Jun 2010
    Location
    Chicago, IL USA
    Posts
    112
    Vote Rating
    7
    cmeans is on a distinguished road

      0  

    Default


    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

  10. #10
    Sencha User
    Join Date
    Mar 2011
    Location
    Boston MA
    Posts
    2
    Vote Rating
    0
    DrewPierce is on a distinguished road

      0  

    Default


    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