ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
150 lines (144 loc) • 5.24 kB
JavaScript
/**
_enyo.JsonpRequest_ is a specialized form of
<a href="#enyo.Async">enyo.Async</a> used for making JSONP requests to a
remote server. This differs from the normal XMLHttpRequest call in that the
external resource is loaded using a <script> tag. This allows us to
bypass the same-domain rules that normally apply to XHR, since the browser
will load scripts from any address.
At the same time, in order to successfully load data via the <script>
tag, your data source must be accessed via an HTTP GET request and must be
able to dynamically add the requested callback name as a function wrapper
around the JSON data.
If you make changes to _enyo.JsonpRequest_, be sure to add or update the
appropriate [unit tests](https://github.com/enyojs/enyo/tree/master/tools/test/ajax/tests).
For more information, see the documentation on [Consuming Web
Services](building-apps/managing-data/consuming-web-services.html) in the Enyo
Developer Guide.
*/
enyo.kind({
name: "enyo.JsonpRequest",
kind: "enyo.Async",
published: {
//* The URL for the service
url: "",
//* Optional character set to use to interpret the return data
charset: null,
/**
Name of the argument that holds the callback name. For example, the
Twitter search API uses "callback" as the parameter to hold the
name of the called function. We will automatically add this to
the encoded arguments.
If this is null, we won't pass a callback name to the JSONP server.
That mode is usually only used if you also set the _overrideCallback_
parameter, since without this, there's no way for the server to know
what function wrapper to use.
*/
callbackName: "callback",
/**
When true, appends a random number as a parameter for GET requests
to try to force a new fetch of the resource instead of reusing a
local cache
*/
cacheBust: true,
/**
When set, use this as the name of the callback method to pass
to the remote server. This is mainly useful when dealing with
servers that aren't flexible in how they specify callback names.
If you specify this, we will add a method to the global namespace
using the specified name, so this can easily stomp on a global
variable. You can't have multiple calls to a JSONP API alive at
the same time using the same callback method.
*/
overrideCallback: null
},
protectedStatics: {
// Counter to allow creation of unique name for each JSONP request
nextCallbackID: 0
},
//* @protected
addScriptElement: function() {
var script = document.createElement('script');
script.src = this.src;
script.async = "async";
if (this.charset) {
script.charset = this.charset;
}
// most modern browsers also have an onerror handler
script.onerror = this.bindSafely(function() {
// we don't get an error code, so we'll just use the generic 400 error status
this.fail(400);
});
// add script before existing script to make sure it's in a valid part of document
// http://www.jspatterns.com/the-ridiculous-case-of-adding-a-script-element/
var first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(script, first);
this.scriptTag = script;
},
removeScriptElement: function() {
var script = this.scriptTag;
this.scriptTag = null;
script.onerror = null;
if (script.parentNode) {
script.parentNode.removeChild(script);
}
},
constructor: enyo.inherit(function (sup) {
return function(inParams) {
enyo.mixin(this, inParams);
sup.apply(this, arguments);
};
}),
//* @public
//* Starts the JSONP request.
go: function(inParams) {
this.startTimer();
this.jsonp(inParams);
return this;
},
//* @protected
jsonp: function(inParams) {
var callbackFunctionName = this.overrideCallback ||
"enyo_jsonp_callback_" + (enyo.JsonpRequest.nextCallbackID++);
//
this.src = this.buildUrl(inParams, callbackFunctionName);
this.addScriptElement();
//
window[callbackFunctionName] = this.bindSafely(this.respond);
//
// setup cleanup handlers for JSONP completion and failure
var cleanup = this.bindSafely(function() {
this.removeScriptElement();
window[callbackFunctionName] = null;
});
this.response(cleanup);
this.error(cleanup);
},
buildUrl: function(inParams, inCallbackFunctionName) {
var parts = this.url.split("?");
var uri = parts.shift() || "";
var args = parts.length? parts.join("?").split("&"): [];
//
var bodyArgs = this.bodyArgsFromParams(inParams, inCallbackFunctionName);
args.push(bodyArgs);
if (this.cacheBust) {
args.push(Math.random());
}
//
return [uri, args.join("&")].join("?");
},
// for a string version of inParams, we follow the convention of
// replacing the string "=?" with the callback name. For the more
// common case of inParams being an object, we'll add a argument named
// using the callbackName published property.
bodyArgsFromParams: function(inParams, inCallbackFunctionName) {
if (enyo.isString(inParams)) {
return inParams.replace("=?", "=" + inCallbackFunctionName);
} else {
var params = enyo.mixin({}, inParams);
if (this.callbackName) {
params[this.callbackName] = inCallbackFunctionName;
}
return enyo.Ajax.objectToQuery(params);
}
}
});