angularjs-rails-resource 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);