| /** |
| * Restful Resources service for AngularJS apps |
| * @version v1.6.1 - 2017-01-06 * @link https://github.com/mgonto/restangular |
| * @author Martin Gontovnikas <martin@gon.to> |
| * @license MIT License, http://www.opensource.org/licenses/MIT |
| */(function(root, factory) { |
| /* global define, require */ |
| // https://github.com/umdjs/umd/blob/master/templates/returnExports.js |
| if (typeof define === 'function' && define.amd) { |
| define(['lodash', 'angular'], factory); |
| } else if (typeof module === 'object' && module.exports) { |
| module.exports = factory(require('lodash'), require('angular')); |
| } else { |
| // No global export, Restangular will register itself as Angular.js module |
| factory(root._, root.angular); |
| } |
| }(this, function(_, angular) { |
| |
| var restangular = angular.module('restangular', []); |
| |
| restangular.provider('Restangular', function() { |
| // Configuration |
| var Configurer = {}; |
| Configurer.init = function(object, config) { |
| object.configuration = config; |
| |
| /** |
| * Those are HTTP safe methods for which there is no need to pass any data with the request. |
| */ |
| var safeMethods = ['get', 'head', 'options', 'trace', 'getlist']; |
| config.isSafe = function(operation) { |
| return _.includes(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) ? true : config.absoluteUrl; |
| 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; |
| }; |
| |
| /** |
| * Always return plain data, no restangularized object |
| **/ |
| config.plainByDefault = config.plainByDefault || false; |
| object.setPlainByDefault = function(value) { |
| config.plainByDefault = value === true ? true : false; |
| 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', |
| customPATCH: 'customPATCH', |
| customPOST: 'customPOST', |
| customDELETE: 'customDELETE', |
| customGET: 'customGET', |
| customGETLIST: 'customGETLIST', |
| customOperation: 'customOperation', |
| doPUT: 'doPUT', |
| doPATCH: 'doPATCH', |
| doPOST: 'doPOST', |
| doDELETE: 'doDELETE', |
| doGET: 'doGET', |
| doGETLIST: 'doGETLIST', |
| fromServer: 'fromServer', |
| withConfig: 'withConfig', |
| withHttpConfig: 'withHttpConfig', |
| singleOne: 'singleOne', |
| plain: 'plain', |
| save: 'save', |
| restangularized: 'restangularized' |
| }; |
| object.setRestangularFields = function(resFields) { |
| config.restangularFields = |
| _.extend(config.restangularFields, resFields); |
| return this; |
| }; |
| |
| config.isRestangularized = function(obj) { |
| return !!obj[config.restangularFields.restangularized]; |
| }; |
| |
| 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 /*, route */ ) { |
| 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 /*, route */ ) { |
| 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; |
| }; |
| |
| config.errorInterceptors = config.errorInterceptors || []; |
| object.addErrorInterceptor = function(interceptor) { |
| config.errorInterceptors.push(interceptor); |
| return this; |
| }; |
| |
| object.setResponseInterceptor = object.addResponseInterceptor; |
| object.setResponseExtractor = object.addResponseInterceptor; |
| object.setErrorInterceptor = object.addErrorInterceptor; |
| |
| /** |
| * 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); |
| var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); |
| return _.reduce(interceptors, function(request, interceptor) { |
| return _.extend(request, interceptor(request.element, operation, |
| path, url, request.headers, request.params, request.httpConfig)); |
| }, defaultRequest); |
| }; |
| |
| 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.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) { |
| return elem; |
| }; |
| object.setOnBeforeElemRestangularized = function(post) { |
| config.onBeforeElemRestangularized = post; |
| return this; |
| }; |
| |
| object.setRestangularizePromiseInterceptor = function(interceptor) { |
| config.restangularizePromiseInterceptor = interceptor; |
| 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 !_.includes(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 || {}; |
| config.matchTransformers = config.matchTransformers || []; |
| object.addElementTransformer = function(type, secondArg, thirdArg) { |
| var isCollection = null; |
| var transformer = null; |
| if (arguments.length === 2) { |
| transformer = secondArg; |
| } else { |
| transformer = thirdArg; |
| isCollection = secondArg; |
| } |
| |
| var transformerFn = function(coll, elem) { |
| if (_.isNull(isCollection) || (coll === isCollection)) { |
| return transformer(elem); |
| } |
| return elem; |
| }; |
| |
| if (_.isRegExp(type)) { |
| config.matchTransformers.push({ |
| regexp: type, |
| transformer: transformerFn |
| }); |
| } else { |
| if (!config.transformers[type]) { |
| config.transformers[type] = []; |
| } |
| config.transformers[type].push(transformerFn); |
| } |
| |
| return object; |
| }; |
| |
| 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, force) { |
| if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { |
| return elem; |
| } |
| |
| var changedElem = elem; |
| |
| var matchTransformers = config.matchTransformers; |
| if (matchTransformers) { |
| _.each(matchTransformers, function(transformer) { |
| if (transformer.regexp.test(route)) { |
| changedElem = transformer.transformer(isCollection, changedElem); |
| } |
| }); |
| } |
| |
| var typeTransformers = config.transformers[route]; |
| if (typeTransformers) { |
| _.each(typeTransformers, function(transformer) { |
| changedElem = transformer(isCollection, changedElem); |
| }); |
| } |
| return config.onElemRestangularized(changedElem, isCollection, route, Restangular); |
| }; |
| |
| config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? |
| false : |
| 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 || what === 0) { |
| 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.normalizeUrl = function(url) { |
| var parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); |
| parts[2] = parts[2].replace(/[\\\/]+/g, '/'); |
| return (typeof parts[1] !== 'undefined') ? parts[1] + parts[2] : parts[2]; |
| }; |
| |
| 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) && !elem.singleOne) { |
| elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); |
| } |
| } |
| } |
| acum = acum.replace(/\/$/, '') + '/' + elemUrl; |
| return __this.normalizeUrl(acum); |
| |
| }, 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 + (this.config.suffix || ''); |
| } |
| |
| 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); |
| elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem); |
| |
| // Tag element as restangularized |
| elem[config.restangularFields.restangularized] = true; |
| |
| // 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', 'singleOne', 'parentResource'])), |
| config.extraFields |
| ); |
| var parentResource = _.pick(parent, restangularFieldsForParent); |
| |
| if (config.isValidId(parentId)) { |
| config.setIdToElem(parentResource, parentId, route); |
| } |
| if (config.isValidId(parentUrl)) { |
| config.setUrlToElem(parentResource, parentUrl, route); |
| } |
| |
| elem[config.restangularFields.parentResource] = parentResource; |
| } else { |
| elem[config.restangularFields.parentResource] = null; |
| } |
| return elem; |
| } |
| |
| function one(parent, route, id, singleOne) { |
| var error; |
| if (_.isNumber(route) || _.isNumber(parent)) { |
| error = 'You\'re creating a Restangular entity with the number '; |
| error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; |
| throw new Error(error); |
| } |
| if (_.isUndefined(route)) { |
| error = 'You\'re creating a Restangular entity either without the path. '; |
| error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; |
| throw new Error(error); |
| } |
| var elem = {}; |
| config.setIdToElem(elem, id, route); |
| config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); |
| 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, route); |
| 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, route); |
| 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; |
| if (config.restangularizePromiseInterceptor) { |
| config.restangularizePromiseInterceptor(promise); |
| } |
| 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(config.isRestangularized(value) ? stripRestangular(value) : value); |
| }); |
| return array; |
| } else { |
| return _.omit(elem, _.values(_.omit(config.restangularFields, 'id'))); |
| } |
| } |
| |
| function addCustomOperation(elem) { |
| elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); |
| var requestMethods = { |
| get: customFunction, |
| delete: customFunction |
| }; |
| _.each(['put', 'patch', 'post'], function(name) { |
| requestMethods[name] = function(operation, elem, path, params, headers) { |
| return _.bind(customFunction, this)(operation, path, params, headers, elem); |
| }; |
| }); |
| _.each(requestMethods, function(requestFunc, name) { |
| var callOperation = name === 'delete' ? 'remove' : name; |
| _.each(['do', 'custom'], function(alias) { |
| elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); |
| }); |
| }); |
| elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); |
| elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; |
| } |
| |
| function copyRestangularizedElement(element) { |
| var copiedElement = angular.copy(element); |
| |
| // check if we're dealing with a collection (i.e. an array) |
| // and restangularize the element using the proper restangularizer, |
| // element / collection |
| if (_.isArray(element)) { |
| return restangularizeCollection( |
| element[config.restangularFields.parentResource], |
| copiedElement, |
| element[config.restangularFields.route], |
| element[config.restangularFields.fromServer], |
| element[config.restangularFields.reqParams] |
| ); |
| } |
| |
| // not a collection, restangularize it as an element |
| return restangularizeElem( |
| element[config.restangularFields.parentResource], |
| copiedElement, |
| element[config.restangularFields.route], |
| element[config.restangularFields.fromServer], |
| element[config.restangularFields.restangularCollection], |
| element[config.restangularFields.reqParams] |
| ); |
| } |
| |
| 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); |
| localElem[config.restangularFields.save] = _.bind(save, localElem); |
| |
| addCustomOperation(localElem); |
| return config.transformElem(localElem, false, route, service, true); |
| } |
| |
| 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, true); |
| } |
| |
| function restangularizeCollectionAndElements(parent, element, route, fromServer) { |
| var collection = restangularizeCollection(parent, element, route, fromServer); |
| _.each(collection, function(elem) { |
| if (elem) { |
| restangularizeElem(parent, elem, route, fromServer); |
| } |
| }); |
| 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, elemToPut[config.restangularFields.route], 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'; |
| } |
| |
| var okCallback = function(response) { |
| var resData = response.data; |
| var fullParams = response.config.params; |
| var data = parseResponse(resData, operation, whatFetched, url, response, deferred); |
| |
| // support empty response for getList() calls (some APIs respond with 204 and empty body) |
| if (_.isUndefined(data) || '' === data) { |
| data = []; |
| } |
| if (!_.isArray(data)) { |
| throw new Error('Response for getList SHOULD be an array and not an object or something else'); |
| } |
| |
| if (true === config.plainByDefault) { |
| return resolvePromise(deferred, response, data, filledArray); |
| } |
| |
| 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 |
| ); |
| } |
| }; |
| |
| urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, |
| this[config.restangularFields.etag], operation)[method]().then(okCallback, function error(response) { |
| if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { |
| resolvePromise(deferred, response, __this, filledArray); |
| } else if (_.every(config.errorInterceptors, function(cb) { |
| return cb(response, deferred, okCallback) !== false; |
| })) { |
| // triggered if no callback returns false |
| deferred.reject(response); |
| } |
| }); |
| |
| return restangularizePromise(deferred.promise, true, filledArray); |
| } |
| |
| function withHttpConfig(httpConfig) { |
| this[config.restangularFields.httpConfig] = httpConfig; |
| return this; |
| } |
| |
| function save(params, headers) { |
| if (this[config.restangularFields.fromServer]) { |
| return this[config.restangularFields.put](params, headers); |
| } else { |
| return _.bind(elemFunction, this)('post', undefined, params, undefined, headers); |
| } |
| } |
| |
| 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); |
| |
| // accept 0 as response |
| if (elem !== null && elem !== undefined && elem !== '') { |
| var data; |
| |
| if (true === config.plainByDefault) { |
| return resolvePromise(deferred, response, elem, filledObject); |
| } |
| |
| if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { |
| data = restangularizeElem( |
| __this[config.restangularFields.parentResource], |
| elem, |
| route, |
| true, |
| null, |
| fullParams |
| ); |
| resolvePromise(deferred, response, data, filledObject); |
| } else { |
| data = restangularizeElem( |
| __this[config.restangularFields.parentResource], |
| elem, |
| __this[config.restangularFields.route], |
| true, |
| null, |
| fullParams |
| ); |
| |
| data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; |
| resolvePromise(deferred, response, data, 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 (_.every(config.errorInterceptors, function(cb) { |
| return cb(response, deferred, okCallback) !== false; |
| })) { |
| // triggered if no callback returns false |
| deferred.reject(response); |
| } |
| }; |
| // Overriding 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.toUpperCase() |
| }); |
| } 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); |
| } |
| |
| function toService(route, parent) { |
| var knownCollectionMethods = _.values(config.restangularFields); |
| var serv = {}; |
| var collection = (parent || service).all(route); |
| serv.one = _.bind(one, (parent || service), parent, route); |
| serv.post = _.bind(collection.post, collection); |
| serv.getList = _.bind(collection.getList, collection); |
| serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); |
| serv.get = _.bind(collection.get, collection); |
| |
| for (var prop in collection) { |
| if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { |
| serv[prop] = _.bind(collection[prop], collection); |
| } |
| } |
| |
| return serv; |
| } |
| |
| |
| Configurer.init(service, config); |
| |
| service.copy = _.bind(copyRestangularizedElement, service); |
| |
| service.service = _.bind(toService, 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); |
| }]; |
| }); |
| return restangular.name; |
| })); |