Ed Tanous | 904063f | 2017-03-02 16:48:24 -0800 | [diff] [blame] | 1 | /** |
| 2 | * Restful Resources service for AngularJS apps |
| 3 | * @version v1.6.1 - 2017-01-06 * @link https://github.com/mgonto/restangular |
| 4 | * @author Martin Gontovnikas <martin@gon.to> |
| 5 | * @license MIT License, http://www.opensource.org/licenses/MIT |
| 6 | */(function(root, factory) { |
| 7 | /* global define, require */ |
| 8 | // https://github.com/umdjs/umd/blob/master/templates/returnExports.js |
| 9 | if (typeof define === 'function' && define.amd) { |
| 10 | define(['lodash', 'angular'], factory); |
| 11 | } else if (typeof module === 'object' && module.exports) { |
| 12 | module.exports = factory(require('lodash'), require('angular')); |
| 13 | } else { |
| 14 | // No global export, Restangular will register itself as Angular.js module |
| 15 | factory(root._, root.angular); |
| 16 | } |
| 17 | }(this, function(_, angular) { |
| 18 | |
| 19 | var restangular = angular.module('restangular', []); |
| 20 | |
| 21 | restangular.provider('Restangular', function() { |
| 22 | // Configuration |
| 23 | var Configurer = {}; |
| 24 | Configurer.init = function(object, config) { |
| 25 | object.configuration = config; |
| 26 | |
| 27 | /** |
| 28 | * Those are HTTP safe methods for which there is no need to pass any data with the request. |
| 29 | */ |
| 30 | var safeMethods = ['get', 'head', 'options', 'trace', 'getlist']; |
| 31 | config.isSafe = function(operation) { |
| 32 | return _.includes(safeMethods, operation.toLowerCase()); |
| 33 | }; |
| 34 | |
| 35 | var absolutePattern = /^https?:\/\//i; |
| 36 | config.isAbsoluteUrl = function(string) { |
| 37 | return _.isUndefined(config.absoluteUrl) || _.isNull(config.absoluteUrl) ? |
| 38 | string && absolutePattern.test(string) : |
| 39 | config.absoluteUrl; |
| 40 | }; |
| 41 | |
| 42 | config.absoluteUrl = _.isUndefined(config.absoluteUrl) ? true : config.absoluteUrl; |
| 43 | object.setSelfLinkAbsoluteUrl = function(value) { |
| 44 | config.absoluteUrl = value; |
| 45 | }; |
| 46 | /** |
| 47 | * This is the BaseURL to be used with Restangular |
| 48 | */ |
| 49 | config.baseUrl = _.isUndefined(config.baseUrl) ? '' : config.baseUrl; |
| 50 | object.setBaseUrl = function(newBaseUrl) { |
| 51 | config.baseUrl = /\/$/.test(newBaseUrl) ? |
| 52 | newBaseUrl.substring(0, newBaseUrl.length - 1) : |
| 53 | newBaseUrl; |
| 54 | return this; |
| 55 | }; |
| 56 | |
| 57 | /** |
| 58 | * Sets the extra fields to keep from the parents |
| 59 | */ |
| 60 | config.extraFields = config.extraFields || []; |
| 61 | object.setExtraFields = function(newExtraFields) { |
| 62 | config.extraFields = newExtraFields; |
| 63 | return this; |
| 64 | }; |
| 65 | |
| 66 | /** |
| 67 | * Some default $http parameter to be used in EVERY call |
| 68 | **/ |
| 69 | config.defaultHttpFields = config.defaultHttpFields || {}; |
| 70 | object.setDefaultHttpFields = function(values) { |
| 71 | config.defaultHttpFields = values; |
| 72 | return this; |
| 73 | }; |
| 74 | |
| 75 | /** |
| 76 | * Always return plain data, no restangularized object |
| 77 | **/ |
| 78 | config.plainByDefault = config.plainByDefault || false; |
| 79 | object.setPlainByDefault = function(value) { |
| 80 | config.plainByDefault = value === true ? true : false; |
| 81 | return this; |
| 82 | }; |
| 83 | |
| 84 | config.withHttpValues = function(httpLocalConfig, obj) { |
| 85 | return _.defaults(obj, httpLocalConfig, config.defaultHttpFields); |
| 86 | }; |
| 87 | |
| 88 | config.encodeIds = _.isUndefined(config.encodeIds) ? true : config.encodeIds; |
| 89 | object.setEncodeIds = function(encode) { |
| 90 | config.encodeIds = encode; |
| 91 | }; |
| 92 | |
| 93 | config.defaultRequestParams = config.defaultRequestParams || { |
| 94 | get: {}, |
| 95 | post: {}, |
| 96 | put: {}, |
| 97 | remove: {}, |
| 98 | common: {} |
| 99 | }; |
| 100 | |
| 101 | object.setDefaultRequestParams = function(param1, param2) { |
| 102 | var methods = [], |
| 103 | params = param2 || param1; |
| 104 | if (!_.isUndefined(param2)) { |
| 105 | if (_.isArray(param1)) { |
| 106 | methods = param1; |
| 107 | } else { |
| 108 | methods.push(param1); |
| 109 | } |
| 110 | } else { |
| 111 | methods.push('common'); |
| 112 | } |
| 113 | |
| 114 | _.each(methods, function(method) { |
| 115 | config.defaultRequestParams[method] = params; |
| 116 | }); |
| 117 | return this; |
| 118 | }; |
| 119 | |
| 120 | object.requestParams = config.defaultRequestParams; |
| 121 | |
| 122 | config.defaultHeaders = config.defaultHeaders || {}; |
| 123 | object.setDefaultHeaders = function(headers) { |
| 124 | config.defaultHeaders = headers; |
| 125 | object.defaultHeaders = config.defaultHeaders; |
| 126 | return this; |
| 127 | }; |
| 128 | |
| 129 | object.defaultHeaders = config.defaultHeaders; |
| 130 | |
| 131 | /** |
| 132 | * Method overriders will set which methods are sent via POST with an X-HTTP-Method-Override |
| 133 | **/ |
| 134 | config.methodOverriders = config.methodOverriders || []; |
| 135 | object.setMethodOverriders = function(values) { |
| 136 | var overriders = _.extend([], values); |
| 137 | if (config.isOverridenMethod('delete', overriders)) { |
| 138 | overriders.push('remove'); |
| 139 | } |
| 140 | config.methodOverriders = overriders; |
| 141 | return this; |
| 142 | }; |
| 143 | |
| 144 | config.jsonp = _.isUndefined(config.jsonp) ? false : config.jsonp; |
| 145 | object.setJsonp = function(active) { |
| 146 | config.jsonp = active; |
| 147 | }; |
| 148 | |
| 149 | config.isOverridenMethod = function(method, values) { |
| 150 | var search = values || config.methodOverriders; |
| 151 | return !_.isUndefined(_.find(search, function(one) { |
| 152 | return one.toLowerCase() === method.toLowerCase(); |
| 153 | })); |
| 154 | }; |
| 155 | |
| 156 | /** |
| 157 | * Sets the URL creator type. For now, only Path is created. In the future we'll have queryParams |
| 158 | **/ |
| 159 | config.urlCreator = config.urlCreator || 'path'; |
| 160 | object.setUrlCreator = function(name) { |
| 161 | if (!_.has(config.urlCreatorFactory, name)) { |
| 162 | throw new Error('URL Path selected isn\'t valid'); |
| 163 | } |
| 164 | |
| 165 | config.urlCreator = name; |
| 166 | return this; |
| 167 | }; |
| 168 | |
| 169 | /** |
| 170 | * You can set the restangular fields here. The 3 required fields for Restangular are: |
| 171 | * |
| 172 | * id: Id of the element |
| 173 | * route: name of the route of this element |
| 174 | * parentResource: the reference to the parent resource |
| 175 | * |
| 176 | * All of this fields except for id, are handled (and created) by Restangular. By default, |
| 177 | * the field values will be id, route and parentResource respectively |
| 178 | */ |
| 179 | config.restangularFields = config.restangularFields || { |
| 180 | id: 'id', |
| 181 | route: 'route', |
| 182 | parentResource: 'parentResource', |
| 183 | restangularCollection: 'restangularCollection', |
| 184 | cannonicalId: '__cannonicalId', |
| 185 | etag: 'restangularEtag', |
| 186 | selfLink: 'href', |
| 187 | get: 'get', |
| 188 | getList: 'getList', |
| 189 | put: 'put', |
| 190 | post: 'post', |
| 191 | remove: 'remove', |
| 192 | head: 'head', |
| 193 | trace: 'trace', |
| 194 | options: 'options', |
| 195 | patch: 'patch', |
| 196 | getRestangularUrl: 'getRestangularUrl', |
| 197 | getRequestedUrl: 'getRequestedUrl', |
| 198 | putElement: 'putElement', |
| 199 | addRestangularMethod: 'addRestangularMethod', |
| 200 | getParentList: 'getParentList', |
| 201 | clone: 'clone', |
| 202 | ids: 'ids', |
| 203 | httpConfig: '_$httpConfig', |
| 204 | reqParams: 'reqParams', |
| 205 | one: 'one', |
| 206 | all: 'all', |
| 207 | several: 'several', |
| 208 | oneUrl: 'oneUrl', |
| 209 | allUrl: 'allUrl', |
| 210 | customPUT: 'customPUT', |
| 211 | customPATCH: 'customPATCH', |
| 212 | customPOST: 'customPOST', |
| 213 | customDELETE: 'customDELETE', |
| 214 | customGET: 'customGET', |
| 215 | customGETLIST: 'customGETLIST', |
| 216 | customOperation: 'customOperation', |
| 217 | doPUT: 'doPUT', |
| 218 | doPATCH: 'doPATCH', |
| 219 | doPOST: 'doPOST', |
| 220 | doDELETE: 'doDELETE', |
| 221 | doGET: 'doGET', |
| 222 | doGETLIST: 'doGETLIST', |
| 223 | fromServer: 'fromServer', |
| 224 | withConfig: 'withConfig', |
| 225 | withHttpConfig: 'withHttpConfig', |
| 226 | singleOne: 'singleOne', |
| 227 | plain: 'plain', |
| 228 | save: 'save', |
| 229 | restangularized: 'restangularized' |
| 230 | }; |
| 231 | object.setRestangularFields = function(resFields) { |
| 232 | config.restangularFields = |
| 233 | _.extend(config.restangularFields, resFields); |
| 234 | return this; |
| 235 | }; |
| 236 | |
| 237 | config.isRestangularized = function(obj) { |
| 238 | return !!obj[config.restangularFields.restangularized]; |
| 239 | }; |
| 240 | |
| 241 | config.setFieldToElem = function(field, elem, value) { |
| 242 | var properties = field.split('.'); |
| 243 | var idValue = elem; |
| 244 | _.each(_.initial(properties), function(prop) { |
| 245 | idValue[prop] = {}; |
| 246 | idValue = idValue[prop]; |
| 247 | }); |
| 248 | idValue[_.last(properties)] = value; |
| 249 | return this; |
| 250 | }; |
| 251 | |
| 252 | config.getFieldFromElem = function(field, elem) { |
| 253 | var properties = field.split('.'); |
| 254 | var idValue = elem; |
| 255 | _.each(properties, function(prop) { |
| 256 | if (idValue) { |
| 257 | idValue = idValue[prop]; |
| 258 | } |
| 259 | }); |
| 260 | return angular.copy(idValue); |
| 261 | }; |
| 262 | |
| 263 | config.setIdToElem = function(elem, id /*, route */ ) { |
| 264 | config.setFieldToElem(config.restangularFields.id, elem, id); |
| 265 | return this; |
| 266 | }; |
| 267 | |
| 268 | config.getIdFromElem = function(elem) { |
| 269 | return config.getFieldFromElem(config.restangularFields.id, elem); |
| 270 | }; |
| 271 | |
| 272 | config.isValidId = function(elemId) { |
| 273 | return '' !== elemId && !_.isUndefined(elemId) && !_.isNull(elemId); |
| 274 | }; |
| 275 | |
| 276 | config.setUrlToElem = function(elem, url /*, route */ ) { |
| 277 | config.setFieldToElem(config.restangularFields.selfLink, elem, url); |
| 278 | return this; |
| 279 | }; |
| 280 | |
| 281 | config.getUrlFromElem = function(elem) { |
| 282 | return config.getFieldFromElem(config.restangularFields.selfLink, elem); |
| 283 | }; |
| 284 | |
| 285 | config.useCannonicalId = _.isUndefined(config.useCannonicalId) ? false : config.useCannonicalId; |
| 286 | object.setUseCannonicalId = function(value) { |
| 287 | config.useCannonicalId = value; |
| 288 | return this; |
| 289 | }; |
| 290 | |
| 291 | config.getCannonicalIdFromElem = function(elem) { |
| 292 | var cannonicalId = elem[config.restangularFields.cannonicalId]; |
| 293 | var actualId = config.isValidId(cannonicalId) ? cannonicalId : config.getIdFromElem(elem); |
| 294 | return actualId; |
| 295 | }; |
| 296 | |
| 297 | /** |
| 298 | * Sets the Response parser. This is used in case your response isn't directly the data. |
| 299 | * For example if you have a response like {meta: {'meta'}, data: {name: 'Gonto'}} |
| 300 | * you can extract this data which is the one that needs wrapping |
| 301 | * |
| 302 | * The ResponseExtractor is a function that receives the response and the method executed. |
| 303 | */ |
| 304 | |
| 305 | config.responseInterceptors = config.responseInterceptors || []; |
| 306 | |
| 307 | config.defaultResponseInterceptor = function(data /*, operation, what, url, response, deferred */ ) { |
| 308 | return data; |
| 309 | }; |
| 310 | |
| 311 | config.responseExtractor = function(data, operation, what, url, response, deferred) { |
| 312 | var interceptors = angular.copy(config.responseInterceptors); |
| 313 | interceptors.push(config.defaultResponseInterceptor); |
| 314 | var theData = data; |
| 315 | _.each(interceptors, function(interceptor) { |
| 316 | theData = interceptor(theData, operation, |
| 317 | what, url, response, deferred); |
| 318 | }); |
| 319 | return theData; |
| 320 | }; |
| 321 | |
| 322 | object.addResponseInterceptor = function(extractor) { |
| 323 | config.responseInterceptors.push(extractor); |
| 324 | return this; |
| 325 | }; |
| 326 | |
| 327 | config.errorInterceptors = config.errorInterceptors || []; |
| 328 | object.addErrorInterceptor = function(interceptor) { |
| 329 | config.errorInterceptors.push(interceptor); |
| 330 | return this; |
| 331 | }; |
| 332 | |
| 333 | object.setResponseInterceptor = object.addResponseInterceptor; |
| 334 | object.setResponseExtractor = object.addResponseInterceptor; |
| 335 | object.setErrorInterceptor = object.addErrorInterceptor; |
| 336 | |
| 337 | /** |
| 338 | * Response interceptor is called just before resolving promises. |
| 339 | */ |
| 340 | |
| 341 | |
| 342 | /** |
| 343 | * Request interceptor is called before sending an object to the server. |
| 344 | */ |
| 345 | config.requestInterceptors = config.requestInterceptors || []; |
| 346 | |
| 347 | config.defaultInterceptor = function(element, operation, path, url, headers, params, httpConfig) { |
| 348 | return { |
| 349 | element: element, |
| 350 | headers: headers, |
| 351 | params: params, |
| 352 | httpConfig: httpConfig |
| 353 | }; |
| 354 | }; |
| 355 | |
| 356 | config.fullRequestInterceptor = function(element, operation, path, url, headers, params, httpConfig) { |
| 357 | var interceptors = angular.copy(config.requestInterceptors); |
| 358 | var defaultRequest = config.defaultInterceptor(element, operation, path, url, headers, params, httpConfig); |
| 359 | return _.reduce(interceptors, function(request, interceptor) { |
| 360 | return _.extend(request, interceptor(request.element, operation, |
| 361 | path, url, request.headers, request.params, request.httpConfig)); |
| 362 | }, defaultRequest); |
| 363 | }; |
| 364 | |
| 365 | object.addRequestInterceptor = function(interceptor) { |
| 366 | config.requestInterceptors.push(function(elem, operation, path, url, headers, params, httpConfig) { |
| 367 | return { |
| 368 | headers: headers, |
| 369 | params: params, |
| 370 | element: interceptor(elem, operation, path, url), |
| 371 | httpConfig: httpConfig |
| 372 | }; |
| 373 | }); |
| 374 | return this; |
| 375 | }; |
| 376 | |
| 377 | object.setRequestInterceptor = object.addRequestInterceptor; |
| 378 | |
| 379 | object.addFullRequestInterceptor = function(interceptor) { |
| 380 | config.requestInterceptors.push(interceptor); |
| 381 | return this; |
| 382 | }; |
| 383 | |
| 384 | object.setFullRequestInterceptor = object.addFullRequestInterceptor; |
| 385 | |
| 386 | config.onBeforeElemRestangularized = config.onBeforeElemRestangularized || function(elem) { |
| 387 | return elem; |
| 388 | }; |
| 389 | object.setOnBeforeElemRestangularized = function(post) { |
| 390 | config.onBeforeElemRestangularized = post; |
| 391 | return this; |
| 392 | }; |
| 393 | |
| 394 | object.setRestangularizePromiseInterceptor = function(interceptor) { |
| 395 | config.restangularizePromiseInterceptor = interceptor; |
| 396 | return this; |
| 397 | }; |
| 398 | |
| 399 | /** |
| 400 | * This method is called after an element has been "Restangularized". |
| 401 | * |
| 402 | * It receives the element, a boolean indicating if it's an element or a collection |
| 403 | * and the name of the model |
| 404 | * |
| 405 | */ |
| 406 | config.onElemRestangularized = config.onElemRestangularized || function(elem) { |
| 407 | return elem; |
| 408 | }; |
| 409 | object.setOnElemRestangularized = function(post) { |
| 410 | config.onElemRestangularized = post; |
| 411 | return this; |
| 412 | }; |
| 413 | |
| 414 | config.shouldSaveParent = config.shouldSaveParent || function() { |
| 415 | return true; |
| 416 | }; |
| 417 | object.setParentless = function(values) { |
| 418 | if (_.isArray(values)) { |
| 419 | config.shouldSaveParent = function(route) { |
| 420 | return !_.includes(values, route); |
| 421 | }; |
| 422 | } else if (_.isBoolean(values)) { |
| 423 | config.shouldSaveParent = function() { |
| 424 | return !values; |
| 425 | }; |
| 426 | } |
| 427 | return this; |
| 428 | }; |
| 429 | |
| 430 | /** |
| 431 | * This lets you set a suffix to every request. |
| 432 | * |
| 433 | * For example, if your api requires that for JSon requests you do /users/123.json, you can set that |
| 434 | * in here. |
| 435 | * |
| 436 | * |
| 437 | * By default, the suffix is null |
| 438 | */ |
| 439 | config.suffix = _.isUndefined(config.suffix) ? null : config.suffix; |
| 440 | object.setRequestSuffix = function(newSuffix) { |
| 441 | config.suffix = newSuffix; |
| 442 | return this; |
| 443 | }; |
| 444 | |
| 445 | /** |
| 446 | * Add element transformers for certain routes. |
| 447 | */ |
| 448 | config.transformers = config.transformers || {}; |
| 449 | config.matchTransformers = config.matchTransformers || []; |
| 450 | object.addElementTransformer = function(type, secondArg, thirdArg) { |
| 451 | var isCollection = null; |
| 452 | var transformer = null; |
| 453 | if (arguments.length === 2) { |
| 454 | transformer = secondArg; |
| 455 | } else { |
| 456 | transformer = thirdArg; |
| 457 | isCollection = secondArg; |
| 458 | } |
| 459 | |
| 460 | var transformerFn = function(coll, elem) { |
| 461 | if (_.isNull(isCollection) || (coll === isCollection)) { |
| 462 | return transformer(elem); |
| 463 | } |
| 464 | return elem; |
| 465 | }; |
| 466 | |
| 467 | if (_.isRegExp(type)) { |
| 468 | config.matchTransformers.push({ |
| 469 | regexp: type, |
| 470 | transformer: transformerFn |
| 471 | }); |
| 472 | } else { |
| 473 | if (!config.transformers[type]) { |
| 474 | config.transformers[type] = []; |
| 475 | } |
| 476 | config.transformers[type].push(transformerFn); |
| 477 | } |
| 478 | |
| 479 | return object; |
| 480 | }; |
| 481 | |
| 482 | object.extendCollection = function(route, fn) { |
| 483 | return object.addElementTransformer(route, true, fn); |
| 484 | }; |
| 485 | |
| 486 | object.extendModel = function(route, fn) { |
| 487 | return object.addElementTransformer(route, false, fn); |
| 488 | }; |
| 489 | |
| 490 | config.transformElem = function(elem, isCollection, route, Restangular, force) { |
| 491 | if (!force && !config.transformLocalElements && !elem[config.restangularFields.fromServer]) { |
| 492 | return elem; |
| 493 | } |
| 494 | |
| 495 | var changedElem = elem; |
| 496 | |
| 497 | var matchTransformers = config.matchTransformers; |
| 498 | if (matchTransformers) { |
| 499 | _.each(matchTransformers, function(transformer) { |
| 500 | if (transformer.regexp.test(route)) { |
| 501 | changedElem = transformer.transformer(isCollection, changedElem); |
| 502 | } |
| 503 | }); |
| 504 | } |
| 505 | |
| 506 | var typeTransformers = config.transformers[route]; |
| 507 | if (typeTransformers) { |
| 508 | _.each(typeTransformers, function(transformer) { |
| 509 | changedElem = transformer(isCollection, changedElem); |
| 510 | }); |
| 511 | } |
| 512 | return config.onElemRestangularized(changedElem, isCollection, route, Restangular); |
| 513 | }; |
| 514 | |
| 515 | config.transformLocalElements = _.isUndefined(config.transformLocalElements) ? |
| 516 | false : |
| 517 | config.transformLocalElements; |
| 518 | |
| 519 | object.setTransformOnlyServerElements = function(active) { |
| 520 | config.transformLocalElements = !active; |
| 521 | }; |
| 522 | |
| 523 | config.fullResponse = _.isUndefined(config.fullResponse) ? false : config.fullResponse; |
| 524 | object.setFullResponse = function(full) { |
| 525 | config.fullResponse = full; |
| 526 | return this; |
| 527 | }; |
| 528 | |
| 529 | |
| 530 | //Internal values and functions |
| 531 | config.urlCreatorFactory = {}; |
| 532 | |
| 533 | /** |
| 534 | * Base URL Creator. Base prototype for everything related to it |
| 535 | **/ |
| 536 | |
| 537 | var BaseCreator = function() {}; |
| 538 | |
| 539 | BaseCreator.prototype.setConfig = function(config) { |
| 540 | this.config = config; |
| 541 | return this; |
| 542 | }; |
| 543 | |
| 544 | BaseCreator.prototype.parentsArray = function(current) { |
| 545 | var parents = []; |
| 546 | while (current) { |
| 547 | parents.push(current); |
| 548 | current = current[this.config.restangularFields.parentResource]; |
| 549 | } |
| 550 | return parents.reverse(); |
| 551 | }; |
| 552 | |
| 553 | function RestangularResource(config, $http, url, configurer) { |
| 554 | var resource = {}; |
| 555 | _.each(_.keys(configurer), function(key) { |
| 556 | var value = configurer[key]; |
| 557 | |
| 558 | // Add default parameters |
| 559 | value.params = _.extend({}, value.params, config.defaultRequestParams[value.method.toLowerCase()]); |
| 560 | // We don't want the ? if no params are there |
| 561 | if (_.isEmpty(value.params)) { |
| 562 | delete value.params; |
| 563 | } |
| 564 | |
| 565 | if (config.isSafe(value.method)) { |
| 566 | |
| 567 | resource[key] = function() { |
| 568 | return $http(_.extend(value, { |
| 569 | url: url |
| 570 | })); |
| 571 | }; |
| 572 | |
| 573 | } else { |
| 574 | |
| 575 | resource[key] = function(data) { |
| 576 | return $http(_.extend(value, { |
| 577 | url: url, |
| 578 | data: data |
| 579 | })); |
| 580 | }; |
| 581 | |
| 582 | } |
| 583 | }); |
| 584 | |
| 585 | return resource; |
| 586 | } |
| 587 | |
| 588 | BaseCreator.prototype.resource = function(current, $http, localHttpConfig, callHeaders, callParams, what, etag, operation) { |
| 589 | |
| 590 | var params = _.defaults(callParams || {}, this.config.defaultRequestParams.common); |
| 591 | var headers = _.defaults(callHeaders || {}, this.config.defaultHeaders); |
| 592 | |
| 593 | if (etag) { |
| 594 | if (!config.isSafe(operation)) { |
| 595 | headers['If-Match'] = etag; |
| 596 | } else { |
| 597 | headers['If-None-Match'] = etag; |
| 598 | } |
| 599 | } |
| 600 | |
| 601 | var url = this.base(current); |
| 602 | |
| 603 | if (what || what === 0) { |
| 604 | var add = ''; |
| 605 | if (!/\/$/.test(url)) { |
| 606 | add += '/'; |
| 607 | } |
| 608 | add += what; |
| 609 | url += add; |
| 610 | } |
| 611 | |
| 612 | if (this.config.suffix && |
| 613 | url.indexOf(this.config.suffix, url.length - this.config.suffix.length) === -1 && |
| 614 | !this.config.getUrlFromElem(current)) { |
| 615 | url += this.config.suffix; |
| 616 | } |
| 617 | |
| 618 | current[this.config.restangularFields.httpConfig] = undefined; |
| 619 | |
| 620 | return RestangularResource(this.config, $http, url, { |
| 621 | getList: this.config.withHttpValues(localHttpConfig, { |
| 622 | method: 'GET', |
| 623 | params: params, |
| 624 | headers: headers |
| 625 | }), |
| 626 | |
| 627 | get: this.config.withHttpValues(localHttpConfig, { |
| 628 | method: 'GET', |
| 629 | params: params, |
| 630 | headers: headers |
| 631 | }), |
| 632 | |
| 633 | jsonp: this.config.withHttpValues(localHttpConfig, { |
| 634 | method: 'jsonp', |
| 635 | params: params, |
| 636 | headers: headers |
| 637 | }), |
| 638 | |
| 639 | put: this.config.withHttpValues(localHttpConfig, { |
| 640 | method: 'PUT', |
| 641 | params: params, |
| 642 | headers: headers |
| 643 | }), |
| 644 | |
| 645 | post: this.config.withHttpValues(localHttpConfig, { |
| 646 | method: 'POST', |
| 647 | params: params, |
| 648 | headers: headers |
| 649 | }), |
| 650 | |
| 651 | remove: this.config.withHttpValues(localHttpConfig, { |
| 652 | method: 'DELETE', |
| 653 | params: params, |
| 654 | headers: headers |
| 655 | }), |
| 656 | |
| 657 | head: this.config.withHttpValues(localHttpConfig, { |
| 658 | method: 'HEAD', |
| 659 | params: params, |
| 660 | headers: headers |
| 661 | }), |
| 662 | |
| 663 | trace: this.config.withHttpValues(localHttpConfig, { |
| 664 | method: 'TRACE', |
| 665 | params: params, |
| 666 | headers: headers |
| 667 | }), |
| 668 | |
| 669 | options: this.config.withHttpValues(localHttpConfig, { |
| 670 | method: 'OPTIONS', |
| 671 | params: params, |
| 672 | headers: headers |
| 673 | }), |
| 674 | |
| 675 | patch: this.config.withHttpValues(localHttpConfig, { |
| 676 | method: 'PATCH', |
| 677 | params: params, |
| 678 | headers: headers |
| 679 | }) |
| 680 | }); |
| 681 | }; |
| 682 | |
| 683 | /** |
| 684 | * This is the Path URL creator. It uses Path to show Hierarchy in the Rest API. |
| 685 | * This means that if you have an Account that then has a set of Buildings, a URL to a building |
| 686 | * would be /accounts/123/buildings/456 |
| 687 | **/ |
| 688 | var Path = function() {}; |
| 689 | |
| 690 | Path.prototype = new BaseCreator(); |
| 691 | |
| 692 | Path.prototype.normalizeUrl = function(url) { |
| 693 | var parts = /((?:http[s]?:)?\/\/)?(.*)?/.exec(url); |
| 694 | parts[2] = parts[2].replace(/[\\\/]+/g, '/'); |
| 695 | return (typeof parts[1] !== 'undefined') ? parts[1] + parts[2] : parts[2]; |
| 696 | }; |
| 697 | |
| 698 | Path.prototype.base = function(current) { |
| 699 | var __this = this; |
| 700 | return _.reduce(this.parentsArray(current), function(acum, elem) { |
| 701 | var elemUrl; |
| 702 | var elemSelfLink = __this.config.getUrlFromElem(elem); |
| 703 | if (elemSelfLink) { |
| 704 | if (__this.config.isAbsoluteUrl(elemSelfLink)) { |
| 705 | return elemSelfLink; |
| 706 | } else { |
| 707 | elemUrl = elemSelfLink; |
| 708 | } |
| 709 | } else { |
| 710 | elemUrl = elem[__this.config.restangularFields.route]; |
| 711 | |
| 712 | if (elem[__this.config.restangularFields.restangularCollection]) { |
| 713 | var ids = elem[__this.config.restangularFields.ids]; |
| 714 | if (ids) { |
| 715 | elemUrl += '/' + ids.join(','); |
| 716 | } |
| 717 | } else { |
| 718 | var elemId; |
| 719 | if (__this.config.useCannonicalId) { |
| 720 | elemId = __this.config.getCannonicalIdFromElem(elem); |
| 721 | } else { |
| 722 | elemId = __this.config.getIdFromElem(elem); |
| 723 | } |
| 724 | |
| 725 | if (config.isValidId(elemId) && !elem.singleOne) { |
| 726 | elemUrl += '/' + (__this.config.encodeIds ? encodeURIComponent(elemId) : elemId); |
| 727 | } |
| 728 | } |
| 729 | } |
| 730 | acum = acum.replace(/\/$/, '') + '/' + elemUrl; |
| 731 | return __this.normalizeUrl(acum); |
| 732 | |
| 733 | }, this.config.baseUrl); |
| 734 | }; |
| 735 | |
| 736 | |
| 737 | |
| 738 | Path.prototype.fetchUrl = function(current, what) { |
| 739 | var baseUrl = this.base(current); |
| 740 | if (what) { |
| 741 | baseUrl += '/' + what; |
| 742 | } |
| 743 | return baseUrl; |
| 744 | }; |
| 745 | |
| 746 | Path.prototype.fetchRequestedUrl = function(current, what) { |
| 747 | var url = this.fetchUrl(current, what); |
| 748 | var params = current[config.restangularFields.reqParams]; |
| 749 | |
| 750 | // From here on and until the end of fetchRequestedUrl, |
| 751 | // the code has been kindly borrowed from angular.js |
| 752 | // The reason for such code bloating is coherence: |
| 753 | // If the user were to use this for cache management, the |
| 754 | // serialization of parameters would need to be identical |
| 755 | // to the one done by angular for cache keys to match. |
| 756 | function sortedKeys(obj) { |
| 757 | var keys = []; |
| 758 | for (var key in obj) { |
| 759 | if (obj.hasOwnProperty(key)) { |
| 760 | keys.push(key); |
| 761 | } |
| 762 | } |
| 763 | return keys.sort(); |
| 764 | } |
| 765 | |
| 766 | function forEachSorted(obj, iterator, context) { |
| 767 | var keys = sortedKeys(obj); |
| 768 | for (var i = 0; i < keys.length; i++) { |
| 769 | iterator.call(context, obj[keys[i]], keys[i]); |
| 770 | } |
| 771 | return keys; |
| 772 | } |
| 773 | |
| 774 | function encodeUriQuery(val, pctEncodeSpaces) { |
| 775 | return encodeURIComponent(val). |
| 776 | replace(/%40/gi, '@'). |
| 777 | replace(/%3A/gi, ':'). |
| 778 | replace(/%24/g, '$'). |
| 779 | replace(/%2C/gi, ','). |
| 780 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); |
| 781 | } |
| 782 | |
| 783 | if (!params) { |
| 784 | return url + (this.config.suffix || ''); |
| 785 | } |
| 786 | |
| 787 | var parts = []; |
| 788 | forEachSorted(params, function(value, key) { |
| 789 | if (value === null || value === undefined) { |
| 790 | return; |
| 791 | } |
| 792 | if (!angular.isArray(value)) { |
| 793 | value = [value]; |
| 794 | } |
| 795 | |
| 796 | angular.forEach(value, function(v) { |
| 797 | if (angular.isObject(v)) { |
| 798 | v = angular.toJson(v); |
| 799 | } |
| 800 | parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(v)); |
| 801 | }); |
| 802 | }); |
| 803 | |
| 804 | return url + (this.config.suffix || '') + ((url.indexOf('?') === -1) ? '?' : '&') + parts.join('&'); |
| 805 | }; |
| 806 | |
| 807 | config.urlCreatorFactory.path = Path; |
| 808 | }; |
| 809 | |
| 810 | var globalConfiguration = {}; |
| 811 | |
| 812 | Configurer.init(this, globalConfiguration); |
| 813 | |
| 814 | |
| 815 | |
| 816 | this.$get = ['$http', '$q', function($http, $q) { |
| 817 | |
| 818 | function createServiceForConfiguration(config) { |
| 819 | var service = {}; |
| 820 | |
| 821 | var urlHandler = new config.urlCreatorFactory[config.urlCreator](); |
| 822 | urlHandler.setConfig(config); |
| 823 | |
| 824 | function restangularizeBase(parent, elem, route, reqParams, fromServer) { |
| 825 | elem[config.restangularFields.route] = route; |
| 826 | elem[config.restangularFields.getRestangularUrl] = _.bind(urlHandler.fetchUrl, urlHandler, elem); |
| 827 | elem[config.restangularFields.getRequestedUrl] = _.bind(urlHandler.fetchRequestedUrl, urlHandler, elem); |
| 828 | elem[config.restangularFields.addRestangularMethod] = _.bind(addRestangularMethodFunction, elem); |
| 829 | elem[config.restangularFields.clone] = _.bind(copyRestangularizedElement, elem, elem); |
| 830 | elem[config.restangularFields.reqParams] = _.isEmpty(reqParams) ? null : reqParams; |
| 831 | elem[config.restangularFields.withHttpConfig] = _.bind(withHttpConfig, elem); |
| 832 | elem[config.restangularFields.plain] = _.bind(stripRestangular, elem, elem); |
| 833 | |
| 834 | // Tag element as restangularized |
| 835 | elem[config.restangularFields.restangularized] = true; |
| 836 | |
| 837 | // RequestLess connection |
| 838 | elem[config.restangularFields.one] = _.bind(one, elem, elem); |
| 839 | elem[config.restangularFields.all] = _.bind(all, elem, elem); |
| 840 | elem[config.restangularFields.several] = _.bind(several, elem, elem); |
| 841 | elem[config.restangularFields.oneUrl] = _.bind(oneUrl, elem, elem); |
| 842 | elem[config.restangularFields.allUrl] = _.bind(allUrl, elem, elem); |
| 843 | |
| 844 | elem[config.restangularFields.fromServer] = !!fromServer; |
| 845 | |
| 846 | if (parent && config.shouldSaveParent(route)) { |
| 847 | var parentId = config.getIdFromElem(parent); |
| 848 | var parentUrl = config.getUrlFromElem(parent); |
| 849 | |
| 850 | var restangularFieldsForParent = _.union( |
| 851 | _.values(_.pick(config.restangularFields, ['route', 'singleOne', 'parentResource'])), |
| 852 | config.extraFields |
| 853 | ); |
| 854 | var parentResource = _.pick(parent, restangularFieldsForParent); |
| 855 | |
| 856 | if (config.isValidId(parentId)) { |
| 857 | config.setIdToElem(parentResource, parentId, route); |
| 858 | } |
| 859 | if (config.isValidId(parentUrl)) { |
| 860 | config.setUrlToElem(parentResource, parentUrl, route); |
| 861 | } |
| 862 | |
| 863 | elem[config.restangularFields.parentResource] = parentResource; |
| 864 | } else { |
| 865 | elem[config.restangularFields.parentResource] = null; |
| 866 | } |
| 867 | return elem; |
| 868 | } |
| 869 | |
| 870 | function one(parent, route, id, singleOne) { |
| 871 | var error; |
| 872 | if (_.isNumber(route) || _.isNumber(parent)) { |
| 873 | error = 'You\'re creating a Restangular entity with the number '; |
| 874 | error += 'instead of the route or the parent. For example, you can\'t call .one(12).'; |
| 875 | throw new Error(error); |
| 876 | } |
| 877 | if (_.isUndefined(route)) { |
| 878 | error = 'You\'re creating a Restangular entity either without the path. '; |
| 879 | error += 'For example you can\'t call .one(). Please check if your arguments are valid.'; |
| 880 | throw new Error(error); |
| 881 | } |
| 882 | var elem = {}; |
| 883 | config.setIdToElem(elem, id, route); |
| 884 | config.setFieldToElem(config.restangularFields.singleOne, elem, singleOne); |
| 885 | return restangularizeElem(parent, elem, route, false); |
| 886 | } |
| 887 | |
| 888 | |
| 889 | function all(parent, route) { |
| 890 | return restangularizeCollection(parent, [], route, false); |
| 891 | } |
| 892 | |
| 893 | function several(parent, route /*, ids */ ) { |
| 894 | var collection = []; |
| 895 | collection[config.restangularFields.ids] = Array.prototype.splice.call(arguments, 2); |
| 896 | return restangularizeCollection(parent, collection, route, false); |
| 897 | } |
| 898 | |
| 899 | function oneUrl(parent, route, url) { |
| 900 | if (!route) { |
| 901 | throw new Error('Route is mandatory when creating new Restangular objects.'); |
| 902 | } |
| 903 | var elem = {}; |
| 904 | config.setUrlToElem(elem, url, route); |
| 905 | return restangularizeElem(parent, elem, route, false); |
| 906 | } |
| 907 | |
| 908 | |
| 909 | function allUrl(parent, route, url) { |
| 910 | if (!route) { |
| 911 | throw new Error('Route is mandatory when creating new Restangular objects.'); |
| 912 | } |
| 913 | var elem = {}; |
| 914 | config.setUrlToElem(elem, url, route); |
| 915 | return restangularizeCollection(parent, elem, route, false); |
| 916 | } |
| 917 | // Promises |
| 918 | function restangularizePromise(promise, isCollection, valueToFill) { |
| 919 | promise.call = _.bind(promiseCall, promise); |
| 920 | promise.get = _.bind(promiseGet, promise); |
| 921 | promise[config.restangularFields.restangularCollection] = isCollection; |
| 922 | if (isCollection) { |
| 923 | promise.push = _.bind(promiseCall, promise, 'push'); |
| 924 | } |
| 925 | promise.$object = valueToFill; |
| 926 | if (config.restangularizePromiseInterceptor) { |
| 927 | config.restangularizePromiseInterceptor(promise); |
| 928 | } |
| 929 | return promise; |
| 930 | } |
| 931 | |
| 932 | function promiseCall(method) { |
| 933 | var deferred = $q.defer(); |
| 934 | var callArgs = arguments; |
| 935 | var filledValue = {}; |
| 936 | this.then(function(val) { |
| 937 | var params = Array.prototype.slice.call(callArgs, 1); |
| 938 | var func = val[method]; |
| 939 | func.apply(val, params); |
| 940 | filledValue = val; |
| 941 | deferred.resolve(val); |
| 942 | }); |
| 943 | return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); |
| 944 | } |
| 945 | |
| 946 | function promiseGet(what) { |
| 947 | var deferred = $q.defer(); |
| 948 | var filledValue = {}; |
| 949 | this.then(function(val) { |
| 950 | filledValue = val[what]; |
| 951 | deferred.resolve(filledValue); |
| 952 | }); |
| 953 | return restangularizePromise(deferred.promise, this[config.restangularFields.restangularCollection], filledValue); |
| 954 | } |
| 955 | |
| 956 | function resolvePromise(deferred, response, data, filledValue) { |
| 957 | _.extend(filledValue, data); |
| 958 | |
| 959 | // Trigger the full response interceptor. |
| 960 | if (config.fullResponse) { |
| 961 | return deferred.resolve(_.extend(response, { |
| 962 | data: data |
| 963 | })); |
| 964 | } else { |
| 965 | deferred.resolve(data); |
| 966 | } |
| 967 | } |
| 968 | |
| 969 | |
| 970 | // Elements |
| 971 | function stripRestangular(elem) { |
| 972 | if (_.isArray(elem)) { |
| 973 | var array = []; |
| 974 | _.each(elem, function(value) { |
| 975 | array.push(config.isRestangularized(value) ? stripRestangular(value) : value); |
| 976 | }); |
| 977 | return array; |
| 978 | } else { |
| 979 | return _.omit(elem, _.values(_.omit(config.restangularFields, 'id'))); |
| 980 | } |
| 981 | } |
| 982 | |
| 983 | function addCustomOperation(elem) { |
| 984 | elem[config.restangularFields.customOperation] = _.bind(customFunction, elem); |
| 985 | var requestMethods = { |
| 986 | get: customFunction, |
| 987 | delete: customFunction |
| 988 | }; |
| 989 | _.each(['put', 'patch', 'post'], function(name) { |
| 990 | requestMethods[name] = function(operation, elem, path, params, headers) { |
| 991 | return _.bind(customFunction, this)(operation, path, params, headers, elem); |
| 992 | }; |
| 993 | }); |
| 994 | _.each(requestMethods, function(requestFunc, name) { |
| 995 | var callOperation = name === 'delete' ? 'remove' : name; |
| 996 | _.each(['do', 'custom'], function(alias) { |
| 997 | elem[alias + name.toUpperCase()] = _.bind(requestFunc, elem, callOperation); |
| 998 | }); |
| 999 | }); |
| 1000 | elem[config.restangularFields.customGETLIST] = _.bind(fetchFunction, elem); |
| 1001 | elem[config.restangularFields.doGETLIST] = elem[config.restangularFields.customGETLIST]; |
| 1002 | } |
| 1003 | |
| 1004 | function copyRestangularizedElement(element) { |
| 1005 | var copiedElement = angular.copy(element); |
| 1006 | |
| 1007 | // check if we're dealing with a collection (i.e. an array) |
| 1008 | // and restangularize the element using the proper restangularizer, |
| 1009 | // element / collection |
| 1010 | if (_.isArray(element)) { |
| 1011 | return restangularizeCollection( |
| 1012 | element[config.restangularFields.parentResource], |
| 1013 | copiedElement, |
| 1014 | element[config.restangularFields.route], |
| 1015 | element[config.restangularFields.fromServer], |
| 1016 | element[config.restangularFields.reqParams] |
| 1017 | ); |
| 1018 | } |
| 1019 | |
| 1020 | // not a collection, restangularize it as an element |
| 1021 | return restangularizeElem( |
| 1022 | element[config.restangularFields.parentResource], |
| 1023 | copiedElement, |
| 1024 | element[config.restangularFields.route], |
| 1025 | element[config.restangularFields.fromServer], |
| 1026 | element[config.restangularFields.restangularCollection], |
| 1027 | element[config.restangularFields.reqParams] |
| 1028 | ); |
| 1029 | } |
| 1030 | |
| 1031 | function restangularizeElem(parent, element, route, fromServer, collection, reqParams) { |
| 1032 | var elem = config.onBeforeElemRestangularized(element, false, route); |
| 1033 | |
| 1034 | var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); |
| 1035 | |
| 1036 | if (config.useCannonicalId) { |
| 1037 | localElem[config.restangularFields.cannonicalId] = config.getIdFromElem(localElem); |
| 1038 | } |
| 1039 | |
| 1040 | if (collection) { |
| 1041 | localElem[config.restangularFields.getParentList] = function() { |
| 1042 | return collection; |
| 1043 | }; |
| 1044 | } |
| 1045 | |
| 1046 | localElem[config.restangularFields.restangularCollection] = false; |
| 1047 | localElem[config.restangularFields.get] = _.bind(getFunction, localElem); |
| 1048 | localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem); |
| 1049 | localElem[config.restangularFields.put] = _.bind(putFunction, localElem); |
| 1050 | localElem[config.restangularFields.post] = _.bind(postFunction, localElem); |
| 1051 | localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); |
| 1052 | localElem[config.restangularFields.head] = _.bind(headFunction, localElem); |
| 1053 | localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); |
| 1054 | localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); |
| 1055 | localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); |
| 1056 | localElem[config.restangularFields.save] = _.bind(save, localElem); |
| 1057 | |
| 1058 | addCustomOperation(localElem); |
| 1059 | return config.transformElem(localElem, false, route, service, true); |
| 1060 | } |
| 1061 | |
| 1062 | function restangularizeCollection(parent, element, route, fromServer, reqParams) { |
| 1063 | var elem = config.onBeforeElemRestangularized(element, true, route); |
| 1064 | |
| 1065 | var localElem = restangularizeBase(parent, elem, route, reqParams, fromServer); |
| 1066 | localElem[config.restangularFields.restangularCollection] = true; |
| 1067 | localElem[config.restangularFields.post] = _.bind(postFunction, localElem, null); |
| 1068 | localElem[config.restangularFields.remove] = _.bind(deleteFunction, localElem); |
| 1069 | localElem[config.restangularFields.head] = _.bind(headFunction, localElem); |
| 1070 | localElem[config.restangularFields.trace] = _.bind(traceFunction, localElem); |
| 1071 | localElem[config.restangularFields.putElement] = _.bind(putElementFunction, localElem); |
| 1072 | localElem[config.restangularFields.options] = _.bind(optionsFunction, localElem); |
| 1073 | localElem[config.restangularFields.patch] = _.bind(patchFunction, localElem); |
| 1074 | localElem[config.restangularFields.get] = _.bind(getById, localElem); |
| 1075 | localElem[config.restangularFields.getList] = _.bind(fetchFunction, localElem, null); |
| 1076 | |
| 1077 | addCustomOperation(localElem); |
| 1078 | return config.transformElem(localElem, true, route, service, true); |
| 1079 | } |
| 1080 | |
| 1081 | function restangularizeCollectionAndElements(parent, element, route, fromServer) { |
| 1082 | var collection = restangularizeCollection(parent, element, route, fromServer); |
| 1083 | _.each(collection, function(elem) { |
| 1084 | if (elem) { |
| 1085 | restangularizeElem(parent, elem, route, fromServer); |
| 1086 | } |
| 1087 | }); |
| 1088 | return collection; |
| 1089 | } |
| 1090 | |
| 1091 | function getById(id, reqParams, headers) { |
| 1092 | return this.customGET(id.toString(), reqParams, headers); |
| 1093 | } |
| 1094 | |
| 1095 | function putElementFunction(idx, params, headers) { |
| 1096 | var __this = this; |
| 1097 | var elemToPut = this[idx]; |
| 1098 | var deferred = $q.defer(); |
| 1099 | var filledArray = []; |
| 1100 | filledArray = config.transformElem(filledArray, true, elemToPut[config.restangularFields.route], service); |
| 1101 | elemToPut.put(params, headers).then(function(serverElem) { |
| 1102 | var newArray = copyRestangularizedElement(__this); |
| 1103 | newArray[idx] = serverElem; |
| 1104 | filledArray = newArray; |
| 1105 | deferred.resolve(newArray); |
| 1106 | }, function(response) { |
| 1107 | deferred.reject(response); |
| 1108 | }); |
| 1109 | |
| 1110 | return restangularizePromise(deferred.promise, true, filledArray); |
| 1111 | } |
| 1112 | |
| 1113 | function parseResponse(resData, operation, route, fetchUrl, response, deferred) { |
| 1114 | var data = config.responseExtractor(resData, operation, route, fetchUrl, response, deferred); |
| 1115 | var etag = response.headers('ETag'); |
| 1116 | if (data && etag) { |
| 1117 | data[config.restangularFields.etag] = etag; |
| 1118 | } |
| 1119 | return data; |
| 1120 | } |
| 1121 | |
| 1122 | |
| 1123 | function fetchFunction(what, reqParams, headers) { |
| 1124 | var __this = this; |
| 1125 | var deferred = $q.defer(); |
| 1126 | var operation = 'getList'; |
| 1127 | var url = urlHandler.fetchUrl(this, what); |
| 1128 | var whatFetched = what || __this[config.restangularFields.route]; |
| 1129 | |
| 1130 | var request = config.fullRequestInterceptor(null, operation, |
| 1131 | whatFetched, url, headers || {}, reqParams || {}, this[config.restangularFields.httpConfig] || {}); |
| 1132 | |
| 1133 | var filledArray = []; |
| 1134 | filledArray = config.transformElem(filledArray, true, whatFetched, service); |
| 1135 | |
| 1136 | var method = 'getList'; |
| 1137 | |
| 1138 | if (config.jsonp) { |
| 1139 | method = 'jsonp'; |
| 1140 | } |
| 1141 | |
| 1142 | var okCallback = function(response) { |
| 1143 | var resData = response.data; |
| 1144 | var fullParams = response.config.params; |
| 1145 | var data = parseResponse(resData, operation, whatFetched, url, response, deferred); |
| 1146 | |
| 1147 | // support empty response for getList() calls (some APIs respond with 204 and empty body) |
| 1148 | if (_.isUndefined(data) || '' === data) { |
| 1149 | data = []; |
| 1150 | } |
| 1151 | if (!_.isArray(data)) { |
| 1152 | throw new Error('Response for getList SHOULD be an array and not an object or something else'); |
| 1153 | } |
| 1154 | |
| 1155 | if (true === config.plainByDefault) { |
| 1156 | return resolvePromise(deferred, response, data, filledArray); |
| 1157 | } |
| 1158 | |
| 1159 | var processedData = _.map(data, function(elem) { |
| 1160 | if (!__this[config.restangularFields.restangularCollection]) { |
| 1161 | return restangularizeElem(__this, elem, what, true, data); |
| 1162 | } else { |
| 1163 | return restangularizeElem(__this[config.restangularFields.parentResource], |
| 1164 | elem, __this[config.restangularFields.route], true, data); |
| 1165 | } |
| 1166 | }); |
| 1167 | |
| 1168 | processedData = _.extend(data, processedData); |
| 1169 | |
| 1170 | if (!__this[config.restangularFields.restangularCollection]) { |
| 1171 | resolvePromise( |
| 1172 | deferred, |
| 1173 | response, |
| 1174 | restangularizeCollection( |
| 1175 | __this, |
| 1176 | processedData, |
| 1177 | what, |
| 1178 | true, |
| 1179 | fullParams |
| 1180 | ), |
| 1181 | filledArray |
| 1182 | ); |
| 1183 | } else { |
| 1184 | resolvePromise( |
| 1185 | deferred, |
| 1186 | response, |
| 1187 | restangularizeCollection( |
| 1188 | __this[config.restangularFields.parentResource], |
| 1189 | processedData, |
| 1190 | __this[config.restangularFields.route], |
| 1191 | true, |
| 1192 | fullParams |
| 1193 | ), |
| 1194 | filledArray |
| 1195 | ); |
| 1196 | } |
| 1197 | }; |
| 1198 | |
| 1199 | urlHandler.resource(this, $http, request.httpConfig, request.headers, request.params, what, |
| 1200 | this[config.restangularFields.etag], operation)[method]().then(okCallback, function error(response) { |
| 1201 | if (response.status === 304 && __this[config.restangularFields.restangularCollection]) { |
| 1202 | resolvePromise(deferred, response, __this, filledArray); |
| 1203 | } else if (_.every(config.errorInterceptors, function(cb) { |
| 1204 | return cb(response, deferred, okCallback) !== false; |
| 1205 | })) { |
| 1206 | // triggered if no callback returns false |
| 1207 | deferred.reject(response); |
| 1208 | } |
| 1209 | }); |
| 1210 | |
| 1211 | return restangularizePromise(deferred.promise, true, filledArray); |
| 1212 | } |
| 1213 | |
| 1214 | function withHttpConfig(httpConfig) { |
| 1215 | this[config.restangularFields.httpConfig] = httpConfig; |
| 1216 | return this; |
| 1217 | } |
| 1218 | |
| 1219 | function save(params, headers) { |
| 1220 | if (this[config.restangularFields.fromServer]) { |
| 1221 | return this[config.restangularFields.put](params, headers); |
| 1222 | } else { |
| 1223 | return _.bind(elemFunction, this)('post', undefined, params, undefined, headers); |
| 1224 | } |
| 1225 | } |
| 1226 | |
| 1227 | function elemFunction(operation, what, params, obj, headers) { |
| 1228 | var __this = this; |
| 1229 | var deferred = $q.defer(); |
| 1230 | var resParams = params || {}; |
| 1231 | var route = what || this[config.restangularFields.route]; |
| 1232 | var fetchUrl = urlHandler.fetchUrl(this, what); |
| 1233 | |
| 1234 | var callObj = obj || this; |
| 1235 | // fallback to etag on restangular object (since for custom methods we probably don't explicitly specify the etag field) |
| 1236 | var etag = callObj[config.restangularFields.etag] || (operation !== 'post' ? this[config.restangularFields.etag] : null); |
| 1237 | |
| 1238 | if (_.isObject(callObj) && config.isRestangularized(callObj)) { |
| 1239 | callObj = stripRestangular(callObj); |
| 1240 | } |
| 1241 | var request = config.fullRequestInterceptor(callObj, operation, route, fetchUrl, |
| 1242 | headers || {}, resParams || {}, this[config.restangularFields.httpConfig] || {}); |
| 1243 | |
| 1244 | var filledObject = {}; |
| 1245 | filledObject = config.transformElem(filledObject, false, route, service); |
| 1246 | |
| 1247 | var okCallback = function(response) { |
| 1248 | var resData = response.data; |
| 1249 | var fullParams = response.config.params; |
| 1250 | var elem = parseResponse(resData, operation, route, fetchUrl, response, deferred); |
| 1251 | |
| 1252 | // accept 0 as response |
| 1253 | if (elem !== null && elem !== undefined && elem !== '') { |
| 1254 | var data; |
| 1255 | |
| 1256 | if (true === config.plainByDefault) { |
| 1257 | return resolvePromise(deferred, response, elem, filledObject); |
| 1258 | } |
| 1259 | |
| 1260 | if (operation === 'post' && !__this[config.restangularFields.restangularCollection]) { |
| 1261 | data = restangularizeElem( |
| 1262 | __this[config.restangularFields.parentResource], |
| 1263 | elem, |
| 1264 | route, |
| 1265 | true, |
| 1266 | null, |
| 1267 | fullParams |
| 1268 | ); |
| 1269 | resolvePromise(deferred, response, data, filledObject); |
| 1270 | } else { |
| 1271 | data = restangularizeElem( |
| 1272 | __this[config.restangularFields.parentResource], |
| 1273 | elem, |
| 1274 | __this[config.restangularFields.route], |
| 1275 | true, |
| 1276 | null, |
| 1277 | fullParams |
| 1278 | ); |
| 1279 | |
| 1280 | data[config.restangularFields.singleOne] = __this[config.restangularFields.singleOne]; |
| 1281 | resolvePromise(deferred, response, data, filledObject); |
| 1282 | } |
| 1283 | |
| 1284 | } else { |
| 1285 | resolvePromise(deferred, response, undefined, filledObject); |
| 1286 | } |
| 1287 | }; |
| 1288 | |
| 1289 | var errorCallback = function(response) { |
| 1290 | if (response.status === 304 && config.isSafe(operation)) { |
| 1291 | resolvePromise(deferred, response, __this, filledObject); |
| 1292 | } else if (_.every(config.errorInterceptors, function(cb) { |
| 1293 | return cb(response, deferred, okCallback) !== false; |
| 1294 | })) { |
| 1295 | // triggered if no callback returns false |
| 1296 | deferred.reject(response); |
| 1297 | } |
| 1298 | }; |
| 1299 | // Overriding HTTP Method |
| 1300 | var callOperation = operation; |
| 1301 | var callHeaders = _.extend({}, request.headers); |
| 1302 | var isOverrideOperation = config.isOverridenMethod(operation); |
| 1303 | if (isOverrideOperation) { |
| 1304 | callOperation = 'post'; |
| 1305 | callHeaders = _.extend(callHeaders, { |
| 1306 | 'X-HTTP-Method-Override': operation === 'remove' ? 'DELETE' : operation.toUpperCase() |
| 1307 | }); |
| 1308 | } else if (config.jsonp && callOperation === 'get') { |
| 1309 | callOperation = 'jsonp'; |
| 1310 | } |
| 1311 | |
| 1312 | if (config.isSafe(operation)) { |
| 1313 | if (isOverrideOperation) { |
| 1314 | urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, |
| 1315 | what, etag, callOperation)[callOperation]({}).then(okCallback, errorCallback); |
| 1316 | } else { |
| 1317 | urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, |
| 1318 | what, etag, callOperation)[callOperation]().then(okCallback, errorCallback); |
| 1319 | } |
| 1320 | } else { |
| 1321 | urlHandler.resource(this, $http, request.httpConfig, callHeaders, request.params, |
| 1322 | what, etag, callOperation)[callOperation](request.element).then(okCallback, errorCallback); |
| 1323 | } |
| 1324 | |
| 1325 | return restangularizePromise(deferred.promise, false, filledObject); |
| 1326 | } |
| 1327 | |
| 1328 | function getFunction(params, headers) { |
| 1329 | return _.bind(elemFunction, this)('get', undefined, params, undefined, headers); |
| 1330 | } |
| 1331 | |
| 1332 | function deleteFunction(params, headers) { |
| 1333 | return _.bind(elemFunction, this)('remove', undefined, params, undefined, headers); |
| 1334 | } |
| 1335 | |
| 1336 | function putFunction(params, headers) { |
| 1337 | return _.bind(elemFunction, this)('put', undefined, params, undefined, headers); |
| 1338 | } |
| 1339 | |
| 1340 | function postFunction(what, elem, params, headers) { |
| 1341 | return _.bind(elemFunction, this)('post', what, params, elem, headers); |
| 1342 | } |
| 1343 | |
| 1344 | function headFunction(params, headers) { |
| 1345 | return _.bind(elemFunction, this)('head', undefined, params, undefined, headers); |
| 1346 | } |
| 1347 | |
| 1348 | function traceFunction(params, headers) { |
| 1349 | return _.bind(elemFunction, this)('trace', undefined, params, undefined, headers); |
| 1350 | } |
| 1351 | |
| 1352 | function optionsFunction(params, headers) { |
| 1353 | return _.bind(elemFunction, this)('options', undefined, params, undefined, headers); |
| 1354 | } |
| 1355 | |
| 1356 | function patchFunction(elem, params, headers) { |
| 1357 | return _.bind(elemFunction, this)('patch', undefined, params, elem, headers); |
| 1358 | } |
| 1359 | |
| 1360 | function customFunction(operation, path, params, headers, elem) { |
| 1361 | return _.bind(elemFunction, this)(operation, path, params, elem, headers); |
| 1362 | } |
| 1363 | |
| 1364 | function addRestangularMethodFunction(name, operation, path, defaultParams, defaultHeaders, defaultElem) { |
| 1365 | var bindedFunction; |
| 1366 | if (operation === 'getList') { |
| 1367 | bindedFunction = _.bind(fetchFunction, this, path); |
| 1368 | } else { |
| 1369 | bindedFunction = _.bind(customFunction, this, operation, path); |
| 1370 | } |
| 1371 | |
| 1372 | var createdFunction = function(params, headers, elem) { |
| 1373 | var callParams = _.defaults({ |
| 1374 | params: params, |
| 1375 | headers: headers, |
| 1376 | elem: elem |
| 1377 | }, { |
| 1378 | params: defaultParams, |
| 1379 | headers: defaultHeaders, |
| 1380 | elem: defaultElem |
| 1381 | }); |
| 1382 | return bindedFunction(callParams.params, callParams.headers, callParams.elem); |
| 1383 | }; |
| 1384 | |
| 1385 | if (config.isSafe(operation)) { |
| 1386 | this[name] = createdFunction; |
| 1387 | } else { |
| 1388 | this[name] = function(elem, params, headers) { |
| 1389 | return createdFunction(params, headers, elem); |
| 1390 | }; |
| 1391 | } |
| 1392 | } |
| 1393 | |
| 1394 | function withConfigurationFunction(configurer) { |
| 1395 | var newConfig = angular.copy(_.omit(config, 'configuration')); |
| 1396 | Configurer.init(newConfig, newConfig); |
| 1397 | configurer(newConfig); |
| 1398 | return createServiceForConfiguration(newConfig); |
| 1399 | } |
| 1400 | |
| 1401 | function toService(route, parent) { |
| 1402 | var knownCollectionMethods = _.values(config.restangularFields); |
| 1403 | var serv = {}; |
| 1404 | var collection = (parent || service).all(route); |
| 1405 | serv.one = _.bind(one, (parent || service), parent, route); |
| 1406 | serv.post = _.bind(collection.post, collection); |
| 1407 | serv.getList = _.bind(collection.getList, collection); |
| 1408 | serv.withHttpConfig = _.bind(collection.withHttpConfig, collection); |
| 1409 | serv.get = _.bind(collection.get, collection); |
| 1410 | |
| 1411 | for (var prop in collection) { |
| 1412 | if (collection.hasOwnProperty(prop) && _.isFunction(collection[prop]) && !_.includes(knownCollectionMethods, prop)) { |
| 1413 | serv[prop] = _.bind(collection[prop], collection); |
| 1414 | } |
| 1415 | } |
| 1416 | |
| 1417 | return serv; |
| 1418 | } |
| 1419 | |
| 1420 | |
| 1421 | Configurer.init(service, config); |
| 1422 | |
| 1423 | service.copy = _.bind(copyRestangularizedElement, service); |
| 1424 | |
| 1425 | service.service = _.bind(toService, service); |
| 1426 | |
| 1427 | service.withConfig = _.bind(withConfigurationFunction, service); |
| 1428 | |
| 1429 | service.one = _.bind(one, service, null); |
| 1430 | |
| 1431 | service.all = _.bind(all, service, null); |
| 1432 | |
| 1433 | service.several = _.bind(several, service, null); |
| 1434 | |
| 1435 | service.oneUrl = _.bind(oneUrl, service, null); |
| 1436 | |
| 1437 | service.allUrl = _.bind(allUrl, service, null); |
| 1438 | |
| 1439 | service.stripRestangular = _.bind(stripRestangular, service); |
| 1440 | |
| 1441 | service.restangularizeElement = _.bind(restangularizeElem, service); |
| 1442 | |
| 1443 | service.restangularizeCollection = _.bind(restangularizeCollectionAndElements, service); |
| 1444 | |
| 1445 | return service; |
| 1446 | } |
| 1447 | |
| 1448 | return createServiceForConfiguration(globalConfiguration); |
| 1449 | }]; |
| 1450 | }); |
| 1451 | return restangular.name; |
| 1452 | })); |