1272 lines
53 KiB
JavaScript
Executable File
1272 lines
53 KiB
JavaScript
Executable File
/**
|
|
* Restful Resources service for AngularJS apps
|
|
* @version v1.3.1 - 2014-01-29 * @link https://github.com/mgonto/restangular
|
|
* @author Martin Gontovnikas <martin@gon.to>
|
|
* @license MIT License, http://www.opensource.org/licenses/MIT
|
|
*/(function() {
|
|
|
|
var module = angular.module('restangular', []);
|
|
|
|
module.provider('Restangular', function() {
|
|
// Configuration
|
|
var Configurer = {};
|
|
Configurer.init = function(object, config) {
|
|
/**
|
|
* Those are HTTP safe methods for which there is no need to pass any data with the request.
|
|
*/
|
|
|
|
object.configuration = config;
|
|
|
|
var safeMethods= ["get", "head", "options", "trace", "getlist"];
|
|
config.isSafe = function(operation) {
|
|
return _.contains(safeMethods, operation.toLowerCase());
|
|
};
|
|
|
|
var absolutePattern = /^https?:\/\//i;
|
|
config.isAbsoluteUrl = function(string) {
|
|
return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ?
|
|
string && absolutePattern.test(string) :
|
|
config.absoluteUrl;
|
|
};
|
|
|
|
config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? false : true;
|
|
object.setSelfLinkAbsoluteUrl = function(value) {
|
|
config.absoluteUrl = value;
|
|
};
|
|
/**
|
|
* This is the BaseURL to be used with Restangular
|
|
*/
|
|
config.baseUrl = _.isUndefined(config.baseUrl) ? "" : config.baseUrl;
|
|
object.setBaseUrl = function(newBaseUrl) {
|
|
config.baseUrl = /\/$/.test(newBaseUrl)
|
|
? newBaseUrl.substring(0, newBaseUrl.length-1)
|
|
: newBaseUrl;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Sets the extra fields to keep from the parents
|
|
*/
|
|
config.extraFields = config.extraFields || [];
|
|
object.setExtraFields = function(newExtraFields) {
|
|
config.extraFields = newExtraFields;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Some default $http parameter to be used in EVERY call
|
|
**/
|
|
config.defaultHttpFields = config.defaultHttpFields || {};
|
|
object.setDefaultHttpFields = function(values) {
|
|
config.defaultHttpFields = values;
|
|
return this;
|
|
};
|
|
|
|
config.withHttpValues = function(httpLocalConfig, obj) {
|
|
return _.defaults(obj, httpLocalConfig, config.defaultHttpFields);
|
|
};
|
|
|
|
config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds;
|
|
object.setEncodeIds = function(encode) {
|
|
config.encodeIds = encode;
|
|
};
|
|
|
|
config.defaultRequestParams = config.defaultRequestParams || {
|
|
get: {},
|
|
post: {},
|
|
put: {},
|
|
remove: {},
|
|
common: {}
|
|
};
|
|
|
|
object.setDefaultRequestParams = function(param1, param2) {
|
|
var methods = [],
|
|
params = param2 || param1;
|
|
if (!_.isUndefined(param2)) {
|
|
if (_.isArray(param1)) {
|
|
methods = param1;
|
|
} else {
|
|
methods.push(param1);
|
|
}
|
|
} else {
|
|
methods.push('common');
|
|
}
|
|
|
|
_.each(methods, function (method) {
|
|
config.defaultRequestParams[method] = params;
|
|
});
|
|
return this;
|
|
};
|
|
|
|
object.requestParams = config.defaultRequestParams;
|
|
|
|
|
|
config.defaultHeaders = config.defaultHeaders || {};
|
|
object.setDefaultHeaders = function(headers) {
|
|
config.defaultHeaders = headers;
|
|
object.defaultHeaders = config.defaultHeaders;
|
|
return this;
|
|
};
|
|
|
|
object.defaultHeaders = config.defaultHeaders;
|
|
|
|
/**
|
|
* Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override
|
|
**/
|
|
config.methodOverriders = config.methodOverriders || [];
|
|
object.setMethodOverriders = function(values) {
|
|
var overriders = _.extend([], values);
|
|
if (config.isOverridenMethod('delete', overriders)) {
|
|
overriders.push("remove");
|
|
}
|
|
config.methodOverriders = overriders;
|
|
return this;
|
|
};
|
|
|
|
config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp;
|
|
object.setJsonp = function(active) {
|
|
config.jsonp = active;
|
|
};
|
|
|
|
config.isOverridenMethod = function(method, values) {
|
|
var search = values || config.methodOverriders;
|
|
return !_.isUndefined(_.find(search, function(one) {
|
|
return one.toLowerCase() === method.toLowerCase();
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams
|
|
**/
|
|
config.urlCreator = config.urlCreator || "path";
|
|
object.setUrlCreator = function(name) {
|
|
if (!_.has(config.urlCreatorFactory, name)) {
|
|
throw new Error("URL Path selected isn't valid");
|
|
}
|
|
|
|
config.urlCreator = name;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* You can set the restangular fields here. The 3 required fields for Restangular are:
|
|
*
|
|
* id: Id of the element
|
|
* route: name of the route of this element
|
|
* parentResource: the reference to the parent resource
|
|
*
|
|
* All of this fields except for id, are handled (and created) by Restangular. By default,
|
|
* the field values will be id, route and parentResource respectively
|
|
*/
|
|
config.restangularFields = config.restangularFields || {
|
|
id: "id",
|
|
route: "route",
|
|
parentResource: "parentResource",
|
|
restangularCollection: "restangularCollection",
|
|
cannonicalId: "__cannonicalId",
|
|
etag: "restangularEtag",
|
|
selfLink: "href",
|
|
get: "get",
|
|
getList: "getList",
|
|
put: "put",
|
|
post: "post",
|
|
remove: "remove",
|
|
head: "head",
|
|
trace: "trace",
|
|
options: "options",
|
|
patch: "patch",
|
|
getRestangularUrl: "getRestangularUrl",
|
|
getRequestedUrl: "getRequestedUrl",
|
|
putElement: "putElement",
|
|
addRestangularMethod: "addRestangularMethod",
|
|
getParentList: "getParentList",
|
|
clone: "clone",
|
|
ids: "ids",
|
|
httpConfig: '_$httpConfig',
|
|
reqParams: 'reqParams',
|
|
one: 'one',
|
|
all: 'all',
|
|
several: 'several',
|
|
oneUrl: 'oneUrl',
|
|
allUrl: 'allUrl',
|
|
customPUT: 'customPUT',
|
|
customPOST: 'customPOST',
|
|
customDELETE: 'customDELETE',
|
|
customGET: 'customGET',
|
|
customGETLIST: 'customGETLIST',
|
|
customOperation: 'customOperation',
|
|
doPUT: 'doPUT',
|
|
doPOST: 'doPOST',
|
|
doDELETE: 'doDELETE',
|
|
doGET: 'doGET',
|
|
doGETLIST: 'doGETLIST',
|
|
fromServer: '$fromServer',
|
|
withConfig: 'withConfig',
|
|
withHttpConfig: 'withHttpConfig'
|
|
};
|
|
object.setRestangularFields = function(resFields) {
|
|
config.restangularFields =
|
|
_.extend(config.restangularFields, resFields);
|
|
return this;
|
|
};
|
|
|
|
config.isRestangularized = function(obj) {
|
|
return !!obj[config.restangularFields.one] || !!obj[config.restangularFields.all];
|
|
};
|
|
|
|
config.setFieldToElem = function(field, elem, value) {
|
|
var properties = field.split('.');
|
|
var idValue = elem;
|
|
_.each(_.initial(properties), function(prop) {
|
|
idValue[prop] = {};
|
|
idValue = idValue[prop];
|
|
});
|
|
idValue[_.last(properties)] = value;
|
|
return this;
|
|
};
|
|
|
|
config.getFieldFromElem = function(field, elem) {
|
|
var properties = field.split('.');
|
|
var idValue = elem;
|
|
_.each(properties, function(prop) {
|
|
if (idValue) {
|
|
idValue = idValue[prop];
|
|
}
|
|
});
|
|
return angular.copy(idValue);
|
|
};
|
|
|
|
config.setIdToElem = function(elem, id) {
|
|
config.setFieldToElem(config.restangularFields.id, elem, id);
|
|
return this;
|
|
};
|
|
|
|
config.getIdFromElem = function(elem) {
|
|
return config.getFieldFromElem(config.restangularFields.id, elem);
|
|
};
|
|
|
|
config.isValidId = function(elemId) {
|
|
return "" !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId);
|
|
};
|
|
|
|
config.setUrlToElem = function(elem, url) {
|
|
config.setFieldToElem(config.restangularFields.selfLink, elem, url);
|
|
return this;
|
|
};
|
|
|
|
config.getUrlFromElem = function(elem) {
|
|
return config.getFieldFromElem(config.restangularFields.selfLink, elem);
|
|
};
|
|
|
|
config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId;
|
|
object.setUseCannonicalId = function(value) {
|
|
config.useCannonicalId = value;
|
|
return this;
|
|
};
|
|
|
|
config.getCannonicalIdFromElem = function(elem) {
|
|
var cannonicalId = elem[config.restangularFields.cannonicalId];
|
|
var actualId = config.isValidId(cannonicalId) ?
|
|
cannonicalId : config.getIdFromElem(elem);
|
|
return actualId;
|
|
};
|
|
|
|
/**
|
|
* Sets the Response parser. This is used in case your response isn't directly the data.
|
|
* For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}}
|
|
* you can extract this data which is the one that needs wrapping
|
|
*
|
|
* The ResponseExtractor is a function that receives the response and the method executed.
|
|
*/
|
|
|
|
config.responseInterceptors = config.responseInterceptors || [];
|
|
|
|
config.defaultResponseInterceptor = function(data, operation,
|
|
what, url, response, deferred) {
|
|
return data;
|
|
};
|
|
|
|
config.responseExtractor = function(data, operation,
|
|
what, url, response, deferred) {
|
|
var interceptors = angular.copy(config.responseInterceptors);
|
|
interceptors.push(config.defaultResponseInterceptor);
|
|
var theData = data;
|
|
_.each(interceptors, function(interceptor) {
|
|
theData = interceptor(theData, operation,
|
|
what, url, response, deferred);
|
|
});
|
|
return theData;
|
|
};
|
|
|
|
object.addResponseInterceptor = function(extractor) {
|
|
config.responseInterceptors.push(extractor);
|
|
return this;
|
|
};
|
|
|
|
object.setResponseInterceptor = object.addResponseInterceptor;
|
|
object.setResponseExtractor = object.addResponseInterceptor;
|
|
|
|
/**
|
|
* Response interceptor is called just before resolving promises.
|
|
*/
|
|
|
|
|
|
/**
|
|
* Request interceptor is called before sending an object to the server.
|
|
*/
|
|
config.requestInterceptors = config.requestInterceptors || [];
|
|
|
|
config.defaultInterceptor = function(element, operation,
|
|
path, url, headers, params, httpConfig) {
|
|
return {
|
|
element: element,
|
|
headers: headers,
|
|
params: params,
|
|
httpConfig: httpConfig
|
|
};
|
|
};
|
|
|
|
config.fullRequestInterceptor = function(element, operation,
|
|
path, url, headers, params, httpConfig) {
|
|
var interceptors = angular.copy(config.requestInterceptors);
|
|
interceptors.push(config.defaultInterceptor);
|
|
return _.reduce(interceptors, function(request, interceptor) {
|
|
return _.defaults(request, interceptor(element, operation,
|
|
path, url, headers, params, httpConfig));
|
|
}, {});
|
|
};
|
|
|
|
object.addRequestInterceptor = function(interceptor) {
|
|
config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) {
|
|
return {
|
|
headers: headers,
|
|
params: params,
|
|
element: interceptor(elem, operation, path, url),
|
|
httpConfig: httpConfig
|
|
};
|
|
});
|
|
return this;
|
|
};
|
|
|
|
object.setRequestInterceptor = object.addRequestInterceptor;
|
|
|
|
object.addFullRequestInterceptor = function(interceptor) {
|
|
config.requestInterceptors.push(interceptor);
|
|
return this;
|
|
};
|
|
|
|
object.setFullRequestInterceptor = object.addFullRequestInterceptor;
|
|
|
|
config.errorInterceptor = config.errorInterceptor || function() {};
|
|
|
|
object.setErrorInterceptor = function(interceptor) {
|
|
config.errorInterceptor = interceptor;
|
|
return this;
|
|
};
|
|
|
|
config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) {
|
|
return elem;
|
|
};
|
|
object.setOnBeforeElemRestangularized = function(post) {
|
|
config.onBeforeElemRestangularized = post;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* This method is called after an element has been "Restangularized".
|
|
*
|
|
* It receives the element, a boolean indicating if it's an element or a collection
|
|
* and the name of the model
|
|
*
|
|
*/
|
|
config.onElemRestangularized = config.onElemRestangularized || function(elem) {
|
|
return elem;
|
|
};
|
|
object.setOnElemRestangularized = function(post) {
|
|
config.onElemRestangularized = post;
|
|
return this;
|
|
};
|
|
|
|
config.shouldSaveParent = config.shouldSaveParent || function() {
|
|
return true;
|
|
};
|
|
object.setParentless = function(values) {
|
|
if (_.isArray(values)) {
|
|
config.shouldSaveParent = function(route) {
|
|
return !_.contains(values, route);
|
|
};
|
|
} else if (_.isBoolean(values)) {
|
|
config.shouldSaveParent = function() {
|
|
return !values;
|
|
};
|
|
}
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* This lets you set a suffix to every request.
|
|
*
|
|
* For example, if your api requires that for JSon requests you do /users/123.json, you can set that
|
|
* in here.
|
|
*
|
|
*
|
|
* By default, the suffix is null
|
|
*/
|
|
config.suffix = _.isUndefined(config.suffix) ? null : config.suffix;
|
|
object.setRequestSuffix = function(newSuffix) {
|
|
config.suffix = newSuffix;
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Add element transformers for certain routes.
|
|
*/
|
|
config.transformers = config.transformers || {};
|
|
object.addElementTransformer = function(type, secondArg, thirdArg) {
|
|
var isCollection = null;
|
|
var transformer = null;
|
|
if (arguments.length === 2) {
|
|
transformer = secondArg;
|
|
} else {
|
|
transformer = thirdArg;
|
|
isCollection = secondArg;
|
|
}
|
|
|
|
var typeTransformers = config.transformers[type];
|
|
if (!typeTransformers) {
|
|
typeTransformers = config.transformers[type] = [];
|
|
}
|
|
|
|
typeTransformers.push(function(coll, elem) {
|
|
if (_.isNull(isCollection) || (coll == isCollection)) {
|
|
return transformer(elem);
|
|
}
|
|
return elem;
|
|
});
|
|
};
|
|
|
|
object.extendCollection = function(route, fn) {
|
|
return object.addElementTransformer(route, true, fn);
|
|
};
|
|
|
|
object.extendModel = function(route, fn) {
|
|
return object.addElementTransformer(route, false, fn);
|
|
};
|
|
|
|
config.transformElem = function(elem, isCollection, route, Restangular) {
|
|
if (!config.transformLocalElements && !elem[config.restangularFields.fromServer]) {
|
|
return elem;
|
|
}
|
|
var typeTransformers = config.transformers[route];
|
|
var changedElem = elem;
|
|
if (typeTransformers) {
|
|
_.each(typeTransformers, function(transformer) {
|
|
changedElem = transformer(isCollection, changedElem);
|
|
});
|
|
}
|
|
return config.onElemRestangularized(changedElem,
|
|
isCollection, route, Restangular);
|
|
};
|
|
|
|
config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? true : config.transformLocalElements;
|
|
object.setTransformOnlyServerElements = function(active) {
|
|
config.transformLocalElements = !active;
|
|
}
|
|
|
|
config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse;
|
|
object.setFullResponse = function(full) {
|
|
config.fullResponse = full;
|
|
return this;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//Internal values and functions
|
|
config.urlCreatorFactory = {};
|
|
|
|
/**
|
|
* Base URL Creator. Base prototype for everything related to it
|
|
**/
|
|
|
|
var BaseCreator = function() {
|
|
};
|
|
|
|
BaseCreator.prototype.setConfig = function(config) {
|
|
this.config = config;
|
|
return this;
|
|
};
|
|
|
|
BaseCreator.prototype.parentsArray = function(current) {
|
|
var parents = [];
|
|
while(current) {
|
|
parents.push(current);
|
|
current = current[this.config.restangularFields.parentResource];
|
|
}
|
|
return parents.reverse();
|
|
};
|
|
|
|
function RestangularResource(config, $http, url, configurer) {
|
|
var resource = {};
|
|
_.each(_.keys(configurer), function(key) {
|
|
var value = configurer[key];
|
|
|
|
// Add default parameters
|
|
value.params = _.extend({}, value.params,
|
|
config.defaultRequestParams[value.method.toLowerCase()]);
|
|
// We don't want the ? if no params are there
|
|
if (_.isEmpty(value.params)) {
|
|
delete value.params;
|
|
}
|
|
|
|
if (config.isSafe(value.method)) {
|
|
|
|
resource[key] = function() {
|
|
return $http(_.extend(value, {
|
|
url: url
|
|
}));
|
|
};
|
|
|
|
} else {
|
|
|
|
resource[key] = function(data) {
|
|
return $http(_.extend(value, {
|
|
url: url,
|
|
data: data
|
|
}));
|
|
};
|
|
|
|
}
|
|
});
|
|
|
|
return resource;
|
|
}
|
|
|
|
BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag, operation) {
|
|
|
|
var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common);
|
|
var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders);
|
|
|
|
if (etag) {
|
|
if (!config.isSafe(operation)) {
|
|
headers['If-Match'] = etag;
|
|
} else {
|
|
headers['If-None-Match'] = etag;
|
|
}
|
|
}
|
|
|
|
var url = this.base(current);
|
|
|
|
if (what) {
|
|
var add = '';
|
|
if (!/\/$/.test(url)) {
|
|
add += '/';
|
|
}
|
|
add += what;
|
|
url += add;
|
|
}
|
|
|
|
if (this.config.suffix
|
|
&& url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1
|
|
&& !this.config.getUrlFromElem(current)) {
|
|
url += this.config.suffix;
|
|
}
|
|
|
|
current[this.config.restangularFields.httpConfig] = undefined;
|
|
|
|
|
|
return RestangularResource(this.config, $http, url, {
|
|
getList: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'GET',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
get: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'GET',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
jsonp: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'jsonp',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
put: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'PUT',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
post: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'POST',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
remove: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'DELETE',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
head: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'HEAD',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
trace: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'TRACE',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
options: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'OPTIONS',
|
|
params: params,
|
|
headers: headers}),
|
|
|
|
patch: this.config.withHttpValues(localHttpConfig,
|
|
{method: 'PATCH',
|
|
params: params,
|
|
headers: headers})
|
|
});
|
|
};
|
|
|
|
/**
|
|
* This is the Path URL creator. It uses Path to show Hierarchy in the Rest API.
|
|
* This means that if you have an Account that then has a set of Buildings, a URL to a building
|
|
* would be /accounts/123/buildings/456
|
|
**/
|
|
var Path = function() {
|
|
};
|
|
|
|
Path.prototype = new BaseCreator();
|
|
|
|
Path.prototype.base = function(current) {
|
|
var __this = this;
|
|
return _.reduce(this.parentsArray(current), function(acum, elem) {
|
|
var elemUrl;
|
|
var elemSelfLink = __this.config.getUrlFromElem(elem);
|
|
if (elemSelfLink) {
|
|
if (__this.config.isAbsoluteUrl(elemSelfLink)) {
|
|
return elemSelfLink;
|
|
} else {
|
|
elemUrl = elemSelfLink;
|
|
}
|
|
} else {
|
|
elemUrl = elem[__this.config.restangularFields.route];
|
|
|
|
if (elem[__this.config.restangularFields.restangularCollection]) {
|
|
var ids = elem[__this.config.restangularFields.ids];
|
|
if (ids) {
|
|
elemUrl += "/" + ids.join(",");
|
|
}
|
|
} else {
|
|
var elemId;
|
|
if (__this.config.useCannonicalId) {
|
|
elemId = __this.config.getCannonicalIdFromElem(elem);
|
|
} else {
|
|
elemId = __this.config.getIdFromElem(elem);
|
|
}
|
|
|
|
if (config.isValidId(elemId)) {
|
|
elemUrl += "/" + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId);
|
|
}
|
|
}
|
|
}
|
|
|
|
return acum.replace(/\/$/, "") + "/" + elemUrl;
|
|
|
|
}, this.config.baseUrl);
|
|
};
|
|
|
|
|
|
|
|
Path.prototype.fetchUrl = function(current, what) {
|
|
var baseUrl = this.base(current);
|
|
if (what) {
|
|
baseUrl += "/" + what;
|
|
}
|
|
return baseUrl;
|
|
};
|
|
|
|
Path.prototype.fetchRequestedUrl = function(current, what) {
|
|
var url = this.fetchUrl(current, what);
|
|
var params = current[config.restangularFields.reqParams];
|
|
|
|
// From here on and until the end of fetchRequestedUrl,
|
|
// the code has been kindly borrowed from angular.js
|
|
// The reason for such code bloating is coherence:
|
|
// If the user were to use this for cache management, the
|
|
// serialization of parameters would need to be identical
|
|
// to the one done by angular for cache keys to match.
|
|
function sortedKeys(obj) {
|
|
var keys = [];
|
|
for (var key in obj) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
keys.push(key);
|
|
}
|
|
}
|
|
return keys.sort();
|
|
}
|
|
|
|
function forEachSorted(obj, iterator, context) {
|
|
var keys = sortedKeys(obj);
|
|
for ( var i = 0; i < keys.length; i++) {
|
|
iterator.call(context, obj[keys[i]], keys[i]);
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
function encodeUriQuery(val, pctEncodeSpaces) {
|
|
return encodeURIComponent(val).
|
|
replace(/%40/gi, '@').
|
|
replace(/%3A/gi, ':').
|
|
replace(/%24/g, '$').
|
|
replace(/%2C/gi, ',').
|
|
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
|
|
}
|
|
|
|
if (!params) return url;
|
|
var parts = [];
|
|
forEachSorted(params, function(value, key) {
|
|
if (value == null || value == undefined) return;
|
|
if (!angular.isArray(value)) value = [value];
|
|
|
|
angular.forEach(value, function(v) {
|
|
if (angular.isObject(v)) {
|
|
v = angular.toJson(v);
|
|
}
|
|
parts.push(encodeUriQuery(key) + '=' +
|
|
encodeUriQuery(v));
|
|
});
|
|
});
|
|
return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&');
|
|
};
|
|
|
|
|
|
|
|
config.urlCreatorFactory.path = Path;
|
|
|
|
};
|
|
|
|
var globalConfiguration = {};
|
|
|
|
Configurer.init(this, globalConfiguration);
|
|
|
|
|
|
|
|
|
|
this.$get = ['$http', '$q', function($http, $q) {
|
|
|
|
function createServiceForConfiguration(config) {
|
|
var service = {};
|
|
|
|
var urlHandler = new config.urlCreatorFactory[config.urlCreator]();
|
|
urlHandler.setConfig(config);
|
|
|
|
function restangularizeBase(parent, elem, route, reqParams, fromServer) {
|
|
elem[config.restangularFields.route] = route;
|
|
elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem);
|
|
elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem);
|
|
elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem);
|
|
elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem);
|
|
elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams;
|
|
elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem);
|
|
|
|
// RequestLess connection
|
|
elem[config.restangularFields.one] = _.bind(one, elem, elem);
|
|
elem[config.restangularFields.all] = _.bind(all, elem, elem);
|
|
elem[config.restangularFields.several] = _.bind(several, elem, elem);
|
|
elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem);
|
|
elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem);
|
|
|
|
elem[config.restangularFields.fromServer] = !!fromServer;
|
|
|
|
if (parent && config.shouldSaveParent(route)) {
|
|
var parentId = config.getIdFromElem(parent);
|
|
var parentUrl = config.getUrlFromElem(parent);
|
|
|
|
var restangularFieldsForParent = _.union(
|
|
_.values( _.pick(config.restangularFields, ['route', 'parentResource']) ),
|
|
config.extraFields
|
|
);
|
|
var parentResource = _.pick(parent, restangularFieldsForParent);
|
|
|
|
if (config.isValidId(parentId)) {
|
|
config.setIdToElem(parentResource, parentId);
|
|
}
|
|
if (config.isValidId(parentUrl)) {
|
|
config.setUrlToElem(parentResource, parentUrl);
|
|
}
|
|
|
|
elem[config.restangularFields.parentResource] = parentResource;
|
|
} else {
|
|
elem[config.restangularFields.parentResource] = null;
|
|
}
|
|
return elem;
|
|
}
|
|
|
|
|
|
|
|
function one(parent, route, id) {
|
|
if (_.isNumber(route) || _.isNumber(parent)) {
|
|
var error = "You're creating a Restangular entity with the number "
|
|
error += "instead of the route or the parent. You can't call .one(12)";
|
|
throw new Error(error);
|
|
}
|
|
var elem = {};
|
|
config.setIdToElem(elem, id);
|
|
return restangularizeElem(parent, elem , route, false);
|
|
}
|
|
|
|
|
|
function all(parent, route) {
|
|
return restangularizeCollection(parent, [] , route, false);
|
|
}
|
|
|
|
function several(parent, route, ids) {
|
|
var collection = [];
|
|
collection[config.restangularFields.ids] =
|
|
Array.prototype.splice.call(arguments, 2);
|
|
return restangularizeCollection(parent, collection , route, false);
|
|
}
|
|
|
|
function oneUrl(parent, route, url) {
|
|
if (!route) {
|
|
throw new Error("Route is mandatory when creating new Restangular objects.");
|
|
}
|
|
var elem = {};
|
|
config.setUrlToElem(elem, url);
|
|
return restangularizeElem(parent, elem , route, false);
|
|
}
|
|
|
|
|
|
function allUrl(parent, route, url) {
|
|
if (!route) {
|
|
throw new Error("Route is mandatory when creating new Restangular objects.");
|
|
}
|
|
var elem = {};
|
|
config.setUrlToElem(elem, url);
|
|
return restangularizeCollection(parent, elem , route, false);
|
|
}
|
|
// Promises
|
|
function restangularizePromise(promise, isCollection, valueToFill) {
|
|
promise.call = _.bind(promiseCall, promise);
|
|
promise.get = _.bind(promiseGet, promise);
|
|
promise[config.restangularFields.restangularCollection] = isCollection;
|
|
if (isCollection) {
|
|
promise.push = _.bind(promiseCall, promise, "push");
|
|
}
|
|
promise.$object = valueToFill;
|
|
return promise;
|
|
}
|
|
|
|
function promiseCall(method) {
|
|
var deferred = $q.defer();
|
|
var callArgs = arguments;
|
|
var filledValue = {};
|
|
this.then(function(val) {
|
|
var params = Array.prototype.slice.call(callArgs, 1);
|
|
var func = val[method];
|
|
func.apply(val, params);
|
|
filledValue = val;
|
|
deferred.resolve(val);
|
|
});
|
|
return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue);
|
|
}
|
|
|
|
function promiseGet(what) {
|
|
var deferred = $q.defer();
|
|
var filledValue = {};
|
|
this.then(function(val) {
|
|
filledValue = val[what];
|
|
deferred.resolve(filledValue);
|
|
});
|
|
return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue);
|
|
}
|
|
|
|
function resolvePromise(deferred, response, data, filledValue) {
|
|
|
|
_.extend(filledValue, data);
|
|
|
|
// Trigger the full response interceptor.
|
|
if (config.fullResponse) {
|
|
return deferred.resolve(_.extend(response, {
|
|
data: data
|
|
}));
|
|
} else {
|
|
deferred.resolve(data);
|
|
}
|
|
}
|
|
|
|
|
|
// Elements
|
|
|
|
function stripRestangular(elem) {
|
|
if (_.isArray(elem)) {
|
|
var array = [];
|
|
_.each(elem, function(value) {
|
|
array.push(stripRestangular(value));
|
|
});
|
|
return array;
|
|
} else {
|
|
return _.omit(elem, _.values(_.omit(config.restangularFields, 'id')));
|
|
}
|
|
|
|
|
|
}
|
|
|
|
function addCustomOperation(elem) {
|
|
elem[config.restangularFields.customOperation] = _.bind(customFunction, elem);
|
|
_.each(["put", "post", "get", "delete"], function(oper) {
|
|
_.each(["do", "custom"], function(alias) {
|
|
var callOperation = oper === 'delete' ? 'remove' : oper;
|
|
var name = alias + oper.toUpperCase();
|
|
var callFunction;
|
|
|
|
if (callOperation !== 'put' && callOperation !== 'post') {
|
|
callFunction = customFunction;
|
|
} else {
|
|
callFunction = function(operation, elem, path, params, headers) {
|
|
return _.bind(customFunction, this)(operation, path, params, headers, elem);
|
|
};
|
|
}
|
|
elem[name] = _.bind(callFunction, elem, callOperation);
|
|
});
|
|
});
|
|
elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem);
|
|
elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST];
|
|
}
|
|
|
|
function copyRestangularizedElement(fromElement) {
|
|
var copiedElement = angular.copy(fromElement);
|
|
return restangularizeElem(copiedElement[config.restangularFields.parentResource],
|
|
copiedElement, copiedElement[config.restangularFields.route], true);
|
|
}
|
|
|
|
function restangularizeElem(parent, element, route, fromServer, collection, reqParams) {
|
|
var elem = config.onBeforeElemRestangularized(element, false, route);
|
|
|
|
var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer);
|
|
|
|
if (config.useCannonicalId) {
|
|
localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem);
|
|
}
|
|
|
|
if (collection) {
|
|
localElem[config.restangularFields.getParentList] = function() {
|
|
return collection;
|
|
};
|
|
}
|
|
|
|
localElem[config.restangularFields.restangularCollection] = false;
|
|
localElem[config.restangularFields.get] = _.bind(getFunction, localElem);
|
|
localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem);
|
|
localElem[config.restangularFields.put] = _.bind(putFunction, localElem);
|
|
localElem[config.restangularFields.post] = _.bind(postFunction, localElem);
|
|
localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem);
|
|
localElem[config.restangularFields.head] = _.bind(headFunction, localElem);
|
|
localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem);
|
|
localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem);
|
|
localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem);
|
|
|
|
addCustomOperation(localElem);
|
|
return config.transformElem(localElem, false, route, service);
|
|
}
|
|
|
|
function restangularizeCollection(parent, element, route, fromServer, reqParams) {
|
|
var elem = config.onBeforeElemRestangularized(element, true, route);
|
|
|
|
var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer);
|
|
localElem[config.restangularFields.restangularCollection] = true;
|
|
localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null);
|
|
localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem);
|
|
localElem[config.restangularFields.head] = _.bind(headFunction, localElem);
|
|
localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem);
|
|
localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem);
|
|
localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem);
|
|
localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem);
|
|
localElem[config.restangularFields.get] = _.bind(getById, localElem);
|
|
localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null);
|
|
|
|
addCustomOperation(localElem);
|
|
return config.transformElem(localElem, true, route, service);
|
|
}
|
|
|
|
function restangularizeCollectionAndElements(parent, element, route) {
|
|
var collection = restangularizeCollection(parent, element, route, false);
|
|
_.each(collection, function(elem) {
|
|
restangularizeElem(parent, elem, route, false);
|
|
});
|
|
return collection;
|
|
}
|
|
|
|
function getById(id, reqParams, headers){
|
|
return this.customGET(id.toString(), reqParams, headers);
|
|
}
|
|
|
|
function putElementFunction(idx, params, headers) {
|
|
var __this = this;
|
|
var elemToPut = this[idx];
|
|
var deferred = $q.defer();
|
|
var filledArray = [];
|
|
filledArray = config.transformElem(filledArray, true, whatFetched, service)
|
|
elemToPut.put(params, headers).then(function(serverElem) {
|
|
var newArray = copyRestangularizedElement(__this);
|
|
newArray[idx] = serverElem;
|
|
filledArray = newArray;
|
|
deferred.resolve(newArray);
|
|
}, function(response) {
|
|
deferred.reject(response);
|
|
});
|
|
|
|
return restangularizePromise(deferred.promise, true, filledArray);
|
|
}
|
|
|
|
function parseResponse(resData, operation, route, fetchUrl, response, deferred) {
|
|
var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred);
|
|
var etag = response.headers("ETag");
|
|
if (data && etag) {
|
|
data[config.restangularFields.etag] = etag;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
|
|
function fetchFunction(what, reqParams, headers) {
|
|
var __this = this;
|
|
var deferred = $q.defer();
|
|
var operation = 'getList';
|
|
var url = urlHandler.fetchUrl(this, what);
|
|
var whatFetched = what || __this[config.restangularFields.route];
|
|
|
|
var request = config.fullRequestInterceptor(null, operation,
|
|
whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {});
|
|
|
|
var filledArray = [];
|
|
filledArray = config.transformElem(filledArray, true, whatFetched, service)
|
|
|
|
var method = "getList";
|
|
|
|
if (config.jsonp) {
|
|
method = "jsonp";
|
|
}
|
|
|
|
urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what,
|
|
this[config.restangularFields.etag], operation)[method]().then(function(response) {
|
|
var resData = response.data;
|
|
var fullParams = response.config.params;
|
|
var data = parseResponse(resData, operation, whatFetched, url, response, deferred);
|
|
if (!_.isArray(data)) {
|
|
throw new Error("Response for getList SHOULD be an array and not an object or something else");
|
|
}
|
|
var processedData = _.map(data, function(elem) {
|
|
if (!__this[config.restangularFields.restangularCollection]) {
|
|
return restangularizeElem(__this, elem, what, true, data);
|
|
} else {
|
|
return restangularizeElem(__this[config.restangularFields.parentResource],
|
|
elem, __this[config.restangularFields.route], true, data);
|
|
}
|
|
|
|
});
|
|
|
|
processedData = _.extend(data, processedData);
|
|
|
|
if (!__this[config.restangularFields.restangularCollection]) {
|
|
resolvePromise(deferred, response, restangularizeCollection(__this, processedData, what, true, fullParams), filledArray);
|
|
} else {
|
|
resolvePromise(deferred, response, restangularizeCollection(__this[config.restangularFields.parentResource], processedData, __this[config.restangularFields.route], true, fullParams), filledArray);
|
|
}
|
|
}, function error(response) {
|
|
if (response.status === 304 && __this[config.restangularFields.restangularCollection]) {
|
|
resolvePromise(deferred, response, __this, filledArray);
|
|
} else if ( config.errorInterceptor(response, deferred) !== false ) {
|
|
deferred.reject(response);
|
|
}
|
|
});
|
|
|
|
return restangularizePromise(deferred.promise, true, filledArray);
|
|
}
|
|
|
|
function withHttpConfig(httpConfig) {
|
|
this[config.restangularFields.httpConfig] = httpConfig;
|
|
return this;
|
|
}
|
|
|
|
function elemFunction(operation, what, params, obj, headers) {
|
|
var __this = this;
|
|
var deferred = $q.defer();
|
|
var resParams = params || {};
|
|
var route = what || this[config.restangularFields.route];
|
|
var fetchUrl = urlHandler.fetchUrl(this, what);
|
|
|
|
var callObj = obj || this;
|
|
// fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field)
|
|
var etag = callObj[config.restangularFields.etag] || (operation != "post" ? this[config.restangularFields.etag] : null);
|
|
|
|
if (_.isObject(callObj) && config.isRestangularized(callObj)) {
|
|
callObj = stripRestangular(callObj);
|
|
}
|
|
var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl,
|
|
headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {});
|
|
|
|
var filledObject = {};
|
|
filledObject = config.transformElem(filledObject, false, route, service);
|
|
|
|
var okCallback = function(response) {
|
|
var resData = response.data;
|
|
var fullParams = response.config.params;
|
|
var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred);
|
|
if (elem) {
|
|
|
|
if (operation === "post" && !__this[config.restangularFields.restangularCollection]) {
|
|
resolvePromise(deferred, response, restangularizeElem(__this, elem, what, true, null, fullParams), filledObject);
|
|
} else {
|
|
resolvePromise(deferred, response, restangularizeElem(__this[config.restangularFields.parentResource], elem, __this[config.restangularFields.route], true, null, fullParams), filledObject);
|
|
}
|
|
|
|
} else {
|
|
resolvePromise(deferred, response, undefined, filledObject);
|
|
}
|
|
};
|
|
|
|
var errorCallback = function(response) {
|
|
if (response.status === 304 && config.isSafe(operation)) {
|
|
resolvePromise(deferred, response, __this, filledObject);
|
|
} else if ( config.errorInterceptor(response, deferred) !== false ) {
|
|
deferred.reject(response);
|
|
}
|
|
};
|
|
// Overring HTTP Method
|
|
var callOperation = operation;
|
|
var callHeaders = _.extend({}, request.headers);
|
|
var isOverrideOperation = config.isOverridenMethod(operation);
|
|
if (isOverrideOperation) {
|
|
callOperation = 'post';
|
|
callHeaders = _.extend(callHeaders, {'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation});
|
|
} else if (config.jsonp && callOperation === 'get') {
|
|
callOperation = 'jsonp';
|
|
}
|
|
|
|
if (config.isSafe(operation)) {
|
|
if (isOverrideOperation) {
|
|
urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params,
|
|
what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback);
|
|
} else {
|
|
urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params,
|
|
what, etag, callOperation)[callOperation]().then(okCallback, errorCallback);
|
|
}
|
|
} else {
|
|
urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params,
|
|
what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback);
|
|
}
|
|
|
|
return restangularizePromise(deferred.promise, false, filledObject);
|
|
}
|
|
|
|
function getFunction(params, headers) {
|
|
return _.bind(elemFunction, this)("get", undefined, params, undefined, headers);
|
|
}
|
|
|
|
function deleteFunction(params, headers) {
|
|
return _.bind(elemFunction, this)("remove", undefined, params, undefined, headers);
|
|
}
|
|
|
|
function putFunction(params, headers) {
|
|
return _.bind(elemFunction, this)("put", undefined, params, undefined, headers);
|
|
}
|
|
|
|
function postFunction(what, elem, params, headers) {
|
|
return _.bind(elemFunction, this)("post", what, params, elem, headers);
|
|
}
|
|
|
|
function headFunction(params, headers) {
|
|
return _.bind(elemFunction, this)("head", undefined, params, undefined, headers);
|
|
}
|
|
|
|
function traceFunction(params, headers) {
|
|
return _.bind(elemFunction, this)("trace", undefined, params, undefined, headers);
|
|
}
|
|
|
|
function optionsFunction(params, headers) {
|
|
return _.bind(elemFunction, this)("options", undefined, params, undefined, headers);
|
|
}
|
|
|
|
function patchFunction(elem, params, headers) {
|
|
return _.bind(elemFunction, this)("patch", undefined, params, elem, headers);
|
|
}
|
|
|
|
function customFunction(operation, path, params, headers, elem) {
|
|
return _.bind(elemFunction, this)(operation, path, params, elem, headers);
|
|
}
|
|
|
|
function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) {
|
|
var bindedFunction;
|
|
if (operation === 'getList') {
|
|
bindedFunction = _.bind(fetchFunction, this, path);
|
|
} else {
|
|
bindedFunction = _.bind(customFunction, this, operation, path);
|
|
}
|
|
|
|
var createdFunction = function(params, headers, elem) {
|
|
var callParams = _.defaults({
|
|
params: params,
|
|
headers: headers,
|
|
elem: elem
|
|
}, {
|
|
params: defaultParams,
|
|
headers: defaultHeaders,
|
|
elem: defaultElem
|
|
});
|
|
return bindedFunction(callParams.params, callParams.headers, callParams.elem);
|
|
};
|
|
|
|
if (config.isSafe(operation)) {
|
|
this[name] = createdFunction;
|
|
} else {
|
|
this[name] = function(elem, params, headers) {
|
|
return createdFunction(params, headers, elem);
|
|
};
|
|
}
|
|
|
|
}
|
|
|
|
function withConfigurationFunction(configurer) {
|
|
var newConfig = angular.copy(_.omit(config, 'configuration'));
|
|
Configurer.init(newConfig, newConfig);
|
|
configurer(newConfig);
|
|
return createServiceForConfiguration(newConfig);
|
|
}
|
|
|
|
|
|
Configurer.init(service, config);
|
|
|
|
service.copy = _.bind(copyRestangularizedElement, service);
|
|
|
|
service.withConfig = _.bind(withConfigurationFunction, service);
|
|
|
|
service.one = _.bind(one, service, null);
|
|
|
|
service.all = _.bind(all, service, null);
|
|
|
|
service.several = _.bind(several, service, null);
|
|
|
|
service.oneUrl = _.bind(oneUrl, service, null);
|
|
|
|
service.allUrl = _.bind(allUrl, service, null);
|
|
|
|
service.stripRestangular = _.bind(stripRestangular, service);
|
|
|
|
service.restangularizeElement = _.bind(restangularizeElem, service);
|
|
|
|
service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service);
|
|
|
|
return service;
|
|
}
|
|
|
|
return createServiceForConfiguration(globalConfiguration);
|
|
|
|
}];
|
|
}
|
|
);
|
|
|
|
})();
|