angularjs-rails-resource 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,5 @@
1
-
2
1
  /**
3
- * @license AngularJS v1.0.2
2
+ * @license AngularJS v1.1.4
4
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
5
4
  * License: MIT
6
5
  *
@@ -203,6 +202,30 @@ angular.mock.$Browser.prototype = {
203
202
  * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed
204
203
  * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration
205
204
  * information.
205
+ *
206
+ *
207
+ * <pre>
208
+ * describe('$exceptionHandlerProvider', function() {
209
+ *
210
+ * it('should capture log messages and exceptions', function() {
211
+ *
212
+ * module(function($exceptionHandlerProvider) {
213
+ * $exceptionHandlerProvider.mode('log');
214
+ * });
215
+ *
216
+ * inject(function($log, $exceptionHandler, $timeout) {
217
+ * $timeout(function() { $log.log(1); });
218
+ * $timeout(function() { $log.log(2); throw 'banana peel'; });
219
+ * $timeout(function() { $log.log(3); });
220
+ * expect($exceptionHandler.errors).toEqual([]);
221
+ * expect($log.assertEmpty());
222
+ * $timeout.flush();
223
+ * expect($exceptionHandler.errors).toEqual(['banana peel']);
224
+ * expect($log.log.logs).toEqual([[1], [2], [3]]);
225
+ * });
226
+ * });
227
+ * });
228
+ * </pre>
206
229
  */
207
230
 
