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.
- data/README.md +63 -6
- data/lib/angularjs-rails-resource/version.rb +1 -1
- data/test/lib/angular/angular-bootstrap-prettify.js +13 -8
- data/test/lib/angular/angular-bootstrap.js +3 -2
- data/test/lib/angular/angular-cookies.js +14 -1
- data/test/lib/angular/angular-loader.js +33 -5
- data/test/lib/angular/angular-locale_en-us.js +4 -0
- data/test/lib/angular/angular-mobile.js +267 -0
- data/test/lib/angular/angular-mocks.js +157 -52
- data/test/lib/angular/angular-resource.js +195 -102
- data/test/lib/angular/angular-sanitize.js +28 -5
- data/test/lib/angular/angular-scenario.js +27862 -0
- data/test/lib/angular/angular.js +2820 -907
- data/test/unit/angularjs/rails/fieldRenamingSpec.js +91 -0
- data/test/unit/angularjs/rails/httpSettingsSpec.js +219 -0
- data/test/unit/angularjs/rails/nestedUrlsSpec.js +327 -0
- data/test/unit/angularjs/rails/resourceSpec.js +37 -662
- data/test/unit/angularjs/rails/rootWrappingSpec.js +46 -0
- data/vendor/assets/javascripts/angularjs/rails/resource.js +62 -16
- metadata +16 -2
@@ -1,6 +1,5 @@
|
|
1
|
-
|
2
1
|
/**
|
3
|
-
* @license AngularJS v1.
|
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
|
225
|
-
*
|
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
|
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 = ['
|
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.
|
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 $
|
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)
|
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"
|
1333
|
-
*/
|
1413
|
+
* that adds a "flush" and "verifyNoPendingTasks" methods.
|
1414
|
+
*/
|
1334
1415
|
|
1335
|
-
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
1341
|
-
|
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',
|
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
|
1594
|
-
var injector = spec.$injector;
|
1706
|
+
var injector = currentSpec.$injector;
|
1595
1707
|
|
1596
|
-
|
1597
|
-
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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
|
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
|
1722
|
-
|
1826
|
+
var modules = currentSpec.$modules || [];
|
1827
|
+
|
1723
1828
|
modules.unshift('ngMock');
|
1724
1829
|
modules.unshift('ng');
|
1725
|
-
var injector =
|
1830
|
+
var injector = currentSpec.$injector;
|
1726
1831
|
if (!injector) {
|
1727
|
-
injector =
|
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.
|
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
|
-
*
|
28
|
-
*
|
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
|
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
|
-
* -
|
64
|
+
* - **`action`** – {string} – The name of action. This name becomes the name of the method on your
|
52
65
|
* resource object.
|
53
|
-
* -
|
54
|
-
* and `JSONP
|
55
|
-
* -
|
56
|
-
*
|
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`
|
71
|
-
* methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
|
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 `
|
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
|
-
*
|
153
|
-
*
|
154
|
-
*
|
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
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
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
|
-
|
330
|
+
setUrlParams: function(config, params, actionUrl) {
|
286
331
|
var self = this,
|
287
|
-
url =
|
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(
|
292
|
-
|
293
|
-
|
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
|
-
|
296
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
386
|
-
}, error);
|
472
|
+
}
|
387
473
|
|
388
|
-
|
389
|
-
};
|
474
|
+
(success||noop)(value, response.headers);
|
390
475
|
|
476
|
+
response.resource = value;
|
477
|
+
return response;
|
478
|
+
}, error).then;
|
391
479
|
|
392
|
-
|
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);
|