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.
- 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);
|