208
231
  angular.mock.$ExceptionHandlerProvider = function() {
@@ -221,8 +244,8 @@ angular.mock.$ExceptionHandlerProvider = function() {
221
244
  * - `rethrow`: If any errors are are passed into the handler in tests, it typically
222
245
  * means that there is a bug in the application or test, so this mock will
223
246
  * make these tests fail.
224
- * - `log`: Sometimes it is desirable to test that an error is throw, for this case the `log` mode stores the
225
- * error and allows later assertion of it.
247
+ * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` mode stores an
248
+ * array of errors in `$exceptionHandler.errors`, to allow later assertion of them.
226
249
  * See {@link ngMock.$log#assertEmpty assertEmpty()} and
227
250
  * {@link ngMock.$log#reset reset()}
228
251
  */
@@ -407,7 +430,7 @@ angular.mock.$LogProvider = function() {
407
430
  *
408
431
  * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
409
432
  *
410
- * Mock of the Date type which has its timezone specified via constroctor arg.
433
+ * Mock of the Date type which has its timezone specified via constructor arg.
411
434
  *
412
435
  * The main purpose is to create Date-like instances with timezone fixed to the specified timezone
413
436
  * offset, so that we can test code that depends on local timezone settings without dependency on
@@ -433,6 +456,7 @@ angular.mock.$LogProvider = function() {
433
456
  * newYearInBratislava.getDate() => 1;
434
457
  * newYearInBratislava.getHours() => 0;
435
458
  * newYearInBratislava.getMinutes() => 0;
459
+ * newYearInBratislava.getSeconds() => 0;
436
460
  * </pre>
437
461
  *
438
462
  */
@@ -489,6 +513,10 @@ angular.mock.$LogProvider = function() {
489
513
  return self.date.getSeconds();
490
514
  };
491
515
 
516
+ self.getMilliseconds = function() {
517
+ return self.date.getMilliseconds();
518
+ };
519
+
492
520
  self.getTimezoneOffset = function() {
493
521
  return offset * 60;
494
522
  };
@@ -539,7 +567,7 @@ angular.mock.$LogProvider = function() {
539
567
  }
540
568
 
541
569
  //hide all methods not implemented in this mock that the Date prototype exposes
542
- var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
570
+ var unimplementedMethods = ['getUTCDay',
543
571
  'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
544
572
  'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
545
573
  'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
@@ -559,10 +587,61 @@ angular.mock.$LogProvider = function() {
559
587
  angular.mock.TzDate.prototype = Date.prototype;
560
588
  })();
561
589
 
590
+ /**
591
+ * @ngdoc function
592
+ * @name angular.mock.createMockWindow
593
+ * @description
594
+ *
595
+ * This function creates a mock window object useful for controlling access ot setTimeout, but mocking out
596
+ * sufficient window's properties to allow Angular to execute.
597
+ *
598
+ * @example
599
+ *
600
+ * <pre>
601
+ beforeEach(module(function($provide) {
602
+ $provide.value('$window', window = angular.mock.createMockWindow());
603
+ }));
604
+
605
+ it('should do something', inject(function($window) {
606
+ var val = null;
607
+ $window.setTimeout(function() { val = 123; }, 10);
608
+ expect(val).toEqual(null);
609
+ window.setTimeout.expect(10).process();
610
+ expect(val).toEqual(123);
611
+ });
612
+ * </pre>
613
+ *
614
+ */
615
+ angular.mock.createMockWindow = function() {
616
+ var mockWindow = {};
617
+ var setTimeoutQueue = [];
618
+
619
+ mockWindow.document = window.document;
620
+ mockWindow.getComputedStyle = angular.bind(window, window.getComputedStyle);
621
+ mockWindow.scrollTo = angular.bind(window, window.scrollTo);
622
+ mockWindow.navigator = window.navigator;
623
+ mockWindow.setTimeout = function(fn, delay) {
624
+ setTimeoutQueue.push({fn: fn, delay: delay});
625
+ };
626
+ mockWindow.setTimeout.queue = setTimeoutQueue;
627
+ mockWindow.setTimeout.expect = function(delay) {
628
+ if (setTimeoutQueue.length > 0) {
629
+ return {
630
+ process: function() {
631
+ setTimeoutQueue.shift().fn();
632
+ }
633
+ };
634
+ } else {
635
+ expect('SetTimoutQueue empty. Expecting delay of ').toEqual(delay);
636
+ }
637
+ };
638
+
639
+ return mockWindow;
640
+ };
562
641
 
563
642
  /**
564
643
  * @ngdoc function
565
- * @name angular.mock.debug
644
+ * @name angular.mock.dump
566
645
  * @description
567
646
  *
568
647
  * *NOTE*: this is not an injectable instance, just a globally available function.
@@ -745,7 +824,7 @@ angular.mock.dump = function(object) {
745
824
  }
746
825
 
747
826
  // testing controller
748
- var $http;
827
+ var $httpBackend;
749
828
 
750
829
  beforeEach(inject(function($injector) {
751
830
  $httpBackend = $injector.get('$httpBackend');
@@ -798,7 +877,7 @@ angular.mock.dump = function(object) {
798
877
  </pre>
799
878
  */
800
879
  angular.mock.$HttpBackendProvider = function() {
801
- this.$get = [createHttpBackendMock];
880
+ this.$get = ['$rootScope', createHttpBackendMock];
802
881
  };
803
882
 
804
883
  /**
@@ -815,7 +894,7 @@ angular.mock.$HttpBackendProvider = function() {
815
894
  * @param {Object=} $browser Auto-flushing enabled if specified
816
895
  * @return {Object} Instance of $httpBackend mock
817
896
  */
818
- function createHttpBackendMock($delegate, $browser) {
897
+ function createHttpBackendMock($rootScope, $delegate, $browser) {
819
898
  var definitions = [],
820
899
  expectations = [],
821
900
  responses = [],
@@ -1145,6 +1224,7 @@ function createHttpBackendMock($delegate, $browser) {
1145
1224
  * is called an exception is thrown (as this typically a sign of programming error).
1146
1225
  */
1147
1226
  $httpBackend.flush = function(count) {
1227
+ $rootScope.$digest();
1148
1228
  if (!responses.length) throw Error('No pending request to flush !');
1149
1229
 
1150
1230
  if (angular.isDefined(count)) {
@@ -1177,6 +1257,7 @@ function createHttpBackendMock($delegate, $browser) {
1177
1257
  * </pre>
1178
1258
  */
1179
1259
  $httpBackend.verifyNoOutstandingExpectation = function() {
1260
+ $rootScope.$digest();
1180
1261
  if (expectations.length) {
1181
1262
  throw Error('Unsatisfied requests: ' + expectations.join(', '));
1182
1263
  }
@@ -1262,7 +1343,7 @@ function MockHttpExpectation(method, url, data, headers) {
1262
1343
  };
1263
1344
 
1264
1345
  this.matchData = function(d) {
1265
- if (angular.isUndefined(data) && angular.isUndefined(d)) return true;
1346
+ if (angular.isUndefined(data)) return true;
1266
1347
  if (data && angular.isFunction(data.test)) return data.test(d);
1267
1348
  if (data && !angular.isString(data)) return angular.toJson(data) == d;
1268
1349
  return data == d;
@@ -1329,17 +1410,49 @@ function MockXhr() {
1329
1410
  * @description
1330
1411
  *
1331
1412
  * This service is just a simple decorator for {@link ng.$timeout $timeout} service
1332
- * that adds a "flush" method.
1333
- */
1413
+ * that adds a "flush" and "verifyNoPendingTasks" methods.
1414
+ */
1334
1415
 
1335
- /**
1336
- * @ngdoc method
1337
- * @name ngMock.$timeout#flush
1338
- * @methodOf ngMock.$timeout
1339
- * @description
1340
- *
1341
- * Flushes the queue of pending tasks.
1342
- */
1416
+ angular.mock.$TimeoutDecorator = function($delegate, $browser) {
1417
+
1418
+ /**
1419
+ * @ngdoc method
1420
+ * @name ngMock.$timeout#flush
1421
+ * @methodOf ngMock.$timeout
1422
+ * @description
1423
+ *
1424
+ * Flushes the queue of pending tasks.
1425
+ */
1426
+ $delegate.flush = function() {
1427
+ $browser.defer.flush();
1428
+ };
1429
+
1430
+ /**
1431
+ * @ngdoc method
1432
+ * @name ngMock.$timeout#verifyNoPendingTasks
1433
+ * @methodOf ngMock.$timeout
1434
+ * @description
1435
+ *
1436
+ * Verifies that there are no pending tasks that need to be flushed.
1437
+ */
1438
+ $delegate.verifyNoPendingTasks = function() {
1439
+ if ($browser.deferredFns.length) {
1440
+ throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
1441
+ formatPendingTasksAsString($browser.deferredFns));
1442
+ }
1443
+ };
1444
+
1445
+ function formatPendingTasksAsString(tasks) {
1446
+ var result = [];
1447
+ angular.forEach(tasks, function(task) {
1448
+ result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
1449
+ });
1450
+
1451
+ return result.join(', ');
1452
+ }
1453
+
1454
+ return $delegate;
1455
+ };
1343
1456
 
1344
1457
  /**
1345
1458
  *
@@ -1365,15 +1478,9 @@ angular.module('ngMock', ['ng']).provider({
1365
1478
  $httpBackend: angular.mock.$HttpBackendProvider,
1366
1479
  $rootElement: angular.mock.$RootElementProvider
1367
1480
  }).config(function($provide) {
1368
- $provide.decorator('$timeout', function($delegate, $browser) {
1369
- $delegate.flush = function() {
1370
- $browser.defer.flush();
1371
- };
1372
- return $delegate;
1373
- });
1481
+ $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
1374
1482
  });
1375
1483
 
1376
-
1377
1484
  /**
1378
1485
  * @ngdoc overview
1379
1486
  * @name ngMockE2E
@@ -1552,7 +1659,7 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) {
1552
1659
  * control how a matched request is handled.
1553
1660
  */
1554
1661
  angular.mock.e2e = {};
1555
- angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock];
1662
+ angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock];
1556
1663
 
1557
1664
 
1558
1665
  angular.mock.clearDataCache = function() {
@@ -1587,14 +1694,20 @@ window.jstestdriver && (function(window) {
1587
1694
  })(window);
1588
1695
 
1589
1696
 
1590
- window.jasmine && (function(window) {
1697
+ (window.jasmine || window.mocha) && (function(window) {
1698
+
1699
+ var currentSpec = null;
1700
+
1701
+ beforeEach(function() {
1702
+ currentSpec = this;
1703
+ });
1591
1704
 
1592
1705
  afterEach(function() {
1593
- var spec = getCurrentSpec();
1594
- var injector = spec.$injector;
1706
+ var injector = currentSpec.$injector;
1595
1707
 
1596
- spec.$injector = null;
1597
- spec.$modules = null;
1708
+ currentSpec.$injector = null;
1709
+ currentSpec.$modules = null;
1710
+ currentSpec = null;
1598
1711
 
1599
1712
  if (injector) {
1600
1713
  injector.get('$rootElement').unbind();
@@ -1616,13 +1729,8 @@ window.jasmine && (function(window) {
1616
1729
  angular.callbacks.counter = 0;
1617
1730
  });
1618
1731
 
1619
- function getCurrentSpec() {
1620
- return jasmine.getEnv().currentSpec;
1621
- }
1622
-
1623
1732
  function isSpecRunning() {
1624
- var spec = getCurrentSpec();
1625
- return spec && spec.queue.running;
1733
+ return currentSpec && (window.mocha || currentSpec.queue.running);
1626
1734
  }
1627
1735
 
1628
1736
  /**
@@ -1630,8 +1738,7 @@ window.jasmine && (function(window) {
1630
1738
  * @name angular.mock.module
1631
1739
  * @description
1632
1740
  *
1633
- * *NOTE*: This is function is also published on window for easy access.<br>
1634
- * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}.
1741
+ * *NOTE*: This function is also published on window for easy access.<br>
1635
1742
  *
1636
1743
  * This function registers a module configuration code. It collects the configuration information
1637
1744
  * which will be used when the injector is created by {@link angular.mock.inject inject}.
@@ -1647,11 +1754,10 @@ window.jasmine && (function(window) {
1647
1754
  return isSpecRunning() ? workFn() : workFn;
1648
1755
  /////////////////////
1649
1756
  function workFn() {
1650
- var spec = getCurrentSpec();
1651
- if (spec.$injector) {
1757
+ if (currentSpec.$injector) {
1652
1758
  throw Error('Injector already created, can not register a module!');
1653
1759
  } else {
1654
- var modules = spec.$modules || (spec.$modules = []);
1760
+ var modules = currentSpec.$modules || (currentSpec.$modules = []);
1655
1761
  angular.forEach(moduleFns, function(module) {
1656
1762
  modules.push(module);
1657
1763
  });
@@ -1664,8 +1770,7 @@ window.jasmine && (function(window) {
1664
1770
  * @name angular.mock.inject
1665
1771
  * @description
1666
1772
  *
1667
- * *NOTE*: This is function is also published on window for easy access.<br>
1668
- * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}.
1773
+ * *NOTE*: This function is also published on window for easy access.<br>
1669
1774
  *
1670
1775
  * The inject function wraps a function into an injectable function. The inject() creates new
1671
1776
  * instance of {@link AUTO.$injector $injector} per test, which is then used for
@@ -1718,19 +1823,19 @@ window.jasmine && (function(window) {
1718
1823
  return isSpecRunning() ? workFn() : workFn;
1719
1824
  /////////////////////
1720
1825
  function workFn() {
1721
- var spec = getCurrentSpec();
1722
- var modules = spec.$modules || [];
1826
+ var modules = currentSpec.$modules || [];
1827
+
1723
1828
  modules.unshift('ngMock');
1724
1829
  modules.unshift('ng');
1725
- var injector = spec.$injector;
1830
+ var injector = currentSpec.$injector;
1726
1831
  if (!injector) {
1727
- injector = spec.$injector = angular.injector(modules);
1832
+ injector = currentSpec.$injector = angular.injector(modules);
1728
1833
  }
1729
1834
  for(var i = 0, ii = blockFns.length; i < ii; i++) {
1730
1835
  try {
1731
1836
  injector.invoke(blockFns[i] || angular.noop, this);
1732
1837
  } catch (e) {
1733
- if(e.stack) e.stack += '\n' + errorForStack.stack;
1838
+ if(e.stack && errorForStack) e.stack += '\n' + errorForStack.stack;
1734
1839
  throw e;
1735
1840
  } finally {
1736
1841
  errorForStack = null;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @license AngularJS v1.0.2
2
+ * @license AngularJS v1.1.4
3
3
  * (c) 2010-2012 Google, Inc. http://angularjs.org
4
4
  * License: MIT
5
5
  */
@@ -12,7 +12,7 @@
12
12
  * @description
13
13
  */
14
14
 
15
- /**
15
+ /**
16
16
  * @ngdoc object
17
17
  * @name ngResource.$resource
18
18
  * @requires $http
@@ -24,11 +24,23 @@
24
24
  * The returned resource object has action methods which provide high-level behaviors without
25
25
  * the need to interact with the low level {@link ng.$http $http} service.
26
26
  *
27
- * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
28
- * `/user/:username`.
27
+ * # Installation
28
+ * To use $resource make sure you have included the `angular-resource.js` that comes in Angular
29
+ * package. You also can find this stuff in {@link http://code.angularjs.org/ code.angularjs.org}.
30
+ * Finally load the module in your application:
31
+ *
32
+ * angular.module('app', ['ngResource']);
33
+ *
34
+ * and you ready to get started!
35
+ *
36
+ * @param {string} url A parametrized URL template with parameters prefixed by `:` as in
37
+ * `/user/:username`. If you are using a URL with a port number (e.g.
38
+ * `http://example.com:8080/api`), you'll need to escape the colon character before the port
39
+ * number, like this: `$resource('http://example.com\\:8080/api')`.
29
40
  *
30
41
  * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
31
- * `actions` methods.
42
+ * `actions` methods. If any of the parameter value is a function, it will be executed every time
43
+ * when a param value needs to be obtained for a request (unless the param was overridden).
32
44
  *
33
45
  * Each key value in the parameter object is first bound to url template if present and then any
34
46
  * excess keys are appended to the url search query after the `?`.
@@ -40,21 +52,42 @@
40
52
  * the data object (useful for non-GET operations).
41
53
  *
42
54
  * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
43
- * default set of resource actions. The declaration should be created in the following format:
55
+ * default set of resource actions. The declaration should be created in the format of {@link
56
+ * ng.$http#Parameters $http.config}:
44
57
  *
45
- * {action1: {method:?, params:?, isArray:?},
46
- * action2: {method:?, params:?, isArray:?},
58
+ * {action1: {method:?, params:?, isArray:?, headers:?, ...},
59
+ * action2: {method:?, params:?, isArray:?, headers:?, ...},
47
60
  * ...}
48
61
  *
49
62
  * Where:
50
63
  *
51
- * - `action` – {string} – The name of action. This name becomes the name of the method on your
64
+ * - **`action`** – {string} – The name of action. This name becomes the name of the method on your
52
65
  * resource object.
53
- * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
54
- * and `JSONP`
55
- * - `params` – {object=} – Optional set of pre-bound parameters for this action.
56
- * - isArray {boolean=} If true then the returned object for this action is an array, see
66
+ * - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
67
+ * and `JSONP`.
68
+ * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of the
69
+ * parameter value is a function, it will be executed every time when a param value needs to be
70
+ * obtained for a request (unless the param was overridden).
71
+ * - **`url`** – {string} – action specific `url` override. The url templating is supported just like
72
+ * for the resource-level urls.
73
+ * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, see
57
74
  * `returns` section.
75
+ * - **`transformRequest`** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
76
+ * transform function or an array of such functions. The transform function takes the http
77
+ * request body and headers and returns its transformed (typically serialized) version.
78
+ * - **`transformResponse`** – `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
79
+ * transform function or an array of such functions. The transform function takes the http
80
+ * response body and headers and returns its transformed (typically deserialized) version.
81
+ * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
82
+ * GET request, otherwise if a cache instance built with
83
+ * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
84
+ * caching.
85
+ * - **`timeout`** – `{number}` – timeout in milliseconds.
86
+ * - **`withCredentials`** - `{boolean}` - whether to to set the `withCredentials` flag on the
87
+ * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
88
+ * requests with credentials} for more information.
89
+ * - **`responseType`** - `{string}` - see {@link
90
+ * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
58
91
  *
59
92
  * @returns {Object} A resource "class" object with methods for the default set of resource actions
60
93
  * optionally extended with custom `actions`. The default set contains these actions:
@@ -67,9 +100,9 @@
67
100
  *
68
101
  * Calling these methods invoke an {@link ng.$http} with the specified http method,
69
102
  * destination and parameters. When the data is returned from the server then the object is an
70
- * instance of the resource class `save`, `remove` and `delete` actions are available on it as
71
- * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read,
72
- * update, delete) on server-side data like this:
103
+ * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
104
+ * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
105
+ * read, update, delete) on server-side data like this:
73
106
  * <pre>
74
107
  var User = $resource('/user/:userId', {userId:'@id'});
75
108
  var user = User.get({userId:123}, function() {
@@ -94,6 +127,24 @@
94
127
  * - non-GET instance actions: `instance.$action([parameters], [success], [error])`
95
128
  *
96
129
  *
130
+ * The Resource instances and collection have these additional properties:
131
+ *
132
+ * - `$then`: the `then` method of a {@link ng.$q promise} derived from the underlying
133
+ * {@link ng.$http $http} call.
134
+ *
135
+ * The success callback for the `$then` method will be resolved if the underlying `$http` requests
136
+ * succeeds.
137
+ *
138
+ * The success callback is called with a single object which is the {@link ng.$http http response}
139
+ * object extended with a new property `resource`. This `resource` property is a reference to the
140
+ * result of the resource action — resource object or array of resources.
141
+ *
142
+ * The error callback is called with the {@link ng.$http http response} object when an http
143
+ * error occurs.
144
+ *
145
+ * - `$resolved`: true if the promise has been resolved (either with success or rejection);
146
+ * Knowing if the Resource has been resolved is useful in data-binding.
147
+ *
97
148
  * @example
98
149
  *
99
150
  * # Credit card resource
@@ -136,7 +187,7 @@
136
187
  * The object returned from this function execution is a resource "class" which has "static" method
137
188
  * for each action in the definition.
138
189
  *
139
- * Calling these methods invoke `$http` on the `url` template with the given `method` and `params`.
190
+ * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and `headers`.
140
191
  * When the data is returned from the server then the object is an instance of the resource type and
141
192
  * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
142
193
  * operations (create, read, update, delete) on server-side data.
@@ -149,9 +200,9 @@
149
200
  });
150
201
  </pre>
151
202
  *
152
- * It's worth noting that the success callback for `get`, `query` and other method gets passed
153
- * in the response that came from the server as well as $http header getter function, so one
154
- * could rewrite the above example and get access to http headers as:
203
+ * It's worth noting that the success callback for `get`, `query` and other method gets passed
204
+ * in the response that came from the server as well as $http header getter function, so one
205
+ * could rewrite the above example and get access to http headers as:
155
206
  *
156
207
  <pre>
157
208
  var User = $resource('/user/:userId', {userId:'@id'});
@@ -230,78 +281,94 @@ angular.module('ngResource', ['ng']).
230
281
  return $parse(path)(obj);
231
282
  };
232
283
 
233
- /**
234
- * We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
235
- * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
236
- * segments:
237
- * segment = *pchar
238
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
239
- * pct-encoded = "%" HEXDIG HEXDIG
240
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
241
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
242
- * / "*" / "+" / "," / ";" / "="
243
- */
244
- function encodeUriSegment(val) {
245
- return encodeUriQuery(val, true).
246
- replace(/%26/gi, '&').
247
- replace(/%3D/gi, '=').
248
- replace(/%2B/gi, '+');
249
- }
250
-
251
-
252
- /**
253
- * This method is intended for encoding *key* or *value* parts of query component. We need a custom
254
- * method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
255
- * encoded per http://tools.ietf.org/html/rfc3986:
256
- * query = *( pchar / "/" / "?" )
257
- * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
258
- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
259
- * pct-encoded = "%" HEXDIG HEXDIG
260
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
261
- * / "*" / "+" / "," / ";" / "="
262
- */
263
- function encodeUriQuery(val, pctEncodeSpaces) {
264
- return encodeURIComponent(val).
265
- replace(/%40/gi, '@').
266
- replace(/%3A/gi, ':').
267
- replace(/%24/g, '$').
268
- replace(/%2C/gi, ',').
269
- replace((pctEncodeSpaces ? null : /%20/g), '+');
270
- }
271
-
272
- function Route(template, defaults) {
284
+ /**
285
+ * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
286
+ * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
287
+ * segments:
288
+ * segment = *pchar
289
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
290
+ * pct-encoded = "%" HEXDIG HEXDIG
291
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
292
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
293
+ * / "*" / "+" / "," / ";" / "="
294
+ */
295
+ function encodeUriSegment(val) {
296
+ return encodeUriQuery(val, true).
297
+ replace(/%26/gi, '&').
298
+ replace(/%3D/gi, '=').
299
+ replace(/%2B/gi, '+');
300
+ }
301
+
302
+
303
+ /**
304
+ * This method is intended for encoding *key* or *value* parts of query component. We need a custom
305
+ * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
306
+ * encoded per http://tools.ietf.org/html/rfc3986:
307
+ * query = *( pchar / "/" / "?" )
308
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
309
+ * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
310
+ * pct-encoded = "%" HEXDIG HEXDIG
311
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
312
+ * / "*" / "+" / "," / ";" / "="
313
+ */
314
+ function encodeUriQuery(val, pctEncodeSpaces) {
315
+ return encodeURIComponent(val).
316
+ replace(/%40/gi, '@').
317
+ replace(/%3A/gi, ':').
318
+ replace(/%24/g, '$').
319
+ replace(/%2C/gi, ',').
320
+ replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
321
+ }
322
+
323
+ function Route(template, defaults) {
273
324
  this.template = template = template + '#';
274
325
  this.defaults = defaults || {};
275
- var urlParams = this.urlParams = {};
276
- forEach(template.split(/\W/), function(param){
277
- if (param && template.match(new RegExp("[^\\\\]:" + param + "\\W"))) {
278
- urlParams[param] = true;
279
- }
280
- });
281
- this.template = template.replace(/\\:/g, ':');
326
+ this.urlParams = {};
282
327
  }
283
328
 
284
329
  Route.prototype = {
285
- url: function(params) {
330
+ setUrlParams: function(config, params, actionUrl) {
286
331
  var self = this,
287
- url = this.template,
332
+ url = actionUrl || self.template,
333
+ val,
288
334
  encodedVal;
289
335
 
336
+ var urlParams = self.urlParams = {};
337
+ forEach(url.split(/\W/), function(param){
338
+ if (param && (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
339
+ urlParams[param] = true;
340
+ }
341
+ });
342
+ url = url.replace(/\\:/g, ':');
343
+
290
344
  params = params || {};
291
- forEach(this.urlParams, function(_, urlParam){
292
- encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "");
293
- url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
345
+ forEach(self.urlParams, function(_, urlParam){
346
+ val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
347
+ if (angular.isDefined(val) && val !== null) {
348
+ encodedVal = encodeUriSegment(val);
349
+ url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1");
350
+ } else {
351
+ url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
352
+ leadingSlashes, tail) {
353
+ if (tail.charAt(0) == '/') {
354
+ return tail;
355
+ } else {
356
+ return leadingSlashes + tail;
357
+ }
358
+ });
359
+ }
294
360
  });
295
- url = url.replace(/\/?#$/, '');
296
- var query = [];
361
+
362
+ // set the url
363
+ config.url = url.replace(/\/?#$/, '').replace(/\/*$/, '');
364
+
365
+ // set params - delegate param encoding to $http
297
366
  forEach(params, function(value, key){
298
367
  if (!self.urlParams[key]) {
299
- query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
368
+ config.params = config.params || {};
369
+ config.params[key] = value;
300
370
  }
301
371
  });
302
- query.sort();
303
- url = url.replace(/\/*$/, '');
304
- return url + (query.length ? '?' + query.join('&') : '');
305
372
  }
306
373
  };
307
374
 
@@ -311,9 +378,11 @@ angular.module('ngResource', ['ng']).
311
378
 
312
379
  actions = extend({}, DEFAULT_ACTIONS, actions);
313
380
 
314
- function extractParams(data){
381
+ function extractParams(data, actionParams){
315
382
  var ids = {};
316
- forEach(paramDefaults || {}, function(value, key){
383
+ actionParams = extend({}, paramDefaults, actionParams);
384
+ forEach(actionParams, function(value, key){
385
+ if (isFunction(value)) { value = value(); }
317
386
  ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
318
387
  });
319
388
  return ids;
@@ -324,12 +393,15 @@ angular.module('ngResource', ['ng']).
324
393
  }
325
394
 
326
395
  forEach(actions, function(action, name) {
396
+ action.method = angular.uppercase(action.method);
327
397
  var hasBody = action.method == 'POST' || action.method == 'PUT' || action.method == 'PATCH';
328
398
  Resource[name] = function(a1, a2, a3, a4) {
329
399
  var params = {};
330
400
  var data;
331
401
  var success = noop;
332
402
  var error = null;
403
+ var promise;
404
+
333
405
  switch(arguments.length) {
334
406
  case 4:
335
407
  error = a4;
@@ -365,32 +437,47 @@ angular.module('ngResource', ['ng']).
365
437
  }
366
438
 
367
439
  var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
368
- $http({
369
- method: action.method,
370
- url: route.url(extend({}, extractParams(data), action.params || {}, params)),
371
- data: data
372
- }).then(function(response) {
373
- var data = response.data;
374
-
375
- if (data) {
376
- if (action.isArray) {
377
- value.length = 0;
378
- forEach(data, function(item) {
379
- value.push(new Resource(item));
380
- });
381
- } else {
382
- copy(data, value);
383
- }
440
+ var httpConfig = {},
441
+ promise;
442
+
443
+ forEach(action, function(value, key) {
444
+ if (key != 'params' && key != 'isArray' ) {
445
+ httpConfig[key] = copy(value);
446
+ }
447
+ });
448
+ httpConfig.data = data;
449
+ route.setUrlParams(httpConfig, extend({}, extractParams(data, action.params || {}), params), action.url);
450
+
451
+ function markResolved() { value.$resolved = true; }
452
+
453
+ promise = $http(httpConfig);
454
+ value.$resolved = false;
455
+
456
+ promise.then(markResolved, markResolved);
457
+ value.$then = promise.then(function(response) {
458
+ var data = response.data;
459
+ var then = value.$then, resolved = value.$resolved;
460
+
461
+ if (data) {
462
+ if (action.isArray) {
463
+ value.length = 0;
464
+ forEach(data, function(item) {
465
+ value.push(new Resource(item));
466
+ });
467
+ } else {
468
+ copy(data, value);
469
+ value.$then = then;
470
+ value.$resolved = resolved;
384
471
  }
385
- (success||noop)(value, response.headers);
386
- }, error);
472
+ }
387
473
 
388
- return value;
389
- };
474
+ (success||noop)(value, response.headers);
390
475
 
476
+ response.resource = value;
477
+ return response;
478
+ }, error).then;
391
479
 
392
- Resource.bind = function(additionalParamDefaults){
393
- return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
480
+ return value;
394
481
  };
395
482
 
396
483
 
@@ -419,10 +506,16 @@ angular.module('ngResource', ['ng']).
419
506
  Resource[name].call(this, params, data, success, error);
420
507
  };
421
508
  });
509
+
510
+ Resource.bind = function(additionalParamDefaults){
511
+ return ResourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
512
+ };
513
+
422
514
  return Resource;
423
515
  }
424
516
 
425
517
  return ResourceFactory;
426
518
  }]);
427
519
 
520
+
428
521
  })(window, window.angular);