@qooxdoo/framework
Version:
The JS Framework for Coders
326 lines (273 loc) • 9.03 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2011 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Tristan Koch (tristankoch)
************************************************************************ */
/**
* A special script loader handling JSONP responses. Automatically
* provides callbacks and populates responseJson property.
*
* Example:
*
* <pre class="javascript">
* var req = new qx.bom.request.Jsonp();
*
* // Some services have a fixed callback name
* // req.setCallbackName("callback");
*
* req.onload = function() {
* // Handle data received
* req.responseJson;
* }
*
* req.open("GET", url);
* req.send();
* </pre>
*
* @require(qx.bom.request.Script#open)
* @require(qx.bom.request.Script#on)
* @require(qx.bom.request.Script#onreadystatechange)
* @require(qx.bom.request.Script#onload)
* @require(qx.bom.request.Script#onloadend)
* @require(qx.bom.request.Script#onerror)
* @require(qx.bom.request.Script#onabort)
* @require(qx.bom.request.Script#ontimeout)
* @require(qx.bom.request.Script#send)
*
* @group (IO)
*/
qx.Bootstrap.define("qx.bom.request.Jsonp",
{
extend : qx.bom.request.Script,
construct : function()
{
// Borrow super-class constructor
qx.bom.request.Script.apply(this);
this.__generateId();
},
members :
{
/**
* @type {Object} Parsed JSON response.
*/
responseJson: null,
/**
* @type {Number} Identifier of this instance.
*/
__id: null,
/**
* @type {String} Callback parameter.
*/
__callbackParam: null,
/**
* @type {String} Callback name.
*/
__callbackName: null,
/**
* @type {Boolean} Whether callback was called.
*/
__callbackCalled: null,
/**
* @type {Boolean} Whether a custom callback was created automatically.
*/
__customCallbackCreated: null,
/**
* @type {String} The generated URL for the current request
*/
__generatedUrl: null,
/**
* @type {Boolean} Whether request was disposed.
*/
__disposed: null,
/** Prefix used for the internal callback name. */
__prefix : "",
/**
* Initializes (prepares) request.
*
* @param method {String}
* The HTTP method to use.
* This parameter exists for compatibility reasons. The script transport
* does not support methods other than GET.
* @param url {String}
* The URL to which to send the request.
*/
open: function(method, url) {
if (this.__disposed) {
return;
}
var query = {},
callbackParam,
callbackName,
that = this;
// Reset properties that may have been set by previous request
this.responseJson = null;
this.__callbackCalled = false;
callbackParam = this.__callbackParam || "callback";
callbackName = this.__callbackName || this.__prefix +
"qx.bom.request.Jsonp." + this.__id + ".callback";
// Default callback
if (!this.__callbackName) {
// Store globally available reference to this object
this.constructor[this.__id] = this;
// Custom callback
} else {
// Dynamically create globally available callback (if it does not
// exist yet) with user defined name. Delegate to this object’s
// callback method.
if (!window[this.__callbackName]) {
this.__customCallbackCreated = true;
window[this.__callbackName] = function(data) {
that.callback(data);
};
} else {
if (qx.core.Environment.get("qx.debug.io")) {
qx.Bootstrap.debug(qx.bom.request.Jsonp, "Callback " +
this.__callbackName + " already exists");
}
}
}
if (qx.core.Environment.get("qx.debug.io")) {
qx.Bootstrap.debug(qx.bom.request.Jsonp,
"Expecting JavaScript response to call: " + callbackName);
}
query[callbackParam] = callbackName;
this.__generatedUrl = url = qx.util.Uri.appendParamsToUrl(url, query);
this.__callBase("open", [method, url]);
},
/**
* Callback provided for JSONP response to pass data.
*
* Called internally to populate responseJson property
* and indicate successful status.
*
* Note: If you write a custom callback you’ll need to call
* this method in order to notify the request about the data
* loaded. Writing a custom callback should not be necessary
* in most cases.
*
* @param data {Object} JSON
*/
callback: function(data) {
if (this.__disposed) {
return;
}
// Signal callback was called
this.__callbackCalled = true;
// Sanitize and parse
if (qx.core.Environment.get("qx.debug")) {
data = qx.lang.Json.stringify(data);
data = qx.lang.Json.parse(data);
}
// Set response
this.responseJson = data;
// Delete global reference to this
this.constructor[this.__id] = undefined;
this.__deleteCustomCallback();
},
/**
* Set callback parameter.
*
* Some JSONP services expect the callback name to be passed labeled with a
* special URL parameter key, e.g. "jsonp" in "?jsonp=myCallback". The
* default is "callback".
*
* @param param {String} Name of the callback parameter.
* @return {qx.bom.request.Jsonp} Self reference for chaining.
*/
setCallbackParam: function(param) {
this.__callbackParam = param;
return this;
},
/**
* Set callback name.
*
* Must be set to the name of the callback function that is called by the
* script returned from the JSONP service. By default, the callback name
* references this instance’s {@link #callback} method, allowing to connect
* multiple JSONP responses to different requests.
*
* If the JSONP service allows to set custom callback names, it should not
* be necessary to change the default. However, some services use a fixed
* callback name. This is when setting the callbackName is useful. A
* function is created and made available globally under the given name.
* The function receives the JSON data and dispatches it to this instance’s
* {@link #callback} method. Please note that this function is only created
* if it does not exist before.
*
* @param name {String} Name of the callback function.
* @return {qx.bom.request.Jsonp} Self reference for chaining.
*/
setCallbackName: function(name) {
this.__callbackName = name;
return this;
},
/**
* Set the prefix used in front of 'qx.' in case 'qx' is not available
* (for qx.Website e.g.)
* @internal
* @param prefix {String} The prefix to put in front of 'qx'
*/
setPrefix : function(prefix) {
this.__prefix = prefix;
},
/**
* Returns the generated URL for the current / last request
*
* @internal
* @return {String} The current generated URL for the request
*/
getGeneratedUrl : function() {
return this.__generatedUrl;
},
dispose: function() {
// In case callback was not called
this.__deleteCustomCallback();
this.__callBase("dispose");
},
/**
* Handle native load.
*/
_onNativeLoad: function() {
// Indicate erroneous status (500) if callback was not called.
//
// Why 500? 5xx belongs to the range of server errors. If the callback was
// not called, it is assumed the server failed to provide an appropriate
// response. Since the exact reason of the error is unknown, the most
// generic message ("500 Internal Server Error") is chosen.
this.status = this.__callbackCalled ? 200 : 500;
this.__callBase("_onNativeLoad");
},
/**
* Delete custom callback if dynamically created before.
*/
__deleteCustomCallback: function() {
if (this.__customCallbackCreated && window[this.__callbackName]) {
window[this.__callbackName] = undefined;
this.__customCallbackCreated = false;
}
},
/**
* Call overridden method.
*
* @param method {String} Name of the overridden method.
* @param args {Array} Arguments.
*/
__callBase: function(method, args) {
qx.bom.request.Script.prototype[method].apply(this, args || []);
},
/**
* Generate ID.
*/
__generateId: function() {
// Add random digits to date to allow immediately following requests
// that may be send at the same time
this.__id = "qx" + (new Date().valueOf()) + ("" + Math.random()).substring(2,5);
}
}
});