reste
Version:
A JavaScript REST / API helper for Titanium with Alloy Models/Collections support
589 lines (481 loc) • 15.8 kB
JavaScript
function main() {
var reste = this;
var config = {};
var requestHeaders = [];
function log(message) {
if (config.debug && message) {
console.log('::RESTE::' + message);
}
}
function warn(message) {
if (config.debug && message) {
console.warn('::RESTE::' + message);
}
}
reste.config = function (args) {
config = args;
reste.setRequestHeaders(config.requestHeaders);
config.methods.forEach((method) => {
reste.addMethod(method);
});
if (config.models) {
initModels();
config.models.forEach((model) => {
reste.addModel(model);
});
}
};
reste.setUrl = function (url) {
config.url = url || config.url;
};
reste.getUrl = function () {
return config.url;
};
function makeHttpRequest(args, onLoad, onError) {
function isJSON(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
function parseJSON(text) {
if (isJSON(text)) {
return JSON.parse(text);
} else {
return text;
}
}
if (args.url.indexOf('http') == 0) {
log(args.url);
} else {
log(config.url ? config.url + args.url : args.url);
}
if (args.params) {
log(JSON.stringify(args.params));
}
var http = Ti.Network.createHTTPClient();
reste.clearCookies = function () {
if (http) http.clearCookies(config.url);
};
var formEncode = false;
http.timeout = args.timeout || config.timeout || 10000;
if (_.has(config, 'validatesSecureCertificate')) {
http.validatesSecureCertificate = config.validatesSecureCertificate;
}
if (args.url.indexOf('http') == 0) {
http.open(args.method, args.url);
} else {
http.open(args.method, config.url ? config.url + args.url : args.url);
}
requestHeaders.forEach((header) => {
if (header.name === 'Content-Type' && header.value === 'application/x-www-form-urlencoded') {
formEncode = true;
}
http.setRequestHeader(header.name, typeof header.value === 'function' ? header.value() : header.value);
log('Setting global header - ' + header.name + ': ' + (typeof header.value === 'function' ? header.value() : header.value));
});
if (args.headers) {
for (var header in args.headers) {
if (header === 'Content-Type' && args.headers[header] === 'application/x-www-form-urlencoded') {
formEncode = true;
} else if (header === 'Content-Type' && args.headers[header] === 'application/json') {
formEncode = false;
}
http.setRequestHeader(header, typeof args.headers[header] === 'function' ? args.headers[header]() : args.headers[header]);
log('Setting local header - ' + header + ': ' + (typeof args.headers[header] === 'function' ? args.headers[header]() : args.headers[header]));
}
}
if (_.has(config, 'securityManager')) {
http.securityManager = config.securityManager;
}
http.onload = function () {
var response = parseJSON(http.responseText);
if (config.onLoad) {
config.onLoad(response, onLoad);
} else if (onLoad) {
onLoad(response);
}
};
http.onerror = function (e) {
e.url = args.url;
function retry() {
log('Retrying...');
return makeHttpRequest(args, onLoad, onError);
}
var error;
if (config.errorsAsObjects) {
error = e;
error.content = parseJSON(http.responseText);
warn('Errors will be returned as objects.');
} else {
error = parseJSON(http.responseText);
warn('Future versions of RESTe will return errors as objects. Use config.errorsAsObjects = true to support this now and update your apps!');
}
if (onError) {
onError(error, retry);
} else if (config.onError) {
config.onError(error, retry);
} else if (onLoad) {
onLoad(error, retry);
} else {
throw 'RESTe :: No error handler / callback for: ' + args.url;
}
};
function send() {
log(args.params);
if (args.params && (args.method === 'POST' || args.method === 'PUT')) {
if (formEncode) {
http.send(args.params);
} else {
http.send(JSON.stringify(args.params));
}
} else {
http.send();
}
}
args.params = args.params || {};
var beforePost = args.beforePost || config.beforePost;
var beforeSend = args.beforeSend || config.beforeSend;
if (args.method === 'POST' && typeof beforePost === 'function') {
beforePost(args.params, (e) => {
args.params = e;
send();
});
} else if (typeof beforeSend === 'function') {
beforeSend(args.params, (e) => {
args.params = e;
send();
});
} else {
send();
}
return http;
}
reste.setRequestHeaders = function (headers) {
requestHeaders = [];
for (var header in headers) {
requestHeaders.push({
name: header,
value: headers[header]
});
}
};
reste.changeRequestHeader = function (header) {
var changed = false;
_.each(requestHeaders, (item) => {
if (item.name === Object.keys(header)[0]) {
item.value = header[Object.keys(header)[0]];
changed = true;
}
});
if (!changed) {
requestHeaders.push({
name: Object.keys(header)[0],
value: header[Object.keys(header)[0]]
});
}
};
reste.removeRequestHeaderItem = function (delItem) {
requestHeaders = _.filter(requestHeaders, (item) => {
return !(item.name === delItem);
});
};
reste.addMethod = function (args) {
log(args.requestHeaders);
if (reste[args.name]) {
throw 'RESTe :: method already defined and will be overwritten: ' + args.name;
}
reste[args.name] = function (params, onLoad, onError) {
var body;
var method = 'GET';
var url;
var deferred;
if (args.post) method = 'POST';
if (args.get) method = 'GET';
if (args.put) method = 'PUT';
if (args.delete) method = 'DELETE';
url = args[method.toLowerCase()] || args.get;
if (config.Q && !onLoad && typeof params !== 'function') {
deferred = config.Q.defer();
onLoad = deferred.resolve;
onError = deferred.reject;
}
if (!onLoad && typeof params === 'function') {
onLoad = params;
} else {
for (var param in params) {
if (param === 'body') {
body = params[param];
} else {
while (url.indexOf('<' + param + '>') >= 0) {
if (typeof params[param] === 'object') {
url = url.replace('<' + param + '>', JSON.stringify(params[param]));
} else {
url = url.replace('<' + param + '>', params[param]);
}
}
}
}
}
if (args.onLoad) {
var originalOnLoad = onLoad;
onLoad = function (e) {
args.onLoad(e, originalOnLoad);
};
}
if (args.onError) {
onError = function (e) {
args.onError(e, onLoad);
};
}
if (args.expects) {
args.expects.forEach((expectedParam) => {
if (method === 'POST' && params.body ? !params.body[expectedParam] : !params[expectedParam]) {
throw 'RESTe :: missing parameter ' + expectedParam + ' for method ' + args.name;
}
});
return makeHttpRequest({
url,
method,
timeout: args.timeout || config.timeout || 10000,
params: body,
headers: args.requestHeaders || args.headers,
beforePost: args.beforePost,
beforeSend: args.beforeSend
},
onLoad, onError);
} else {
var m;
var missing = [];
var re = /(\<\w*\>)/g;
if (config.autoValidateParams) {
while ((m = re.exec(url)) !== null) {
if (m.index === re.lastIndex) {
re.lastIndex++;
}
missing.push(m[0]);
}
}
if (missing.length > 0) {
throw 'RESTe :: missing parameter/s ' + missing + ' for method ' + args.name;
} else {
return makeHttpRequest({
url,
method,
timeout: args.timeout || config.timeout || 10000,
params: body,
headers: args.requestHeaders || args.headers,
beforePost: args.beforePost,
beforeSend: args.beforeSend
},
onLoad, onError);
}
}
if (deferred) {
return deferred.promise;
}
};
};
reste.createModel = function (name, attributes) {
var model = new Backbone.Model(attributes);
if (reste.modelConfig && reste.modelConfig[name] && reste.modelConfig[name].transform) {
// eslint-disable-next-line no-unused-vars
model.transform = function (_model, transform) {
if (transform) {
this.__transform = transform(this);
} else {
this.__transform = reste.modelConfig[name].transform(this);
}
return this.__transform;
};
} else {
// eslint-disable-next-line no-unused-vars
model.transform = function (_model, transform) {
if (transform) {
this.__transform = transform(this);
} else {
this.__transform = this.toJSON();
}
return this.__transform;
};
}
model._type = name;
return model;
};
reste.createCollection = function (name, content) {
if (!Alloy.Collections[name]) {
Alloy.Collections[name] = new Backbone.Collection();
}
if (content instanceof Array) {
Alloy.Collections[name].reset(content);
Alloy.Collections[name].fetch = function () {
Alloy.Collections[name].trigger('change');
};
} else {
throw 'No Array specified for createCollection';
}
};
function initModels() {
reste.addModel = function (args) {
reste.modelConfig = reste.modelConfig || {};
reste.modelConfig[args.name] = args;
var model = Backbone.Model.extend({
_type: args.name,
_method: args.name,
// eslint-disable-next-line no-unused-vars
transform(_model, transform) {
if (transform) {
this.__transform = transform(this);
} else if (args.transform) {
this.__transform = args.transform(this);
} else {
this.__transform = this.toJSON();
}
return this.__transform;
}
});
if (args.collections) {
args.collections.forEach((collection) => {
Alloy.Collections[collection.name] = Alloy.Collections[collection.name] || new Backbone.Collection();
Alloy.Collections[collection.name]._type = args.name;
Alloy.Collections[collection.name]._name = collection.name;
Alloy.Collections[collection.name].model = model;
});
}
};
Backbone.sync = function (method, model, options) {
log('Backbone.sync: ' + method + ' ' + model._type);
var modelConfig = reste.modelConfig[model._type];
var body;
var onError;
if (model instanceof Backbone.Collection && modelConfig && modelConfig.collections) {
var collectionConfig = _.where(modelConfig.collections, {
name: model._name
})[0];
var methodCall = reste[collectionConfig.read];
methodCall(options, (response) => {
if (response !== null && response !== undefined) {
if (response[collectionConfig.content]) {
response[collectionConfig.content].forEach((item) => {
item.id = item[modelConfig.id];
});
if (options.success) options.success(response[collectionConfig.content]);
Alloy.Collections[collectionConfig.name].trigger('sync');
} else {
response.forEach((item) => {
item.id = item[modelConfig.id];
});
if (options.success) options.success(response);
Alloy.Collections[collectionConfig.name].trigger('sync');
}
}
}, (response) => {
if (options.error) {
options.error(response);
}
});
} else if (model instanceof Backbone.Model) {
if (model.get('id') && method === 'create') {
method = 'update';
}
if (method === 'update') {
var params = {};
if (options.changes) {
params.body = {};
for (var attr in options.changes) {
params.body[attr] = model.get(attr);
}
}
params[modelConfig.id] = model.get('id');
params.body = params.body || model.toJSON();
delete params.body.id;
delete params.body[modelConfig.id];
if (modelConfig.beforeUpdate) {
params.body = modelConfig.beforeUpdate(params.body);
}
options.error ? onError = function (e) {
options.error(e);
} : onError = null;
reste[modelConfig.update](params, (e) => {
if (e.code > 200) {
onError(e);
} else {
options.success(e);
}
}, onError);
}
if (method === 'read') {
if (modelConfig.read) {
if (model[modelConfig.id]) {
options[modelConfig.id] = model[modelConfig.id];
} else if (model.get('id')) {
options[modelConfig.id] = model.get('id');
}
options.error ? onError = function (e) {
options.error(e);
} : onError = null;
reste[modelConfig.read](options, (e) => {
if (modelConfig.content) {
var result = e[modelConfig.content];
if (result.length === 1) {
options.success(result[0]);
} else {
options.success(result);
}
} else if (e.code > 200) {
onError(e);
} else {
options.success(e);
}
}, onError);
}
}
if (method === 'create') {
body = model.toJSON();
delete body.id;
delete body[modelConfig.id];
if (modelConfig.beforeDelete) {
body = modelConfig.beforeDelete(body);
}
options.error ? onError = function (e) {
options.error(e);
} : onError = null;
reste[modelConfig.create]({
body
},
(e) => {
if (e.code > 200) {
onError(e);
} else {
e.id = e[modelConfig.id];
model.set('id', e[modelConfig.id]);
options.success(e);
}
}, onError);
}
if (method === 'delete') {
body = {};
body[modelConfig.id] = model.get('id');
body.body = model.toJSON();
if (modelConfig.beforeCreate) {
body.body = modelConfig.beforeCreate(body.body);
}
options.error ? onError = function (e) {
options.error(e);
} : onError = null;
reste[modelConfig.delete](body, (e) => {
if (e.code > 200) {
onError(e);
} else {
options.success(e);
}
}, onError);
}
}
};
}
return reste;
}
module.exports = main;