js/NativeXhrDataProvider.js
"use strict";
/**
* @class circuits.NativeXhrDataProvider
*
* Data provider implementation that wraps the native XmlHttpRequest.
*/
define([
"./declare",
"./DataProvider",
"./Request",
"./log"
], function (
declare,
DataProvider,
Request,
logger
) {
var module = declare(DataProvider, {
/**
* @constructor - warn on level compliance
*/
constructor: function (config) {
var test = new XMLHttpRequest(), that = this;
if (!test.upload) {
logger.warn("NativeXhrDataProvider: This runtime is not XHR level 2 compliant.");
}
this.asynchronous = (config && typeof (config.asynchronous) === 'boolean') ? config.asynchronous : true;
this.hitchedInvoke = function () {
that.invokeXhrSend.apply(that, arguments);
};
},
/**
* test - do a network test with a synchronous call.
* @param url to test.
*/
test: function (url) {
var ret = true, xhr;
try {
// try the lowest footprint synchronous request to specific url.
xhr = new XMLHttpRequest();
xhr.open("HEAD", url, false);
xhr.send();
} catch (e) {
ret = false;
}
return ret;
},
create: function (params) {
var payload = params.payload,
request,
contentType = params.headers && params.headers["Content-Type"];
contentType = contentType || "application/json";
if (contentType === "application/json") {
payload = JSON.stringify(params.payload);
}
request = new Request({
url: params.url,
method: "POST",
headers: params.headers || { "Content-Type": contentType},
payload: payload,
asynchronous: params.asynchronous,
responseType: params.responseType || "json",
onprogress: params.onprogress || this.defaultOnProgress,
handler: params.handler || this.defaultHandler,
timeout: params.timeout || "none"
}, this.hitchedInvoke);
logger.debug("Xhr-based store executing POST to " + params.url, payload);
if (!params.dontExecute) {
request.execute();
}
return request;
},
read: function (params) {
var request = new Request({
url: params.url,
method: "GET",
asynchronous: params.asynchronous,
handler: params.handler || this.defaultHandler,
responseType: params.responseType || "json",
timeout: params.timeout || "none"
}, this.hitchedInvoke);
logger.debug("Xhr-based store executing GET to " + params.url);
if (!params.dontExecute) {
request.execute();
}
return request;
},
update: function (params) {
var payload = params.payload,
request,
contentType = params.headers && params.headers["Content-Type"];
contentType = contentType || "application/json";
if (contentType === "application/json") {
payload = JSON.stringify(params.payload);
}
request = new Request({
url: params.url,
method: "PUT",
asynchronous: params.asynchronous,
headers: params.headers || { "Content-Type": contentType},
payload: payload,
responseType: params.responseType || "json",
handler: params.handler || this.defaultHandler,
timeout: params.timeout || "none"
}, this.hitchedInvoke);
logger.debug("Xhr-based store executing PUT to " + params.url, payload);
if (!params.dontExecute) {
request.execute();
}
return request;
},
del: function (params) {
var request = new Request({
url: params.url,
asynchronous: params.asynchronous,
method: "DELETE",
handler: params.handler || this.defaultHandler,
responseType: params.responseType || "json",
timeout: params.timeout || "none"
}, this.hitchedInvoke);
logger.debug("Xhr-based store executing DELETE to " + params.url);
if (!params.dontExecute) {
request.execute();
}
return request;
},
/**
* @param {string} transport
* @return {boolean}
* @overrides
*/
supportsTransport: function (transport) {
return transport === 'REST';
},
/**
* Perform the actual XMLHttpRequest call, interpreting the params as necessary.
* Although this method accepts the bulk of the parameters that can be set on XMLHttpRequest 2,
* only a few are passed through from the upstream calls.
*
* @param params - object of the form:
* {
* onprogress {function}
* onloadstart {function}
* onabort {function}
* ontimeout {function}
* onloadend {function}
* onreadystatechange {function}
* asynchronous {boolean}
* method {String}
* headers {Object}
* payload {Object}
* url {String}
* timeout {Number}
* responseType {String}
* handler {function}
* }
*/
invokeXhrSend: function (params) {
var xhr = new XMLHttpRequest(),
async = typeof (params.asynchronous) === "boolean" ? params.asynchronous : this.asynchronous,
readystatechange = function () {
if (params.onreadystatechange) {
params.onreadystatechange.call(this);
} else {
if (this.readyState === this.DONE) {
if (!this.loadcalled) { // prevent multiple done calls from xhr.
var resp;
if (!xhr.timedOut) {
resp = this.response || this.responseText;
}
this.loadcalled = true;
if (resp && !this.responseType &&
params.responseType === "json") {
try {
resp = JSON.parse(resp);
} catch (e) {
logger.debug('Unable to parse JSON: ' + resp + '\n' + e);
}
}
params.handler.call(this, (xhr.timedOut) ? -1 : this.status, resp, params);
}
}
}
};
// setup pre-open parameters
params.xhr = xhr;
if (params.responseType && typeof (xhr.responseType) === 'string') {
// types for level 2 are still draft. Don't attempt to set until
// support is more universal.
//
// xhr.responseType = params.responseType;
logger.debug("Ignoring responseType on XHR until fully supported.");
}
xhr.open(params.method, params.url, async);
Object.keys((params.headers || {})).forEach(function (val) {
if (!(val === "Content-Type" && params.headers[val] === "multipart/form-data")) {
xhr.setRequestHeader(val, params.headers[val]);
}
});
// If level 2, then attach the handlers directly.
if (xhr.upload) {
Object.keys(params).forEach(function (key) {
if (key.indexOf("on") === 0 && typeof (params[key]) === 'function') {
if (typeof (xhr[key]) !== 'undefined') {
xhr[key] = params[key];
}
if (typeof (xhr.upload[key]) !== 'undefined') {
xhr.upload[key] = params[key];
}
}
});
}
// still support readystate event.
if (params.handler || params.onreadystatechange) {
xhr.onreadystatechange = readystatechange;
}
if (params.timeout && typeof (params.timeout) === 'number') {
xhr.timeout = params.timeout;
xhr.ontimeout = function () {
xhr.timedOut = true;
xhr.abort();
};
setTimeout(function () { /* vs. xhr.timeout */
this.response = {};
if (xhr.readyState < 4 && !xhr.timedOut) {
xhr.ontimeout();
}
}, xhr.timeout);
}
xhr.send(params.payload);
},
defaultHandler: function (data, ioArgs) {
logger.debug("No handler specified for XHR call to " + ioArgs.url);
},
defaultProgress: function (data, ioArgs) {
logger.debug("No handler specified for XHR call to " + ioArgs.url);
},
defaultOnProgress: function (event) {
logger.warn("No progress handler specified for XHR call to " + event);
}
});
return module;
});