js/Mobilizing/net/Ajax.js
import EventEmitter from "../core/util/EventEmitter";
// Events
/**
* Fired when the readystate of the request changes
* @event readystatechange
* @param {Ajax} ajax The Ajax instance
*/
const EVT_READYSTATE_CHANGE = "readystatechange";
/**
* Fired when the request is opened
* @event start
* @param {Ajax} ajax The Ajax instance
*/
const EVT_START = "start";
/**
* Fired when an operation is in progress
* @event progress
* @param {Ajax} ajax The Ajax instance
*/
const EVT_PROGRESS = "progress";
/**
* Fired when the request has finished loading
* @event load
* @param {Ajax} ajax The Ajax instance
* @param {Boolean} success Whether the request was successfull or not
*/
const EVT_LOAD = "load";
/**
* Fired when the request has finished loading and the status is greater or equal to 200 and less than 300, or is equal to 304
* @event success
* @param {Ajax} ajax The Ajax instance
*/
const EVT_SUCCESS = "success";
/**
* Fired when the request encountered an error, or if it finished loading with an error status
* @event error
* @param {Ajax} ajax The Ajax instance
*/
const EVT_ERROR = "error";
/**
* Ajax is a helper class used to simplify the use of the XMLHttpRequest API
*
* @example
* //TODO
*/
export default class Ajax {
/**
* @param {Object} params Parameters object, given by the constructor.
* @param {String} params.url The URL to which the request is sent
* @param {String} [params.method = "GET"] The method used for the request (GET, POST, or PUT)
* @param {Object} [params.data = null] Data to be send along with the request
* @param {Object} [params.headers = null] An object of header key/value pairs to send along with requests
* @param {Boolean} [params.async = true] Whether the request is asynchronous or not
* @param {Boolean} [params.autoSend = true] Whether the request should be automatically sent
* @param {Boolean} [params.withCredentials = null] Whether cross-site Access-Control requests should be made using credentials such as cookies or authorization headers
* @param {String} [params.responseType = ""] The responseType this request must return
* @param {Number} [params.timeout = null] The number of milliseconds the request can take before automatically being terminated
* @param {Function} [params.onReadyStateChange = null] A function to be called when the readyState of the request changes
* @param {Function} [params.onStart = null] A function to be called when the request starts
* @param {Function} [params.onProgress = null] A function to be called when the request progresses
* @param {Function} [params.onLoad = null] A function to be called when the request finishes
* @param {Function} [params.onSuccess = null] A function to be called if the request succeeds
* @param {Function} [params.onError = null] A function to be called if the request fails
*/
constructor({
url = "",
method = "GET",
async = true,
data = null,
headers = null,
autoSend = true,
withCredentials = null,
timeout = null,
responseType = "text",
onReadyStateChange = null,
onStart = null,
onProgress = null,
onLoad = null,
onSuccess = null,
onError = null,
} = {}) {
this.url = url;
this.method = method;
this.async = async;
this.data = data;
this.headers = headers;
this.autoSend = autoSend;
this.withCredentials = withCredentials;
this.timeout = timeout;
this.responseType = responseType;
this.onReadyStateChange = onReadyStateChange;
this.onStart = onStart;
this.onProgress = onProgress;
this.onLoad = onLoad;
this.onSuccess = onSuccess;
this.onError = onError;
this._xhr = new XMLHttpRequest();
this.events = new EventEmitter({ "scope": this });
// bind custom callbacks to events
if (this.onReadyStateChange) {
this.events.on(EVT_READYSTATE_CHANGE, this.onReadyStateChange);
}
if (this.onStart) {
this.events.on(EVT_START, this.onStart);
}
if (this.onProgress) {
this.events.on(EVT_PROGRESS, this.onProgress);
}
if (this.onLoad) {
this.events.on(EVT_LOAD, this.onLoad);
}
if (this.onSuccess) {
this.events.on(EVT_SUCCESS, this.onSuccess);
}
if (this.onError) {
this.events.on(EVT_ERROR, this.onError);
}
this._xhr.addEventListener("readystatechange", this.onXHRReadyStateChange.bind(this));
// if this is a GET request, the data needs to be treated as URL parameters
if (this.method === "GET" && this.data) {
const params = [];
Object.keys(this._data).forEach((key) => {
params.push(`${key}=${encodeURIComponent(this._data[key])}`);
});
this._url += `?${params.join("&")}`;
this._data = null;
}
this.setResponseType(this.responseType);
if (this.withCredentials !== null) {
this.setWithCredentials(this.withCredentials);
}
if (this.timeout !== null) {
this.setTimeout(this.timeout);
}
if (this.autoSend) {
this.send(this.data);
}
}
/**
* A helper function to create a request of type GET
*
* @param {Object} params Parameters object to pass to the constructor.
*/
static get(params) {
return new Ajax(Object.assign({}, params, {
method: "GET"
}));
}
/**
* A helper function to create a request of type POST
*
* @param {Object} params Parameters object to pass to the constructor.
*/
static post(params) {
return new Ajax(Object.assign({}, params, {
method: "POST"
}));
}
/**
* A helper function to create a request of type PUT
*
* @param {Object} params Parameters object to pass to the constructor.
*/
static put(params) {
return new Ajax(Object.assign({}, params, {
method: "PUT"
}));
}
/**
* The readystatechange event handler
*
* @private
*/
onXHRReadyStateChange() {
this.events.trigger(EVT_READYSTATE_CHANGE, this);
switch (this.getReadyState()) {
case XMLHttpRequest.OPENED:
this.events.trigger(EVT_START, this);
break;
case XMLHttpRequest.LOADING:
this.events.trigger(EVT_PROGRESS, this);
break;
case XMLHttpRequest.DONE:
{
const status = this.getStatus();
let success = false;
if (status === 200 && status < 300 || status === 304) {
success = true;
}
// local requests can return a status of 0 even if no error occurs
else if (status === 0 && !this._xhr.error) {
success = true;
}
this.events.trigger(EVT_LOAD, this, success);
if (success) {
this.events.trigger(EVT_SUCCESS, this);
}
else {
this.events.trigger(EVT_ERROR, this);
}
break;
}
}
}
/**
* Get the XMLHttpRequest instance for the request
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
*
* @return {XMLHttpRequest} The XMLHttpRequest instance
*/
getXHR() {
return this._xhr;
}
/**
* Wrapping method for the setRequestHeader method of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/setRequestHeader
*
* @param {String} header The name of the header
* @param {String} value The value of the header
*/
setRequestHeader(header, value) {
this._xhr.setRequestHeader(header, value);
}
/**
* Wrapping method for the withCredentials property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials
*
* @param {Boolean} value Whether cross-site Access-Control requests should be made using credentials such as cookies or authorization headers
*/
setWithCredentials(value) {
this._xhr.withCredentials = value;
}
/**
* Wrapping method for the timeout property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout
*
* @param {Number} value The number of milliseconds the request can take before automatically being terminated
*/
setTimeout(value) {
this._xhr.timeout = value;
}
/**
* Wrapping method for the responseType property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responsetype
*
* @param {String} value The type of response to be given by this request. Possible values are : "arraybuffer", "text", "json", "document", "blob"
*/
setResponseType(value) {
this._xhr.responseType = value;
}
/**
* Wrapping method for the send method of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/send
*
* @param {Mixed} data The data to send along the request
*/
send(data) {
this._xhr.open(this.method, this.url, this.async);
// set custom headers, they must be set after opening the XHR
if (this.headers !== null) {
Object.keys(this.headers).forEach((name) => {
this.setRequestHeader(name, this.headers[name]);
});
}
this._xhr.send(data);
}
/**
* Wrapping method for abort method of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort
*
*/
abort() {
this._xhr.abort();
}
/**
* Wrapping method for status property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/status
*
* @return {Number} The status of the request
*/
getStatus() {
return this._xhr.status;
}
/**
* Wrapping method for statusText property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/statusText
*
* @return {DOMString} The status message of the request
*/
getStatusText() {
return this._xhr.statusText;
}
/**
* Wrapping method for readyState property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState
*
* @return {Number} The state the XMLHttpRequest client is in
*/
getReadyState() {
return this._xhr.readyState;
}
/**
* Wrapping method for responseType property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
*
* @return {String} The response"s type
*/
getResponseType() {
return this._xhr.responseType;
}
/**
* Wrapping method for response property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response
*
* @return {Mixed} The response"s body
*/
getResponse() {
return this._xhr.response;
}
/**
* Wrapping method for responseText property of XMLHttpRequest
* see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText
*
* @return {String} The text response
*/
getResponseText() {
return this._xhr.responseText;
}
/**
* Return the XMLHttpRequest responseText as a JSON object
*
* @return {Object} The responseText parsed as a JSON object
*/
getJSON() {
return JSON.parse(this.getResponseText());
}
}