blob: b59b25f9f59aba8efbf81704e4d5fb40503f48c3 [file] [log] [blame]
Ed Tanous904063f2017-03-02 16:48:24 -08001/**
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}));